From 063f2d2211e16140cbb1b9ec6226ee4c3f844b02 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 16:19:48 -0700 Subject: [PATCH 01/82] fix(frontend): send passwordConfirm from profile change-password (#793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Profile dialog's self-service password change posted only oldPassword + password to /api/v4/auth/password/change, but that endpoint is rest_registration's ChangePasswordView whose serializer requires password_confirm (camelCased passwordConfirm) — so the request 400'd with "passwordConfirm field is required". The dialog already collects and validates passwordConfirm; forward it in the changePassword payload, matching change-password-dialog.vue and the register/reset flows. Add a regression test asserting the field is sent. Co-Authored-By: Claude Opus 4.8 --- .../src/components/auth/profile-dialog.vue | 1 + frontend/tests/unit/profile-dialog.test.js | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/frontend/src/components/auth/profile-dialog.vue b/frontend/src/components/auth/profile-dialog.vue index 9400dac8e..44b197a47 100644 --- a/frontend/src/components/auth/profile-dialog.vue +++ b/frontend/src/components/auth/profile-dialog.vue @@ -309,6 +309,7 @@ export default { await this.changePassword({ oldPassword: this.profile.oldPassword, password: this.profile.password, + passwordConfirm: this.profile.passwordConfirm, }); } }, diff --git a/frontend/tests/unit/profile-dialog.test.js b/frontend/tests/unit/profile-dialog.test.js index 2c8cf3118..484678c78 100644 --- a/frontend/tests/unit/profile-dialog.test.js +++ b/frontend/tests/unit/profile-dialog.test.js @@ -16,6 +16,7 @@ import { mount } from "@vue/test-utils"; import { describe, expect, test } from "vitest"; import ProfileDialog from "@/components/auth/profile-dialog.vue"; +import { useAuthStore } from "@/stores/auth"; import vuetify from "@/plugins/vuetify"; function mountDialog() { @@ -113,6 +114,38 @@ describe("ProfileDialog Save gating", () => { }); }); +describe("ProfileDialog submit payload", () => { + test("forwards passwordConfirm to changePassword (issue #793)", async () => { + const wrapper = mountDialog(); + const vm = wrapper.vm; + const store = useAuthStore(); + + vm.reset(); + vm.passwordPanel = "password"; + vm.profile.oldPassword = "old-secret"; + vm.profile.password = "new-secret"; + vm.profile.passwordConfirm = "new-secret"; + await vm.$nextTick(); + + // Focus this test on the submitted payload, not Vuetify's async form + // validation (covered by the Save-gating tests above). Assign the raw + // internal refs object; vm.$refs is a readonly proxy. + vm.$.refs.form = { validate: async () => ({ valid: true }) }; + + await vm.submit(); + + // The self-service /auth/password/change endpoint (rest_registration) + // *requires* passwordConfirm; omitting it caused the 400 in issue #793. + expect(store.changePassword).toHaveBeenCalledWith( + expect.objectContaining({ + oldPassword: "old-secret", + password: "new-secret", + passwordConfirm: "new-secret", + }), + ); + }); +}); + describe("ProfileDialog client-side rules", () => { test("email accepts blank or valid, rejects malformed", () => { const rule = mountDialog().vm.rules.email[0]; From 3020d679aca7f7996e70b056a6d2d236cf331db9 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 16:25:32 -0700 Subject: [PATCH 02/82] update deps and version to v2.1.1. bump news --- NEWS.md | 5 + bun.lock | 8 +- cfg/node_root.mk | 2 +- frontend/bun.lock | 16 +- frontend/package.json | 8 +- package.json | 4 +- pyproject.toml | 2 +- uv.lock | 630 +++++++++++++++++++++--------------------- 8 files changed, 341 insertions(+), 334 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4e87b7afb..5866c0940 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,11 @@ width: 128px; border-radius: 128px; " /> +## v2.1.1 + +- Fixes + - Fix unable to change password. + ## v2.1.0 - Renaming - Features diff --git a/bun.lock b/bun.lock index 45010eccd..8c110c96e 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", - "@eslint/json": "^2.0.0", + "@eslint/json": "^2.0.1", "@fsouza/prettierd": "^0.28.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", @@ -32,7 +32,7 @@ "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.1.0", "eslint-plugin-toml": "^1.4.0", - "eslint-plugin-unicorn": "^69.0.0", + "eslint-plugin-unicorn": "^70.0.0", "eslint-plugin-vue": "^10.9.2", "eslint-plugin-vue-scoped-css": "^3.1.1", "eslint-plugin-yml": "^3.5.0", @@ -85,7 +85,7 @@ "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], - "@eslint/json": ["@eslint/json@2.0.0", "", { "dependencies": { "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanwhocodes/momoa": "^3.3.10", "natural-compare": "^1.4.0" } }, "sha512-P32ZJMIopNWQd1SFhd0tgjfA/hgzUuVSqHmMi2273QaLWHWimXq6V+qL4DNKnjGzO/aNECtYW+rEJ/pWB6uP+w=="], + "@eslint/json": ["@eslint/json@2.0.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanwhocodes/momoa": "^3.3.10", "natural-compare": "^1.4.0" } }, "sha512-Thz2j92ceUF3Bq/0TuWb3MWn3Z+Cwc8k5ptF0fakl2D4Mp8mSx07Xr1aQM4R5NoihzarWUdxfOmQ8DesGy4jOg=="], "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], @@ -413,7 +413,7 @@ "eslint-plugin-toml": ["eslint-plugin-toml@1.4.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "debug": "^4.1.1", "toml-eslint-parser": "^1.0.1" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-3ErTnfUjXq/23f72XeyRcE0Y4Sd/ME1lsZeezczqpn2R4tE7+Sgco/NUKDXm0xAMz15tzcRz/9RfJRm6AqRO+A=="], - "eslint-plugin-unicorn": ["eslint-plugin-unicorn@69.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "@eslint-community/eslint-utils": "^4.9.1", "browserslist": "^4.28.2", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.49.0", "detect-indent": "^7.0.2", "find-up-simple": "^1.0.1", "globals": "^17.6.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", "regjsparser": "^0.13.1", "semver": "^7.8.4", "strip-indent": "^4.1.1" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-ZN/KtHr9hQ6AOByANSNJpsDbo/+Nn+EyQ6blK4w+dcmS/xpYkqLLfrUc+NA/wOK6vF5uEUvhn8my5B/3sruB9g=="], + "eslint-plugin-unicorn": ["eslint-plugin-unicorn@70.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "@eslint-community/eslint-utils": "^4.9.1", "browserslist": "^4.28.2", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.49.0", "detect-indent": "^7.0.2", "find-up-simple": "^1.0.1", "globals": "^17.6.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", "regjsparser": "^0.13.1", "semver": "^7.8.4", "strip-indent": "^4.1.1" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-uAF9xMcVvvhTfvusCgogJ1wh4To3q2KhVMw3i1Apf/ILTbxsCjscvraAZACsEurb7no2fdXblD3whcbVnjw5zg=="], "eslint-plugin-vue": ["eslint-plugin-vue@10.9.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.3.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA=="], diff --git a/cfg/node_root.mk b/cfg/node_root.mk index 1420b6ef0..b12f6c154 100644 --- a/cfg/node_root.mk +++ b/cfg/node_root.mk @@ -3,4 +3,4 @@ export DEVENV_NODE_ROOT # Dummy target for mbake linting .PHONY: all -all: ; \ No newline at end of file +all:: ; \ No newline at end of file diff --git a/frontend/bun.lock b/frontend/bun.lock index b676d418c..e8570a768 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,7 +5,7 @@ "": { "name": "codex", "dependencies": { - "@unhead/vue": "^3.1.6", + "@unhead/vue": "^3.1.7", "@vueuse/core": "^14.3.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", @@ -15,15 +15,15 @@ "vue": "^3.5.39", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.1.0", - "vuetify": "^4.1.2", + "vuetify": "^4.1.3", "xior": "^0.8.3", }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", - "@types/node": "^26.0.1", - "@unhead/bundler": "^3.1.6", + "@types/node": "^26.1.0", + "@unhead/bundler": "^3.1.7", "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.6", @@ -33,7 +33,7 @@ "sass-loader": "^17.0.0", "toml": "^4.1.2", "typeface-roboto": "^1.1.13", - "vite": "^8.1.2", + "vite": "^8.1.3", "vite-plugin-checker": "^0.14.4", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.0", @@ -324,7 +324,7 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], + "@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="], "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], @@ -874,7 +874,7 @@ "valibot": ["valibot@1.4.1", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g=="], - "vite": ["vite@8.1.2", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ=="], + "vite": ["vite@8.1.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA=="], "vite-plugin-checker": ["vite-plugin-checker@0.14.4", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "chokidar": "^5.0.0", "npm-run-path": "^6.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.4", "proper-lockfile": "^4.1.2", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@biomejs/biome": ">=2.4.12", "eslint": ">=9.39.4", "meow": "^13.2.0 || ^14.0.0", "optionator": "^0.9.4", "oxlint": ">=1", "stylelint": ">=16.26.1", "typescript": "*", "vite": ">=5.4.21", "vue-tsc": "~2.2.10 || ^3.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "meow", "optionator", "oxlint", "stylelint", "typescript", "vue-tsc"] }, "sha512-Tw0U9UgHIRiZ+Yoe4Gh0RrYoBiCVmO9j4tomVdYr0KUjUsqXMPhqW8ouoSWmOzGp5Iimipbl3bNXZcK7OeP7Qg=="], @@ -900,7 +900,7 @@ "vue-router": ["vue-router@5.1.0", "", { "dependencies": { "@babel/generator": "^8.0.0-rc.4", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.1.2", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.16", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34", "pinia": "^3.0.4", "vite": "^7.0.0 || ^8.0.0", "vue": "^3.5.34" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g=="], - "vuetify": ["vuetify@4.1.2", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-2SUzXt2Q71/cVmSZhU6kOmAo1ev3NZaI/ynj55TTcu6jXAc9kezoqZKY+Xm+0STnAOcCoTseF0qPSs01LjUaaA=="], + "vuetify": ["vuetify@4.1.3", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-tI6GHgICixvnoncWkSBNMKfwzjbt6H8FxEtGvUxYirOIBu00u5TUANgUbu1ly6wr9sVjvwqakCjUiAYJFsqpMw=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], diff --git a/frontend/package.json b/frontend/package.json index 86b99bd8e..d18e9aa18 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "codex", - "version": "2.1.0", + "version": "2.1.1", "private": true, "description": "ui for codex api", "type": "module", @@ -26,14 +26,14 @@ "vue": "^3.5.39", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.1.0", - "vuetify": "^4.1.2", + "vuetify": "^4.1.3", "xior": "^0.8.3" }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", - "@types/node": "^26.0.1", + "@types/node": "^26.1.0", "@unhead/bundler": "^3.1.7", "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.11", @@ -44,7 +44,7 @@ "sass-loader": "^17.0.0", "toml": "^4.1.2", "typeface-roboto": "^1.1.13", - "vite": "^8.1.2", + "vite": "^8.1.3", "vite-plugin-checker": "^0.14.4", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.0", diff --git a/package.json b/package.json index 8b4a1e27b..235ab73f8 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", - "@eslint/json": "^2.0.0", + "@eslint/json": "^2.0.1", "@fsouza/prettierd": "^0.28.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", @@ -87,7 +87,7 @@ "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.1.0", "eslint-plugin-toml": "^1.4.0", - "eslint-plugin-unicorn": "^69.0.0", + "eslint-plugin-unicorn": "^70.0.0", "eslint-plugin-vue": "^10.9.2", "eslint-plugin-vue-scoped-css": "^3.1.1", "eslint-plugin-yml": "^3.5.0", diff --git a/pyproject.toml b/pyproject.toml index d6041e1b6..68ead7117 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ readme = "README.md" requires-python = ">=3.12" license = "GPL-3.0-only" name = "codex" -version = "2.1.0" +version = "2.1.1" [[project.authors]] name = "AJ Slater" email = "aj@slater.net" diff --git a/uv.lock b/uv.lock index 9157bb4b5..240d9cca7 100644 --- a/uv.lock +++ b/uv.lock @@ -551,7 +551,7 @@ wheels = [ [[package]] name = "codex" -version = "2.1.0" +version = "2.1.1" source = { editable = "." } dependencies = [ { name = "adrf" }, @@ -797,62 +797,62 @@ wheels = [ [[package]] name = "complexipy" -version = "6.0.0" +version = "6.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tomli" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/04/2f6763931b09c89b33f58b8562ecb37ee3ec0683b3e8f6e3435cf1d8fbef/complexipy-6.0.0.tar.gz", hash = "sha256:b8a1797cecba2a3f44ccf8a5eb5b080f43ca576100d4d56cc806eb364da00224", size = 352221, upload-time = "2026-06-26T23:28:29.71Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/d9/71ea6005aa68f803763135641a8a667f033415ca100c4de4c7a419ab0547/complexipy-6.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4e95e7199f1b3f3f4fa7981767295aab1ee730cd1affc622744553aa03282129", size = 2330566, upload-time = "2026-06-26T23:26:03.344Z" }, - { url = "https://files.pythonhosted.org/packages/44/99/fe69ffbc26de61723942580d41ed9499976d6c0b7604043ee36d4303bc09/complexipy-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eaa602bd9995585e9439bc069455581261fda7904d919356f211e1dc48552e96", size = 2267100, upload-time = "2026-06-26T23:26:05.208Z" }, - { url = "https://files.pythonhosted.org/packages/47/a3/4a3f6345d084733189bfaf749806ba9d1a2ad3b74ae19996b243c300e437/complexipy-6.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5212ac8464a24a33467ce378333f4edc77d86fcbed0ac6e878b878862b44399a", size = 2451550, upload-time = "2026-06-26T23:26:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/e6/20/c79be2b36df4e80afb947bc679966eb606c8e49766d840047fc26bdd0746/complexipy-6.0.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b36aaecfb199cf3a4e7dfb1f86e85005b44e667e454a2df1e8516c9c1b10cee3", size = 2380956, upload-time = "2026-06-26T23:26:08.582Z" }, - { url = "https://files.pythonhosted.org/packages/27/3c/e8415d235a9b4c09e0521f91bae25d88a53d5ceecfc8cd8b4d87526319bf/complexipy-6.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fde6f56f1e3bcd066d82affea2f5396c52432dd7bad2d1c788b022748dbd88d", size = 2593948, upload-time = "2026-06-26T23:26:10.108Z" }, - { url = "https://files.pythonhosted.org/packages/4a/15/61447323ea0a4f87a3eb20e63c0bda9fc1c16d7e65dbea0665864181a05b/complexipy-6.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61c6ac48f1706beee7bf1a6a2def1b904e37b163425ec4e60448063797ef4728", size = 2831031, upload-time = "2026-06-26T23:26:12.381Z" }, - { url = "https://files.pythonhosted.org/packages/0a/00/249424308ae2129b7d6fa660fe98417d4c1f8a969c0cd0c8866e8ad939b3/complexipy-6.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e0ef1ddec08dedf2577a92e761519ec947998e43f19660891ae74001374b702", size = 2510391, upload-time = "2026-06-26T23:26:14.056Z" }, - { url = "https://files.pythonhosted.org/packages/44/30/73cd829832bdc25a011b36ce016d8a848714181b590b7b3fa708ea86074c/complexipy-6.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de951bbc174d5403507e21c0499d53dd365b1eb471d4db0d9df14f4b39155874", size = 2496041, upload-time = "2026-06-26T23:26:16.011Z" }, - { url = "https://files.pythonhosted.org/packages/73/af/44700d1c1ecd8251f0b365ef0ab66db67a5534547b3a2bfd53064a9fb31b/complexipy-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e5e254c616bbfee51652a4db894f85223bd6a08d04030cedcd59fec429f9a41", size = 2628994, upload-time = "2026-06-26T23:26:17.768Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d6/1c76e492ebd38b023c424d87523deca05896bef24da2975994952e27cf15/complexipy-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18dedbe4e1d29372bb2a56f947c597624815ed3782b1be345829c1d6a00542c1", size = 2726903, upload-time = "2026-06-26T23:26:19.487Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/5d5d2d75205f669f372e8f6e77d513d775622a254e20e313fe77cb7c837b/complexipy-6.0.0-cp312-cp312-win32.whl", hash = "sha256:58115283778e9b17240579109c18109c59302a96a5685c341407de5fe25dd0bb", size = 2030597, upload-time = "2026-06-26T23:26:21.191Z" }, - { url = "https://files.pythonhosted.org/packages/01/a8/e7f8744d3a1b78378ae368ee510e525f2f6dc1f3cd49b166910a09b274e8/complexipy-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:07bf7ef9cc2951818527468aba04f55000c2f61f33d5a1fc605df0a40d9f7858", size = 2185127, upload-time = "2026-06-26T23:26:22.872Z" }, - { url = "https://files.pythonhosted.org/packages/8d/8b/4f72e60152690ae2f5125958ce1c3d55733be44e7994d5cbceb0086e46fc/complexipy-6.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3f49230e3b1ec3ebd377f5da5cd925474c0f81571dfa8a1ff4363285796c04b7", size = 2329898, upload-time = "2026-06-26T23:26:24.689Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c6/54a336b8d137243849e35851a848a684c4f8564f6237a16980223fe95553/complexipy-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:156d354ff5cea8bb8402738f04822e8dd3cce84b96966124269276271b62680b", size = 2266296, upload-time = "2026-06-26T23:26:26.526Z" }, - { url = "https://files.pythonhosted.org/packages/83/02/e765c59ede1c49372363eae40f4ef11494da8807b57bf91c747d7079503e/complexipy-6.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8022ce76649ea62268de1825d47c1450cfe4e56efbca8eda959bf7967981c2cf", size = 2451585, upload-time = "2026-06-26T23:26:28.345Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/71700a3dcc63b9ff0ca9be9db8e0d9d933b1ad375dc997725c33210fdd5d/complexipy-6.0.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0cf7bb0f1eb4d6a8f1d5157421c224c7316be960fb37ded9d0b23f579a9c088d", size = 2380906, upload-time = "2026-06-26T23:26:30.154Z" }, - { url = "https://files.pythonhosted.org/packages/13/d3/24e432d3ae45d6b6eb66a9ccef2089a5712f474d93084db445946cad0f7d/complexipy-6.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b191fa3aba9f5108983397f1fac8510563d8b2248e7c5ede87d11949ab9a50f9", size = 2593560, upload-time = "2026-06-26T23:26:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4f/dd83c579ad5033ba641ff4af7adf5a11529d4e265a0c4c997558c22164f8/complexipy-6.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c227c62edb4e57f4f5c8e944e0ffe318576678dcc7ef7d6791d607e0c2ce779a", size = 2831299, upload-time = "2026-06-26T23:26:33.909Z" }, - { url = "https://files.pythonhosted.org/packages/40/af/d972f64d2acb340abcf47eea09835f253777b76fb19609ebbdcf393c3b0f/complexipy-6.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d62af0072479ef0b9656e4f2428a16ee1d5ef9771fe0895af13b1fbd0a89d44b", size = 2510609, upload-time = "2026-06-26T23:26:35.976Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6f/5c01ec1af861113dc42db070830c384df5371183ecd2200ab0a294bd8a94/complexipy-6.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acbaf5a4156459cb3126d36f4e0dc91ee70fab3a0a30eea42a93bc957954444", size = 2495560, upload-time = "2026-06-26T23:26:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/d5/32/d0641a9c5a7dac54586b4f71c921ae15cd2b73230dd429a7d4fa440e63b8/complexipy-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd6d9746a376df09d7fce6c88147067e4ec4f3b93c2338ae5d92f31f1475870e", size = 2628513, upload-time = "2026-06-26T23:26:39.179Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8d/4384a2acacd78f0fa2e01741c2331a90ab407be4553823021fa96144446c/complexipy-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:496d19ac94bdc3ea7f61b998c10dcb6841ebb3c15c2b9a125c2e9ed4ae8834cc", size = 2726116, upload-time = "2026-06-26T23:26:40.973Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e9/083fe8da2133fc1e155185681059cc09c1e95700ec2f101ba170d78e0b0e/complexipy-6.0.0-cp313-cp313-win32.whl", hash = "sha256:6e248bd6123d48274368a89ff37c0fafcf1d8be7d1643edbd024a99c5c868a1d", size = 2030495, upload-time = "2026-06-26T23:26:43.338Z" }, - { url = "https://files.pythonhosted.org/packages/ab/6d/330bd13defd7e581ecd3b9f7e08abeae394642aecca02afe7ad4befcd020/complexipy-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f0823305fbae10bda55e02353c69e983a86d8735f6367f2e8831de8642a51bc", size = 2184557, upload-time = "2026-06-26T23:26:44.984Z" }, - { url = "https://files.pythonhosted.org/packages/04/77/192836d5730ea5011004636fdd2b2bf85ab1c4b36cc8cbdf312ec445dac8/complexipy-6.0.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8576da1b69486719a8fd3ab6532feb998489c3eeccbd39348d2875bb1da6d73", size = 2331520, upload-time = "2026-06-26T23:26:46.664Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/161d9cabfc954d98e18dec71e397022e646118fcf5bac0591628d15dc303/complexipy-6.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4d69423a41ea491ef7518089a0e06b0bce4271a0b048f99d3d5ba0243d298920", size = 2269847, upload-time = "2026-06-26T23:26:48.375Z" }, - { url = "https://files.pythonhosted.org/packages/43/8b/68dd27fb76cafc96ca239bb0bdd33469ad89390c69482b597ea55ee5c77d/complexipy-6.0.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20831a6cc1e34b087508c09cb084548f0e1c4fe8d377f19e808b2bfdbb89fc64", size = 2452747, upload-time = "2026-06-26T23:26:50.17Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8f/952659c358e0fd06cbce62323cbd0223ebebd120f22c1499cd755ed0c39b/complexipy-6.0.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aea6ceae272a0712810193332cba68868d5884d0be880e67499f156d986bfcc2", size = 2378289, upload-time = "2026-06-26T23:26:51.858Z" }, - { url = "https://files.pythonhosted.org/packages/c1/18/e96a1cf00fb81137456ec27ae5cd0ffe674bab1bbda81eae1823f00b9b60/complexipy-6.0.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e96f9d9f92e2d0db534154fa3dd89b7cea78ae880190ddab78c1411c4a09988", size = 2592582, upload-time = "2026-06-26T23:26:53.7Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ee/92304640b66d68c3bdb21420608837b35b07b021af47ef1977146aec7691/complexipy-6.0.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:67a2888ad16d3d6ab6c0e829b384ffc61ebd2e8451854dc03ce9b2c091fb5fb6", size = 2833288, upload-time = "2026-06-26T23:26:55.439Z" }, - { url = "https://files.pythonhosted.org/packages/0f/68/5769aa10ae3b54c4c7c9836824f9145cf09600c6dd1145e29d7fdc0c6ee3/complexipy-6.0.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26cb2fc2f9773e18f893236e451d15af50e3f2afb63368dc831f1ff6e63a9ae6", size = 2511513, upload-time = "2026-06-26T23:26:57.134Z" }, - { url = "https://files.pythonhosted.org/packages/2e/15/e81ca129fd35e4b9fe31edbd2d44e6192694f19e8a10ea5b51308f7e78af/complexipy-6.0.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e07e03c9fdc392a0cb7cf1173e77b230e1680679de4dc6f19e56546c299d89f0", size = 2496350, upload-time = "2026-06-26T23:26:58.873Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/65b603f6396efd48a470414dd59be047d5ef05d1cc61a2f22d4e73ca2dc2/complexipy-6.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:08809b985e62d420914625ab457e8a08d2ab3b7abba911204745c109b44e9378", size = 2631755, upload-time = "2026-06-26T23:27:00.538Z" }, - { url = "https://files.pythonhosted.org/packages/3c/c4/172aa80e24ce12216dda9574a3c0c291fa6e1b329b468f8f115b59b018c7/complexipy-6.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:27f06383023ef1ebcb7f589078aae2bc0b1d0548b53940e2f77e88b38022a566", size = 2727050, upload-time = "2026-06-26T23:27:02.278Z" }, - { url = "https://files.pythonhosted.org/packages/1f/11/1120c854d6baed175e6222786515ebffef178f65f21a17093892953e28a3/complexipy-6.0.0-cp314-cp314-win32.whl", hash = "sha256:d60f0ab5a97e2f0e90bde40f00cca94ecaa9d2c2aa5932e390cc245205d3637d", size = 2030624, upload-time = "2026-06-26T23:27:04.549Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e4/342572d6124b2a621f0d795c870bb56ec68ceba58594f98d7ed582a4aafa/complexipy-6.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:f633bd905ac2525d22eed5f6562159925157ff8f29098989f85f4c207e39bc89", size = 2184811, upload-time = "2026-06-26T23:27:06.575Z" }, - { url = "https://files.pythonhosted.org/packages/48/bd/d748456c30b2b91111555d8281480938a6911e1679bf73622646e612bbc6/complexipy-6.0.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46507fdcba82de9f99e060de400a0d1262903f762fbcc31319b2c3c28dc57ce1", size = 2451132, upload-time = "2026-06-26T23:27:08.262Z" }, - { url = "https://files.pythonhosted.org/packages/e1/af/1056f696a13831d5478f265156a2970b0568c228adf4902bcd7d2df23acf/complexipy-6.0.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1822aa5c7687ce0f7c28be3b585ac7d41bf8a3c7f7a0faacdb9d1e8c4498fef3", size = 2377139, upload-time = "2026-06-26T23:27:11.153Z" }, - { url = "https://files.pythonhosted.org/packages/65/a1/342d704b242afacb81a0e1d757fc0d975e6514e11b43f4b6b1c45e8f3d63/complexipy-6.0.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c42d83bf580fe94a6c03c6416b824c8ae0d5ee5adadd5ce9bbe3d4a2dde4e453", size = 2596046, upload-time = "2026-06-26T23:27:12.868Z" }, - { url = "https://files.pythonhosted.org/packages/30/f8/a6db45359e6dfe085cfa2c5f6543f9a5f260bf82b1c505ace1bfade67906/complexipy-6.0.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f13adaf927fb89076c6ab8bfd7fe6f5357ba6971010c36c42a6028a8b3bd5575", size = 2827908, upload-time = "2026-06-26T23:27:14.61Z" }, - { url = "https://files.pythonhosted.org/packages/26/a4/914862a47d46df6624179afe99d549a57bc6373e1be34bb3c394060a574d/complexipy-6.0.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:908db0568d8206f8297a6c35815db558c5373341d619b0b7237772ed88e137b4", size = 2510973, upload-time = "2026-06-26T23:27:16.633Z" }, - { url = "https://files.pythonhosted.org/packages/4d/2c/6751c897fe28c0c35ed1d46e911447c40609560c0a374b39dc70aac7d65d/complexipy-6.0.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad7934ad3f26048860fe40fec6d19cc7a3a672fd39fa612b0046320d43ae34d", size = 2495872, upload-time = "2026-06-26T23:27:18.401Z" }, - { url = "https://files.pythonhosted.org/packages/5c/56/6a159d83a2311299bc647b637d679f7a5b044408ec908b2b3e866558375f/complexipy-6.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b5e2740d220b0d30c62146d8922713032e2dd970dfd68c75aa5fef63aad1be1", size = 2629430, upload-time = "2026-06-26T23:27:20.484Z" }, - { url = "https://files.pythonhosted.org/packages/65/d3/75f2076e7e2a2f467eeee81582bf7fc469628a5ee36231baaf8173a33793/complexipy-6.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d397b65ac058983753ac4b168a05847eab63ef8098a227e02deed0f693e45b9", size = 2726734, upload-time = "2026-06-26T23:27:22.162Z" }, - { url = "https://files.pythonhosted.org/packages/d3/39/1e90bef2211f180cb14f2df6e63b422c50457291545c9476a9b5702d13fa/complexipy-6.0.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae060afa16576db02b751581090c9a067ebfdb9f60c9ab2192ea4290091ba6c7", size = 2593077, upload-time = "2026-06-26T23:27:23.959Z" }, - { url = "https://files.pythonhosted.org/packages/63/86/902f397c6713b32aa26c9a45aed9483219e835f0d34e60ee70751f9ba372/complexipy-6.0.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd99e401033e06eb1028fc64582e45ab26b20ba62172156b9b2df98844273ef0", size = 2496657, upload-time = "2026-06-26T23:27:25.755Z" }, - { url = "https://files.pythonhosted.org/packages/10/d1/a5ea773f3bf77e0d6b436bc9544fc28cf146f42123468254e4b6ec47012f/complexipy-6.0.0-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57abc9706ad0a96bc8979a6b04e37075ec3eced9a949344370da398ffa6cec0", size = 2595739, upload-time = "2026-06-26T23:27:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/e3/00/fa2cf32da5702fcf462b88d405ea3842e70579d61f9a5a9bd4beba3bc033/complexipy-6.0.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975340f50683d7047680a0ded036306a2e098c50a72bf3e3ca11c1e0a1ba3315", size = 2496306, upload-time = "2026-06-26T23:27:29.425Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b3/5b/8ad2d572e42e222d8127b37cad8595d6b7bb1d1d8e0a5bd3cb378f0f1d2c/complexipy-6.0.1.tar.gz", hash = "sha256:ab0ab0c38962a3f58062a16badfb30b88c4a68029040fc5333e1d644b41dc744", size = 352346, upload-time = "2026-07-03T03:25:52.548Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/a8/ee7a6bfe3d54a62f604918b2de7e66f342a33414aa38d0584531762b3286/complexipy-6.0.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a00a018244c5e6f0b6e145517ff59f40c8455775cf0be1337e47d8ba8fc08478", size = 2333561, upload-time = "2026-07-03T03:23:31.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/42/07aba6c0fcf8db82027d3f915d121c52cdf6927722489bc2c8f81ee31f5b/complexipy-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a4a6158431bbccfc5d033c46cafe683b204090a7d62e2d708fc3e7ff0009c09", size = 2270718, upload-time = "2026-07-03T03:23:33.5Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1e/5b869db5eb6213623b9d6f8e0a97c7f53bec0963d8ea2a66ac4ce63b772b/complexipy-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc2fd8ed07086b9f1d669da6f901733984721800854807ecead2bdc79414bfac", size = 2452189, upload-time = "2026-07-03T03:23:35.067Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a7/a6c2c0d28da6caa292a44ffe6bb99bc8217c5222511fdb773de60c6e70e8/complexipy-6.0.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02d4045421fc5f1fb5cfb04ba3a7dac705be76fd45e80da5c699a98735d0b3fe", size = 2381579, upload-time = "2026-07-03T03:23:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/e3/21/fca14853331bd5ce966db33bbda982a6c0b640d14f53c0281e1c81f72cdd/complexipy-6.0.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7a4b4058b19b832a43ca4bf370ce8c618489fa3bc5354c247a4d9a9f7593e37", size = 2598711, upload-time = "2026-07-03T03:23:38.862Z" }, + { url = "https://files.pythonhosted.org/packages/b3/01/1686158d43bd8397171a3c2f0e7a7938b861fc822c6346b02bb72e305dd8/complexipy-6.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbfa2957ea052d3749bed14b0ef78b93c8b443b2fd558a1cb585f574d2f68e3c", size = 2837911, upload-time = "2026-07-03T03:23:40.491Z" }, + { url = "https://files.pythonhosted.org/packages/a3/8c/19f4a3ca2cf4245fefb681a872ebca4a52ab2ebb1d94893da97c1bad684d/complexipy-6.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7df5faeb40b1d3b9020fb42acb29370571661715d3cb853f98aa5d6a242d545a", size = 2514337, upload-time = "2026-07-03T03:23:42.044Z" }, + { url = "https://files.pythonhosted.org/packages/62/39/81423ffe22709d8f4c3918f03cbf9e3d7e0dffaf73cc1c167769bc08a06e/complexipy-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3aa9b1efec9dd32714a40af3ff545bd6024def2dcef37940c91643fc35486f4", size = 2496146, upload-time = "2026-07-03T03:23:43.68Z" }, + { url = "https://files.pythonhosted.org/packages/59/c8/438b45da13ed43a44135da34abdb9e48ba8d3332ee1166bd58c59cd03675/complexipy-6.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15bdef5eff10cd801f5fa44243b5e110bc4f44d194f62b80c196245e798a896c", size = 2628582, upload-time = "2026-07-03T03:23:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/36/47/f5fa4218d0acd80f1a17fb4dca64c8c7a278bcef40755c8340a68e546e9c/complexipy-6.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f7e89d0f286c3d327e257b2e874c646e9726f9e09caa59b903a9f9f15b46d962", size = 2728422, upload-time = "2026-07-03T03:23:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/69/dc/94feddb3cc37c6bdcc445b37e7ee1e99151644511a07bdd03c98fc8103d5/complexipy-6.0.1-cp312-cp312-win32.whl", hash = "sha256:3513b6b6afe73f71ca8c1ff52c5b1a8d1aaebb9934d2279bd002e9fc4b6fbf8b", size = 2031415, upload-time = "2026-07-03T03:23:49.124Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8e/ea9b294b9fca952076a1edd826f6ff964913f36b28654f757587a7207273/complexipy-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0ba9cf20affaa91979cc9a664d833f29fc0c02c44b91a889930c434e4c3e8f65", size = 2185216, upload-time = "2026-07-03T03:23:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/97/b5/8cbadf95c24c08c784ecb6c965d6fda10f332cddf7ba6a5221af1c835a33/complexipy-6.0.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b51ef6cc39fb678d417a2145a65596ad5904a4e87137089e32d409ec38e13eff", size = 2332682, upload-time = "2026-07-03T03:23:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a4/78dda753999d5706a05489ed37fc11f1140fce93ea6db993a4ec738bd7ed/complexipy-6.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7b2c57c166fc72d8f5fcd2f84b2577201ec3546b2c95c2a637b074d2e462c14", size = 2270641, upload-time = "2026-07-03T03:23:54.157Z" }, + { url = "https://files.pythonhosted.org/packages/32/b2/3621279ba8ffe008dd7d6ab77132a4a9d9c7f5558eecbdb9c06f689a1cb4/complexipy-6.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01970de229707df12d78e2b0dcd4227e863d8224790ed0cb43a5501821736a6a", size = 2451874, upload-time = "2026-07-03T03:23:55.698Z" }, + { url = "https://files.pythonhosted.org/packages/91/d0/ca2b5d3d0e7ea098c8171ca4c14e791a5d47e3cca2fa1067b4bce83c0610/complexipy-6.0.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f300897b20ab75c8bb5c26aafc69a6feff022355be0af59d8c8314b1d408a4e3", size = 2381901, upload-time = "2026-07-03T03:23:57.512Z" }, + { url = "https://files.pythonhosted.org/packages/a4/72/b8d11386924ee291f86d60a58bc5e9cae50c739a569b9abab3be23d6e477/complexipy-6.0.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b65d9673d15e372629f9b564fbb4ce46f9293cba721c9be3def490aa8b1eb7", size = 2597418, upload-time = "2026-07-03T03:23:59.361Z" }, + { url = "https://files.pythonhosted.org/packages/f0/60/bec4001c8f384d636aa8489309278d59e1699c8864bef321703238f2d872/complexipy-6.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13c51a3ee518011a554624ded5bb8665eac3c9080636c3cc08efd7282eb718b2", size = 2837794, upload-time = "2026-07-03T03:24:00.953Z" }, + { url = "https://files.pythonhosted.org/packages/d3/33/158945d9fec78d854c15147b2cf1ecd8ea6f6410ff75ca2aa0d8e79f2784/complexipy-6.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71549e2b14a3fbdc73c3d87f2b8afea86a196d727ac0d9b07c0c3edf92b6afef", size = 2514715, upload-time = "2026-07-03T03:24:02.546Z" }, + { url = "https://files.pythonhosted.org/packages/e2/55/10689195791286c69a6dc5d15e75675d4dd5aa3682f330b7239bbd63c182/complexipy-6.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4199549e08ba3e8d40877da74225efcad39caf7eb35f07ff3cc4ba8a6bbcc00", size = 2496272, upload-time = "2026-07-03T03:24:04.178Z" }, + { url = "https://files.pythonhosted.org/packages/57/3a/9bd04f1985686f926ee146a78749e08363ba2a7c6d101d49d4a358f719c0/complexipy-6.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ad6516ede6ba71d8b68cee1b8f670d6ac6d9de88cebd82951d2f893587f4ae5", size = 2628251, upload-time = "2026-07-03T03:24:05.998Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e4/b338966c3106344e49cd9abe650840f67969a8abf5d6445e185e1019bc84/complexipy-6.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d86e41ca6264778344fc7bf9ad101de740d94ec0e51bc1c7d5eb06b29d62f56", size = 2727617, upload-time = "2026-07-03T03:24:07.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a4/f4b756595968ddf76c70219a253271eaab83ddedc7000296b7410dbb090b/complexipy-6.0.1-cp313-cp313-win32.whl", hash = "sha256:3ad75ba1918af0b88dee5aa5e35c99d3aae705e0fb55b9c518dffbd9bbf5cab1", size = 2031258, upload-time = "2026-07-03T03:24:09.5Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ed/a4a55b9cefec4f8e5b0630614e0fdd6f854d96ad2afe5fd6a5c67cad10da/complexipy-6.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:914da63e59780fd28f29ad84d51473ddfb4d35cfa600d9231eb92f1ccc3c83d5", size = 2184770, upload-time = "2026-07-03T03:24:10.991Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/819f85bc6598b3228461e3e0870300fe93b989ad02d59a25a7e647af0203/complexipy-6.0.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9cdd4b30b7f2151a16f5827dae20ac4be8544c13ced4a9fdb4daf37bd38c7b82", size = 2334443, upload-time = "2026-07-03T03:24:12.733Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/84ebf2c02f65d9e518a364f58779c46e118ca8b2ad8a39f342cee932811b/complexipy-6.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dd7656fe1c3e6bd5ea2ad3e042e2de4e26c2fb60e6f8ec1871ed75d9d1adef7d", size = 2271854, upload-time = "2026-07-03T03:24:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/43/34/ad2b796d21b05f0b9ae2507c9c4cf57a3abbe4c4806c856f5f12e1bda274/complexipy-6.0.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c790634d70faaaa90f01124e305ed0db6f30668e260fb578d7db3c35a106e0", size = 2451616, upload-time = "2026-07-03T03:24:17.044Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d6/da63d7b1557546c14294a47ee7617838b5d35508738c5cf65b37b38e0df1/complexipy-6.0.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbad719a6a74db3cb952abed96c5ea172ffb78416a143271e2736c3660b3e354", size = 2381811, upload-time = "2026-07-03T03:24:18.684Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d7/cf08b7155f38fda311aef87b67ed6f47bb74f56f878bf1af7635e074f6db/complexipy-6.0.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf32d9bde0fe90b7c0c36c56fcb8adf7810d4b0eda0ee1d37019ff31ced33c3a", size = 2598714, upload-time = "2026-07-03T03:24:20.687Z" }, + { url = "https://files.pythonhosted.org/packages/8d/57/c363a9a6b3efd98b775fb9bf138dfdc5cc035edc15f501f2865f189aa0cd/complexipy-6.0.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf4602eb9896cd21145fd6dbe40c101dfd45b9d55a316e93dca256b2939738cc", size = 2840964, upload-time = "2026-07-03T03:24:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/50d536bf67323e0abe2ae5ac929750402ea48dc0275ef826e604230a4356/complexipy-6.0.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d4903b4cf14bce2e5b51e417c1875cd64b72d03c1886dd959da00ce69772fee", size = 2514328, upload-time = "2026-07-03T03:24:24.616Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/635c0291acb531cb15ce138d976c8ecc021c9d8c84286da4efa66e4f68d1/complexipy-6.0.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7fa5e803bcece8e95f98570e03a11455c889ab780c6b43108aee0c48340191", size = 2496921, upload-time = "2026-07-03T03:24:26.292Z" }, + { url = "https://files.pythonhosted.org/packages/5b/39/fb86363098388a4c93bedbe4fa1e1ccb250e8f3d3ca3dd5aae0b1e147ed4/complexipy-6.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fac0f09da5c831ead52c060c49b0cfdfa9f59b9f7d76648f00d8aa320cd598da", size = 2626855, upload-time = "2026-07-03T03:24:27.964Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/2f23cf237eb8b948494e1cf323c3d8b25ff570aede04d0ddbb4465dda604/complexipy-6.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4cee67c6fb1e98bbc30ba44dceff10f64822449d7ee46d71aaa4239f381e6825", size = 2728611, upload-time = "2026-07-03T03:24:29.783Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/e5bcd00f0581430f7ff10a5a0b21e7bee5ee385a2bc6c19fdbf1404075a7/complexipy-6.0.1-cp314-cp314-win32.whl", hash = "sha256:f50d2b62383715f88e6e9a28705ce341e6c8b4d80ec699a823c6b079a8c7ca03", size = 2031432, upload-time = "2026-07-03T03:24:31.815Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/d474bcc4de54bc972088069c66a20be78ea0bd080d2b0b971897dfd77a3b/complexipy-6.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:d5e31cc445a9fc787a47b438f495069a303072b03608e649900b4c0865f4f6c4", size = 2185680, upload-time = "2026-07-03T03:24:33.587Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f7/a00bad292745623d1b16fcdc3f5b933fda31b1f69ff8c2bdc5c3922cf996/complexipy-6.0.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635bc9fe5aa26ab07599872ce529da7770af1870417712f8954e0ca7e08a2de5", size = 2451689, upload-time = "2026-07-03T03:24:35.189Z" }, + { url = "https://files.pythonhosted.org/packages/eb/70/5021c3f8dd6645e00ff8b6f9da549ab3f1d75dd036b1517acb790cf2b261/complexipy-6.0.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92697f7536418244c4d0286c0cf4aa76c86b37835aea438a530956e5fc53c7ec", size = 2378153, upload-time = "2026-07-03T03:24:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/cd42a9a4d7b6c488fa1bf1f0f030da87931fb770a3ea0aef13d5cd614839/complexipy-6.0.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a69df4a46b79dce380979e07d4c6e87d0aaf7943669c20672635d45c612e7bf", size = 2597988, upload-time = "2026-07-03T03:24:39.429Z" }, + { url = "https://files.pythonhosted.org/packages/53/d6/ea382f6f43f79a25264813de5ba90f20edd654063403123fe5b8013afd1c/complexipy-6.0.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be35712c7d5ebdcc91fcfc8c986540f40c22c7fe08d8233df5406e91a08b5918", size = 2836822, upload-time = "2026-07-03T03:24:41.209Z" }, + { url = "https://files.pythonhosted.org/packages/56/3c/44d0191e59f6f65477b5c7f8940cecc498574b2f34067fbc83be08452df5/complexipy-6.0.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87a19d4baac23883198867e48f3f84af8bb5d87b3e0a6d67e34e328576a7aafe", size = 2513728, upload-time = "2026-07-03T03:24:44.073Z" }, + { url = "https://files.pythonhosted.org/packages/06/f3/58e50e2b4031be7b0ca09a44d013d7b367888a4d5e74fc16daa3b09b49fb/complexipy-6.0.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a15c2167caa2d187c060869dc20b9cacb2bcfccd557cced4326b4696ad2941a", size = 2496613, upload-time = "2026-07-03T03:24:45.782Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a0/1aad33f322c7f25f0bfa168c19ee0df9e95423c01827c7f4f9b89666c9bf/complexipy-6.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9232716d9314f12165b32b5ef533af0526b2ee64fd768dd7c816e3bc020a385", size = 2628105, upload-time = "2026-07-03T03:24:47.59Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/4d1ad63f222d218f8513cd5bf88baf2388a4cdc00488d0b4f22e461fe57e/complexipy-6.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3c57db8b2b4150cf5ee6738d2048f4cee0d674f18ff1d124f063cdba6ae4ac67", size = 2727809, upload-time = "2026-07-03T03:24:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d0/746d70761b0d8b06ecd0ef8e1343a173c15c09135142ffe26f528063dfee/complexipy-6.0.1-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e49e032cc28d194f44cff1e9eb6660385007eebdfde9e5b748a7a6c70878dbb", size = 2599630, upload-time = "2026-07-03T03:24:51.71Z" }, + { url = "https://files.pythonhosted.org/packages/5f/65/629a909a351d4690a52e435630d527f143b319b181eb2cce20c47fa6e0e2/complexipy-6.0.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca8529285881e2deddd72e245e4ff1daa3a9e72eec0371112e4146364dfbd8", size = 2497198, upload-time = "2026-07-03T03:24:53.498Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/4ff4defe8517d33db6eecdb55d418678dac0f3b03e805ce3a2565e2323a5/complexipy-6.0.1-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb94e4d203eb2a3a5c5b9588d911450b76d817e04bf81900d225a72a53a17b48", size = 2598463, upload-time = "2026-07-03T03:24:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/da/b1/695c48cbb030235e1bf7b93e98ae37535da533a7de49fd81a5230673e3bc/complexipy-6.0.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393aa190df961a17cf7ef582b652a07c4b8402cc00a40f06098bf119eaa764b0", size = 2496649, upload-time = "2026-07-03T03:24:56.885Z" }, ] [[package]] @@ -869,71 +869,71 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, - { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, - { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, - { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, - { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, - { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, - { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, - { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, - { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, - { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, - { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, - { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, - { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, - { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, - { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, - { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, - { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, - { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, - { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, - { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, - { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, - { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, - { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, - { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, - { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, - { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, - { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, - { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, - { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, - { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, - { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, - { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, +version = "7.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, + { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, + { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, + { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, + { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, + { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, + { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, + { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, + { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, + { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, + { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, + { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, + { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, + { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, + { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, + { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, ] [[package]] @@ -1188,7 +1188,7 @@ wheels = [ [[package]] name = "djlint" -version = "1.40.0" +version = "1.40.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1199,45 +1199,45 @@ dependencies = [ { name = "pyyaml" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/b3/1f89f154905e67fc8777d668dfaeb0f6a817b7d4579b024aa9d7d4ad711f/djlint-1.40.0.tar.gz", hash = "sha256:d9f51a2f906a8eb0e53335d2f93f971c35cc04f37ba5447cf78daca0fd761473", size = 57282, upload-time = "2026-07-01T02:51:34.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/e2/fa08ab453f38fcbc0e7ad9ea3b2373996306d5f6635c936e6cc4f201fa50/djlint-1.40.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac5e123846c316bd48cfaa6aec0264a0a25964f704ed19929079d39703aed1ff", size = 540920, upload-time = "2026-07-01T02:50:48.876Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3b/fe79c2349778d87c6169adf7c8310c278de1623df60608fcffd368884e88/djlint-1.40.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbcaf528b85c0fe9c7b5bf022a599731550c89da8d2c480b35021094a22a77ee", size = 511977, upload-time = "2026-07-01T02:50:49.975Z" }, - { url = "https://files.pythonhosted.org/packages/83/b3/fe0048e6d025a0cb08ccc9ec32de248e6886b2b43f5741d6f664b8ff0887/djlint-1.40.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18937b21182388c9fa19bd135c978cc66dd7d0492eb670bb5fc9dd88f15348e9", size = 542379, upload-time = "2026-07-01T02:50:51.141Z" }, - { url = "https://files.pythonhosted.org/packages/03/85/9d09113396991c6c04696b7c76174bdfabd0adc0a8f1509fe23a04a427ca/djlint-1.40.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc7caeca3d947ed37b45e32dbdeb35bae615a01d080f06f9469ca3300f3037a0", size = 567077, upload-time = "2026-07-01T02:50:52.29Z" }, - { url = "https://files.pythonhosted.org/packages/53/f2/aad40d337880d86294e967ae92295852ac16bfeb87be09b95a092504c478/djlint-1.40.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2f4d241cf5661112465d12dbdce310b30d90f1a30a2577a82c99b53042f88888", size = 549798, upload-time = "2026-07-01T02:50:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/bf/54/d5effa3b5f1e39e7414ff700a1463c484b6660dd6945b49030e756c1b59f/djlint-1.40.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1c632aef08a3bc07298853077beb283786987fb8c8626b0055319a23b0297d13", size = 576762, upload-time = "2026-07-01T02:50:55.054Z" }, - { url = "https://files.pythonhosted.org/packages/5a/dd/48f9cf365be5d1b8207b33343d0ed769f73126e185ccbc92cd5df662f002/djlint-1.40.0-cp312-cp312-win32.whl", hash = "sha256:411a590f123603c90a3204eb9a06aeb19dc280cff1ce13abe8ccb6392d1a405c", size = 415689, upload-time = "2026-07-01T02:50:56.64Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9c/e219b04ceb59e372f89d79ab60f6a45986a59982a51971819ae18a99e185/djlint-1.40.0-cp312-cp312-win_amd64.whl", hash = "sha256:7846ac8b5c6b4de5208ef4c4eb086b80fef9d128c28733cd393b30a30a96112c", size = 462298, upload-time = "2026-07-01T02:50:57.764Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/f20e97cc6ea609a05f630cdfd468806d8cdf6246f8f02a042448009a4fd6/djlint-1.40.0-cp312-cp312-win_arm64.whl", hash = "sha256:5f31eacfc4ab696c4febbe3d640fb19e9ce9a24950541472e732ed086fc07d96", size = 408327, upload-time = "2026-07-01T02:50:58.921Z" }, - { url = "https://files.pythonhosted.org/packages/75/68/649899766a126404ef4efb17ea603742ad99e7c86b8e46a1d5959a3e9455/djlint-1.40.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e9ee699fc4aa3325a7ed9542eb8780b53e4169041e36d9e4e6d86272dc70c8c", size = 537688, upload-time = "2026-07-01T02:51:00.212Z" }, - { url = "https://files.pythonhosted.org/packages/ec/29/8fea3fbfc11cd160226d623de150ff84dc5cd7aef8667863e9e9a7e20697/djlint-1.40.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0db420847928b2b62f0f440506cf7538678e89f0c67bfbcb1da8a1728bf56f2", size = 510806, upload-time = "2026-07-01T02:51:01.398Z" }, - { url = "https://files.pythonhosted.org/packages/93/1d/8b03816e329c87445669e4424c934e8933862cda822121c3b89c60b958a9/djlint-1.40.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c8542f60ceec900527ed54ad5efed893e440a7f3dc66a1e0605ecf086e03237", size = 540814, upload-time = "2026-07-01T02:51:02.537Z" }, - { url = "https://files.pythonhosted.org/packages/23/65/b98a865ff4e464c3f3657c891ce9b5a8bb11a156e281dcb87f06a182c439/djlint-1.40.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac34ede821c08c9ee52f46fa52e1e9ecfec05e37ee6c4c795f1cc9a94fc6f65b", size = 565362, upload-time = "2026-07-01T02:51:03.706Z" }, - { url = "https://files.pythonhosted.org/packages/47/8d/4dacb70a67f9cb560b7c5be8177fef38b0fbdaffc433aac534dfc49ad8fd/djlint-1.40.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:95e1dc37aeabff9395e7534ad8427fd44e3b6d13dc34f772310732728c8f0484", size = 548034, upload-time = "2026-07-01T02:51:04.819Z" }, - { url = "https://files.pythonhosted.org/packages/8d/5a/a113206698bcb06c3fe2ff22686d4287754dd24b51179f89a21f8075520d/djlint-1.40.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c5d7596faefa8fa0f9d6757cb4ce748aa0b95a733975be6ac6d878e751eab9e", size = 575356, upload-time = "2026-07-01T02:51:05.937Z" }, - { url = "https://files.pythonhosted.org/packages/02/37/6f5d9068e3f21dd1ad79129581f0e71a5353e90dba8590d7075d4cb8899d/djlint-1.40.0-cp313-cp313-win32.whl", hash = "sha256:f67168ed8229a4ae81c391eb5dcefce36495e7df3f90f12cfa132bfa1f57983e", size = 415334, upload-time = "2026-07-01T02:51:07.47Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6b/f4dd6c5865ee4ab71b6dd9a8cb264742219924e92939345bc2977e3ef777/djlint-1.40.0-cp313-cp313-win_amd64.whl", hash = "sha256:dec6873d1765b1a9ef3e9f8dd30b76e9b9e3e556cf01ce0c5c0bc0a5901cbd64", size = 461822, upload-time = "2026-07-01T02:51:08.599Z" }, - { url = "https://files.pythonhosted.org/packages/36/27/fc325fe06dcba8e05f6b0a9e84c65dbf76e6789fb8429029402c6da2dc05/djlint-1.40.0-cp313-cp313-win_arm64.whl", hash = "sha256:02816499e712ff9de9bb0ed8c9d21c6ce27a6997a7265e9cc628a625f8c97f9e", size = 407834, upload-time = "2026-07-01T02:51:09.842Z" }, - { url = "https://files.pythonhosted.org/packages/67/a0/ccfa6fe87fa83df572d5d641121f8ac881787c4751719e6ba963c58cf15a/djlint-1.40.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa86ae4c3dd6215d4857bc871b0748789f80e793c5d3b51edec3f6a941a25a87", size = 536644, upload-time = "2026-07-01T02:51:11.422Z" }, - { url = "https://files.pythonhosted.org/packages/26/87/124ebdeb4b0699085596894b5d749182387408cf3b4a1c50834dd4af37d4/djlint-1.40.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7e712a46833e48452ed3ef42325d45658dc12d516aa050eb670d5a04ec730408", size = 510447, upload-time = "2026-07-01T02:51:12.753Z" }, - { url = "https://files.pythonhosted.org/packages/e6/de/d3d5435e9b08364d4e1405f85486568b1d05c3cbb280859822f6c1c4f90d/djlint-1.40.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37bac4e0fea5790c1da958440b1cf8a5f7469e29b630779592222b7dfac92bc4", size = 543448, upload-time = "2026-07-01T02:51:13.864Z" }, - { url = "https://files.pythonhosted.org/packages/22/cc/80f9de5e5890b246814eb1b2eb8fda8975f8563023d4c2377d2120e03bb8/djlint-1.40.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:403dda65e3703bc960425f297f706219d8bfe35f315aaee7b728f83b024530cb", size = 564472, upload-time = "2026-07-01T02:51:15.04Z" }, - { url = "https://files.pythonhosted.org/packages/55/38/61ccabfd6e521a471f661962c5c1a5061a08372e411cbfd42459ebe60da7/djlint-1.40.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e7a9e15e7c69cb5b9fa77979b1efb596fcb43bb92ba5aa115171f1604a93cc7d", size = 551686, upload-time = "2026-07-01T02:51:16.407Z" }, - { url = "https://files.pythonhosted.org/packages/03/34/6f3aab22596cc365a27e07beb2335d9a05489a2bc2878b5d9affa86cf9f0/djlint-1.40.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c25acfec0cb92e98ce6141f421b4a2f298b5370cbf0c3389c3d465d3c63dad50", size = 574685, upload-time = "2026-07-01T02:51:17.767Z" }, - { url = "https://files.pythonhosted.org/packages/63/4e/04cb7ef51707808cf44db6b7d0161645e8c3d0cbea9bd1a41e3c43244fa4/djlint-1.40.0-cp314-cp314-win32.whl", hash = "sha256:52b55054d819c6d12aab52a151990d37d0c0cf8762312c14c9320dafcb91d69d", size = 420275, upload-time = "2026-07-01T02:51:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/4e/eb/e7faf63fef26e2720c2244f9aa4b001568edec193a977e6145cfbe5b8c6c/djlint-1.40.0-cp314-cp314-win_amd64.whl", hash = "sha256:386c038261f38398309279902d1569b5a4cc3d35d290f8914ebe2600eeed1858", size = 469454, upload-time = "2026-07-01T02:51:20.338Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/a3e0f4b5e1df090796f39e386506c7caad17a2be2f04042be85fbc7ee093/djlint-1.40.0-cp314-cp314-win_arm64.whl", hash = "sha256:1375508369c0823791e3de2f35b269b7154721c50e155409e732e552e4bc2188", size = 416231, upload-time = "2026-07-01T02:51:21.421Z" }, - { url = "https://files.pythonhosted.org/packages/b1/14/3bca60c13c25630c35421b27ba37e52b4926466775111de01dd469616340/djlint-1.40.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:333bbd3ff1d5fb6060b79e51f6fa3226bc0a72a4420d34a3cc116c21b1571077", size = 583182, upload-time = "2026-07-01T02:51:22.64Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ba/c055ed80b62ca7b64df346d9d0184747a7631c149dadbdc31e06243c449a/djlint-1.40.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:72e54b420454a1c8a75e2948ca1b4343036e1f44072b508626961d6b563b90d9", size = 558137, upload-time = "2026-07-01T02:51:23.824Z" }, - { url = "https://files.pythonhosted.org/packages/b6/09/b1c7d1c4025954e2ccc18c7cffe9ceb44c67a57a56d7cd8ee2eaa547233b/djlint-1.40.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c455be937397f6d9a0ea5b4c6cb8cff609a7c3e0dae8d41589d813a17e1bf1f", size = 581288, upload-time = "2026-07-01T02:51:25.038Z" }, - { url = "https://files.pythonhosted.org/packages/fa/da/942df736bf17688e2d6d4bbe2cadf9986d811675e821852a80514dabadf5/djlint-1.40.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3681d2171f768bd123b2a55843bfc60d35bb9d642f4bca65169c751d25f33af6", size = 601433, upload-time = "2026-07-01T02:51:26.251Z" }, - { url = "https://files.pythonhosted.org/packages/90/59/d5d6ebef0825df4845fed30835248cf352d2fefc8128dc96b7b7c8b32029/djlint-1.40.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1b4425675d21d31d807d1646a9b10a9cf5b4f0ec6db574bb1d6bde7a0b871", size = 588058, upload-time = "2026-07-01T02:51:27.526Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c3/0cd92ba21bc61134e8cc29b428db7da86776900d44cd4d4f75637cbaa5a0/djlint-1.40.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:57f977770ddc6e7b71e43fc05378086b38f539975fcc574707762ab6cac42f6d", size = 610206, upload-time = "2026-07-01T02:51:28.697Z" }, - { url = "https://files.pythonhosted.org/packages/a6/0e/6e4b289d372eb20c65f2463a30208699aa5dd1a929ce8262f60fc22b6f79/djlint-1.40.0-cp314-cp314t-win32.whl", hash = "sha256:2b4bfc6270bf649cf9278ba1bff70bd7bd432db501f094964204688415c7788c", size = 442087, upload-time = "2026-07-01T02:51:29.823Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9c/b22bbfccca581c9e86869fd0ecaaddf39557585fd16d16ae27b2ac9b4afb/djlint-1.40.0-cp314-cp314t-win_amd64.whl", hash = "sha256:cdab141e407d854062b4e47d383f05733f6d9e9fd86762c373d3ab42632ee096", size = 486583, upload-time = "2026-07-01T02:51:31.209Z" }, - { url = "https://files.pythonhosted.org/packages/27/2e/966f4334272fa3693c6a38b7a300af87cc10631404d840071b57a2ed6d12/djlint-1.40.0-cp314-cp314t-win_arm64.whl", hash = "sha256:70f5da2b7e6381e9cdaa0ba4b5f60e6785176bf0c26f1f2e9fbca436ce42ebe5", size = 432818, upload-time = "2026-07-01T02:51:32.351Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b4/22413d43c66237e60e2d82a3c09be3017c6802e848401b325f29bf4544d9/djlint-1.40.0-py3-none-any.whl", hash = "sha256:c10cf5abdd7e13af4cb7d6f1b9a3bdaa76317a11f101c2f4e71f0176d62eca67", size = 63291, upload-time = "2026-07-01T02:51:33.411Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/63a4ce9608c7051ab20954a150f6c0f2bfe7332b14cb4f11ee77036b3c6d/djlint-1.40.2.tar.gz", hash = "sha256:b39ff27a9806e4c4ce319b76a058c94811ef73dbba4a21dbec4d8b5009b48d5f", size = 58974, upload-time = "2026-07-03T00:50:27.469Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/60/8d7c37b6dce0d04eebf07c8c6b7de2941a8db06babbba69a04feea115d6a/djlint-1.40.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:775df87f239927afead55eba79e041a75cdffb5000eb09db5f959122b79062f5", size = 572293, upload-time = "2026-07-03T00:49:35.313Z" }, + { url = "https://files.pythonhosted.org/packages/c1/26/58fb1338c832e183c797d553949557810ba5e65aabd232c1445d2bfc3bb1/djlint-1.40.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f0d6c97c99b1e538ad36d604b2fdbad31142ce3a116389f3b9c3a56db0ae8ce6", size = 539842, upload-time = "2026-07-03T00:49:36.666Z" }, + { url = "https://files.pythonhosted.org/packages/b2/aa/4524a5409344d8221aad7f0bdb1002555c4c8f0577aead459eeb335b9b43/djlint-1.40.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7dfbd4a8073a171a5ffcc01269a9e5b8f7a7225f37fa43168c5172951a5dd", size = 571205, upload-time = "2026-07-03T00:49:38.022Z" }, + { url = "https://files.pythonhosted.org/packages/da/a2/58772dfb57f5e49d10aea99dcc4d6180497d9f3cc9759aa96a54fea244ea/djlint-1.40.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20a50da7799653163ddafcc3af43c2179e0a8657d402b772e10e3b628b24deca", size = 598632, upload-time = "2026-07-03T00:49:39.29Z" }, + { url = "https://files.pythonhosted.org/packages/48/a2/e1e4f31340580bc2a5308f9e75c55277e7273919e4c7b3e4e3b443f03fb7/djlint-1.40.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afc96e355b8d8b60215fab201465201c2595040b2f36095954416b0652119d65", size = 579727, upload-time = "2026-07-03T00:49:40.938Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7a/ea6729fcb39b053d1f5df0a7b1f7f268c2e792a5a36bf31c029f350ad1a5/djlint-1.40.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2cb90f921636a5cfab27f672cca38cee6823bad66f657eeb5c79d542f42c29d", size = 608067, upload-time = "2026-07-03T00:49:42.344Z" }, + { url = "https://files.pythonhosted.org/packages/91/af/9dcd98a260e5929b0690352300f2d37c56a0acf53ce25a7f881b59a022b4/djlint-1.40.2-cp312-cp312-win32.whl", hash = "sha256:9bd4cb9e47e7be21aee243f8be6013dea7fd5c8ef8556876ff6eb8ee94cc216d", size = 434780, upload-time = "2026-07-03T00:49:43.565Z" }, + { url = "https://files.pythonhosted.org/packages/b8/26/fa11eb09ea47005d33da21370b509f3fb065d65499478b4b92e6661436ed/djlint-1.40.2-cp312-cp312-win_amd64.whl", hash = "sha256:b139459a6843d07123182b84d27c1dd4da0e9b9ba52dab00c3a6d3a0b6cf0541", size = 484621, upload-time = "2026-07-03T00:49:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d6/0d42dbba8e13bdc1defdb3d213596c69b4da8751426399f5d8ba36a31b9c/djlint-1.40.2-cp312-cp312-win_arm64.whl", hash = "sha256:914ebed15d84276021bb720e7e9d66eb8c6587e9724af23a0875dae101e141a5", size = 428267, upload-time = "2026-07-03T00:49:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/86/24/3cbb5bf5681af9d68d5dcc23c0cf2421500a086f7da389287387441ce410/djlint-1.40.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0496e44b61c7581c6e02df9c3b621d04172405dc7186ced9376e0a6a356a5861", size = 569009, upload-time = "2026-07-03T00:49:47.615Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b7/af7eb51a428081116e2aa464b1df190bb7955049e7f39bd1bb358d9c1f14/djlint-1.40.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a4a98dcfc7af894603f64bba29ef9572e3e447d0c0466198eb4a7be5fefd3e4", size = 538693, upload-time = "2026-07-03T00:49:49.042Z" }, + { url = "https://files.pythonhosted.org/packages/d9/60/f1c7c3b8223cc01fdd3bf9bb06afa07a7935e4fbf97d72b69d4e5c7b8300/djlint-1.40.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffa475bab3e8be757d8b75d3e0ae6327d44937209951925c64042f7d89cb17", size = 569448, upload-time = "2026-07-03T00:49:50.407Z" }, + { url = "https://files.pythonhosted.org/packages/d7/23/90554b25ef09c6048659fa7fc44c1267a965962854dc7aa7632913303ebf/djlint-1.40.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49886bdf08e1d896f91eb008b1f7c754031242feb39036cc09a13a7daea41e7b", size = 597012, upload-time = "2026-07-03T00:49:51.675Z" }, + { url = "https://files.pythonhosted.org/packages/2d/dc/317374b75b8085d33fdb103c8490c575050798d6991e7876cc4446e3852a/djlint-1.40.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0476479e5ad676a89d9a8ead2c74d26f608750bbf146dd4530dc571a601940c9", size = 577408, upload-time = "2026-07-03T00:49:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/ea93e7f48d3e8a36410c596796c6f132baed90e241edcb235693caba00a9/djlint-1.40.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:109f89b1ceb3e1c37bf9e02cb2d73c4a65a020a3fafc4fda91e8128ea87c0388", size = 606770, upload-time = "2026-07-03T00:49:54.457Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b8/dc7c3c1fb6903652187fcaa562998560d821097a715978bf8c54ec8dea08/djlint-1.40.2-cp313-cp313-win32.whl", hash = "sha256:9c548033768d51701b3f561c3dffdf5f09a718f9332d58a7039a437778e0cfc3", size = 434326, upload-time = "2026-07-03T00:49:55.675Z" }, + { url = "https://files.pythonhosted.org/packages/97/9a/4cb35d83c165661d494603b1a16c8eaad28f2dc2825ca218f93c1a5a1ff3/djlint-1.40.2-cp313-cp313-win_amd64.whl", hash = "sha256:e9d97bfcc3e35c991ed7b76cbf0d2a50b68dd68ce96fb1e4a1845fb1d1182642", size = 484132, upload-time = "2026-07-03T00:49:57.096Z" }, + { url = "https://files.pythonhosted.org/packages/09/dd/22a2699bf7e0907a78c258138a74d9f3c0709448c8397f3ca29d6f4d5a66/djlint-1.40.2-cp313-cp313-win_arm64.whl", hash = "sha256:e46012fa48495717db33e1e9f9d4472e2413c3f165016691b42b20377b1f4cf6", size = 427278, upload-time = "2026-07-03T00:49:58.354Z" }, + { url = "https://files.pythonhosted.org/packages/61/55/1769a60325e5a4a93305a69e424e747cb2088fe99fa9a0961010fbff1a2e/djlint-1.40.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a2732aa3d8513d7b9c8fbaad70a3eb572267594e94c555aa099ca5c194e42782", size = 567625, upload-time = "2026-07-03T00:49:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/47/38/8512f315ad0b74453bfec16b85964f8ef586f2a07a7b7b85624b89e51e05/djlint-1.40.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e074858fa9fe8ff005b567a2325717b9bcd0e7f067d86ee513d92459c1d22209", size = 538175, upload-time = "2026-07-03T00:50:01.188Z" }, + { url = "https://files.pythonhosted.org/packages/fe/28/a0625a3f251b4299a40477ac1cc5fa70ddcd2988e4f8622011e583ab70c4/djlint-1.40.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af9c71981467ddd2eadd415889638b0f73c3a320747e899268045ab36f9610c0", size = 572323, upload-time = "2026-07-03T00:50:02.466Z" }, + { url = "https://files.pythonhosted.org/packages/d6/0a/d2968b2c62248e1052da7b6af037e6b89cea68a016e65af82c9ca6377db8/djlint-1.40.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a6156f2de3705c43132100995af254296983831ec4fbb7c6ae25983163e23f", size = 595952, upload-time = "2026-07-03T00:50:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/57/5f/1b2153a08b058bdf2a633543ca1592ec9d84ccac3e725e7917175d686b91/djlint-1.40.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71435a4a6adffeef0c849bb523d22a27f4d9c433895d1ea2e0db112355c3b469", size = 580515, upload-time = "2026-07-03T00:50:05.185Z" }, + { url = "https://files.pythonhosted.org/packages/15/bb/d1383799594d5f2556caa2ae97cebf9aaeba22cc131c54d773ab03b5a433/djlint-1.40.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6e7ddbe49919093ef386f013e79b55bd5b923ef224cfc19bfb090da6f8555cda", size = 605908, upload-time = "2026-07-03T00:50:06.483Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e5/5e66780ad129ec8b80d4eb410eb1e381c406356ed1f2de4aea1d4b667ba4/djlint-1.40.2-cp314-cp314-win32.whl", hash = "sha256:b6b10d2e1d7400453695257eb9b343a52a6814e2203778d5f9b9b5bbf7228033", size = 440078, upload-time = "2026-07-03T00:50:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3c/dc2d5dd03b47b39f37e125d9d8f2e8faaa033a9121d7b9423bd888538d6e/djlint-1.40.2-cp314-cp314-win_amd64.whl", hash = "sha256:ee825c58347ab0ba7a8383402840c3294a9c03461cf957753bf7254eef8a87a6", size = 492181, upload-time = "2026-07-03T00:50:09.468Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/c5e9b2dad272acfa13f50bfaf6eab33165591d9970a75d8fe003a8d5c373/djlint-1.40.2-cp314-cp314-win_arm64.whl", hash = "sha256:69fc1c7c5704964977e2a12eb870840c5819fa0e47fc49adb2d36e4db1707ade", size = 436170, upload-time = "2026-07-03T00:50:10.729Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a3/a639d6b9126e8d62bbeed3122a9f13f0f4f427f5bb6603b0e7a38b2a8323/djlint-1.40.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:94dc6e7be625c86ca7395c2bcf750e65587d10eecf65752ff3b11fdb6790fee1", size = 617749, upload-time = "2026-07-03T00:50:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fb/49fea811fc720a6e588259ecb95d158edd5a80ed9d6244119ae6f2e6c9f5/djlint-1.40.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4f3d5835adbc05eddb9b05feff1465630015b0639a5a3360e7f194ae80ddecf4", size = 589156, upload-time = "2026-07-03T00:50:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5c/d750f26136d433f76398469e09ccd778aa6383915c91fa511a9b0ff2f5e3/djlint-1.40.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c0c55caee9843895acb6032d72e67f8af1e60c2153c20eded6b07cd873f4bb", size = 611560, upload-time = "2026-07-03T00:50:15.142Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4f/2cb74536eb817b6ccabeb19b508f40319cab7ce805c3cb92dc3d873671e2/djlint-1.40.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:828e443e9b1edd8b73047e1f4e1f81dc93e7f6ece36c8fc0500b457eab040088", size = 633415, upload-time = "2026-07-03T00:50:16.556Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9c/6e5ae6a41b6406389ee0b1ec05491cf35957858694dc1b0f39122984b35d/djlint-1.40.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d116c1d51a5034fb1d2fa8403ae5c544b3f4c3fcffff4f62fbf82e4211b7a2fb", size = 618263, upload-time = "2026-07-03T00:50:18.579Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7e/19d7c473c6e023c419cc196688bdd1958902a1aafbc9d4524c3e9b5ddc93/djlint-1.40.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d8e8853f35c24b86bec1259b85c5f3ef6d6dbf6b261cfbe2c5bc8652919aeb24", size = 642095, upload-time = "2026-07-03T00:50:20.031Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d9/882a4f5027f12b2a3ce09bd68f4d922a27e8af132e2ff7af46a597d9556a/djlint-1.40.2-cp314-cp314t-win32.whl", hash = "sha256:e4ca9de0e528695f9486566066f664ec994928f3d68741ebf63cc6ed3531655e", size = 462707, upload-time = "2026-07-03T00:50:21.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/393f3b78735a4d6e41e2ee2bb0f4b9207c93244667d77ab286e46ae91ed3/djlint-1.40.2-cp314-cp314t-win_amd64.whl", hash = "sha256:17d3e40e9a84d55708214ec57db1f6f5ddeb01feedd56696fad85b872a148d67", size = 511638, upload-time = "2026-07-03T00:50:23.204Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/3a44bbb2a918c23e93846895e19ccbce7baab8e2e5be1cca757b3bcd6215/djlint-1.40.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5712c2ac633317b039ef7123ebac197f177d49dc0ddc8a3891edb688ccb5dde9", size = 454371, upload-time = "2026-07-03T00:50:25.063Z" }, + { url = "https://files.pythonhosted.org/packages/0d/8f/463b58ad48f7ebcf1d72d0ce34b9a4c86cf056885240f66e286ba83f4c64/djlint-1.40.2-py3-none-any.whl", hash = "sha256:861ae83cf2aa0754d401e0eea074a8d2c9b5a13b1c930e46a494979f7d112c7e", size = 65070, upload-time = "2026-07-03T00:50:26.193Z" }, ] [[package]] @@ -1351,63 +1351,63 @@ wheels = [ [[package]] name = "granian" -version = "2.7.8" +version = "2.7.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/d5/70694e4d7ed4e065a1b3a53695a02fe575bad5cca91c4a0107dfb02711ed/granian-2.7.8.tar.gz", hash = "sha256:e384b1f08ed3deebdee3516c6db583a26aa20d5e893ce8e8867301fda3902c10", size = 128783, upload-time = "2026-06-30T16:28:27.522Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/59/d5aa3d0866b9ac5437b628a6661584522b5c54ec67247f13beebcf524502/granian-2.7.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7ea0f5e70bae07da53435e43dd2e0c4313dbcdd79cb02fb6e5b22d6740a7b389", size = 6424099, upload-time = "2026-06-30T16:26:34.188Z" }, - { url = "https://files.pythonhosted.org/packages/b6/54/87cdd465be35ee3eb3cf8202c2823c6d65d3d903de5ed1fed305b74819c0/granian-2.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7be22a64a52322a7521bdd427197478c54158136093e38eddc6f23c102897134", size = 6157004, upload-time = "2026-06-30T16:26:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/69/6b/b4bb56842b2798c3d03dd4e69c7d31ae8b2b837a7a48fa7b5ff2ed7195cc/granian-2.7.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:806451ace414b5069d683486340d697a68bc0c67347c7fbc2eaa9c2f97aad4f0", size = 7238218, upload-time = "2026-06-30T16:26:37.735Z" }, - { url = "https://files.pythonhosted.org/packages/57/b0/5ce2ad7fc6621f7bc8c32dcbaacbb432912c09b8c9f31355bedbf561e3cf/granian-2.7.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba4b7d0b058a562cbce184076999df929c2a2fad3686532f4260dd8c19e8ad4f", size = 6456624, upload-time = "2026-06-30T16:26:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/90/e1/e9535c775dcdbea52a7ac9ca46b7be788bde2050a12b11d3f99eb5ce4427/granian-2.7.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b212dc338595d3ee0b25af172413e5de7497ce2120c75e03f5808c8ba68b987", size = 6921518, upload-time = "2026-06-30T16:26:41.211Z" }, - { url = "https://files.pythonhosted.org/packages/16/a7/49b0a0a0bc8457148518f87a776098abe5ff374f82ca5d48788ee94478e6/granian-2.7.8-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:7a5832921130cdfbc67a893e284ccc90e4ed09208eee9574e110cd18506ad676", size = 7167834, upload-time = "2026-06-30T16:26:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e6/686452abe5a46708047d7e3ed50798534bd9745163fd830ae8b7843af98a/granian-2.7.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cf58286ae4a34b8d58d327d82943e92221343e2a65b85f0c6d1ce41e2ed93b3d", size = 7126269, upload-time = "2026-06-30T16:26:44.928Z" }, - { url = "https://files.pythonhosted.org/packages/16/a3/606da026cd23dfcf566a786a35cf780dc9ba15c0dd46c506253934ab0f03/granian-2.7.8-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:bca090674248ce007f9996458b88746980a415432301fb6cad7ca9cfd7b19db5", size = 7345953, upload-time = "2026-06-30T16:26:46.871Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f6/6601110db897c76c69025ede25041f174090810b735ffa90ca16f48406ba/granian-2.7.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6714233ac6041ae201cfbf9b5978b18979268604ac2616278a4281a973ddfe18", size = 6956904, upload-time = "2026-06-30T16:26:48.787Z" }, - { url = "https://files.pythonhosted.org/packages/0c/04/dccd3f261affa376adf679b88264cd3456ab19c0e408435e3107acaf581b/granian-2.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:46c4b81ad590af60aed5727926edcc80f0f8ebf16e6e2effb3c78eb4399c5be6", size = 4027708, upload-time = "2026-06-30T16:26:51.273Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/d00becd226e5076da180e9a735a397c6c750590e49f04004411949fd5b53/granian-2.7.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d75394668a8465cc053266b589593d43b185f986ec67d324d91d02a227a35f8c", size = 6423620, upload-time = "2026-06-30T16:26:52.985Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d7/b52fdb8202afac25ee638a07561464b598b26ff1542186d54113d5b13724/granian-2.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c532e2d58a6d7078caabbbd725d3d08414d0929438b1c28af88255d0fdde615", size = 6157326, upload-time = "2026-06-30T16:26:55.242Z" }, - { url = "https://files.pythonhosted.org/packages/d1/17/25a622b68afe99f81a60f2e94232e4bedc888beea415d806fbc8d2e37bf1/granian-2.7.8-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20378307e8705cc328ab512de6a7d72590e3406bbd7094f90b1124c2d402224f", size = 7238012, upload-time = "2026-06-30T16:26:57.141Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5a/c0e616da2ada06cb455b81833d2ca5ac567c762082c88f285489beaca183/granian-2.7.8-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab76cb78726adee52cd2565e0cc5e26508fd3519a16b634fb0207c95405c9814", size = 6456801, upload-time = "2026-06-30T16:26:59.012Z" }, - { url = "https://files.pythonhosted.org/packages/20/82/61859412d613c29f4a8ffe9fdcaf1393958eaf4932891ec3b7ee45464344/granian-2.7.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d22943980c7516a64411e04d7119c0ae7cf540fc4a6ec09c60f4c8d633581df", size = 6921466, upload-time = "2026-06-30T16:27:00.682Z" }, - { url = "https://files.pythonhosted.org/packages/90/c1/a4f84be3a4ae7fe3c028ad7ab2cbec683b79ee21be7020ab3adbeff3abb2/granian-2.7.8-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:46feb2cc36bbf262ac29e79496bd42554d036a35ab45f7e517c315925c8f8034", size = 7167926, upload-time = "2026-06-30T16:27:02.586Z" }, - { url = "https://files.pythonhosted.org/packages/36/5e/737d0d84456d5be9a5249bcf7cc9eba731a70a0a03ccd7cdecd2addddccb/granian-2.7.8-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53f588a59db89c422aeaf6f0a5d1144dd688a753223abdb5a22c980792c80962", size = 7126258, upload-time = "2026-06-30T16:27:04.832Z" }, - { url = "https://files.pythonhosted.org/packages/65/3e/7aa8a1968e309f79ef9c1a196485392cbb4ea90886f067a0060ab5f88029/granian-2.7.8-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:055ab90bf11b7e3e149f104a4e33069748cb09997f57b68a248f447dd9f8a9ec", size = 7346786, upload-time = "2026-06-30T16:27:06.877Z" }, - { url = "https://files.pythonhosted.org/packages/07/89/57b6dc76eb0a686c5ddd383f68a7d301e717e73b0867399071b8c86b02dc/granian-2.7.8-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0572d0fc41c7c6429ed8adf0c37979e2fe661a9410d795991e32c06771ed972f", size = 6956971, upload-time = "2026-06-30T16:27:09.119Z" }, - { url = "https://files.pythonhosted.org/packages/e8/3e/a0bada2a74d67166f872251f66195cd0d369f2a500abf161f2766c8f3197/granian-2.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:b40187b6bf8b98c3c5f55989b443280a234c66b3b40e4cf2a8e76303a1e78658", size = 4027664, upload-time = "2026-06-30T16:27:11.03Z" }, - { url = "https://files.pythonhosted.org/packages/b9/95/1d1384785249012d4d91ad14594fc5609017bd864474919c6dc463f873bb/granian-2.7.8-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:c49c42d50a89ac66dede690b8a5b41d84973e9df3bd5f604e4338c134e9a2619", size = 6320512, upload-time = "2026-06-30T16:27:12.692Z" }, - { url = "https://files.pythonhosted.org/packages/df/9d/5310ff5c409c45e5b4265ef8b7845aeef1b8748ef53e5b3451bb4b416a8a/granian-2.7.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfa1e8745d3a9f855eb6213fba33a6fab0fafe28aa0bf12d511e1174415f7066", size = 6056082, upload-time = "2026-06-30T16:27:14.383Z" }, - { url = "https://files.pythonhosted.org/packages/48/88/4d6834d5a7eb646c8a79f9a2ac9fe9bac1a68058ba199fecfb7b9013c74a/granian-2.7.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a0c2448ddc8eb96db67531d4fdd71d9349de57b923756b54060f90ec0fe7bdc9", size = 6280702, upload-time = "2026-06-30T16:27:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/11/41/d22e4b7dead21558da9c07a2cd8436912c77b49f114a59a71fe7bd49c3d2/granian-2.7.8-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff6e3d4eabef33d76cf02afc50b7b77460cf0ba8e0bd00e9b705a6c2d6b7c6c6", size = 7209010, upload-time = "2026-06-30T16:27:18.412Z" }, - { url = "https://files.pythonhosted.org/packages/23/bb/bc1b836e1a7efb03e78c8ac94e9ae2dd76606ffd48b5ac1db4756ea45eb1/granian-2.7.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fbd4910078f950944f418e49d244a4a8bb0ac7bf548deb6b0c3ae41726ed16f", size = 6767695, upload-time = "2026-06-30T16:27:20.221Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8a/5c6e47be75f8af62d942d574e114318a96116e0ff79da49bbd477c7b0022/granian-2.7.8-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:4282c77e583c7e69701c94e590cbdd5f3b7e9cfbe2f3a018cd14f428cf9587e6", size = 6887708, upload-time = "2026-06-30T16:27:22.011Z" }, - { url = "https://files.pythonhosted.org/packages/ea/0f/68769de4e9ab2bcc845e889dee149c86df1b455ecb8af011d624f87c4498/granian-2.7.8-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:85570cc41db97a11dbd6c2e393d6438f62a649de11f11bafb09c99b8127e2aae", size = 7056044, upload-time = "2026-06-30T16:27:23.767Z" }, - { url = "https://files.pythonhosted.org/packages/40/2d/81fe4cddceaabc6bf454a64e3141725cbad93043445c34519fed0a984237/granian-2.7.8-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:1f861fdb01aa86682f786d62320c60d114101945c8a6c4d46e1a5133ed170fa5", size = 7291952, upload-time = "2026-06-30T16:27:25.503Z" }, - { url = "https://files.pythonhosted.org/packages/76/7c/971c4ebd7e7bf68d8731a822d720911c1252fd0cfe1b77c951c81a6c0985/granian-2.7.8-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:6c27288f561689bfbc91275ce4fe3596d1e1f6923158b528673014f6d52f63bd", size = 6982314, upload-time = "2026-06-30T16:27:27.979Z" }, - { url = "https://files.pythonhosted.org/packages/ee/98/26b0c48e120cea36762b738751dea42b5b5529c74c0c48e9bd524c064252/granian-2.7.8-cp313-cp313t-win_amd64.whl", hash = "sha256:6fb67bf33cd6a7d30df2b21c973699d932cb15ee84f5573d13a985dacb5e7261", size = 3989007, upload-time = "2026-06-30T16:27:29.755Z" }, - { url = "https://files.pythonhosted.org/packages/02/f4/0a54b6d7b244938e16bef7ab35501c9946012305c00d1438fc78d060d606/granian-2.7.8-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6e044d41e3610c79c23694dd780225745da743df77085bdc02db0aa9bb1616e0", size = 6441789, upload-time = "2026-06-30T16:27:32.221Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a9/b84432d92ebba56402ca82a8482da4bf4b229411a17b806547da0d4830e3/granian-2.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2b667b2af53e1536535d41232593640603d1098be192efa1bb20a0aa53f4efad", size = 6140933, upload-time = "2026-06-30T16:27:34.216Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/0d9d5cd84e7ee3003d7fed448b4547c0ceaea649e6500ca69541c35c420a/granian-2.7.8-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c20c6dbbb0027b7c07eb19d24bbe497632e1f07537f922f5feebecb36f2d0d6b", size = 7272554, upload-time = "2026-06-30T16:27:36.25Z" }, - { url = "https://files.pythonhosted.org/packages/74/18/f14b882c99446a6fcdcc0317cc4e35cea939122dbd4e86da400cf9315a7a/granian-2.7.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f87f8dc11fc4bfcdffb7dfa2f6927cb08cd1f34d688dbdcb9a399f457f170949", size = 6460220, upload-time = "2026-06-30T16:27:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/12/cf/437bbddc90c8d93121df8ba8c33a45e7f839d5d1d9f62cb0599eeb6f4570/granian-2.7.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee71a2e3da5fb8c673eaa9a3cdbfe231555a62a5dd2ffbbdd45dad3640747fd9", size = 6958690, upload-time = "2026-06-30T16:27:40.085Z" }, - { url = "https://files.pythonhosted.org/packages/91/2c/9dac305c16276cc8b2b4a74ab47a8deaeff9ae8e4d9455d1322cf7477429/granian-2.7.8-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:df2f19313a399b02df5edcd4113bd8a9e08dcc05a1dc92c7f6f72517121389c1", size = 7174405, upload-time = "2026-06-30T16:27:42.022Z" }, - { url = "https://files.pythonhosted.org/packages/d1/16/9e7d1aff7ff58ed6c746dfb27398c91995d78844f46229e63c406403a3ed/granian-2.7.8-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:d8cf6e5b0f13f09136f3573f8317eeccf625c65daf28b35e54ea3378938739cc", size = 7133570, upload-time = "2026-06-30T16:27:43.891Z" }, - { url = "https://files.pythonhosted.org/packages/01/10/986ae9776f346aaf2d1ca6c96f4ea4bd2ceb64e0b348d747161f231d2ce5/granian-2.7.8-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:28167bbd887a91e537e66a4b52e117b270bc67b5c9ce6e21734fd200acfdae24", size = 7351717, upload-time = "2026-06-30T16:27:45.71Z" }, - { url = "https://files.pythonhosted.org/packages/03/2c/f8031160c11464fc9f9c4a87dbbda69cc86afa52f6e93866a0b65851958d/granian-2.7.8-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:805a4cf75dff1ff4f8ac0bd46016e74893e43f28e319bed50ab46149fabf2ea7", size = 7005529, upload-time = "2026-06-30T16:27:47.578Z" }, - { url = "https://files.pythonhosted.org/packages/4c/09/e21ca0e8174dd6502151a9420d780c2acb94ee90a1b22ce0a26a479f32a6/granian-2.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:22096b38513b142e1bccc42c2a8fac013250e47f26c1bf1514c24a5ac33ccb87", size = 4036355, upload-time = "2026-06-30T16:27:49.444Z" }, - { url = "https://files.pythonhosted.org/packages/ac/62/f56c030b7f6f912b7b7227771b247a7bceffe9b7ee513d9822c4e1b0a0b3/granian-2.7.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:0508113eaf4d74079e110b267927b843dc8528f56ca1a84bc9a7e5438507b449", size = 6357646, upload-time = "2026-06-30T16:27:51.248Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/84ac8f8e7258d1e70864e11f7f8146538ffe7e45d2dbcb698d5f97251805/granian-2.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5e79f15e461e5cf94e3f13d6f1eba6d777db128e5b64c284c077dabdc7d879fa", size = 6082427, upload-time = "2026-06-30T16:27:53.115Z" }, - { url = "https://files.pythonhosted.org/packages/51/00/17e53f13c6f27f96fa8566b5585d25b5ed27a2658ddc7166bdd03d09019c/granian-2.7.8-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7697330c3a6316d29dcc92de264a6b89d9bb45a1601ee76652f43dab6ac03126", size = 6281317, upload-time = "2026-06-30T16:27:55.14Z" }, - { url = "https://files.pythonhosted.org/packages/e7/32/e99264e9074533a00bb8c2f0586e1895e898a3f57212d79f34aadda22e23/granian-2.7.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686aac81a28f6285a8748f847a6650e2096da6c93e022b4fafbd2c4bd1a27469", size = 7251923, upload-time = "2026-06-30T16:27:56.933Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b4/3d81ef6a048ca2c860be884b141db65945326edd31f95d841fdd03be7bee/granian-2.7.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5333919dc120abb09e5fefc8d61a56a7729d30ddd612974a0e8589cfccea8ad3", size = 6714603, upload-time = "2026-06-30T16:27:58.997Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/ac3333fa30bce76092797b5b2abcc095c0af412a958201778da63808df18/granian-2.7.8-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3ed3e3f0c0813a6df13da2a1f738afaab41183d7171e65eeb0088204e5fb8ec9", size = 6823097, upload-time = "2026-06-30T16:28:00.958Z" }, - { url = "https://files.pythonhosted.org/packages/97/47/24d84410f76eb5abd6c96d982ffeed02369bce98543487cd37407639aa63/granian-2.7.8-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:5e4583a1b013eb7eb4032cfdf7f755714d2df1fb876f73357239a8fff22e579e", size = 7027152, upload-time = "2026-06-30T16:28:03.078Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/8621d04b92eb1380ef0b2539b9dcac161af494681e479328556aaa39fb34/granian-2.7.8-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:efe6bf9cb1e220d6eefb529f34c7a7edf30003a4cd023e65b8a58f2c9feca769", size = 7330281, upload-time = "2026-06-30T16:28:05.166Z" }, - { url = "https://files.pythonhosted.org/packages/51/99/1976784d46e78452555b2c552e7ec636b5c10f9f4a7e18dddc0d9718e235/granian-2.7.8-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a2c0be25fc3c55a6cad9b6c2e9f54fd55937591e708ca0bf5db394cc0b34bf7c", size = 6919657, upload-time = "2026-06-30T16:28:07.641Z" }, - { url = "https://files.pythonhosted.org/packages/e9/20/c14507ee2e62800bb356ca782757e2bb7acc65cff8ff80b83fffe71e3301/granian-2.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:b0d588fd0769730b68d767bdc9013e8069a1a29bbe16b67699bf8500227f8f2b", size = 3958245, upload-time = "2026-06-30T16:28:09.471Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b0/cc/9c752e6173df02c5e37c0df7bffd50c1341109e4b4f8e5073bfd3a72dc82/granian-2.7.9.tar.gz", hash = "sha256:096d9a3396b13826bc63d2cf424ed04daf1ea077beed361d48dca2d55cb4b527", size = 129789, upload-time = "2026-07-03T12:42:03.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/7d/ea14f692056fc08d625aa62b404c10781393f98eadef0208d2fade1f5028/granian-2.7.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e04d86c24948dbf3de0b6b1ac9a9d6c82514f5a6c4f27fe49991e5178a1d0d9f", size = 6534691, upload-time = "2026-07-03T12:40:33.299Z" }, + { url = "https://files.pythonhosted.org/packages/68/6e/6e172bad9a56ee79b9eb530dc45f993159872eb948868c6eb529828d46d8/granian-2.7.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3d741def2ad09c8e30880c909023c6d6b78ebe528c5c2e95de8523d967f625f1", size = 6209984, upload-time = "2026-07-03T12:40:34.666Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/f580baa79fc38c6d0ac83e6047150e66d842e5b2e393acc5188f356c73e4/granian-2.7.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:497c99fc3d4fe0342add24e3c53da78f0d9505ec332dae9075527e00918617d4", size = 7161937, upload-time = "2026-07-03T12:40:36.203Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ed/600a820132ce60a987091b8688e60fad3276fd63c1b726c329958d5a4148/granian-2.7.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e49f859672fe1e8e062f3be791bec2b3169505719ccf83459ff48aac52ee1d0f", size = 6440102, upload-time = "2026-07-03T12:40:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/e9/c2/f38c504bdd150a5f8dc4907309d5467df827401614edabe0d7453e384770/granian-2.7.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a720940970ce47b4b348d4e3c9fefe6ce0a40c46ac851c2e4cc0dcf5bd6e2a7", size = 6951056, upload-time = "2026-07-03T12:40:38.953Z" }, + { url = "https://files.pythonhosted.org/packages/78/01/f6ece64a623e604eac9024751aaf1fab11bf38483a8563a7e009ffa55e00/granian-2.7.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:809ec8a9f44c69c49b158811ad78a3195d5e361662ce93dcb7afb848f5f2fd10", size = 7106920, upload-time = "2026-07-03T12:40:40.311Z" }, + { url = "https://files.pythonhosted.org/packages/62/18/e6f612945ab888cd7a4ff0d42a51f0d3d274bb1860ef90dfa7386d25ffbb/granian-2.7.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea963e616edba406969579e296f853bee164db23c1a63b8d2268459797c64810", size = 7053385, upload-time = "2026-07-03T12:40:41.6Z" }, + { url = "https://files.pythonhosted.org/packages/db/01/25eda803061400b4176d7bb3f144948a065dc76299112ac9ec1d91871cc4/granian-2.7.9-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:61270c22d6172fbb6f8e363bad19e8dcaefde27a9b0685bbffaa09835fdf44e3", size = 7346261, upload-time = "2026-07-03T12:40:43.286Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e3/b3b049d22fc1dedf771410036da06c9bf05f9d24b63474147970ebbb99a0/granian-2.7.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b3000658f76ef2069aee455d3a0835e36fb073e8fd6e95d777cc0179eb97f5b2", size = 6991885, upload-time = "2026-07-03T12:40:44.742Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/d71bf6b7e40f9d44f67b9b0ed861fb89d0912daf88cdcb2b7c69feb7f6c2/granian-2.7.9-cp312-cp312-win_amd64.whl", hash = "sha256:eecd2aa017aa92bc165a9ad33c494e1cfcbfc68a5f055b43e729749899590867", size = 4066976, upload-time = "2026-07-03T12:40:46.135Z" }, + { url = "https://files.pythonhosted.org/packages/34/c4/a66d5c6daf849d012e871ed2684a2e7f81c518ce9f9e827f5900d99d5e7e/granian-2.7.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:23aa08e4d7563f0acb45424676a7d892a146493b5db0895efb5bc8e5121e3051", size = 6534637, upload-time = "2026-07-03T12:40:47.541Z" }, + { url = "https://files.pythonhosted.org/packages/c0/5e/27fe98fabcae553e3f5087b46f3185a257ed9ee5d7f145d2fd03309bcb60/granian-2.7.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:233baf237d4d3768b4ca6cb6d26546de242e437e1af6050405dcc4673032977f", size = 6210219, upload-time = "2026-07-03T12:40:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b3/9f72bd1f36bd3825d35eff3bd8d00c7a01affdec91387fec6e33e314dc6d/granian-2.7.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64eb08bcf4aca0375fae95a27ca2e78e9dfef7ccae8e12daaac8b2f0bd1cc718", size = 7162144, upload-time = "2026-07-03T12:40:50.497Z" }, + { url = "https://files.pythonhosted.org/packages/67/e3/21e699362e4ecd56d254519d042316444b34c2048e32869101db9bfb6db7/granian-2.7.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff89b203cebba0780efa0360b6e23520323d05f799a610d4a07a5681067b7249", size = 6440457, upload-time = "2026-07-03T12:40:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/25/d4/4afb3e49ca32b7b6528c2cde626ff60d43663c3567b500bcfbe40af3ca73/granian-2.7.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4ddd82390b6fb9c025007f7ccd92e99d244211898a7851103f28688889dd905", size = 6950842, upload-time = "2026-07-03T12:40:53.317Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1d/2a19bce46d187296752c9a54cf833f0472232eb6ad4152e99439163fbd1a/granian-2.7.9-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18e3f870a8c42500fe6c8d73d24a6888920e2a8740810ef1ddb83b0b67ce643e", size = 7106973, upload-time = "2026-07-03T12:40:54.916Z" }, + { url = "https://files.pythonhosted.org/packages/e2/36/091e26516a7d9093068665dfeffc61dd87c0cb9b9869ffdf452296ab237b/granian-2.7.9-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5dda81fbf58d8f173b9990e6300525359dc2641b88f03bbbe31cc693db5f530b", size = 7052872, upload-time = "2026-07-03T12:40:56.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/a4dca67e6b68014d548d957735521ce68b2e99a5474722663d048c740063/granian-2.7.9-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:cae7ccfd34519241e92311e966763650f839b07b1195541cb0f5c180ef22df99", size = 7346092, upload-time = "2026-07-03T12:40:58.276Z" }, + { url = "https://files.pythonhosted.org/packages/c8/88/842e71b39a3a02a16d49b4495c20150055e0888aec6ceb1b965df3a872a5/granian-2.7.9-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed501eafd6cb3c63924466860bf478ad23691d7ad16678b2069e75b5328a074c", size = 6991558, upload-time = "2026-07-03T12:41:00.072Z" }, + { url = "https://files.pythonhosted.org/packages/ba/2f/51a4db315adbc5e28bf5ac97cb93e04c5e7e6705dc0ef6c8220851453074/granian-2.7.9-cp313-cp313-win_amd64.whl", hash = "sha256:e79316015dd68624e021281b3d0e2d9446f04160db4f14140561fe4c0ffeaaa0", size = 4066887, upload-time = "2026-07-03T12:41:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7f/62673800ed73a85d5b629f493d06a387834196e9463b115ef4fee35d1928/granian-2.7.9-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:9aa2087ae2103e99ba169f53d718258205bf2ace7df87c9b6dd52e3f71a90335", size = 6331598, upload-time = "2026-07-03T12:41:03.07Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/08878d6a5b010e39f168b3cffe5011183eab4464a58144ea5711d22d9888/granian-2.7.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d52c262b5e0d345c0c0684b7536ea42f370a7bbb4720b1b625d60d84d87c81be", size = 6099199, upload-time = "2026-07-03T12:41:04.617Z" }, + { url = "https://files.pythonhosted.org/packages/34/b3/fc344b4bc668d5f4eae9bddbd0eb8f2517214470dd40d178c6c51d14be22/granian-2.7.9-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6d08149dfa9777ba281ddf7d7df7ae5473cf6c2fa8815ac33301a24acf33e875", size = 6299537, upload-time = "2026-07-03T12:41:05.99Z" }, + { url = "https://files.pythonhosted.org/packages/4f/32/65a9b41f107bf76fd23c6c6102eee6ac1112cb84250408d034517cb415aa/granian-2.7.9-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d019f88e7944c3e3967da833e758c85cb6c35c7a82a8185eba23243b5cf24b7c", size = 7210150, upload-time = "2026-07-03T12:41:07.633Z" }, + { url = "https://files.pythonhosted.org/packages/29/f3/60413bf1a6f6da32b10a3dcfb45c2d09de2dc2f6829555323239635b6398/granian-2.7.9-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e155eb6d3c8827ef3821c80ae6a5f7a61f37562d121240128ce099e3db06ad2f", size = 6673489, upload-time = "2026-07-03T12:41:09.429Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/6cd1b05e0a868b224232fb203838272cdd3165d16338a847f726b21926e2/granian-2.7.9-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:ac516f3cbdce733578ee91a8139d624a1dccf9ab782fb9f3ab58254ebe6c29ad", size = 6829812, upload-time = "2026-07-03T12:41:10.945Z" }, + { url = "https://files.pythonhosted.org/packages/9e/72/cbb85914df3e09e9ad6a59406157622a50e64df960e378d1091195a5a0a2/granian-2.7.9-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:73e8fe3d162c66d1eddaee825e59379bbf7a384ff776ae984baf796cf1b81fe3", size = 6976912, upload-time = "2026-07-03T12:41:12.412Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/ce3d1ef6f8999106a41d828c88f154b6bc6f700950894613e62a3bb46e2e/granian-2.7.9-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:12da3d84a9dce7706a19f6a98e8f7c3624721ff66bc34a60cfc4d58e2d6593b9", size = 7418223, upload-time = "2026-07-03T12:41:13.956Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2f/38daac752e7384c5da56803a65428ef29a196df8e436082813b391877ef5/granian-2.7.9-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:887f0f1e045314044712a2e3799991c7be0ba4129f6806b82e40f502417694d9", size = 6951268, upload-time = "2026-07-03T12:41:15.646Z" }, + { url = "https://files.pythonhosted.org/packages/e4/95/4cbe8728c9e609f33ec676e194a6296cd628bfb9d6c1310f4ab06634f09a/granian-2.7.9-cp313-cp313t-win_amd64.whl", hash = "sha256:2836fd595a144be7e70faebddf1ce7b4e0c136a7017d75660b88c8da63c0f0ff", size = 4003202, upload-time = "2026-07-03T12:41:17.072Z" }, + { url = "https://files.pythonhosted.org/packages/43/fa/b388d36bef00f28f2c99df3ab7a08878116d4d1d654b6f93f7b0ef9cde5b/granian-2.7.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:17e6975266757b05fd59163a3b5ccd7a9d50c227b13e58b9e5b47eff0609c17f", size = 6451170, upload-time = "2026-07-03T12:41:18.484Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ac/2fdc222d98960c5d0a1d1970ee52f9f4e3a953b2862d7be8fb043e74e0c1/granian-2.7.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:554543e1085ff6bf60eb0f0e995a8a412e92165e92113f9bae9e1fdfeec2ca72", size = 6169769, upload-time = "2026-07-03T12:41:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/b2/79/beaa9a25285aea37b7bf9c2fa7357871b51ae1a7ee79501d570386e188ab/granian-2.7.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1f06eb3a1c5ce9bf050e94f37310a6add0410ddee75fc65563e86d1619eea384", size = 7269037, upload-time = "2026-07-03T12:41:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/be/e4/ef1ca00cc17664548427908da36d9a16e29e7adb34318d5173d1c00ecd1c/granian-2.7.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d17720e112e40563bfefa7b8031d37372234b568784c07250322887631f168f8", size = 6539295, upload-time = "2026-07-03T12:41:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c5/80826359e26079665562b362f0a5f05261183a8b423e31c954f110313bb4/granian-2.7.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607a02fbda1905cc0b0764f09fab1d601299e482ebb1d9722e61ca08742fb0df", size = 6981221, upload-time = "2026-07-03T12:41:24.861Z" }, + { url = "https://files.pythonhosted.org/packages/ab/77/d595c9698b7799e28c4dc9a3091d30dbea9e20e9e92926e47f9c974b7a77/granian-2.7.9-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2eb74974682bf8635dff79f356d3fd86193c8c1ebed1baffa75ad1793fddbc7", size = 7131507, upload-time = "2026-07-03T12:41:26.347Z" }, + { url = "https://files.pythonhosted.org/packages/99/46/5200e2b09feae19b7a96371eb2f1523faf2e99aae60584f286db0263cea6/granian-2.7.9-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:33ba7e10bfd30b196b866a9b4b828c9a108a22325936f7d07def89a6b9f21dce", size = 7067731, upload-time = "2026-07-03T12:41:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/432aaa769de91176944d3210734e27c223c77b76da359699067cd90d4c65/granian-2.7.9-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:e2cf4c926e99718576e4ba201d884a56b0e62d8ede39c7468b26cbe2d98c30e5", size = 7449207, upload-time = "2026-07-03T12:41:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/d8/83/8e5e429ebb1465c998403a60e0078784d28b819954c387a9558d99b6c3c5/granian-2.7.9-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:665a82ce3f48a4b5e1f4cc83115d3b9a2647b3f222d072db6716223e6dfc86d2", size = 7031706, upload-time = "2026-07-03T12:41:31.209Z" }, + { url = "https://files.pythonhosted.org/packages/51/5c/b82a31436c740dd0a87e091998f9c350739cbd260aa3880db6f5418112a7/granian-2.7.9-cp314-cp314-win_amd64.whl", hash = "sha256:74d6e50277621e2faa41931d4ddaeae0735e39a20ebaccc49a2282549a4d5297", size = 4080721, upload-time = "2026-07-03T12:41:32.64Z" }, + { url = "https://files.pythonhosted.org/packages/11/fb/aa241630b4e987c0b343d08c71e20a89098f9c863a12842afa19930f82a0/granian-2.7.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:1a9b34e5d46a48fa6f017d6afa546f4d35aa6a2a10737e9c49c5c9108e6a4280", size = 6254314, upload-time = "2026-07-03T12:41:34.139Z" }, + { url = "https://files.pythonhosted.org/packages/76/1c/787f63df68e28b9e5793efd80c7ba9e1a0e77663c921525552ac3b0d9305/granian-2.7.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea97a1928b414a35903667440875e0f0f4d2f7a341e8d198ab57f0b2cb70af28", size = 6078452, upload-time = "2026-07-03T12:41:36.228Z" }, + { url = "https://files.pythonhosted.org/packages/b5/60/fcc41cd8f394c12785fc2f9d9843ad4329e90a2468d0aaf3474be3255e90/granian-2.7.9-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:57edbef65585ac69d6ada7b9d7cb6fcd6ebeb2e663833c246f72d3634851f5ac", size = 6298703, upload-time = "2026-07-03T12:41:37.906Z" }, + { url = "https://files.pythonhosted.org/packages/05/0f/555c2f436cf386614e8197ffc3a27c6e3a812149fd84367d2f988361b4fe/granian-2.7.9-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e39cdfcd5a0ed8e9e8a5e21cd65085ac3a4c73ac5f258f804aedc729cdfa2bb", size = 7241919, upload-time = "2026-07-03T12:41:39.679Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4c/13bd13b0dcc2697b521e75efbeede7328c0809f3e93e964362fd334c0dd4/granian-2.7.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c254a1b2027612f0df6e47e8059fd304cda287be25421e1dced4d6b56fb054c", size = 6673295, upload-time = "2026-07-03T12:41:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/02/0a/c0b977247bf3dd4efa92903dd316b8089f7e67cb21922e83cdcdd98201f7/granian-2.7.9-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f9669323b5cd90be7565711bc75363eccf75b6aecdbb0b286f1a860199ef800c", size = 6830753, upload-time = "2026-07-03T12:41:42.979Z" }, + { url = "https://files.pythonhosted.org/packages/08/b2/8f50b2d83452ee3100fcd1b26a5de5206b8befeb9d36b79e6c20d5f6dacd/granian-2.7.9-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:c6475981a0cbf766575a0146bdbe52439185a5040cef2a3969214ca1ef6a5e18", size = 6976795, upload-time = "2026-07-03T12:41:44.6Z" }, + { url = "https://files.pythonhosted.org/packages/92/3a/bec3533aaaff69a9a984f3fd578a2ca29548c9d8503770b1ba5c2260aecd/granian-2.7.9-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3022cde5c662716db8fe16838ba351e0897768435b9c732afc9a4a7322ed05d5", size = 7420697, upload-time = "2026-07-03T12:41:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/4526cc50a1fe3addcc29fe3c7c676d64046f724a4d8e4e29d76ba7dcbe04/granian-2.7.9-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c7e3193da47554561142be7a58520b36a01f6a2da57a7bcbe61f970fb53cc0c", size = 6950255, upload-time = "2026-07-03T12:41:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/0a/62/aa89482ffbc3e56d533cf8c87a91cfb58c486e6a77c522fa34c9f8874113/granian-2.7.9-cp314-cp314t-win_amd64.whl", hash = "sha256:d2cef5a15afee90683944a3b23694e97d75adeafd59ef85ff3896bc9462695d2", size = 3992366, upload-time = "2026-07-03T12:41:49.523Z" }, ] [package.optional-dependencies] @@ -2136,16 +2136,16 @@ wheels = [ [[package]] name = "mokkari" -version = "3.27.0" +version = "3.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "pyrate-limiter" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/04/6134ee63b1c0079bdd96633fcd3cb353ebf46c27691d18042f3f64a6ba73/mokkari-3.27.0.tar.gz", hash = "sha256:d45c6ee18e3de3f5c8e7cffbaae44a44969e1f82fa6c82db49b06aae845d13ce", size = 74713, upload-time = "2026-05-28T18:21:23.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/39/4c5543a72c16116772680e4b0cac22b770b1e89daab6d91ad607c46a0d47/mokkari-3.28.0.tar.gz", hash = "sha256:d47d32c511d3e08d8300d4aef9bd94e62fab98b7913d381e5352f8d85d2efc8b", size = 74728, upload-time = "2026-07-02T19:46:26.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/44/3a9847ee5c6d14f4580e1fa94cb7f72d2d344193bf769a10218dc820dc60/mokkari-3.27.0-py3-none-any.whl", hash = "sha256:96c9dcc8297a3376345750eaa9d2d58b99ae366033be792eb147b520b18deaa4", size = 56453, upload-time = "2026-05-28T18:21:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/d44a2af5192c77345ea64da74307cd899584c8cea6112ffd2f5e6fba4cb6/mokkari-3.28.0-py3-none-any.whl", hash = "sha256:db9aeb6bf1e9d81f2fadf8808050116511707ed0707f4bd0a4487d1b35f27bde", size = 56456, upload-time = "2026-07-02T19:46:25.008Z" }, ] [[package]] @@ -2403,20 +2403,20 @@ wheels = [ [[package]] name = "oxipng-pybind" -version = "10.1.1.post2" +version = "10.1.1.post3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/a1/7b3ba274fafeba75a9c6109d34bcecb3504087f5287c0345f3a9fdf58ac2/oxipng_pybind-10.1.1.post2.tar.gz", hash = "sha256:cf71ed8f447ee1db1d1dad19144f8999917e13df0ac4de3776826c12387bf5b8", size = 179553, upload-time = "2026-05-29T11:37:46.13Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/9b/eb6b06f24b765f0dcb7d85c2834541fae9ce6f0be85e2c8eab7a636f1ec0/oxipng_pybind-10.1.1.post3.tar.gz", hash = "sha256:ca9d9cfa015a1bc0fcb341eb5e7c08ef8cb6577917d6d72eee70a10087e076b2", size = 187238, upload-time = "2026-07-03T22:32:24.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/80/5fca2f205121719ac231ff7055961f8ee8c25266d00eb6ae7d0d1bf0e6ef/oxipng_pybind-10.1.1.post2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d8fc5404cd16ea1e48030dbd2d1e3006ded13cd250d6470e37ae2600df67d83e", size = 523107, upload-time = "2026-05-29T11:37:32.235Z" }, - { url = "https://files.pythonhosted.org/packages/42/cc/46d12fd03651a4b480b0eda9f1e0acde07910be445cbc45b2bdc7652192f/oxipng_pybind-10.1.1.post2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b4a7a16b8abbf39c7153aac0c0252aff7ad94a10e3fd4521d7ff00924f5254d0", size = 489513, upload-time = "2026-05-29T11:37:33.719Z" }, - { url = "https://files.pythonhosted.org/packages/c7/50/758b1fbe5c7e45307b1c8c04b94c3491f9f0d64ef40f564e498af1d6cf86/oxipng_pybind-10.1.1.post2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bec6f12dba48907e60f3338cda898c85f833fb0cc168fcfd263e489654136f6", size = 529315, upload-time = "2026-05-29T11:37:35.21Z" }, - { url = "https://files.pythonhosted.org/packages/db/5d/cdfd3f0c71d04650885c62426fa4d4cf69f3a5705378445e6dfc154e45eb/oxipng_pybind-10.1.1.post2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f3d3272b67ff49e8bc3cffa04d9c3ae765edf4901632a7cd02c809d47a4263b3", size = 559277, upload-time = "2026-05-29T11:37:36.466Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bf/b81474e0c18c893de88663d6de1029ace3f4cd6280af8ade6f45d2266e0b/oxipng_pybind-10.1.1.post2-cp310-abi3-win_amd64.whl", hash = "sha256:198a9b88b3b7833ee63396b5ddbc6317e8f102bbf66b043c8474b8a40149bdee", size = 456413, upload-time = "2026-05-29T11:37:37.769Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ed/60409eb255bc0fa4aee3ad0bf5b65e965da4be52da4851b60f67a8cc6208/oxipng_pybind-10.1.1.post2-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d8405c3b6024b22fc5a94e1848d20ea69544ea7d52d2afa3d504d9c777918db4", size = 522784, upload-time = "2026-05-29T11:37:39.01Z" }, - { url = "https://files.pythonhosted.org/packages/22/f2/18031795b4d716d2941e498b4fcbeb23f7f7a106e474207805465e3dd003/oxipng_pybind-10.1.1.post2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:cbbde8dc9385af4e656679e04e45690fd406eb5d6a1621d9b14a5cef148fc692", size = 489085, upload-time = "2026-05-29T11:37:40.639Z" }, - { url = "https://files.pythonhosted.org/packages/31/df/9384a6dd3e61d567c67a5b051e816cd8eda7f1a731e19a6a75eb725c57a3/oxipng_pybind-10.1.1.post2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a21a243dbb0d57e89818739a6ac05172fe25011918088bf1e77d64c2cb696bdc", size = 528754, upload-time = "2026-05-29T11:37:42.074Z" }, - { url = "https://files.pythonhosted.org/packages/20/57/8384e6a2bbc48cd928236e1111b5faa2c16f46c21cfecee5253d9afc7eb9/oxipng_pybind-10.1.1.post2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e9847e0a210b7d971867fea5d11ee81ff86195166e92ce2790e427b38cf4404a", size = 558139, upload-time = "2026-05-29T11:37:43.56Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d4/d8ddd3d4cfc6f0884ee82fb09f04db88c5e19ed3b4af03b931d3800be6cd/oxipng_pybind-10.1.1.post2-cp311-abi3-win_amd64.whl", hash = "sha256:abff496f93da56739979bb84090bdf55bac9b84fbce3b42f1ce0f7ef0b46b7fb", size = 455806, upload-time = "2026-05-29T11:37:44.87Z" }, + { url = "https://files.pythonhosted.org/packages/23/0d/b0c330b7df466a34bd1450d8a948c38b2b1c5a16ca180060d0eb71b2204a/oxipng_pybind-10.1.1.post3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:04849d2d0f2014836c9e5031b4806684e5c8671e1f32aa47fb1995e1212ca3b4", size = 516855, upload-time = "2026-07-03T22:32:08.104Z" }, + { url = "https://files.pythonhosted.org/packages/c2/72/45ff201144278aa0a41a4acb38de46b0cad7fda2bdb048987e6f08db7670/oxipng_pybind-10.1.1.post3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:6fe7a719572d0135c3b512c877a2104c0ff287d3eda9e114492649ad6506eef5", size = 487366, upload-time = "2026-07-03T22:32:09.807Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8e/1a829ef08a7c89e788988f7120aeebe859d6a43c99bb860a02858aa8d0a0/oxipng_pybind-10.1.1.post3-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2dfc6b40abebd58ee643a09d084c7977f9951ea3e56515f8cb9c96a4c09e3975", size = 531147, upload-time = "2026-07-03T22:32:11.679Z" }, + { url = "https://files.pythonhosted.org/packages/79/04/33c4272d73aedc99bef561091623a43d62f3a6de942e47e71943cd652f0e/oxipng_pybind-10.1.1.post3-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ed13db1dcc530ebeeb0d36fb422f945017edf4dee50304182c23665ec9d466be", size = 559186, upload-time = "2026-07-03T22:32:13.366Z" }, + { url = "https://files.pythonhosted.org/packages/16/bc/819547edcd3cab84517672d214dbeb3caa094bcbf4183c1888bbc9c68b1a/oxipng_pybind-10.1.1.post3-cp310-abi3-win_amd64.whl", hash = "sha256:dc7313b75855047499318897a58d761e5883c6a98fa119010ab2a100fc1b785e", size = 461254, upload-time = "2026-07-03T22:32:15.102Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c0/c15f18e7a1b5528e84d453c12c54ee95754a12452b3328c0f5195789eea8/oxipng_pybind-10.1.1.post3-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0de38c64cef6551c94481f9953e2edddd8d9c7ae5387103ff9e0e3c86bfd29c4", size = 515594, upload-time = "2026-07-03T22:32:16.737Z" }, + { url = "https://files.pythonhosted.org/packages/37/91/acf8bf94a762677fe0101e35c45472ee173717ab7395654b947e9a52b4a5/oxipng_pybind-10.1.1.post3-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:f20745f1c9c7e88ab2056442a2184d3ab619c9108e3771169aeb559b85046b9a", size = 486006, upload-time = "2026-07-03T22:32:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e1/338fccf1a0991889bd72b2e346f46935e95abf19e7002a7fc7132c4b5a39/oxipng_pybind-10.1.1.post3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a2d473e6333f90f02d1b400c890edac745d1f95cb27aaa6521a77a9a14a2525a", size = 528312, upload-time = "2026-07-03T22:32:20.167Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/5bd5b6510567c64563967d38d36b9d7aec62df48f3a17299f6fe63e338db/oxipng_pybind-10.1.1.post3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b5b441203f1675b42d0049aa000cc1c5fd672c1ae8604f86c89a880ba60a9751", size = 557812, upload-time = "2026-07-03T22:32:21.937Z" }, + { url = "https://files.pythonhosted.org/packages/11/91/5b6c5661dc6abb2df1b7ed3932c483c9c842aed5fd6148644a8fb48bfe8b/oxipng_pybind-10.1.1.post3-cp311-abi3-win_amd64.whl", hash = "sha256:8de00b373808def0f9090670d429ae3c2e05fefe1b0307a6e60290355f44394c", size = 460466, upload-time = "2026-07-03T22:32:23.347Z" }, ] [[package]] @@ -2518,71 +2518,73 @@ wheels = [ [[package]] name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, ] [[package]] @@ -3964,27 +3966,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.55" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/48/f687c8d268e3581f2f104d1f2ac5944d5b5e841b3695c613b3f263e5bbf7/ty-0.0.55.tar.gz", hash = "sha256:88ca87073825a79a8327c550efcc86cec94344890244c5946f84c9e44a969f31", size = 6040230, upload-time = "2026-06-27T00:27:29.385Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a3/1a90ba7e5a61c6d09adb92346ddba97668095fc257b577af433e5ac4f404/ty-0.0.55-py3-none-linux_armv6l.whl", hash = "sha256:31e83eef512d066542fe990fe1a3b814423abd1616376c54e48af7045b3e1749", size = 11677249, upload-time = "2026-06-27T00:26:52.18Z" }, - { url = "https://files.pythonhosted.org/packages/82/3a/669f9aa478c38243e213a2684db1502086026cfadc15bb1b29b7cbde030d/ty-0.0.55-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab4bca857950608fea73e269e2da369d43e6467131de85160d68e2fa466fa248", size = 11444180, upload-time = "2026-06-27T00:26:54.576Z" }, - { url = "https://files.pythonhosted.org/packages/15/a4/6a4b2507a53ce6530c66c5b4fe0d58551eb1748ffa9e0696c32fdd55bbd4/ty-0.0.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:55032bfd31bf2c5355ee81bdc6407b144a1cc7ee41e5681dd1368e4cef2ba327", size = 10963134, upload-time = "2026-06-27T00:26:57.348Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ae/a3b1a0f1cc83b7d258662cb98aa80a720c2e671d0e8fa0d17a4d5d057a7a/ty-0.0.55-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1e049f69ce65b3c269af67624607f435e1c32319786c1e453ef9611502f295", size = 11493517, upload-time = "2026-06-27T00:26:59.26Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9f/311ce39065a979ef40a9b847f685c8e02464e53adf1671e081eea90640ca/ty-0.0.55-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:631409975c681d5a280fc5a99b7b32e9e801f33be7567c6b42ec331362f59d7d", size = 11460590, upload-time = "2026-06-27T00:27:01.425Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/3bf29aa77bd78aae48275153135a2052fa7d3ccdf1ecabeb99c8773abd66/ty-0.0.55-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e08cb0436e68b9351555ae8f2697138c9009b4d5b4ae4272232988b2a431a98f", size = 12098430, upload-time = "2026-06-27T00:27:03.596Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6e/e88411a88240b94640bba06fb6d0d92b247fbeef47ee2bc71f39e58c2558/ty-0.0.55-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16c215ad9f823829409b94ee188cfaa4563f6e1384f6ce3fecb1db75f6c7cf7c", size = 12673086, upload-time = "2026-06-27T00:27:05.589Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7e/8f1762fb7f9245a68ba5ae338d73c59403ce57554e5d311b8bb55027b0ec/ty-0.0.55-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b510eb8f4032baf11b7aee2f1d53babc3b4ca03939b9cdcf6a9d15761d575188", size = 12242559, upload-time = "2026-06-27T00:27:07.714Z" }, - { url = "https://files.pythonhosted.org/packages/72/1f/143657daf2670d977dac83435f1fe03d4843efb798d8e1e75950e541aadd/ty-0.0.55-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ddc05e7959709c3b9b83aa627128a80446865e3c1a4882638dcff6d776dc34a", size = 12021409, upload-time = "2026-06-27T00:27:09.881Z" }, - { url = "https://files.pythonhosted.org/packages/6d/30/69487c439dd1fad3a4a3d96f0a472193de297eaba6fc4b8ea687ce434ac2/ty-0.0.55-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:636e8e5078787b8c6916c94e1406719f10189a4ca6b37b813a5922ce5857a8c7", size = 12303807, upload-time = "2026-06-27T00:27:11.986Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ca/cd88b6493dafc7db077f5e17c0438eb3af6e2d6d08f616dbb52a8ddfd567/ty-0.0.55-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ef7d6deaacb73fec603666b5471f1dc5a5699aa84e11a6d4d644dd07ca72121e", size = 11441263, upload-time = "2026-06-27T00:27:14.087Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fe/66b6915671653ab739f71e4f1b0528e69da64429b7ebf3840c625b6e43f2/ty-0.0.55-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9aeea0fe5875d3cf37faf0e44d0fdf9669335467749741b8fc0103916fb5cd32", size = 11484584, upload-time = "2026-06-27T00:27:16.311Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4f/7a9c0bbac8b899e9f6c0ec110c6612f52e4db35f6bb17ddc0ef60384fa3e/ty-0.0.55-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b699c01310dbd2705a07c97c5f4aaeedef61bd9adeea2e7c46aed32401d3576", size = 11759309, upload-time = "2026-06-27T00:27:18.471Z" }, - { url = "https://files.pythonhosted.org/packages/ca/de/b6f8b1b69aa631b5716ef3f985c3b56de0e46c2499cc00d30c402b41f714/ty-0.0.55-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32cbeba543e46de2a983ec6d525d8b56514f7422bd1e1b57c44ccf7bfa72c38a", size = 12128755, upload-time = "2026-06-27T00:27:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/7d/90/a912531e51ee7e076b42972479290fa687c0f5e747b7e773f3033164acaa/ty-0.0.55-py3-none-win32.whl", hash = "sha256:52b968e24eb4f7a5c3bd251db1f99f60dd385890356d38fc619d84f1b423446a", size = 11117501, upload-time = "2026-06-27T00:27:22.714Z" }, - { url = "https://files.pythonhosted.org/packages/4c/7a/99d59843bf8908a7f9f4d13fda107dbad07b7faa28ecd7860eacf363fb1c/ty-0.0.55-py3-none-win_amd64.whl", hash = "sha256:bf39cbfdc0add44d94bd3fff1f53c351418d134b6a66b87efdb7876d7b7a2224", size = 12150106, upload-time = "2026-06-27T00:27:24.881Z" }, - { url = "https://files.pythonhosted.org/packages/b3/44/20987505cedf2a865b08482f0eabc181fd9599b062964057ec8a128a4296/ty-0.0.55-py3-none-win_arm64.whl", hash = "sha256:f7f3700a9a060e8f1af11e4fb63fafcaf272b041781f4ccdfda2b3b5c6c1e439", size = 11560157, upload-time = "2026-06-27T00:27:27.332Z" }, +version = "0.0.56" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/07/fb29aea5235b0aa8ecfc4d1cc6ddf9fba8b863d67d96c6d345694d644c43/ty-0.0.56.tar.gz", hash = "sha256:84d114dc3796361c0fc72945016eabd74d46b9ee64f198cb0e485719704681e5", size = 6050123, upload-time = "2026-07-01T16:44:56.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/48/bce79e7ca5c1cc529d3e0d37ddd1121aea4b68a4f749974ad1cc77161871/ty-0.0.56-py3-none-linux_armv6l.whl", hash = "sha256:186d4a53e15747c947e1ec3d7eec8e345d8e40a1ca10e634c585db52497e87dd", size = 11643066, upload-time = "2026-07-01T16:44:18.374Z" }, + { url = "https://files.pythonhosted.org/packages/80/d1/22555d8a1d719661f10050f3865d877bbf497da908961c75fe22217dd18a/ty-0.0.56-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aae1a980fd9535da0469b7ba2b2e1b54a907743a5e0f442dd57eee9f5bfd034c", size = 11407487, upload-time = "2026-07-01T16:44:20.956Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2d/b3b7a74ce8bc59ef48843ad80179bb0d9598bbd6cfc0d11d519bdf6b1352/ty-0.0.56-py3-none-macosx_11_0_arm64.whl", hash = "sha256:afd3058c0a6c5f241e814734f133008c93ee805f61c9cf4ce7412b8822b5d9ad", size = 10962270, upload-time = "2026-07-01T16:44:22.959Z" }, + { url = "https://files.pythonhosted.org/packages/64/ac/6c2fd7de0304a8a7218a756af74f7e62a5e8540fdb175e0a869e51042345/ty-0.0.56-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058b52f7a823ac13aae3cae30809dd6b5145794b64d8478f9ef38c75d79b4483", size = 11471406, upload-time = "2026-07-01T16:44:25.327Z" }, + { url = "https://files.pythonhosted.org/packages/50/b6/11d861156861c03c7726b74558f9a0e0092661aff83a4fda1279df28c425/ty-0.0.56-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c66e00c1522add1f2bbdd2e45828c953b35c306b7bef03ec9169c75a63699a0", size = 11445612, upload-time = "2026-07-01T16:44:27.531Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ba/09df108582090f3c0770ec4bc8675affed60248f6793a78d909be16211d9/ty-0.0.56-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40903d71c669a30691b5a5d5728056c7877a1bd6be4f233a38883a8b28cf34d7", size = 12093889, upload-time = "2026-07-01T16:44:29.548Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f7/dbb4b4ccb69cd64c209ae55b1ab788ace8222c2bc1f6845be9e7cbedbf25/ty-0.0.56-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63fe3947fe0c46c69a7d950e6832ee70a9ec17321fefbff3d2e3c20baf9e5bd0", size = 12666337, upload-time = "2026-07-01T16:44:31.586Z" }, + { url = "https://files.pythonhosted.org/packages/86/e9/73f903fe4a3d9ea02f26f57c1eb07e3b1029ec92b0e8c2364718893440e3/ty-0.0.56-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a0c1a72f9854532e710e119b6871ffe4542c8a65146f1f65dcd78fecd885b4", size = 12280247, upload-time = "2026-07-01T16:44:33.637Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/cebd222495832f1a00dcd321ba25f3cab804221a4991b992c2178bec68ee/ty-0.0.56-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70d1665596494e24d8ebd198438872b5a56ec3cae5f2bcf6c673be797acc4e3c", size = 11991107, upload-time = "2026-07-01T16:44:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/b7/07/8f7337a07250f42d975cdb6decf47fc5b421e6c7da5e3e7be1e85f63a7e5/ty-0.0.56-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:778f99e51558afc1dbbe48ee38ab6aae7b31390ed8c1a1ef1499b295e9f1e82f", size = 12298970, upload-time = "2026-07-01T16:44:38.243Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b9/a52cd59034a48f5f18c6b155cc2cc36861d874b6d0af204b12c898024c3d/ty-0.0.56-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:867bc5708e0066bb4ff6c7db524bd5deea2676c62bfe71d3303138b3be850af0", size = 11425683, upload-time = "2026-07-01T16:44:40.473Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2e/48e42d33357d52eefb695c0c3fcfc96879b73668a7447d1d1e0ad774fedc/ty-0.0.56-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6012f4189c928edb330a37deb9930f982380bd4aa7c4b8e0428eec9651c7551", size = 11469258, upload-time = "2026-07-01T16:44:42.513Z" }, + { url = "https://files.pythonhosted.org/packages/d5/01/ad1b4138be1e3fa97863af3925aa2134f17a593240c35dc38c3429fb5ad1/ty-0.0.56-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee83de1a7ff4cc32837ec06134ce391d441bc5b35ecd8d3cfe053f120f3e4c1", size = 11758736, upload-time = "2026-07-01T16:44:44.567Z" }, + { url = "https://files.pythonhosted.org/packages/09/34/9d81967ff240eaa57e9249728ef7b7790747cf6d3c9a98ec86b2cfdcc8ee/ty-0.0.56-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:62619b3b0e2c6248ef30d3f0e2f2217ae9893040585be07f32324242f197cd6f", size = 12100242, upload-time = "2026-07-01T16:44:46.584Z" }, + { url = "https://files.pythonhosted.org/packages/c3/36/f51d4666d2de6cf33c1f3a1fcc4bb6b70b197dd6ceaa491eef71d78fe8e8/ty-0.0.56-py3-none-win32.whl", hash = "sha256:b30687bb5cd9729d34c889a289edf32770388d9bb05243e534e723fb45e0381b", size = 11093759, upload-time = "2026-07-01T16:44:49.171Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b4/8fb5d4acfa4afb152245b20fa263069a7547bd1f8e4bfca4eda280c897d7/ty-0.0.56-py3-none-win_amd64.whl", hash = "sha256:ad4c8c47b6f4e3f9ed3fc0b1a5d650088d229e17dd8f63c1826d6bbe94cc3235", size = 12100327, upload-time = "2026-07-01T16:44:51.26Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fc/6a183e71edde90d0c35c2303f23f7a45b6891d1a2c45daf7b8f869831e19/ty-0.0.56-py3-none-win_arm64.whl", hash = "sha256:57538f273d444a5f1293fa7860e967178afe3917611fc5eff16b64e1204fe0d6", size = 11538780, upload-time = "2026-07-01T16:44:53.8Z" }, ] [[package]] @@ -4035,11 +4037,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] From 27783ee991b02ec16dcd491f21f366944e4e245f Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 19:19:55 -0700 Subject: [PATCH 03/82] test(frontend): work around vitest/valid-expect false positive on expect.any The @vitest/eslint-plugin valid-expect rule misclassifies expect.any() as chai's `.any` flag chain and reports "unknown modifier". Disable the rule on the one nested assertion with a documented reason rather than weakening it. Co-Authored-By: Claude Opus 4.8 --- frontend/tests/unit/rename-toggle.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/tests/unit/rename-toggle.test.js b/frontend/tests/unit/rename-toggle.test.js index 17899d611..6a2182567 100644 --- a/frontend/tests/unit/rename-toggle.test.js +++ b/frontend/tests/unit/rename-toggle.test.js @@ -114,6 +114,7 @@ describe("EditPanel — rename toggle", () => { await wrapper.vm.fetchRenamePreview(); expect(HTTP.post).toHaveBeenCalledWith( "/admin/tag-write/preflight", + // eslint-disable-next-line vitest/valid-expect -- false positive: the plugin misreads expect.any() as chai's `.any` flag chain expect.objectContaining({ patch: expect.any(String) }), ); expect(wrapper.vm.renamePreviews).toEqual([ From 4963ad9b931486fa4bad07d385bf0fb947396ac4 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 19:20:11 -0700 Subject: [PATCH 04/82] update deps --- bun.lock | 4 ++-- package.json | 2 +- pyproject.toml | 2 +- uv.lock | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 8c110c96e..841e1577f 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,7 @@ "@fsouza/prettierd": "^0.28.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.20", + "@vitest/eslint-plugin": "^1.6.21", "eslint": "^10.6.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-array-func": "^5.1.1", @@ -225,7 +225,7 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.12.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA=="], - "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.20", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-xRwWHFG0Utp6hXtbGiWk4VdKXCGdExD8kbWrrmFEiG5dk8anOJ+vbWbeOa8EbkocKQRTsx7JAWETccZiBgFp/Q=="], + "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.21", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-5nu7QuW5Dg9v4I9t9ZiU1Hh91BoG0x3lOzOWYTsr1bqpIqcHYzFSQ052LvZwGtEm7Trh+Mr2heYEmZ4LkDjfaA=="], "@xml-tools/parser": ["@xml-tools/parser@1.0.11", "", { "dependencies": { "chevrotain": "7.1.1" } }, "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA=="], diff --git a/package.json b/package.json index 235ab73f8..5fd1e49ec 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@fsouza/prettierd": "^0.28.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.20", + "@vitest/eslint-plugin": "^1.6.21", "eslint": "^10.6.0", "eslint_d": "^15.0.2", "eslint-config-prettier": "^10.1.8", diff --git a/pyproject.toml b/pyproject.toml index 68ead7117..28dc0fbb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "adrf~=0.1.12", "bidict~=0.23", "channels~=4.2", - "comicbox[pdf]~=4.0.4", + "comicbox[pdf]~=4.0.5", "cryptography>=48.0.0", "dateparser~=1.2", "django-cachalot~=2.8", diff --git a/uv.lock b/uv.lock index 240d9cca7..7361ed8ca 100644 --- a/uv.lock +++ b/uv.lock @@ -636,7 +636,7 @@ requires-dist = [ { name = "adrf", specifier = "~=0.1.12" }, { name = "bidict", specifier = "~=0.23" }, { name = "channels", specifier = "~=4.2" }, - { name = "comicbox", extras = ["pdf"], specifier = "~=4.0.4" }, + { name = "comicbox", extras = ["pdf"], specifier = "~=4.0.5" }, { name = "cryptography", specifier = ">=48.0.0" }, { name = "dateparser", specifier = "~=1.2" }, { name = "django", specifier = "~=6.0" }, @@ -721,7 +721,7 @@ wheels = [ [[package]] name = "comicbox" -version = "4.0.4" +version = "4.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, @@ -758,9 +758,9 @@ dependencies = [ { name = "xmltodict" }, { name = "zipremove" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/ea/2067d26988699d7fcba228d4848356ad55c2a424f052e3c666a1121054e4/comicbox-4.0.4.tar.gz", hash = "sha256:acf185767938c7ae15d401716961fe95de3e6756e9b8f30d85b9f88b94b1f270", size = 978642, upload-time = "2026-06-30T16:48:26.325Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/b7/06d22b40edba18a7d66988d376205184b1b580ed0f7ab69d7340051f36b6/comicbox-4.0.5.tar.gz", hash = "sha256:337b8ae057a684d814d78b2460efdbe6d2df403d23060977b2f853bc1f0dfd0b", size = 978125, upload-time = "2026-07-04T02:11:17.052Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/a5/4e4dbcb9e7635df0039aabdda3796a1ad2d79c20782ede1e8e633d8d4d83/comicbox-4.0.4-py3-none-any.whl", hash = "sha256:31ac3b960531930c498fd3f6645ef65311868178f583ee55374187a720756cea", size = 328967, upload-time = "2026-06-30T16:48:24.8Z" }, + { url = "https://files.pythonhosted.org/packages/8c/86/ee66f8fd03982dc931ebf17cb68c05a7834d90078ba5ec9c302d8b879dcf/comicbox-4.0.5-py3-none-any.whl", hash = "sha256:6c71ed43fd0c7295cdbc7380fe5bbf04ef5560f00f3356d6f97559b4ca401185", size = 328601, upload-time = "2026-07-04T02:11:15.394Z" }, ] [package.optional-dependencies] From 2462a41adc6eb28bcaaf88551afd17923f4e07d3 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 19:36:00 -0700 Subject: [PATCH 05/82] fix(onlinetag): drop dead effort option; model Metron's fewer requests comicbox 4.0.5 no longer applies the effort knob to Metron tagging, and Metron's search is now a flat two-step (series_list + issues_list) that match mode does not change. - Remove the vestigial `effort` option (serializer, task, resume params, and test). It was collected by the API but never passed to comicbox's OnlineSession. - Count estimate calls-per-comic per source: Metron a flat 2, Comic Vine keeps its per-mode 2/3/5. First-match-wins bills the costliest single source; merge sums per-source calls. Mirrored in the launcher dialog. - Resume view drops unknown persisted params so a pre-upgrade `effort` key in the file-based cache can't crash the task rebuild. Co-Authored-By: Claude Opus 4.8 --- NEWS.md | 5 ++ codex/librarian/onlinetag/estimate.py | 47 +++++++++++----- codex/librarian/onlinetag/session_manager.py | 1 - codex/librarian/onlinetag/tasks.py | 1 - codex/serializers/admin/tagging.py | 1 - codex/views/admin/onlinetag.py | 7 +++ .../components/online-tag/launcher-dialog.vue | 39 +++++++++----- frontend/tests/unit/launcher-dialog.test.js | 10 ++-- tests/test_onlinetag_estimate.py | 54 +++++++++++-------- tests/test_onlinetag_resume_endpoints.py | 1 - 10 files changed, 112 insertions(+), 54 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5866c0940..637e08ea5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,11 @@ border-radius: 128px; - Fixes - Fix unable to change password. + - Online tagging time and request estimates now reflect Metron's fewer + requests. + +- Performance + - Metron online tagging makes far fewer API requests (comicbox 4.0.5). ## v2.1.0 - Renaming diff --git a/codex/librarian/onlinetag/estimate.py b/codex/librarian/onlinetag/estimate.py index b681294b6..f7f2cbf50 100644 --- a/codex/librarian/onlinetag/estimate.py +++ b/codex/librarian/onlinetag/estimate.py @@ -10,12 +10,18 @@ The model is deliberately simple: a run's wall-clock is bound by the slowest enabled source's per-minute throughput (first-match-wins means a comic isn't done until the binding source answers), times the number of API -calls each comic needs (a function of match mode), over the comics left. +calls each comic needs, over the comics left. -When merging all sources (comicbox first_wins=False) every enabled source is -queried for every comic instead of stopping at the first match, so the call -count is multiplied by the number of sources — a conservative over-estimate -consistent with the slowest-source bottleneck above. +API calls per comic are counted per source. Metron (comicbox 4.0.5+) is a +fixed two-step search — one series_list discovery call plus one issues_list +lookup — that no longer scales with match mode, so it is a flat count. Comic +Vine's fuzzy volume→issue path does more verification the tighter the match +mode, so it keeps a per-mode count. + +Under first-match-wins a comic stops at the first source that answers, so we +conservatively bill the costliest single selected source. When merging all +sources (comicbox first_wins=False) every enabled source is queried for +every comic, so the per-comic calls are summed instead. """ from __future__ import annotations @@ -29,16 +35,27 @@ # truth shared with the estimate math below. SOURCE_RATE_PER_MINUTE: Final = MappingProxyType({"metron": 20, "comicvine": 3}) -# API calls per comic by match mode. Matches MATCH_MODE_CALLS_PER_COMIC. -_MATCH_MODE_CALLS: Final = MappingProxyType({"eager": 2, "auto": 3, "careful": 5}) +# API calls per comic, per source. Matches the launcher-dialog's +# METRON_CALLS_PER_COMIC / COMICVINE_CALLS_BY_MODE. Metron's two-step search +# (series_list + issues_list) is a flat count that match mode does not change; +# Comic Vine's count scales with match mode. +_METRON_CALLS_PER_COMIC: Final = 2 +_COMICVINE_CALLS_BY_MODE: Final = MappingProxyType( + {"eager": 2, "auto": 3, "careful": 5} +) -# Fallbacks for unknown mode / no recognized source, mirroring the dialog. +# Fallbacks for an unknown mode / unrecognized source, mirroring the dialog. _DEFAULT_CALLS_PER_COMIC: Final = 3 _DEFAULT_RATE_PER_MINUTE: Final = 10 -def _calls_per_comic(mode: str) -> int: - return _MATCH_MODE_CALLS.get(mode, _DEFAULT_CALLS_PER_COMIC) +def _calls_per_comic(source: str, mode: str) -> int: + """Return the API calls one comic costs against a single source under ``mode``.""" + if source == "metron": + return _METRON_CALLS_PER_COMIC + if source == "comicvine": + return _COMICVINE_CALLS_BY_MODE.get(mode, _DEFAULT_CALLS_PER_COMIC) + return _DEFAULT_CALLS_PER_COMIC def _slowest_rate_per_minute(sources: tuple[str, ...]) -> int: @@ -56,8 +73,12 @@ def estimate_seconds( """Estimated seconds to look up ``remaining_comics`` more comics.""" if remaining_comics <= 0 or not sources: return 0.0 - # Merge mode queries every source per comic instead of the first match. - source_multiplier = len(sources) if merge_all_sources else 1 - total_calls = remaining_comics * _calls_per_comic(mode) * source_multiplier + per_source_calls = [_calls_per_comic(source, mode) for source in sources] + # Merge queries every source per comic (sum); first-match-wins stops at + # the first source that answers, so bill the costliest single source. + calls_per_comic = ( + sum(per_source_calls) if merge_all_sources else max(per_source_calls) + ) + total_calls = remaining_comics * calls_per_comic minutes = total_calls / _slowest_rate_per_minute(sources) return minutes * 60.0 diff --git a/codex/librarian/onlinetag/session_manager.py b/codex/librarian/onlinetag/session_manager.py index 773e5398f..a45df5a3d 100644 --- a/codex/librarian/onlinetag/session_manager.py +++ b/codex/librarian/onlinetag/session_manager.py @@ -606,7 +606,6 @@ def run_session(self, task: BulkOnlineTagTask) -> None: "sources": list(task.sources), "mode": task.mode, "prompts_mode": task.prompts_mode, - "effort": task.effort, "auto_threshold": task.auto_threshold, "delete_original": task.delete_original, "merge_all_sources": task.merge_all_sources, diff --git a/codex/librarian/onlinetag/tasks.py b/codex/librarian/onlinetag/tasks.py index 44adb2f59..f34d3fbb7 100644 --- a/codex/librarian/onlinetag/tasks.py +++ b/codex/librarian/onlinetag/tasks.py @@ -19,7 +19,6 @@ class BulkOnlineTagTask(OnlineTagTask): sources: tuple[str, ...] = ("metron", "comicvine") mode: str = "auto" prompts_mode: str = "ask" - effort: str = "balanced" auto_threshold: float = 0.85 delete_original: bool = False dry_run: bool = False diff --git a/codex/serializers/admin/tagging.py b/codex/serializers/admin/tagging.py index c01c3a344..61d1e2a18 100644 --- a/codex/serializers/admin/tagging.py +++ b/codex/serializers/admin/tagging.py @@ -58,7 +58,6 @@ class OnlineTagStartSerializer(Serializer): ) mode = CharField(required=False, default="auto") prompts_mode = CharField(required=False, default="ask") - effort = CharField(required=False, default="balanced") auto_threshold = CharField(required=False, default="0.85") dry_run = CharField(required=False, default="false") delete_original = BooleanField(required=False, default=None) diff --git a/codex/views/admin/onlinetag.py b/codex/views/admin/onlinetag.py index 7b1f3722c..df1a914bd 100644 --- a/codex/views/admin/onlinetag.py +++ b/codex/views/admin/onlinetag.py @@ -2,6 +2,7 @@ import contextlib import uuid +from dataclasses import fields from rest_framework.response import Response from rest_framework.status import HTTP_202_ACCEPTED, HTTP_409_CONFLICT @@ -147,6 +148,12 @@ def post(self, _request): params = dict(resume.get("params") or {}) # sources round-trips through JSON as a list; the task wants a tuple. params["sources"] = tuple(params.get("sources") or ()) + # A resume descriptor persists to a file-based cache, so one written by + # an older Codex may carry keys a task field no longer accepts (e.g. the + # removed ``effort`` knob). Drop unknown keys so resuming across an + # upgrade rebuilds the task instead of raising TypeError. + valid_fields = {f.name for f in fields(BulkOnlineTagTask)} + params = {k: v for k, v in params.items() if k in valid_fields} session_id = str(uuid.uuid4()) task = BulkOnlineTagTask( comic_pks=frozenset(remaining), diff --git a/frontend/src/components/online-tag/launcher-dialog.vue b/frontend/src/components/online-tag/launcher-dialog.vue index f472d847d..ef135d3cf 100644 --- a/frontend/src/components/online-tag/launcher-dialog.vue +++ b/frontend/src/components/online-tag/launcher-dialog.vue @@ -204,11 +204,25 @@ const SOURCE_RATES = { comicvine: { perMinute: 3, perHour: 200, label: "Comic Vine" }, }; -const MATCH_MODE_CALLS_PER_COMIC = { +// API calls per comic, per source. Keep in sync with +// codex/librarian/onlinetag/estimate.py. Metron (comicbox 4.0.5+) is a flat +// two-step search (series_list + issues_list) that match mode doesn't change; +// Comic Vine's count scales with match mode. +const METRON_CALLS_PER_COMIC = 2; +const COMICVINE_CALLS_BY_MODE = { eager: 2, auto: 3, careful: 5, }; +const DEFAULT_CALLS_PER_COMIC = 3; + +function callsForSource(source, mode) { + if (source === "metron") return METRON_CALLS_PER_COMIC; + if (source === "comicvine") { + return COMICVINE_CALLS_BY_MODE[mode] || DEFAULT_CALLS_PER_COMIC; + } + return DEFAULT_CALLS_PER_COMIC; +} // Only these sources support online id tagging (mirrors the backend // KNOWN_SOURCES); other identifiers a comic carries aren't offered. @@ -414,18 +428,19 @@ export default { // Nothing to merge with fewer than two sources selected. return this.activeSources.length >= 2; }, - callsPerComic() { - return MATCH_MODE_CALLS_PER_COMIC[this.matchMode] || 3; - }, totalCalls() { - // Merge mode queries every active source per comic instead of stopping - // at the first match, so calls scale with the number of sources. Keep - // this in sync with estimate_seconds in codex/librarian/onlinetag. - const sourceMultiplier = - this.mergeAllSources && this.activeSources.length - ? this.activeSources.length - : 1; - return this.comicCount * this.callsPerComic * sourceMultiplier; + // Per-comic API calls are counted per source. Merge mode queries every + // active source per comic (sum); first-match-wins stops at the first + // source that answers, so bill the costliest single source. Keep this in + // sync with estimate_seconds in codex/librarian/onlinetag/estimate.py. + const perSource = this.activeSources.map((s) => + callsForSource(s, this.matchMode), + ); + if (perSource.length === 0) return 0; + const callsPerComic = this.mergeAllSources + ? perSource.reduce((sum, n) => sum + n, 0) + : Math.max(...perSource); + return this.comicCount * callsPerComic; }, slowestRate() { let minRate = Infinity; diff --git a/frontend/tests/unit/launcher-dialog.test.js b/frontend/tests/unit/launcher-dialog.test.js index 4a4f36487..b00377e11 100644 --- a/frontend/tests/unit/launcher-dialog.test.js +++ b/frontend/tests/unit/launcher-dialog.test.js @@ -374,7 +374,7 @@ describe("OnlineTagLauncherDialog", () => { expect(wrapper.vm.canMerge).toBe(false); }); - test("doubles the estimated calls when merging two sources", async () => { + test("sums per-source calls when merging two sources", async () => { const { wrapper } = mountDialog({ book: { collection: "series", pk: 3, ids: [3], childCount: 10 }, }); @@ -382,12 +382,14 @@ describe("OnlineTagLauncherDialog", () => { wrapper.vm.activeTab = "search"; wrapper.vm.sources = ["metron", "comicvine"]; await wrapper.vm.$nextTick(); - const firstWinsCalls = wrapper.vm.totalCalls; + // First-match-wins bills the costliest source: max(metron 2, comicvine + // auto 3) = 3 calls/comic x 10 comics. + expect(wrapper.vm.totalCalls).toBe(30); wrapper.vm.mergeAllSources = true; await wrapper.vm.$nextTick(); - - expect(wrapper.vm.totalCalls).toBe(firstWinsCalls * 2); + // Merge sums per-source calls: (metron 2 + comicvine 3) x 10 comics. + expect(wrapper.vm.totalCalls).toBe(50); }); }); }); diff --git a/tests/test_onlinetag_estimate.py b/tests/test_onlinetag_estimate.py index 595ca394f..279b97667 100644 --- a/tests/test_onlinetag_estimate.py +++ b/tests/test_onlinetag_estimate.py @@ -1,22 +1,25 @@ """ Unit tests for the online tagging time estimate. -Mirrors the launcher dialog's algorithm: total calls (comics x calls-per-mode) -over the slowest enabled source's per-minute rate. +Mirrors the launcher dialog's algorithm: per-comic API calls (counted per +source — first-match-wins bills the costliest source, merge sums them) over +the slowest enabled source's per-minute rate. """ from __future__ import annotations from typing import Final +import pytest + from codex.librarian.onlinetag.estimate import estimate_seconds -_AUTO_METRON_SECONDS: Final = 90.0 +_AUTO_METRON_SECONDS: Final = 60.0 _SLOWEST_SOURCE_SECONDS: Final = 600.0 -_CAREFUL_SECONDS: Final = 150.0 -_EAGER_SECONDS: Final = 60.0 +_COMICVINE_CAREFUL_SECONDS: Final = 1000.0 +_COMICVINE_EAGER_SECONDS: Final = 400.0 _UNKNOWN_DEFAULTS_SECONDS: Final = 180.0 -_MERGE_TWO_SOURCES_SECONDS: Final = 1200.0 +_MERGE_TWO_SOURCES_SECONDS: Final = 1000.0 def test_zero_remaining_is_zero() -> None: @@ -30,14 +33,23 @@ def test_no_sources_is_zero() -> None: def test_auto_mode_metron() -> None: - """10 comics x 3 calls / 20 per-minute = 1.5 min = 90s.""" + """Metron's flat two-step search: 10 comics x 2 calls / 20 per-minute = 60s.""" assert estimate_seconds(10, "auto", ("metron",)) == _AUTO_METRON_SECONDS +def test_metron_calls_are_mode_independent() -> None: + """Match mode does not change Metron's fixed two-step request count.""" + auto = estimate_seconds(10, "auto", ("metron",)) + careful = estimate_seconds(10, "careful", ("metron",)) + eager = estimate_seconds(10, "eager", ("metron",)) + assert auto == careful == eager == _AUTO_METRON_SECONDS + + def test_slowest_source_binds() -> None: """ Comicvine (3/min) is slower than metron (20/min) and binds the rate. + First-match-wins bills the costliest source: max(metron 2, comicvine 3) = 3. 10 x 3 / 3 = 10 min = 600s. """ assert ( @@ -45,12 +57,12 @@ def test_slowest_source_binds() -> None: ) -def test_match_mode_scales_calls() -> None: - """Careful (5 calls/comic) and eager (2 calls/comic) scale total calls.""" - careful = estimate_seconds(10, "careful", ("metron",)) - eager = estimate_seconds(10, "eager", ("metron",)) - assert careful == _CAREFUL_SECONDS - assert eager == _EAGER_SECONDS +def test_comicvine_match_mode_scales_calls() -> None: + """Comic Vine's per-comic calls scale with match mode (careful 5, eager 2).""" + careful = estimate_seconds(10, "careful", ("comicvine",)) + eager = estimate_seconds(10, "eager", ("comicvine",)) + assert careful == pytest.approx(_COMICVINE_CAREFUL_SECONDS) + assert eager == pytest.approx(_COMICVINE_EAGER_SECONDS) def test_unknown_mode_and_source_use_defaults() -> None: @@ -62,17 +74,17 @@ def test_unknown_mode_and_source_use_defaults() -> None: assert estimate_seconds(10, "bogus", ("unknown",)) == _UNKNOWN_DEFAULTS_SECONDS -def test_merge_all_sources_multiplies_calls() -> None: +def test_merge_all_sources_sums_per_source_calls() -> None: """ - Merge mode queries every source per comic, so calls scale with source count. + Merge mode queries every source per comic, so per-comic calls are summed. - 10 x 3 x 2 sources / 3 per-minute (slowest) = 20 min = 1200s, double the - first-match-wins estimate. + metron 2 + comicvine 3 = 5 calls/comic over 3 per-minute (slowest). + 10 x 5 / 3 = 1000s — more than the 600s first-match-wins estimate but not + double it, since the two sources cost different amounts. """ - assert ( - estimate_seconds(10, "auto", ("metron", "comicvine"), merge_all_sources=True) - == _MERGE_TWO_SOURCES_SECONDS - ) + assert estimate_seconds( + 10, "auto", ("metron", "comicvine"), merge_all_sources=True + ) == pytest.approx(_MERGE_TWO_SOURCES_SECONDS) def test_merge_all_sources_single_source_is_noop() -> None: diff --git a/tests/test_onlinetag_resume_endpoints.py b/tests/test_onlinetag_resume_endpoints.py index 174e2ce66..265c5acd4 100644 --- a/tests/test_onlinetag_resume_endpoints.py +++ b/tests/test_onlinetag_resume_endpoints.py @@ -26,7 +26,6 @@ "sources": ["metron", "comicvine"], "mode": "auto", "prompts_mode": "ask", - "effort": "balanced", "auto_threshold": 0.85, "delete_original": False, "merge_all_sources": False, From 8245efa8c40560550653561c84ac4fb90df402bf Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 19:40:04 -0700 Subject: [PATCH 06/82] trim news --- NEWS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 637e08ea5..4677afc65 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,8 +10,6 @@ border-radius: 128px; - Fixes - Fix unable to change password. - - Online tagging time and request estimates now reflect Metron's fewer - requests. - Performance - Metron online tagging makes far fewer API requests (comicbox 4.0.5). From a1845afdcb2c0cf3658c2fa0b0789651f2e2430b Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 20:25:05 -0700 Subject: [PATCH 07/82] refactor(onlinetag): derive source list + issue-id parser from comicbox Two facilities codex hand-synced from comicbox now consume it directly: - Source names: KNOWN_SOURCES and the task/serializer/frontend default lists derive from comicbox's canonical SOURCE_NAMES tuple instead of repeating {"metron","comicvine"} literals in four places. The frontend gets it through the tagging choices JSON (build-choices), so a new comicbox source propagates without hand-editing every site. - Issue-id parsing: the two byte-identical trailing-int regex copies (stored_id_prepass, explicit_id) collapse into one issue_id.parse_issue_id built on comicbox's canonical PARSE_COMICVINE_RE. It honors the real Comic Vine 4-digit long-key rule instead of grabbing any trailing int; an unrecognized key returns None, which safely falls back to search / rejects the id rather than guessing wrong. No user-visible behavior change. Co-Authored-By: Claude Opus 4.8 --- codex/choices/admin.py | 4 +++ .../onlinetag/credential_validator.py | 6 ++++- codex/librarian/onlinetag/explicit_id.py | 14 +++------- codex/librarian/onlinetag/issue_id.py | 24 +++++++++++++++++ .../librarian/onlinetag/stored_id_prepass.py | 14 ++-------- codex/librarian/onlinetag/tasks.py | 4 ++- codex/serializers/admin/tagging.py | 5 ++-- .../components/online-tag/launcher-dialog.vue | 9 ++++--- tests/test_onlinetag_issue_id.py | 27 +++++++++++++++++++ 9 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 codex/librarian/onlinetag/issue_id.py create mode 100644 tests/test_onlinetag_issue_id.py diff --git a/codex/choices/admin.py b/codex/choices/admin.py index fcfa35653..3d1ddc3af 100644 --- a/codex/choices/admin.py +++ b/codex/choices/admin.py @@ -2,6 +2,7 @@ from types import MappingProxyType +from comicbox.formats.base.online import SOURCE_NAMES from django.db.models.enums import TextChoices @@ -63,6 +64,9 @@ class AdminFlagChoices(TextChoices): "never": "Never", } ), + # Online-tag source names in priority order, derived from comicbox so + # the frontend's source list tracks comicbox instead of hardcoding it. + "sources": SOURCE_NAMES, } ) diff --git a/codex/librarian/onlinetag/credential_validator.py b/codex/librarian/onlinetag/credential_validator.py index 2be2626e6..eec933dc1 100644 --- a/codex/librarian/onlinetag/credential_validator.py +++ b/codex/librarian/onlinetag/credential_validator.py @@ -12,12 +12,16 @@ from dataclasses import dataclass from typing import TYPE_CHECKING +from comicbox.formats.base.online import SOURCE_NAMES + if TYPE_CHECKING: from collections.abc import Collection from comicbox.online_session import OnlineCredentials -KNOWN_SOURCES: frozenset[str] = frozenset({"metron", "comicvine"}) +# comicbox owns the canonical online-tag source names (metron, comicvine); derive +# from it so codex tracks a new source instead of hand-syncing a literal. +KNOWN_SOURCES: frozenset[str] = frozenset(SOURCE_NAMES) _COMICVINE_TIMEOUT_SECS: float = 10.0 diff --git a/codex/librarian/onlinetag/explicit_id.py b/codex/librarian/onlinetag/explicit_id.py index ca6ce6af6..9fd00676b 100644 --- a/codex/librarian/onlinetag/explicit_id.py +++ b/codex/librarian/onlinetag/explicit_id.py @@ -12,7 +12,6 @@ from __future__ import annotations -import re from dataclasses import replace from typing import TYPE_CHECKING, Any @@ -28,16 +27,14 @@ from comicbox.formats.comicbox.schema import IDENTIFIERS_KEY from comicbox.identifiers import ID_KEY_KEY +from codex.librarian.onlinetag.issue_id import parse_issue_id + if TYPE_CHECKING: from pathlib import Path from comicbox.config.settings import ComicboxSettings from comicbox.online_session import OnlineCredentials -# Comic Vine keys may be stored long-form (``4000-67890``); the issue id is the -# trailing integer. Metron keys are already bare integers. -_TRAILING_INT_RE = re.compile(r"(\d+)$") - def _build_auth_source( source: str, credentials: OnlineCredentials @@ -105,11 +102,8 @@ def _result_has_requested_id(tags: dict[str, Any], source: str, issue_id: int) - a wrong id even on an already-tagged comic, whose stale id won't match. """ id_obj = (tags.get(IDENTIFIERS_KEY) or {}).get(source) or {} - key = id_obj.get(ID_KEY_KEY) - if not key: - return False - match = _TRAILING_INT_RE.search(str(key)) - return bool(match) and int(match.group(1)) == issue_id + parsed = parse_issue_id(id_obj.get(ID_KEY_KEY)) + return parsed is not None and parsed == issue_id def fetch_tags_by_explicit_id( diff --git a/codex/librarian/onlinetag/issue_id.py b/codex/librarian/onlinetag/issue_id.py new file mode 100644 index 000000000..f277e201c --- /dev/null +++ b/codex/librarian/onlinetag/issue_id.py @@ -0,0 +1,24 @@ +""" +Extract the numeric issue id from a stored online identifier key. + +Comic Vine issue keys are stored long-form (``4000-12345``); comicbox owns the +canonical rule for that ``-`` shape (``PARSE_COMICVINE_RE``), so we +consume it here instead of hand-rolling the format. Metron keys are already +bare integers. Anything else yields ``None`` — the callers (stored-id prepass, +explicit-id confirmation) treat that as "not resolvable", which safely falls +back to search or rejects the id rather than guessing a wrong one. +""" + +from __future__ import annotations + +from comicbox.identifiers import PARSE_COMICVINE_RE + + +def parse_issue_id(key: str | None) -> int | None: + """Return the numeric issue id from a stored identifier key, or ``None``.""" + if not key: + return None + key = str(key) + if match := PARSE_COMICVINE_RE.search(key): + return int(match.group("id_key")) + return int(key) if key.isdigit() else None diff --git a/codex/librarian/onlinetag/stored_id_prepass.py b/codex/librarian/onlinetag/stored_id_prepass.py index ca03e830e..bd41f1cba 100644 --- a/codex/librarian/onlinetag/stored_id_prepass.py +++ b/codex/librarian/onlinetag/stored_id_prepass.py @@ -13,24 +13,14 @@ from __future__ import annotations -import re from typing import TYPE_CHECKING +from codex.librarian.onlinetag.issue_id import parse_issue_id from codex.models.identifier import Identifier, IdentifierType if TYPE_CHECKING: from collections.abc import Collection -# Comic Vine keys may be stored long-form (``4000-12345``); the issue id is the -# trailing integer. Metron keys are already bare integers. -_TRAILING_INT_RE = re.compile(r"(\d+)$") - - -def _parse_issue_id(key: str | None) -> int | None: - """Pull the trailing integer issue id out of a stored identifier key.""" - match = _TRAILING_INT_RE.search(key or "") - return int(match.group(1)) if match else None - def build_stored_id_map( comic_pks: Collection[int], sources: tuple[str, ...] @@ -53,7 +43,7 @@ def build_stored_id_map( priority = {source: i for i, source in enumerate(sources)} id_map: dict[int, dict[str, int]] = {} for pk, source_name, key in rows: - issue_id = _parse_issue_id(key) + issue_id = parse_issue_id(key) if issue_id is None: continue id_map.setdefault(pk, {})[source_name] = issue_id diff --git a/codex/librarian/onlinetag/tasks.py b/codex/librarian/onlinetag/tasks.py index f34d3fbb7..4e916fcb8 100644 --- a/codex/librarian/onlinetag/tasks.py +++ b/codex/librarian/onlinetag/tasks.py @@ -3,6 +3,8 @@ from dataclasses import dataclass from typing import Any +from comicbox.formats.base.online import SOURCE_NAMES + from codex.librarian.tasks import LibrarianTask @@ -16,7 +18,7 @@ class BulkOnlineTagTask(OnlineTagTask): comic_pks: frozenset[int] session_id: str - sources: tuple[str, ...] = ("metron", "comicvine") + sources: tuple[str, ...] = SOURCE_NAMES mode: str = "auto" prompts_mode: str = "ask" auto_threshold: float = 0.85 diff --git a/codex/serializers/admin/tagging.py b/codex/serializers/admin/tagging.py index 61d1e2a18..dcb995369 100644 --- a/codex/serializers/admin/tagging.py +++ b/codex/serializers/admin/tagging.py @@ -1,5 +1,6 @@ """Comicbox tagging serializers.""" +from comicbox.formats.base.online import SOURCE_NAMES from rest_framework.fields import SerializerMethodField from rest_framework.serializers import ( BooleanField, @@ -53,9 +54,7 @@ class OnlineTagStartSerializer(Serializer): collection = CharField() pks = ListField(child=CharField()) - sources = ListField( - child=CharField(), required=False, default=["metron", "comicvine"] - ) + sources = ListField(child=CharField(), required=False, default=list(SOURCE_NAMES)) mode = CharField(required=False, default="auto") prompts_mode = CharField(required=False, default="ask") auto_threshold = CharField(required=False, default="0.85") diff --git a/frontend/src/components/online-tag/launcher-dialog.vue b/frontend/src/components/online-tag/launcher-dialog.vue index ef135d3cf..f4fd08dc0 100644 --- a/frontend/src/components/online-tag/launcher-dialog.vue +++ b/frontend/src/components/online-tag/launcher-dialog.vue @@ -224,9 +224,10 @@ function callsForSource(source, mode) { return DEFAULT_CALLS_PER_COMIC; } -// Only these sources support online id tagging (mirrors the backend -// KNOWN_SOURCES); other identifiers a comic carries aren't offered. -const TAGGABLE_SOURCES = Object.freeze(new Set(["metron", "comicvine"])); +// Only these sources support online id tagging; derived from comicbox's +// SOURCE_NAMES via the tagging choices so it tracks the backend. Other +// identifiers a comic carries aren't offered. +const TAGGABLE_SOURCES = Object.freeze(new Set(TAGGING_CHOICES.sources)); // A comic's own issue identifier is stored with id_type "comic" — codex's // IdentifierType.ISSUE value is the table name, not "issue". Accept that (and // a bare "issue" defensively); skip non-issue types like publisher / series. @@ -269,7 +270,7 @@ export default { // Search matchModeChoices: TAGGING_CHOICES.matchMode, promptsModeChoices: TAGGING_CHOICES.promptsMode, - sources: ["metron", "comicvine"], + sources: [...TAGGING_CHOICES.sources], matchMode: "auto", promptsMode: "ask", mergeAllSources: false, diff --git a/tests/test_onlinetag_issue_id.py b/tests/test_onlinetag_issue_id.py new file mode 100644 index 000000000..ede91318e --- /dev/null +++ b/tests/test_onlinetag_issue_id.py @@ -0,0 +1,27 @@ +"""Unit tests for parsing the numeric issue id out of a stored identifier key.""" + +from __future__ import annotations + +import pytest + +from codex.librarian.onlinetag.issue_id import parse_issue_id + + +@pytest.mark.parametrize( + ("key", "expected"), + [ + ("12345", 12345), # bare Metron key + ("4000-67890", 67890), # Comic Vine long-form -> id_key + (67890, 67890), # non-str key coerces + ("https://comicvine.gamespot.com/x/4000-67890/", 67890), # CV rule anywhere + ("", None), + (None, None), + ("abc", None), # no digits at all + ("metron", None), + ("4000-", None), # long-form type without a key + ("12-99", None), # not the 4-digit CV rule -> not a lossy trailing int + ], +) +def test_parse_issue_id(key, expected) -> None: + """The CV long-key rule wins; bare digits pass; everything else is None.""" + assert parse_issue_id(key) == expected From cd84ed99e1260ff65a7236800304986700bf5721 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 3 Jul 2026 20:48:23 -0700 Subject: [PATCH 08/82] fix(onlinetag): scope the match-mode request-count hint to Comic Vine The "~N requests/comic" tail in the match-mode hints only describes Comic Vine, whose calls scale with match mode; Metron is a flat two-step search regardless of mode. Drop the tail from the base hints and append a "~N Comic Vine requests/comic" suffix only when Comic Vine is an active source, so a Metron-only run no longer shows a count that doesn't apply. The number derives from the existing COMICVINE_CALLS_BY_MODE constant. Co-Authored-By: Claude Opus 4.8 --- .../components/online-tag/launcher-dialog.vue | 20 ++++++++++++--- frontend/tests/unit/launcher-dialog.test.js | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/online-tag/launcher-dialog.vue b/frontend/src/components/online-tag/launcher-dialog.vue index f4fd08dc0..bba5f5d43 100644 --- a/frontend/src/components/online-tag/launcher-dialog.vue +++ b/frontend/src/components/online-tag/launcher-dialog.vue @@ -186,12 +186,15 @@ import { useAdminStore } from "@/stores/admin"; import { useCommonStore } from "@/stores/common"; import { useOnlineTagStore } from "@/stores/online-tag"; +// Base match-mode semantics, shown for any source. The per-comic request +// count is Comic Vine-specific (Metron is a flat two-step regardless of mode), +// so it's appended by matchModeHint only when Comic Vine is an active source. const MATCH_MODE_HINTS = { careful: - "Only accepts high-confidence matches. Defers ambiguous results for manual review. ~5 requests/comic.", - auto: "Balances accuracy and speed. Accepts good matches automatically, defers uncertain ones. ~3 requests/comic.", + "Only accepts high-confidence, unambiguous matches. Defers anything uncertain for manual review.", + auto: "Balances accuracy and speed. Accepts confident matches automatically and defers uncertain ones.", eager: - "Accepts the best available match with minimal verification. Fastest but least precise. ~2 requests/comic.", + "Accepts the best available match with minimal verification. Fastest, but least precise.", }; const PROMPTS_MODE_HINTS = { ask: "Pauses on ambiguous matches and asks you to choose the correct result.", @@ -411,7 +414,16 @@ export default { }, // --- search hints / estimates --------------------------------------- matchModeHint() { - return MATCH_MODE_HINTS[this.matchMode] || ""; + const base = MATCH_MODE_HINTS[this.matchMode] || ""; + // The request-count tail only applies to Comic Vine, whose calls scale + // with match mode; Metron's flat two-step doesn't, so skip it otherwise. + if (!this.activeSources.includes("comicvine")) { + return base; + } + const requests = COMICVINE_CALLS_BY_MODE[this.matchMode]; + return requests + ? `${base} ~${requests} Comic Vine requests/comic.` + : base; }, promptsModeHint() { return PROMPTS_MODE_HINTS[this.promptsMode] || ""; diff --git a/frontend/tests/unit/launcher-dialog.test.js b/frontend/tests/unit/launcher-dialog.test.js index b00377e11..08c3e1a4e 100644 --- a/frontend/tests/unit/launcher-dialog.test.js +++ b/frontend/tests/unit/launcher-dialog.test.js @@ -392,6 +392,31 @@ describe("OnlineTagLauncherDialog", () => { expect(wrapper.vm.totalCalls).toBe(50); }); }); + + describe("match mode hint", () => { + test("appends the Comic Vine request count when Comic Vine is active", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.sources = ["metron", "comicvine"]; + wrapper.vm.matchMode = "auto"; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.matchModeHint).toContain( + "~3 Comic Vine requests/comic.", + ); + }); + + test("omits the request-count tail on a Metron-only run", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.sources = ["metron"]; + wrapper.vm.matchMode = "careful"; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.matchModeHint).toContain("high-confidence"); + expect(wrapper.vm.matchModeHint).not.toContain("requests/comic"); + }); + }); }); export default {}; From 9980c04493585ef4be022a9855a0f4f13a8cad83 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 15:45:25 -0700 Subject: [PATCH 09/82] fix(fs): don't let one unreadable folder crash the library scan The poller's DiskSnapshot._walk() called os.scandir() with no guard around the directory open, so a single permission-denied folder (e.g. a Synology /comics/#recycle bin) raised PermissionError that propagated up and killed the LibraryPollerThread, aborting the scan of every other folder (issue #795). - Wrap os.scandir so an unreadable/vanished directory is logged and skipped instead of aborting the whole poll, and widen the per-entry guard to cover entry.is_dir(), which can also raise PermissionError. This matches the os.walk default-onerror behavior the watcher relies on. - Register the OS/NAS metadata basenames the filters module already documented but never populated (@eaDir, #recycle, __MACOSX, Thumbs.db, desktop.ini), so the walker skips the recycle bin entirely and NAS/OS junk never enters the library. Co-Authored-By: Claude Opus 4.8 --- codex/librarian/fs/filters.py | 23 ++++++++--- codex/librarian/fs/poller/snapshot.py | 53 +++++++++++++++--------- tests/test_fs_filters_ignore.py | 59 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 25 deletions(-) diff --git a/codex/librarian/fs/filters.py b/codex/librarian/fs/filters.py index f68bfb99a..4229cb984 100644 --- a/codex/librarian/fs/filters.py +++ b/codex/librarian/fs/filters.py @@ -11,11 +11,14 @@ # the watchfiles filter. Either constant can be extended to add new # patterns without touching the walker/filter call sites: # -# _IGNORED_BASENAMES — exact-match basenames (case-sensitive). Use -# this for OS / archive metadata trees that don't follow a prefix -# convention. Examples for future extension: ``"@eaDir"`` -# (Synology), ``"__MACOSX"`` (Mac archive metadata), ``"Thumbs.db"`` -# / ``"desktop.ini"`` (Windows), ``"#recycle"`` (recycle bins). +# _IGNORED_BASENAMES — exact-match basenames (case-sensitive) for +# OS / archive metadata trees that don't follow a prefix +# convention: ``@eaDir`` (Synology thumbnails), ``#recycle`` +# (Synology recycle bin), ``__MACOSX`` (Mac archive metadata), +# ``Thumbs.db`` / ``desktop.ini`` (Windows). Add more here as +# needed. Skipping these keeps NAS/OS junk out of the library and +# stops the walker from descending into permission-restricted +# trees like the recycle bin (issue #795). # # _IGNORED_BASENAME_PREFIXES — prefix matches. Currently catches all # hidden files / directories ("." prefix) so VCS metadata (.git), @@ -25,7 +28,15 @@ # A path is ignored when *any* component (relative to its library # root) matches either rule. The check is applied to every entry the # walker sees and every event the watcher receives. -_IGNORED_BASENAMES: frozenset[str] = frozenset() +_IGNORED_BASENAMES: frozenset[str] = frozenset( + { + "@eaDir", # Synology thumbnail / extended-attribute store + "#recycle", # Synology recycle bin + "__MACOSX", # macOS archive metadata + "Thumbs.db", # Windows thumbnail cache + "desktop.ini", # Windows folder settings + } +) _IGNORED_BASENAME_PREFIXES: tuple[str, ...] = (".",) diff --git a/codex/librarian/fs/poller/snapshot.py b/codex/librarian/fs/poller/snapshot.py index cecd88c93..0070ec0ed 100644 --- a/codex/librarian/fs/poller/snapshot.py +++ b/codex/librarian/fs/poller/snapshot.py @@ -138,25 +138,40 @@ def _should_include(self, path: Path, *, is_dir: bool) -> bool: def _walk(self, root: str) -> None: """Walk the directory tree and populate lookups.""" - for entry in os.scandir(root): - # Walking only the surviving children means the recursion - # never enters an ignored dir (``.git`` / ``.Trashes`` / - # ``@eaDir`` if registered / …), so a per-component check - # at each scandir level suffices — no need to re-check - # ancestors. See ``filters._IGNORED_BASENAMES`` / - # ``_IGNORED_BASENAME_PREFIXES`` for the registered rules. - if is_ignored_basename(entry.name): - continue - is_dir = entry.is_dir(follow_symlinks=self._follow_symlinks) - if not self._should_include(Path(entry.path), is_dir=is_dir): - continue - try: - st = entry.stat(follow_symlinks=self._follow_symlinks) - except OSError: - continue - self._set_lookups(entry.path, st) - if is_dir: - self._walk(entry.path) + try: + scandir = os.scandir(root) + except OSError as exc: + # A single unreadable directory (permission denied, or one + # that vanished mid-scan) must not abort the whole library + # poll — otherwise one locked folder crashes the poller + # thread and no other folder gets scanned (issue #795). + # Skip it and keep walking the rest of the tree, matching + # the ``os.walk`` default-onerror behavior the watcher relies + # on. + self.log.warning(f"Skipping unreadable directory {root}: {exc}") + return + with scandir: + for entry in scandir: + # Walking only the surviving children means the recursion + # never enters an ignored dir (``.git`` / ``.Trashes`` / + # ``@eaDir`` / ``#recycle`` / …), so a per-component check + # at each scandir level suffices — no need to re-check + # ancestors. See ``filters._IGNORED_BASENAMES`` / + # ``_IGNORED_BASENAME_PREFIXES`` for the registered rules. + if is_ignored_basename(entry.name): + continue + try: + is_dir = entry.is_dir(follow_symlinks=self._follow_symlinks) + if not self._should_include(Path(entry.path), is_dir=is_dir): + continue + st = entry.stat(follow_symlinks=self._follow_symlinks) + except OSError: + # An entry that can't be stat'd (broken symlink, + # permission denied, race) is skipped, not fatal. + continue + self._set_lookups(entry.path, st) + if is_dir: + self._walk(entry.path) class DatabaseSnapshot(Snapshot): diff --git a/tests/test_fs_filters_ignore.py b/tests/test_fs_filters_ignore.py index 4811fab0e..ddd5e7597 100644 --- a/tests/test_fs_filters_ignore.py +++ b/tests/test_fs_filters_ignore.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os import shutil from logging import getLogger from pathlib import Path @@ -13,6 +14,7 @@ from codex.librarian.fs import filters as filters_mod from codex.librarian.fs.filters import is_ignored_basename, is_ignored_path +from codex.librarian.fs.poller import snapshot as snapshot_mod from codex.librarian.fs.poller.snapshot import DiskSnapshot from codex.librarian.fs.watcher.data import ChangeBatch from codex.librarian.fs.watcher.dirs import expand_dir_added @@ -83,6 +85,12 @@ def test_prefix_registry_picked_up(self) -> None: assert is_ignored_basename("#recycle") is True assert is_ignored_basename(".DS_Store") is False + def test_os_metadata_basenames_registered_by_default(self) -> None: + # NAS / OS junk trees are ignored out of the box so they are + # neither imported nor descended into (issue #795). + for name in ("@eaDir", "#recycle", "__MACOSX", "Thumbs.db", "desktop.ini"): + assert is_ignored_basename(name) is True + class TestDiskSnapshotSkipsDotfiles: """The poller's walker must not surface dotfile paths.""" @@ -122,6 +130,57 @@ def test_folder_cover_dotfile_skipped(self) -> None: names = {Path(p).name for p in snap.paths} assert ".codex-cover.jpg" not in names + def test_recycle_bin_directory_pruned(self) -> None: + # Synology recycle bins are registered noise: the walker must + # never descend into them, so their permission-restricted + # contents are never scanned (issue #795). + (_TEST_LIB_ROOT / "comic.cbz").touch() + recycle = _TEST_LIB_ROOT / "#recycle" + recycle.mkdir() + (recycle / "deleted.cbz").touch() + snap = DiskSnapshot(str(_TEST_LIB_ROOT), getLogger("test")) + assert not any("#recycle" in Path(p).parts for p in snap.paths) + + +class TestDiskSnapshotSurvivesUnreadableDir: + """A single permission-denied directory must not abort the whole walk.""" + + @pytest.fixture(autouse=True) + def _tmp_library(self): + shutil.rmtree(_TEST_LIB_ROOT, ignore_errors=True) + _TEST_LIB_ROOT.mkdir(parents=True) + yield + shutil.rmtree(_TEST_LIB_ROOT, ignore_errors=True) + + def test_unreadable_subdir_skipped_siblings_survive(self) -> None: + # Reproduces issue #795: a subdirectory that raises + # PermissionError on scandir (e.g. a NAS recycle bin) used to + # crash the poller thread and abort the entire scan. It must now + # be skipped while every other folder still gets walked. Mock + # scandir so the test holds regardless of the runner's uid + # (CI runs as root, where a 0o000 dir stays readable). + (_TEST_LIB_ROOT / "comic.cbz").touch() + locked = _TEST_LIB_ROOT / "locked" + locked.mkdir() + (locked / "hidden.cbz").touch() + + real_scandir = os.scandir + + def fake_scandir(path): + if Path(path) == locked: + raise PermissionError(13, "Permission denied") + return real_scandir(path) + + with patch.object(snapshot_mod.os, "scandir", side_effect=fake_scandir): + snap = DiskSnapshot(str(_TEST_LIB_ROOT), getLogger("test")) + + names = {Path(p).name for p in snap.paths} + # The sibling comic is still surfaced despite the locked dir. + assert "comic.cbz" in names + # The locked directory stat'd fine so its own entry is recorded, + # but its unreadable contents are not. + assert "hidden.cbz" not in names + class TestCodexWatchFilter: """The watchfiles filter rejects hidden-tree events before classification.""" From 42b7fe939856caae57afd4a85e1d945bf7be07c0 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 15:46:56 -0700 Subject: [PATCH 10/82] update deps --- uv.lock | 174 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/uv.lock b/uv.lock index 7361ed8ca..5e823d9ce 100644 --- a/uv.lock +++ b/uv.lock @@ -1188,7 +1188,7 @@ wheels = [ [[package]] name = "djlint" -version = "1.40.2" +version = "1.40.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1199,45 +1199,45 @@ dependencies = [ { name = "pyyaml" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/ff/63a4ce9608c7051ab20954a150f6c0f2bfe7332b14cb4f11ee77036b3c6d/djlint-1.40.2.tar.gz", hash = "sha256:b39ff27a9806e4c4ce319b76a058c94811ef73dbba4a21dbec4d8b5009b48d5f", size = 58974, upload-time = "2026-07-03T00:50:27.469Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/60/8d7c37b6dce0d04eebf07c8c6b7de2941a8db06babbba69a04feea115d6a/djlint-1.40.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:775df87f239927afead55eba79e041a75cdffb5000eb09db5f959122b79062f5", size = 572293, upload-time = "2026-07-03T00:49:35.313Z" }, - { url = "https://files.pythonhosted.org/packages/c1/26/58fb1338c832e183c797d553949557810ba5e65aabd232c1445d2bfc3bb1/djlint-1.40.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f0d6c97c99b1e538ad36d604b2fdbad31142ce3a116389f3b9c3a56db0ae8ce6", size = 539842, upload-time = "2026-07-03T00:49:36.666Z" }, - { url = "https://files.pythonhosted.org/packages/b2/aa/4524a5409344d8221aad7f0bdb1002555c4c8f0577aead459eeb335b9b43/djlint-1.40.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7dfbd4a8073a171a5ffcc01269a9e5b8f7a7225f37fa43168c5172951a5dd", size = 571205, upload-time = "2026-07-03T00:49:38.022Z" }, - { url = "https://files.pythonhosted.org/packages/da/a2/58772dfb57f5e49d10aea99dcc4d6180497d9f3cc9759aa96a54fea244ea/djlint-1.40.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20a50da7799653163ddafcc3af43c2179e0a8657d402b772e10e3b628b24deca", size = 598632, upload-time = "2026-07-03T00:49:39.29Z" }, - { url = "https://files.pythonhosted.org/packages/48/a2/e1e4f31340580bc2a5308f9e75c55277e7273919e4c7b3e4e3b443f03fb7/djlint-1.40.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afc96e355b8d8b60215fab201465201c2595040b2f36095954416b0652119d65", size = 579727, upload-time = "2026-07-03T00:49:40.938Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7a/ea6729fcb39b053d1f5df0a7b1f7f268c2e792a5a36bf31c029f350ad1a5/djlint-1.40.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2cb90f921636a5cfab27f672cca38cee6823bad66f657eeb5c79d542f42c29d", size = 608067, upload-time = "2026-07-03T00:49:42.344Z" }, - { url = "https://files.pythonhosted.org/packages/91/af/9dcd98a260e5929b0690352300f2d37c56a0acf53ce25a7f881b59a022b4/djlint-1.40.2-cp312-cp312-win32.whl", hash = "sha256:9bd4cb9e47e7be21aee243f8be6013dea7fd5c8ef8556876ff6eb8ee94cc216d", size = 434780, upload-time = "2026-07-03T00:49:43.565Z" }, - { url = "https://files.pythonhosted.org/packages/b8/26/fa11eb09ea47005d33da21370b509f3fb065d65499478b4b92e6661436ed/djlint-1.40.2-cp312-cp312-win_amd64.whl", hash = "sha256:b139459a6843d07123182b84d27c1dd4da0e9b9ba52dab00c3a6d3a0b6cf0541", size = 484621, upload-time = "2026-07-03T00:49:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d6/0d42dbba8e13bdc1defdb3d213596c69b4da8751426399f5d8ba36a31b9c/djlint-1.40.2-cp312-cp312-win_arm64.whl", hash = "sha256:914ebed15d84276021bb720e7e9d66eb8c6587e9724af23a0875dae101e141a5", size = 428267, upload-time = "2026-07-03T00:49:46.122Z" }, - { url = "https://files.pythonhosted.org/packages/86/24/3cbb5bf5681af9d68d5dcc23c0cf2421500a086f7da389287387441ce410/djlint-1.40.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0496e44b61c7581c6e02df9c3b621d04172405dc7186ced9376e0a6a356a5861", size = 569009, upload-time = "2026-07-03T00:49:47.615Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b7/af7eb51a428081116e2aa464b1df190bb7955049e7f39bd1bb358d9c1f14/djlint-1.40.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a4a98dcfc7af894603f64bba29ef9572e3e447d0c0466198eb4a7be5fefd3e4", size = 538693, upload-time = "2026-07-03T00:49:49.042Z" }, - { url = "https://files.pythonhosted.org/packages/d9/60/f1c7c3b8223cc01fdd3bf9bb06afa07a7935e4fbf97d72b69d4e5c7b8300/djlint-1.40.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffa475bab3e8be757d8b75d3e0ae6327d44937209951925c64042f7d89cb17", size = 569448, upload-time = "2026-07-03T00:49:50.407Z" }, - { url = "https://files.pythonhosted.org/packages/d7/23/90554b25ef09c6048659fa7fc44c1267a965962854dc7aa7632913303ebf/djlint-1.40.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49886bdf08e1d896f91eb008b1f7c754031242feb39036cc09a13a7daea41e7b", size = 597012, upload-time = "2026-07-03T00:49:51.675Z" }, - { url = "https://files.pythonhosted.org/packages/2d/dc/317374b75b8085d33fdb103c8490c575050798d6991e7876cc4446e3852a/djlint-1.40.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0476479e5ad676a89d9a8ead2c74d26f608750bbf146dd4530dc571a601940c9", size = 577408, upload-time = "2026-07-03T00:49:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/9e/85/ea93e7f48d3e8a36410c596796c6f132baed90e241edcb235693caba00a9/djlint-1.40.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:109f89b1ceb3e1c37bf9e02cb2d73c4a65a020a3fafc4fda91e8128ea87c0388", size = 606770, upload-time = "2026-07-03T00:49:54.457Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b8/dc7c3c1fb6903652187fcaa562998560d821097a715978bf8c54ec8dea08/djlint-1.40.2-cp313-cp313-win32.whl", hash = "sha256:9c548033768d51701b3f561c3dffdf5f09a718f9332d58a7039a437778e0cfc3", size = 434326, upload-time = "2026-07-03T00:49:55.675Z" }, - { url = "https://files.pythonhosted.org/packages/97/9a/4cb35d83c165661d494603b1a16c8eaad28f2dc2825ca218f93c1a5a1ff3/djlint-1.40.2-cp313-cp313-win_amd64.whl", hash = "sha256:e9d97bfcc3e35c991ed7b76cbf0d2a50b68dd68ce96fb1e4a1845fb1d1182642", size = 484132, upload-time = "2026-07-03T00:49:57.096Z" }, - { url = "https://files.pythonhosted.org/packages/09/dd/22a2699bf7e0907a78c258138a74d9f3c0709448c8397f3ca29d6f4d5a66/djlint-1.40.2-cp313-cp313-win_arm64.whl", hash = "sha256:e46012fa48495717db33e1e9f9d4472e2413c3f165016691b42b20377b1f4cf6", size = 427278, upload-time = "2026-07-03T00:49:58.354Z" }, - { url = "https://files.pythonhosted.org/packages/61/55/1769a60325e5a4a93305a69e424e747cb2088fe99fa9a0961010fbff1a2e/djlint-1.40.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a2732aa3d8513d7b9c8fbaad70a3eb572267594e94c555aa099ca5c194e42782", size = 567625, upload-time = "2026-07-03T00:49:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/47/38/8512f315ad0b74453bfec16b85964f8ef586f2a07a7b7b85624b89e51e05/djlint-1.40.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e074858fa9fe8ff005b567a2325717b9bcd0e7f067d86ee513d92459c1d22209", size = 538175, upload-time = "2026-07-03T00:50:01.188Z" }, - { url = "https://files.pythonhosted.org/packages/fe/28/a0625a3f251b4299a40477ac1cc5fa70ddcd2988e4f8622011e583ab70c4/djlint-1.40.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af9c71981467ddd2eadd415889638b0f73c3a320747e899268045ab36f9610c0", size = 572323, upload-time = "2026-07-03T00:50:02.466Z" }, - { url = "https://files.pythonhosted.org/packages/d6/0a/d2968b2c62248e1052da7b6af037e6b89cea68a016e65af82c9ca6377db8/djlint-1.40.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a6156f2de3705c43132100995af254296983831ec4fbb7c6ae25983163e23f", size = 595952, upload-time = "2026-07-03T00:50:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/57/5f/1b2153a08b058bdf2a633543ca1592ec9d84ccac3e725e7917175d686b91/djlint-1.40.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71435a4a6adffeef0c849bb523d22a27f4d9c433895d1ea2e0db112355c3b469", size = 580515, upload-time = "2026-07-03T00:50:05.185Z" }, - { url = "https://files.pythonhosted.org/packages/15/bb/d1383799594d5f2556caa2ae97cebf9aaeba22cc131c54d773ab03b5a433/djlint-1.40.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6e7ddbe49919093ef386f013e79b55bd5b923ef224cfc19bfb090da6f8555cda", size = 605908, upload-time = "2026-07-03T00:50:06.483Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e5/5e66780ad129ec8b80d4eb410eb1e381c406356ed1f2de4aea1d4b667ba4/djlint-1.40.2-cp314-cp314-win32.whl", hash = "sha256:b6b10d2e1d7400453695257eb9b343a52a6814e2203778d5f9b9b5bbf7228033", size = 440078, upload-time = "2026-07-03T00:50:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/c9/3c/dc2d5dd03b47b39f37e125d9d8f2e8faaa033a9121d7b9423bd888538d6e/djlint-1.40.2-cp314-cp314-win_amd64.whl", hash = "sha256:ee825c58347ab0ba7a8383402840c3294a9c03461cf957753bf7254eef8a87a6", size = 492181, upload-time = "2026-07-03T00:50:09.468Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/c5e9b2dad272acfa13f50bfaf6eab33165591d9970a75d8fe003a8d5c373/djlint-1.40.2-cp314-cp314-win_arm64.whl", hash = "sha256:69fc1c7c5704964977e2a12eb870840c5819fa0e47fc49adb2d36e4db1707ade", size = 436170, upload-time = "2026-07-03T00:50:10.729Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a3/a639d6b9126e8d62bbeed3122a9f13f0f4f427f5bb6603b0e7a38b2a8323/djlint-1.40.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:94dc6e7be625c86ca7395c2bcf750e65587d10eecf65752ff3b11fdb6790fee1", size = 617749, upload-time = "2026-07-03T00:50:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/d3/fb/49fea811fc720a6e588259ecb95d158edd5a80ed9d6244119ae6f2e6c9f5/djlint-1.40.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4f3d5835adbc05eddb9b05feff1465630015b0639a5a3360e7f194ae80ddecf4", size = 589156, upload-time = "2026-07-03T00:50:13.786Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5c/d750f26136d433f76398469e09ccd778aa6383915c91fa511a9b0ff2f5e3/djlint-1.40.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c0c55caee9843895acb6032d72e67f8af1e60c2153c20eded6b07cd873f4bb", size = 611560, upload-time = "2026-07-03T00:50:15.142Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4f/2cb74536eb817b6ccabeb19b508f40319cab7ce805c3cb92dc3d873671e2/djlint-1.40.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:828e443e9b1edd8b73047e1f4e1f81dc93e7f6ece36c8fc0500b457eab040088", size = 633415, upload-time = "2026-07-03T00:50:16.556Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9c/6e5ae6a41b6406389ee0b1ec05491cf35957858694dc1b0f39122984b35d/djlint-1.40.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d116c1d51a5034fb1d2fa8403ae5c544b3f4c3fcffff4f62fbf82e4211b7a2fb", size = 618263, upload-time = "2026-07-03T00:50:18.579Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7e/19d7c473c6e023c419cc196688bdd1958902a1aafbc9d4524c3e9b5ddc93/djlint-1.40.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d8e8853f35c24b86bec1259b85c5f3ef6d6dbf6b261cfbe2c5bc8652919aeb24", size = 642095, upload-time = "2026-07-03T00:50:20.031Z" }, - { url = "https://files.pythonhosted.org/packages/a8/d9/882a4f5027f12b2a3ce09bd68f4d922a27e8af132e2ff7af46a597d9556a/djlint-1.40.2-cp314-cp314t-win32.whl", hash = "sha256:e4ca9de0e528695f9486566066f664ec994928f3d68741ebf63cc6ed3531655e", size = 462707, upload-time = "2026-07-03T00:50:21.558Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/393f3b78735a4d6e41e2ee2bb0f4b9207c93244667d77ab286e46ae91ed3/djlint-1.40.2-cp314-cp314t-win_amd64.whl", hash = "sha256:17d3e40e9a84d55708214ec57db1f6f5ddeb01feedd56696fad85b872a148d67", size = 511638, upload-time = "2026-07-03T00:50:23.204Z" }, - { url = "https://files.pythonhosted.org/packages/34/50/3a44bbb2a918c23e93846895e19ccbce7baab8e2e5be1cca757b3bcd6215/djlint-1.40.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5712c2ac633317b039ef7123ebac197f177d49dc0ddc8a3891edb688ccb5dde9", size = 454371, upload-time = "2026-07-03T00:50:25.063Z" }, - { url = "https://files.pythonhosted.org/packages/0d/8f/463b58ad48f7ebcf1d72d0ce34b9a4c86cf056885240f66e286ba83f4c64/djlint-1.40.2-py3-none-any.whl", hash = "sha256:861ae83cf2aa0754d401e0eea074a8d2c9b5a13b1c930e46a494979f7d112c7e", size = 65070, upload-time = "2026-07-03T00:50:26.193Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/75/98/fdee72fa3bd20af0ca42c136d076c8a2069cd7303d66cb77c60fc901914f/djlint-1.40.3.tar.gz", hash = "sha256:4fe34e932c55f5b87c8df0daf60c07afbe8f620e1a24da1a2bde0ea2651c430e", size = 59215, upload-time = "2026-07-04T11:48:31.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ef/c9b7728d1345ef773698983119e15cda95aedacc9a978e0776184a73c898/djlint-1.40.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e2dbb2219d9133ac5d3dfb9b6bb6a7db582ebcfe005679c16d6cbf284a8fe5", size = 577267, upload-time = "2026-07-04T11:47:46.954Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/d41a15487a18e544bb9b7c2bbc07a38cf56e13b62cae85fd78a6ffb7df50/djlint-1.40.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47b29ce89517a2ebbfdfc5de74c49f39b0f1c19a01d04aeaed4f8ddf844be367", size = 542842, upload-time = "2026-07-04T11:47:48.057Z" }, + { url = "https://files.pythonhosted.org/packages/93/5e/2dbc6d1ef05716b53db877b52fb2b6de1b0cff3ce7309cec041236563b3f/djlint-1.40.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b54bf1812e2abcff9a97d9208c6fcd0943ed311d345a0c3364fb2e8adf907eda", size = 575133, upload-time = "2026-07-04T11:47:49.585Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c7/25eac5e98b878f572bae8a4e6697b8495666f21aa1db59d0f336d26b1760/djlint-1.40.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec29ff8987808121fcc962e14c66cd929d656d63d3b5b2e04c34c435adf162fe", size = 602415, upload-time = "2026-07-04T11:47:50.674Z" }, + { url = "https://files.pythonhosted.org/packages/ac/20/78d983573e742f8df01a7942c5b0ec57e0b9b0f7fe6b533a7f003916e6ea/djlint-1.40.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62dfa80599ab3f0a1d3c43d85dffa4b42a11933626a6c5fcf03823c066109c19", size = 583668, upload-time = "2026-07-04T11:47:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/80/9b/6dfa0574e980741c9bcc75168c3c790f714647dd526af88a42acfdc6b45f/djlint-1.40.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6715fb4204436013a4eb7e1e7f7edcce44eb03ec94e428ca886c5f1787540e63", size = 611619, upload-time = "2026-07-04T11:47:52.825Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1b/a9f764dcf2dfa058472b95e7e148432b109aa9569e1714ca5c22787ce6d0/djlint-1.40.3-cp312-cp312-win32.whl", hash = "sha256:54e34f363278b292b274c28326542bc0f42c06a6bd99867c630864fd772b50d7", size = 437224, upload-time = "2026-07-04T11:47:53.973Z" }, + { url = "https://files.pythonhosted.org/packages/22/8e/c9d4d8f36bda28784d42fae7c067e8951e7b3ac7e3d1e3fd5f043bc42cad/djlint-1.40.3-cp312-cp312-win_amd64.whl", hash = "sha256:00e053bf26d5c9ddcad67455c597a6ef47ad8debfc59911c00d17b72c17625ab", size = 487134, upload-time = "2026-07-04T11:47:54.997Z" }, + { url = "https://files.pythonhosted.org/packages/72/38/0f665b8a24db2818b15abb459590e56a44909016cd8056906768c0790507/djlint-1.40.3-cp312-cp312-win_arm64.whl", hash = "sha256:33ca64c999010426643977eb4538e8fa790150f601cfb7af778e2cd1652d9ad8", size = 430019, upload-time = "2026-07-04T11:47:56.217Z" }, + { url = "https://files.pythonhosted.org/packages/d9/32/3b9519f888b3983ef3bfbaf220b3a493ccbf68d7a1752e05189fc9309b56/djlint-1.40.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e02a4a3cd5f5489b5dd6dc8f0807105cdb1faba1c7ff011cdc539e215b3f945d", size = 574381, upload-time = "2026-07-04T11:47:57.323Z" }, + { url = "https://files.pythonhosted.org/packages/4c/32/61d2c87fadc03044a8ec131d10087ce54196192dde864ec300a3b0494282/djlint-1.40.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ea2e27d4f23da2d397f7dd1af7a9b38b6866dc5c3697229469f31449cb8e756", size = 541643, upload-time = "2026-07-04T11:47:58.609Z" }, + { url = "https://files.pythonhosted.org/packages/66/d6/69518734dd0b266778d3800c8f6871c645dbdce64241514d4fc531f4202e/djlint-1.40.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191c439e57be56327be260e2b6d9d888f81b1016f388af5f55dc063146e6e290", size = 572995, upload-time = "2026-07-04T11:47:59.775Z" }, + { url = "https://files.pythonhosted.org/packages/01/ad/cf94cfae8f07d98b5c2f8b41f7755d20982edcad571bd1d17331af8b162f/djlint-1.40.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7704817e41e795e2d62b2e1646c8ef512678b484bae9b15d10a4968ff6cc77c", size = 600618, upload-time = "2026-07-04T11:48:00.844Z" }, + { url = "https://files.pythonhosted.org/packages/78/1a/0fae63824b1cd6f17788e474ca149bccbc79896960e1f75e8c10fb35b240/djlint-1.40.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:94a5d64dd7e0396ecdc953f7cbc3f935427330f63b5a3ee80551c3ca56a11bd2", size = 581418, upload-time = "2026-07-04T11:48:01.909Z" }, + { url = "https://files.pythonhosted.org/packages/a5/25/789fcf430e3eb5e07abfe637407cbb29ad620d1f1a186c6dd113ef5c4b7a/djlint-1.40.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bd6d14cc6ac7732a81d704276bf0865749c640c149dc7218da940c6b9a89e4c4", size = 610274, upload-time = "2026-07-04T11:48:03.196Z" }, + { url = "https://files.pythonhosted.org/packages/30/e2/8690bc27594f50b2682873affe46eab1e1b7cb6492b1c64b140981851c1c/djlint-1.40.3-cp313-cp313-win32.whl", hash = "sha256:2e858b98c5961bbdfe20a02dd3e49115de804182601a2f1c1e27b2f6a1a68f56", size = 437031, upload-time = "2026-07-04T11:48:04.354Z" }, + { url = "https://files.pythonhosted.org/packages/47/52/04c3022eb839c063a1791560e02404b2b82f1ebb486bfe9dee93f2d945ff/djlint-1.40.3-cp313-cp313-win_amd64.whl", hash = "sha256:e7db951983c33c27bf2a32153f11a05d0ec91e2f56c6d95c92c0f9a324df7676", size = 486723, upload-time = "2026-07-04T11:48:05.616Z" }, + { url = "https://files.pythonhosted.org/packages/50/ad/8460d545a0875d777d85f7ae36300d779b09b9810579a98a05ab15a83961/djlint-1.40.3-cp313-cp313-win_arm64.whl", hash = "sha256:b12581b55e2b4acf3e9d8f57ef18188618fc31a82500ea9974276428af0a32be", size = 429482, upload-time = "2026-07-04T11:48:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/80/66/36c7f35408d47b88d4547a7a707c2ce4db9128b19fb549b271a53076204c/djlint-1.40.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:669a2b157d2a878eb82e7c85a65e0c57fdfc5193b2c19508d88d64ac82e83e00", size = 572309, upload-time = "2026-07-04T11:48:07.896Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8f/a83bdfe5f5ea4af351755ac5923304e21fe3d12c15659608e56ecd728a0e/djlint-1.40.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85fe6a62e4759647c98f1800923ad3a186d5a34ecb027da29bf8c825c62236c8", size = 541432, upload-time = "2026-07-04T11:48:09.015Z" }, + { url = "https://files.pythonhosted.org/packages/03/75/ab7225f497acf5bbae18e381cd25d11de2beff9d7d60d058128b90adc172/djlint-1.40.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:498f66913d456da7621e3ba7a712719204d893c6f50b32d860595c1e4e565de7", size = 577447, upload-time = "2026-07-04T11:48:10.175Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7f/52cfe4727bdb2a574acf5e7c17588f851127a75c665b5380edc46a4e5eb3/djlint-1.40.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4539ac7689f4b37b813b36f88679e3c444b8372d0c76fa6d13788069ddf295c", size = 599804, upload-time = "2026-07-04T11:48:11.61Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/8305bf213c9065e3f955ad9fdbf1c88c97eee69a5f4783978b5f4ad8eac4/djlint-1.40.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:976b023f2cd2c564eb71ebc618b308d8c38eb67c5d24574bd0d29d445f2106e5", size = 584955, upload-time = "2026-07-04T11:48:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/0a/85/8b6afa4018fe3c70adecbb6ae19f6372d883d16662a5d39caa3e68da83fc/djlint-1.40.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e61c5d4ad684eeaa4134c5e51e652eb96f3d342d05f5b617eaa629accf591096", size = 609521, upload-time = "2026-07-04T11:48:14.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/42/3e087f577bae675e51c47d123e38c83b249fa5ac29140740e78512b9c36c/djlint-1.40.3-cp314-cp314-win32.whl", hash = "sha256:341dbb91b1ebb96f86804a338c7c40b3a3cb860ace949c40a79da6d45272ff11", size = 442482, upload-time = "2026-07-04T11:48:15.176Z" }, + { url = "https://files.pythonhosted.org/packages/44/15/d71bd699b69f0a1d004f4451779543f778a12bac26c3e9f6ad25a910f205/djlint-1.40.3-cp314-cp314-win_amd64.whl", hash = "sha256:7917f50c96e28c5ecb48312f1696043eba8dd09a497af99f500e020ad0613d36", size = 494803, upload-time = "2026-07-04T11:48:16.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/84/2e9a86cd16d9a17c52945cec926dd4fb3db1a1e91443a5e8002b8e2e45df/djlint-1.40.3-cp314-cp314-win_arm64.whl", hash = "sha256:b09952d9cd50874287a56aaddda8caf98b33b6a94991195d717cc3b68629438a", size = 438407, upload-time = "2026-07-04T11:48:17.482Z" }, + { url = "https://files.pythonhosted.org/packages/45/2e/ae7714a5db1747c4ed14b71583baf84c53646aa6a2aa37916b55ebc46d5d/djlint-1.40.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:97c83b7497686749f7649973882c2fdd239675662f445135957f19bcc815536c", size = 623561, upload-time = "2026-07-04T11:48:18.604Z" }, + { url = "https://files.pythonhosted.org/packages/af/83/cec6eefd521993531e66e6912db85cccd5f871df65e263715a38f6b21e12/djlint-1.40.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:19969e5e5391cb97c755d1e04158456422e51a1808aacb27af624e2c47a45814", size = 593177, upload-time = "2026-07-04T11:48:19.916Z" }, + { url = "https://files.pythonhosted.org/packages/80/39/954d6465f8a072f8ef8d7468d1e33ca7c36912428bb42bb4d4d55145817a/djlint-1.40.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c02df10cf101b3908ff31128d326e23efa66e68cfaef4430e890e32c61efc129", size = 616979, upload-time = "2026-07-04T11:48:21.086Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/f1c1c0fdd7163256cc48192ce0d9bed54cc6e9a537ec66257ea721a9de5d/djlint-1.40.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e3900f481b4dcad5b4d149611b0e2c72c4955cdb1ceb5c1074ecb457cb2cb36", size = 638069, upload-time = "2026-07-04T11:48:22.463Z" }, + { url = "https://files.pythonhosted.org/packages/00/b2/834c1aba299454855555792e71610d4b128456748b1335ede89095158792/djlint-1.40.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:152b2854f92285b0ddb9b730426dfe9e7cc64603a92bde9934d51e80518a0e40", size = 623183, upload-time = "2026-07-04T11:48:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e9/1858be94f1205c85505d8646ac6b85d1312791e8b8d19f94548cf54ccb5c/djlint-1.40.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:617be8d11f337cc1e4034b8a035ef1c21dcdf4578571c4029d6108439e55765c", size = 646847, upload-time = "2026-07-04T11:48:25.11Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d8/45405e2389f7e7ff74add6abf563e66cb79934809c4298e4412a1e07f216/djlint-1.40.3-cp314-cp314t-win32.whl", hash = "sha256:a44c8248569e783e9ca5eb9588374b1b265b800b93633527d8b084ff93828287", size = 465322, upload-time = "2026-07-04T11:48:26.421Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/cac6d8e0b348c1eddb37f64bf4362e0f43dde0e122de670061386d458747/djlint-1.40.3-cp314-cp314t-win_amd64.whl", hash = "sha256:af048452b63eaf329bb9c011085b554a81ef2417724f3a0f55dd26825383d85f", size = 514250, upload-time = "2026-07-04T11:48:27.886Z" }, + { url = "https://files.pythonhosted.org/packages/13/86/69541eb6079d8eda51085fefae0e7f8603390679894231dac763d90c7ca8/djlint-1.40.3-cp314-cp314t-win_arm64.whl", hash = "sha256:5e125ec3742d710b838c49e74fb5074e0efc80c3261c2bcc36a9817510da438e", size = 456540, upload-time = "2026-07-04T11:48:29.18Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/f29fc555c8fb3a0cf2d12346ee05b8e8c6183ba98b82e5c8ee5453018891/djlint-1.40.3-py3-none-any.whl", hash = "sha256:d2b396a2cb288409fa83abc3f2b17c04aa98b0238bceaeb9d02c1db66e4f0e8f", size = 65272, upload-time = "2026-07-04T11:48:30.239Z" }, ] [[package]] @@ -2343,53 +2343,53 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, - { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, - { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, - { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, - { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, - { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, - { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, - { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, - { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, - { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, - { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, - { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, - { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, - { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, - { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, - { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, + { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, ] [[package]] From dfbf3906506581e002a9d283d7febc9ee4ddd1ba Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 15:47:25 -0700 Subject: [PATCH 11/82] update version to 2.1.2 --- frontend/package.json | 2 +- pyproject.toml | 2 +- tests/test_fs_filters_ignore.py | 1 - uv.lock | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index d18e9aa18..d11db2abf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "codex", - "version": "2.1.1", + "version": "2.1.2", "private": true, "description": "ui for codex api", "type": "module", diff --git a/pyproject.toml b/pyproject.toml index 28dc0fbb3..eeda95f8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ readme = "README.md" requires-python = ">=3.12" license = "GPL-3.0-only" name = "codex" -version = "2.1.1" +version = "2.1.2" [[project.authors]] name = "AJ Slater" email = "aj@slater.net" diff --git a/tests/test_fs_filters_ignore.py b/tests/test_fs_filters_ignore.py index ddd5e7597..fc81d6ce5 100644 --- a/tests/test_fs_filters_ignore.py +++ b/tests/test_fs_filters_ignore.py @@ -14,7 +14,6 @@ from codex.librarian.fs import filters as filters_mod from codex.librarian.fs.filters import is_ignored_basename, is_ignored_path -from codex.librarian.fs.poller import snapshot as snapshot_mod from codex.librarian.fs.poller.snapshot import DiskSnapshot from codex.librarian.fs.watcher.data import ChangeBatch from codex.librarian.fs.watcher.dirs import expand_dir_added diff --git a/uv.lock b/uv.lock index 5e823d9ce..d6e19c432 100644 --- a/uv.lock +++ b/uv.lock @@ -551,7 +551,7 @@ wheels = [ [[package]] name = "codex" -version = "2.1.1" +version = "2.1.2" source = { editable = "." } dependencies = [ { name = "adrf" }, From 1d066076e52bd816c9df4235402e422625e6b8b4 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 15:48:50 -0700 Subject: [PATCH 12/82] fix typechecking --- tests/test_fs_filters_ignore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_fs_filters_ignore.py b/tests/test_fs_filters_ignore.py index fc81d6ce5..8b807a9cf 100644 --- a/tests/test_fs_filters_ignore.py +++ b/tests/test_fs_filters_ignore.py @@ -170,7 +170,7 @@ def fake_scandir(path): raise PermissionError(13, "Permission denied") return real_scandir(path) - with patch.object(snapshot_mod.os, "scandir", side_effect=fake_scandir): + with patch.object(os, "scandir", side_effect=fake_scandir): snap = DiskSnapshot(str(_TEST_LIB_ROOT), getLogger("test")) names = {Path(p).name for p in snap.paths} From 07ffa2aa5e203561ced021ae1c3075be518b30dc Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 15:50:50 -0700 Subject: [PATCH 13/82] news for v2.1.2 Co-Authored-By: Claude Opus 4.8 --- NEWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.md b/NEWS.md index 4677afc65..159bc9335 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,14 @@ width: 128px; border-radius: 128px; " /> +## v2.1.2 + +- Fixes + - A folder with denied permissions no longer crashes the whole library + scan. + - Ignore NAS and OS junk folders, including Synology recycle bins and macOS + and Windows metadata. + ## v2.1.1 - Fixes From ed38cb5554efd73b166b826e8ed297229b9ed853 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 15:57:29 -0700 Subject: [PATCH 14/82] format --- NEWS.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 159bc9335..ff7a4fcdb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,8 +9,7 @@ border-radius: 128px; ## v2.1.2 - Fixes - - A folder with denied permissions no longer crashes the whole library - scan. + - A folder with denied permissions no longer crashes the whole library scan. - Ignore NAS and OS junk folders, including Synology recycle bins and macOS and Windows metadata. From a41eede6af41854006d7b25c70386016a3cb02ae Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 16:02:12 -0700 Subject: [PATCH 15/82] Squashed commit of the following: commit 9db6fe273622635700defb5c9015bf540630e40a Merge: c8984b9ed ed38cb555 Author: AJ Slater Date: Sat Jul 4 15:57:53 2026 -0700 Merge branch 'develop' into online-estimate-consume-comicbox commit c8984b9ede16f82f398501adb49c58d5428c168d Merge: 2b2e63a35 cd84ed99e Author: AJ Slater Date: Sat Jul 4 13:22:52 2026 -0700 Merge branch 'develop' into online-estimate-consume-comicbox commit 2b2e63a35013de0cd5593c8c5cadb360ffbd23ab Author: AJ Slater Date: Fri Jul 3 20:36:09 2026 -0700 feat(onlinetag): consume comicbox 4.1.0 estimate; drop the codex copy Pin comicbox ~=4.1.0 and move the online-tag run estimate onto its comicbox.online_estimate.estimate_run() home: - estimate.py becomes a thin seam over comicbox: estimate_seconds() forwards to estimate_run().seconds and re-exports SOURCE_RATE_PER_MINUTE. The request/rate constants and math are deleted -- comicbox owns and tests them now. - The launcher dialog's per-source rates and per-comic request model derive from comicbox via a new tagging-estimate.json (choices/onlinetag.py, build-choices); only display labels stay in the component, so the JS estimate can no longer drift from the backend. - The codex estimate test slims to an adapter / re-export guard. Prep branch: the ~=4.1.0 pin does not resolve until comicbox 4.1.0 is published, so uv.lock is untouched and CI targets that shell out to `uv` will fail until then. Post-publish, run `uv lock`; the change was validated locally with the 4.1.0 modules installed into the venv. Co-Authored-By: Claude Opus 4.8 --- codex/choices/choices_to_json.py | 2 + codex/choices/onlinetag.py | 50 ++++++++++ codex/librarian/onlinetag/estimate.py | 85 ++++------------- .../components/online-tag/launcher-dialog.vue | 36 ++++--- pyproject.toml | 2 +- tests/test_onlinetag_estimate.py | 95 ++++--------------- 6 files changed, 107 insertions(+), 163 deletions(-) create mode 100644 codex/choices/onlinetag.py diff --git a/codex/choices/choices_to_json.py b/codex/choices/choices_to_json.py index 2aac6ce9e..c5e5e04ce 100755 --- a/codex/choices/choices_to_json.py +++ b/codex/choices/choices_to_json.py @@ -20,6 +20,7 @@ ) from codex.choices.jobs import ADMIN_JOBS from codex.choices.notifications import WebsocketMessages +from codex.choices.onlinetag import TAGGING_ESTIMATE from codex.choices.reader import READER_CHOICES, READER_DEFAULTS from codex.choices.search import SEARCH_FIELDS from codex.choices.statii import ADMIN_STATUS_TITLES @@ -74,6 +75,7 @@ "reader-defaults.json": READER_DEFAULTS, "reader-map.json": READER_CHOICES, "search-map.json": SEARCH_FIELDS, + "tagging-estimate.json": TAGGING_ESTIMATE, } ) diff --git a/codex/choices/onlinetag.py b/codex/choices/onlinetag.py new file mode 100644 index 000000000..c65d739da --- /dev/null +++ b/codex/choices/onlinetag.py @@ -0,0 +1,50 @@ +""" +Online-tag run estimate model, derived from comicbox. + +The launcher dialog (``frontend/src/components/online-tag/launcher-dialog.vue``) +estimates a run's time and request count client-side. The per-source rates and +the per-comic request model come from comicbox (``comicbox.online_estimate``, +4.1.0) so the JS estimate tracks the backend instead of hand-syncing constants. +Emitted to ``frontend/src/choices/tagging-estimate.json`` by +``choices_to_json.py``. Display labels stay in the component — they're UI copy. +""" + +from types import MappingProxyType + +from comicbox.formats.base.online.rate_limits import COMICVINE_DEFAULT_PER_HOUR +from comicbox.online_estimate import ( + COMICVINE_REQUESTS_BY_MODE, + DEFAULT_REQUESTS_PER_COMIC, + METRON_REQUESTS_PER_COMIC, + SOURCE_RATE_PER_MINUTE, +) + +# Effective per-source rate limits for the launcher's rate-limit warning. +# ``per_minute`` is comicbox's estimate rate; ``per_hour`` is the effective +# hourly ceiling — Metron's per-minute cap projected over the hour, Comic Vine's +# documented 200/hour cap. +_SOURCE_RATES = MappingProxyType( + { + "metron": MappingProxyType( + { + "per_minute": SOURCE_RATE_PER_MINUTE["metron"], + "per_hour": SOURCE_RATE_PER_MINUTE["metron"] * 60, + } + ), + "comicvine": MappingProxyType( + { + "per_minute": SOURCE_RATE_PER_MINUTE["comicvine"], + "per_hour": COMICVINE_DEFAULT_PER_HOUR, + } + ), + } +) + +TAGGING_ESTIMATE = MappingProxyType( + { + "source_rates": _SOURCE_RATES, + "metron_requests_per_comic": METRON_REQUESTS_PER_COMIC, + "comicvine_requests_by_mode": COMICVINE_REQUESTS_BY_MODE, + "default_requests_per_comic": DEFAULT_REQUESTS_PER_COMIC, + } +) diff --git a/codex/librarian/onlinetag/estimate.py b/codex/librarian/onlinetag/estimate.py index f7f2cbf50..d21d57626 100644 --- a/codex/librarian/onlinetag/estimate.py +++ b/codex/librarian/onlinetag/estimate.py @@ -1,84 +1,35 @@ """ -Estimate how long an online tagging run will take. - -This mirrors the "tag online" launcher dialog's estimate -(frontend/src/components/online-tag/launcher-dialog.vue) so the number the -admin saw before starting carries forward into the live "Look Up Online -Tags" status countdown. Keep the constants and the math in sync with that -component — the whole point is that the two agree. - -The model is deliberately simple: a run's wall-clock is bound by the -slowest enabled source's per-minute throughput (first-match-wins means a -comic isn't done until the binding source answers), times the number of API -calls each comic needs, over the comics left. - -API calls per comic are counted per source. Metron (comicbox 4.0.5+) is a -fixed two-step search — one series_list discovery call plus one issues_list -lookup — that no longer scales with match mode, so it is a flat count. Comic -Vine's fuzzy volume→issue path does more verification the tighter the match -mode, so it keeps a per-mode count. - -Under first-match-wins a comic stops at the first source that answers, so we -conservatively bill the costliest single selected source. When merging all -sources (comicbox first_wins=False) every enabled source is queried for -every comic, so the per-comic calls are summed instead. +Adapt comicbox's online-tag run estimator to codex's callers. + +The request/rate model that used to live here now lives in +``comicbox.online_estimate`` (comicbox 4.1.0), next to the search flow it +describes and the rate limits it divides by. This module is the thin +codex-facing seam over it: callers keep importing ``estimate_seconds`` / +``SOURCE_RATE_PER_MINUTE`` from here, and the live status countdown and the +launcher dialog stay in agreement with comicbox because they read the same +source of truth instead of a hand-synced copy. """ from __future__ import annotations -from types import MappingProxyType -from typing import Final - -# Per-minute request budgets per source. Matches launcher-dialog SOURCE_RATES. -# Public because the live session snapshot reports each source's budget; the -# leading underscore is gone but the values are still the single source of -# truth shared with the estimate math below. -SOURCE_RATE_PER_MINUTE: Final = MappingProxyType({"metron": 20, "comicvine": 3}) - -# API calls per comic, per source. Matches the launcher-dialog's -# METRON_CALLS_PER_COMIC / COMICVINE_CALLS_BY_MODE. Metron's two-step search -# (series_list + issues_list) is a flat count that match mode does not change; -# Comic Vine's count scales with match mode. -_METRON_CALLS_PER_COMIC: Final = 2 -_COMICVINE_CALLS_BY_MODE: Final = MappingProxyType( - {"eager": 2, "auto": 3, "careful": 5} -) - -# Fallbacks for an unknown mode / unrecognized source, mirroring the dialog. -_DEFAULT_CALLS_PER_COMIC: Final = 3 -_DEFAULT_RATE_PER_MINUTE: Final = 10 - +from typing import TYPE_CHECKING -def _calls_per_comic(source: str, mode: str) -> int: - """Return the API calls one comic costs against a single source under ``mode``.""" - if source == "metron": - return _METRON_CALLS_PER_COMIC - if source == "comicvine": - return _COMICVINE_CALLS_BY_MODE.get(mode, _DEFAULT_CALLS_PER_COMIC) - return _DEFAULT_CALLS_PER_COMIC +from comicbox.online_session import SOURCE_RATE_PER_MINUTE, estimate_run +if TYPE_CHECKING: + from collections.abc import Sequence -def _slowest_rate_per_minute(sources: tuple[str, ...]) -> int: - rates = [SOURCE_RATE_PER_MINUTE[s] for s in sources if s in SOURCE_RATE_PER_MINUTE] - return min(rates) if rates else _DEFAULT_RATE_PER_MINUTE +__all__ = ("SOURCE_RATE_PER_MINUTE", "estimate_seconds") def estimate_seconds( remaining_comics: int, mode: str, - sources: tuple[str, ...], + sources: Sequence[str], *, merge_all_sources: bool = False, ) -> float: """Estimated seconds to look up ``remaining_comics`` more comics.""" - if remaining_comics <= 0 or not sources: - return 0.0 - per_source_calls = [_calls_per_comic(source, mode) for source in sources] - # Merge queries every source per comic (sum); first-match-wins stops at - # the first source that answers, so bill the costliest single source. - calls_per_comic = ( - sum(per_source_calls) if merge_all_sources else max(per_source_calls) - ) - total_calls = remaining_comics * calls_per_comic - minutes = total_calls / _slowest_rate_per_minute(sources) - return minutes * 60.0 + return estimate_run( + remaining_comics, mode, sources, merge_all_sources=merge_all_sources + ).seconds diff --git a/frontend/src/components/online-tag/launcher-dialog.vue b/frontend/src/components/online-tag/launcher-dialog.vue index bba5f5d43..cae3bf656 100644 --- a/frontend/src/components/online-tag/launcher-dialog.vue +++ b/frontend/src/components/online-tag/launcher-dialog.vue @@ -182,6 +182,7 @@ import { mapActions, mapState } from "pinia"; import { HTTP } from "@/api/v4/base"; import TAGGING_CHOICES from "@/choices/tagging-choices.json"; +import TAGGING_ESTIMATE from "@/choices/tagging-estimate.json"; import { useAdminStore } from "@/stores/admin"; import { useCommonStore } from "@/stores/common"; import { useOnlineTagStore } from "@/stores/online-tag"; @@ -202,29 +203,26 @@ const PROMPTS_MODE_HINTS = { "Skips all ambiguous matches without prompting. Unmatched comics are left unchanged.", }; -const SOURCE_RATES = { - metron: { perMinute: 20, perHour: 1200, label: "Metron Cloud" }, - comicvine: { perMinute: 3, perHour: 200, label: "Comic Vine" }, -}; - -// API calls per comic, per source. Keep in sync with -// codex/librarian/onlinetag/estimate.py. Metron (comicbox 4.0.5+) is a flat -// two-step search (series_list + issues_list) that match mode doesn't change; -// Comic Vine's count scales with match mode. -const METRON_CALLS_PER_COMIC = 2; -const COMICVINE_CALLS_BY_MODE = { - eager: 2, - auto: 3, - careful: 5, -}; -const DEFAULT_CALLS_PER_COMIC = 3; +// Display names are UI copy; the per-source rates and per-comic request model +// derive from comicbox via tagging-estimate.json (build-choices), so the client +// estimate tracks the backend instead of hand-syncing constants. +const SOURCE_LABELS = { metron: "Metron Cloud", comicvine: "Comic Vine" }; +const SOURCE_RATES = Object.fromEntries( + Object.entries(TAGGING_ESTIMATE.sourceRates).map(([source, rate]) => [ + source, + { ...rate, label: SOURCE_LABELS[source] || source }, + ]), +); function callsForSource(source, mode) { - if (source === "metron") return METRON_CALLS_PER_COMIC; + if (source === "metron") return TAGGING_ESTIMATE.metronRequestsPerComic; if (source === "comicvine") { - return COMICVINE_CALLS_BY_MODE[mode] || DEFAULT_CALLS_PER_COMIC; + return ( + TAGGING_ESTIMATE.comicvineRequestsByMode[mode] || + TAGGING_ESTIMATE.defaultRequestsPerComic + ); } - return DEFAULT_CALLS_PER_COMIC; + return TAGGING_ESTIMATE.defaultRequestsPerComic; } // Only these sources support online id tagging; derived from comicbox's diff --git a/pyproject.toml b/pyproject.toml index eeda95f8a..1d4a6dd25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "adrf~=0.1.12", "bidict~=0.23", "channels~=4.2", - "comicbox[pdf]~=4.0.5", + "comicbox[pdf]~=4.1.0", "cryptography>=48.0.0", "dateparser~=1.2", "django-cachalot~=2.8", diff --git a/tests/test_onlinetag_estimate.py b/tests/test_onlinetag_estimate.py index 279b97667..c5fed9ece 100644 --- a/tests/test_onlinetag_estimate.py +++ b/tests/test_onlinetag_estimate.py @@ -1,9 +1,9 @@ """ -Unit tests for the online tagging time estimate. +The codex estimate seam delegates to comicbox.online_estimate. -Mirrors the launcher dialog's algorithm: per-comic API calls (counted per -source — first-match-wins bills the costliest source, merge sums them) over -the slowest enabled source's per-minute rate. +The request/rate model and its full test matrix live in comicbox (4.1.0); these +just guard that codex's thin adapter forwards correctly and re-exports the rate +map the session snapshot reads. """ from __future__ import annotations @@ -12,84 +12,27 @@ import pytest -from codex.librarian.onlinetag.estimate import estimate_seconds +from codex.librarian.onlinetag.estimate import SOURCE_RATE_PER_MINUTE, estimate_seconds -_AUTO_METRON_SECONDS: Final = 60.0 -_SLOWEST_SOURCE_SECONDS: Final = 600.0 -_COMICVINE_CAREFUL_SECONDS: Final = 1000.0 -_COMICVINE_EAGER_SECONDS: Final = 400.0 -_UNKNOWN_DEFAULTS_SECONDS: Final = 180.0 -_MERGE_TWO_SOURCES_SECONDS: Final = 1000.0 +_METRON_AUTO_SECONDS: Final = 60.0 +_MERGE_SECONDS: Final = 1000.0 +_METRON_RATE: Final = 20 +_COMICVINE_RATE: Final = 3 -def test_zero_remaining_is_zero() -> None: - """No remaining comics means no time.""" - assert estimate_seconds(0, "auto", ("metron",)) == 0.0 +def test_estimate_seconds_delegates_to_comicbox() -> None: + """Metron's flat two-step: 10 comics x 2 requests / 20 per-minute = 60s.""" + assert estimate_seconds(10, "auto", ("metron",)) == _METRON_AUTO_SECONDS -def test_no_sources_is_zero() -> None: - """No enabled sources means no time.""" - assert estimate_seconds(10, "auto", ()) == 0.0 - - -def test_auto_mode_metron() -> None: - """Metron's flat two-step search: 10 comics x 2 calls / 20 per-minute = 60s.""" - assert estimate_seconds(10, "auto", ("metron",)) == _AUTO_METRON_SECONDS - - -def test_metron_calls_are_mode_independent() -> None: - """Match mode does not change Metron's fixed two-step request count.""" - auto = estimate_seconds(10, "auto", ("metron",)) - careful = estimate_seconds(10, "careful", ("metron",)) - eager = estimate_seconds(10, "eager", ("metron",)) - assert auto == careful == eager == _AUTO_METRON_SECONDS - - -def test_slowest_source_binds() -> None: - """ - Comicvine (3/min) is slower than metron (20/min) and binds the rate. - - First-match-wins bills the costliest source: max(metron 2, comicvine 3) = 3. - 10 x 3 / 3 = 10 min = 600s. - """ - assert ( - estimate_seconds(10, "auto", ("metron", "comicvine")) == _SLOWEST_SOURCE_SECONDS - ) - - -def test_comicvine_match_mode_scales_calls() -> None: - """Comic Vine's per-comic calls scale with match mode (careful 5, eager 2).""" - careful = estimate_seconds(10, "careful", ("comicvine",)) - eager = estimate_seconds(10, "eager", ("comicvine",)) - assert careful == pytest.approx(_COMICVINE_CAREFUL_SECONDS) - assert eager == pytest.approx(_COMICVINE_EAGER_SECONDS) - - -def test_unknown_mode_and_source_use_defaults() -> None: - """ - Unknown mode -> 3 calls/comic; unknown source -> 10/min default. - - 10 x 3 / 10 = 3 min = 180s. - """ - assert estimate_seconds(10, "bogus", ("unknown",)) == _UNKNOWN_DEFAULTS_SECONDS - - -def test_merge_all_sources_sums_per_source_calls() -> None: - """ - Merge mode queries every source per comic, so per-comic calls are summed. - - metron 2 + comicvine 3 = 5 calls/comic over 3 per-minute (slowest). - 10 x 5 / 3 = 1000s — more than the 600s first-match-wins estimate but not - double it, since the two sources cost different amounts. - """ +def test_estimate_seconds_passes_merge_flag() -> None: + """merge_all_sources reaches comicbox: (metron 2 + comicvine 3) x 10 / 3.""" assert estimate_seconds( 10, "auto", ("metron", "comicvine"), merge_all_sources=True - ) == pytest.approx(_MERGE_TWO_SOURCES_SECONDS) + ) == pytest.approx(_MERGE_SECONDS) -def test_merge_all_sources_single_source_is_noop() -> None: - """With one source there's nothing to merge, so the estimate is unchanged.""" - assert ( - estimate_seconds(10, "auto", ("metron",), merge_all_sources=True) - == _AUTO_METRON_SECONDS - ) +def test_source_rate_per_minute_reexported() -> None: + """The snapshot reads the per-source rate map through this seam.""" + assert SOURCE_RATE_PER_MINUTE["metron"] == _METRON_RATE + assert SOURCE_RATE_PER_MINUTE["comicvine"] == _COMICVINE_RATE From 8ebc051f907f594aae7189d66f6e4354ab36d8b2 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 16:05:43 -0700 Subject: [PATCH 16/82] update deps --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index d6e19c432..d62cd7156 100644 --- a/uv.lock +++ b/uv.lock @@ -636,7 +636,7 @@ requires-dist = [ { name = "adrf", specifier = "~=0.1.12" }, { name = "bidict", specifier = "~=0.23" }, { name = "channels", specifier = "~=4.2" }, - { name = "comicbox", extras = ["pdf"], specifier = "~=4.0.5" }, + { name = "comicbox", extras = ["pdf"], specifier = "~=4.1.0" }, { name = "cryptography", specifier = ">=48.0.0" }, { name = "dateparser", specifier = "~=1.2" }, { name = "django", specifier = "~=6.0" }, @@ -721,7 +721,7 @@ wheels = [ [[package]] name = "comicbox" -version = "4.0.5" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, @@ -758,9 +758,9 @@ dependencies = [ { name = "xmltodict" }, { name = "zipremove" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/b7/06d22b40edba18a7d66988d376205184b1b580ed0f7ab69d7340051f36b6/comicbox-4.0.5.tar.gz", hash = "sha256:337b8ae057a684d814d78b2460efdbe6d2df403d23060977b2f853bc1f0dfd0b", size = 978125, upload-time = "2026-07-04T02:11:17.052Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/b3/988bfc40cc288e1036ed9b8d9a37cacbc4b63948a4decde6d855714b1c23/comicbox-4.1.0.tar.gz", hash = "sha256:8c14e015ad0829651f17d10f8bae34d2d378ff9385c3800e2fb85fe126f8cf56", size = 981621, upload-time = "2026-07-04T23:01:22.83Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/86/ee66f8fd03982dc931ebf17cb68c05a7834d90078ba5ec9c302d8b879dcf/comicbox-4.0.5-py3-none-any.whl", hash = "sha256:6c71ed43fd0c7295cdbc7380fe5bbf04ef5560f00f3356d6f97559b4ca401185", size = 328601, upload-time = "2026-07-04T02:11:15.394Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/717c5ea5de203da3e1c0e2722da51c88adfcecb9c193fe32e28d142ab6d5/comicbox-4.1.0-py3-none-any.whl", hash = "sha256:4287bc0664260e93605617640fc47762ae742eb52739083aac9a32ef9d781a5a", size = 330884, upload-time = "2026-07-04T23:01:21.444Z" }, ] [package.optional-dependencies] From 08eadba9aa645c58db71af49eccb21dafe03ef86 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 16:07:28 -0700 Subject: [PATCH 17/82] refactor(onlinetag): extract resume-param sanitizer to cut complexity AdminOnlineTagResumeView.post crossed radon's C threshold once the resume descriptor sanitization landed. Move that logic (sources tuple coercion + dropping keys no task field accepts) into a module-level helper; the view falls to rank B and reads more directly. No behavior change. Co-Authored-By: Claude Opus 4.8 --- codex/views/admin/onlinetag.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/codex/views/admin/onlinetag.py b/codex/views/admin/onlinetag.py index df1a914bd..c0b0bce32 100644 --- a/codex/views/admin/onlinetag.py +++ b/codex/views/admin/onlinetag.py @@ -130,6 +130,22 @@ def delete(self, _request, session_id): return Response({"detail": "Pause signal sent."}, status=HTTP_202_ACCEPTED) +def _resume_task_params(resume: dict) -> dict: + """ + Sanitize a stored resume descriptor into ``BulkOnlineTagTask`` kwargs. + + ``sources`` round-trips through JSON as a list; the task wants a tuple. A + resume descriptor persists to a file-based cache, so one written by an older + Codex may carry keys a task field no longer accepts (e.g. the removed + ``effort`` knob) — drop unknown keys so resuming across an upgrade rebuilds + the task instead of raising ``TypeError``. + """ + params = dict(resume.get("params") or {}) + params["sources"] = tuple(params.get("sources") or ()) + valid_fields = {f.name for f in fields(BulkOnlineTagTask)} + return {k: v for k, v in params.items() if k in valid_fields} + + class AdminOnlineTagResumeView(AdminAPIView): """Resume a paused/interrupted scan over the comics it never reached.""" @@ -145,20 +161,11 @@ def post(self, _request): if not resume or not remaining: return Response({"detail": "Nothing to resume."}, status=400) - params = dict(resume.get("params") or {}) - # sources round-trips through JSON as a list; the task wants a tuple. - params["sources"] = tuple(params.get("sources") or ()) - # A resume descriptor persists to a file-based cache, so one written by - # an older Codex may carry keys a task field no longer accepts (e.g. the - # removed ``effort`` knob). Drop unknown keys so resuming across an - # upgrade rebuilds the task instead of raising TypeError. - valid_fields = {f.name for f in fields(BulkOnlineTagTask)} - params = {k: v for k, v in params.items() if k in valid_fields} session_id = str(uuid.uuid4()) task = BulkOnlineTagTask( comic_pks=frozenset(remaining), session_id=session_id, - **params, + **_resume_task_params(resume), ) LIBRARIAN_QUEUE.put(task) return Response( From 227cea4f3381ffe361d7be0ac5099a5319838b41 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 4 Jul 2026 16:27:04 -0700 Subject: [PATCH 18/82] fix(onlinetag): reference defined constant in match-mode hint matchModeHint read an undefined COMICVINE_CALLS_BY_MODE, throwing a ReferenceError on every launcher-dialog render (and failing tests/unit/launcher-dialog.test.js). Point at the real TAGGING_ESTIMATE.comicvineRequestsByMode map that callsForSource already uses. Co-Authored-By: Claude Opus 4.8 --- frontend/src/components/online-tag/launcher-dialog.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/online-tag/launcher-dialog.vue b/frontend/src/components/online-tag/launcher-dialog.vue index cae3bf656..bf27ef9ce 100644 --- a/frontend/src/components/online-tag/launcher-dialog.vue +++ b/frontend/src/components/online-tag/launcher-dialog.vue @@ -418,7 +418,7 @@ export default { if (!this.activeSources.includes("comicvine")) { return base; } - const requests = COMICVINE_CALLS_BY_MODE[this.matchMode]; + const requests = TAGGING_ESTIMATE.comicvineRequestsByMode[this.matchMode]; return requests ? `${base} ~${requests} Comic Vine requests/comic.` : base; From 97c62aab58cd4d4c4ce9cc48209a37199c3efbe0 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 6 Jul 2026 23:54:56 -0700 Subject: [PATCH 19/82] update deps --- bun.lock | 72 +++++++++-- frontend/bun.lock | 26 ++-- frontend/package.json | 4 +- package.json | 6 +- uv.lock | 270 ++++++++++++++++++++++-------------------- 5 files changed, 224 insertions(+), 154 deletions(-) diff --git a/bun.lock b/bun.lock index 841e1577f..d7db82ab8 100644 --- a/bun.lock +++ b/bun.lock @@ -25,17 +25,17 @@ "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", "eslint-plugin-package-json": "^1.5.0", - "eslint-plugin-perfectionist": "^5.9.1", + "eslint-plugin-perfectionist": "^5.10.0", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-regexp": "^3.1.1", "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.1.0", "eslint-plugin-toml": "^1.4.0", - "eslint-plugin-unicorn": "^70.0.0", + "eslint-plugin-unicorn": "^71.1.0", "eslint-plugin-vue": "^10.9.2", "eslint-plugin-vue-scoped-css": "^3.1.1", - "eslint-plugin-yml": "^3.5.0", + "eslint-plugin-yml": "^3.6.0", "eslint_d": "^15.0.2", "postcss-scss": "^4.0.9", "prettier": "^3.9.4", @@ -303,6 +303,8 @@ "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], + "convert-hrtime": ["convert-hrtime@5.0.0", "", {}, "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg=="], + "core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="], "core_d": ["core_d@6.1.1", "", { "dependencies": { "supports-color": "^8.1.0" } }, "sha512-96Pba8AFV6AjBwOz0PxHXkKm4CzmrNA2Im8NlPCsi70OefzgXxw3VDuKIMC18SXV1GF2oyNfIdh3w+JaNkSCCA=="], @@ -399,7 +401,7 @@ "eslint-plugin-package-json": ["eslint-plugin-package-json@1.5.0", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.1", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.5.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-KQkAdn+7I4vn1W4Va+V8M94RhcgZBvgrxMglDqgEhJgQDRUJ9RdtrEiHoe/84oVTIlp0jiPttLYwkExdvHYhZQ=="], - "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.9.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.61.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-30mHLNfEhzwaq5cquyWgnzrNXvT8AzwIwyeH5aj4U5ajhHSF2uiO6i09xpMDLv7koaZVTjLsvYF4m3gK/15tyA=="], + "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.10.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.62.1", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-HiqpDrUDbGrMC6iHQbemgDyHJ0366Vyz/qRWmxQcSAkmG25cXr8BdRgx8yAhOKhEfBXn8Rnf/mTCsV4EqUJSxg=="], "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.6", "", { "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.13" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ=="], @@ -413,13 +415,13 @@ "eslint-plugin-toml": ["eslint-plugin-toml@1.4.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "debug": "^4.1.1", "toml-eslint-parser": "^1.0.1" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-3ErTnfUjXq/23f72XeyRcE0Y4Sd/ME1lsZeezczqpn2R4tE7+Sgco/NUKDXm0xAMz15tzcRz/9RfJRm6AqRO+A=="], - "eslint-plugin-unicorn": ["eslint-plugin-unicorn@70.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "@eslint-community/eslint-utils": "^4.9.1", "browserslist": "^4.28.2", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.49.0", "detect-indent": "^7.0.2", "find-up-simple": "^1.0.1", "globals": "^17.6.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", "regjsparser": "^0.13.1", "semver": "^7.8.4", "strip-indent": "^4.1.1" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-uAF9xMcVvvhTfvusCgogJ1wh4To3q2KhVMw3i1Apf/ILTbxsCjscvraAZACsEurb7no2fdXblD3whcbVnjw5zg=="], + "eslint-plugin-unicorn": ["eslint-plugin-unicorn@71.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "browserslist": "^4.28.4", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.49.0", "detect-indent": "^7.0.2", "find-up-simple": "^1.0.1", "globals": "^17.7.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "is-identifier": "^1.1.0", "pluralize": "^8.0.0", "quote-js-string": "^0.1.0", "regjsparser": "^0.13.2", "reserved-identifiers": "^1.2.0", "semver": "^7.8.5", "strip-indent": "^4.1.1" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-dn3YmR3qLLUeYyo/os3ubZ7UHQJ1WbBAgC9cIhnLTyMj9J6kivuc2U1fCmYetLexUlTDVYtBqhjSj/VaebTe6Q=="], "eslint-plugin-vue": ["eslint-plugin-vue@10.9.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.3.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA=="], "eslint-plugin-vue-scoped-css": ["eslint-plugin-vue-scoped-css@3.1.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "es-toolkit": "^1.46.1", "postcss": "^8.4.31", "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "eslint": ">=9.38.0", "postcss-scss": "^4.0.3", "postcss-styl": "^0.12.0", "vue-eslint-parser": ">=7.1.0" }, "optionalPeers": ["postcss-scss", "postcss-styl"] }, "sha512-GIskMvLPnDtiu88rWXQHy2b2QZ4j959N5UgghML64jH0sg3Km+HRa9m7nkpcEBGLD4iA4vtMDbBIoLdFcbT8lQ=="], - "eslint-plugin-yml": ["eslint-plugin-yml@3.5.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-u2UkSIp/+th1wYCt0QWeCI6agf24dxX6PbFfCCN18gGQHmXh3Cn9D/U5OiP5RNYTEwjXCLusj1OJRK+zwdvFqQ=="], + "eslint-plugin-yml": ["eslint-plugin-yml@3.6.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-8lSqBIiOJO8ms1gktdlNaVGm3zXQJiYeKhQmtIG66XzU58kFDOjQae4sufQEMGHC4bP8VxiaH3tjU5PpDB6euw=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], @@ -471,6 +473,8 @@ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-timeout": ["function-timeout@1.0.2", "", {}, "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA=="], + "functional-red-black-tree": ["functional-red-black-tree@1.0.1", "", {}, "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g=="], "get-set-props": ["get-set-props@0.2.0", "", {}, "sha512-YCmOj+4YAeEB5Dd9jfp6ETdejMet4zSxXjNkgaa4npBEKRI9uDOGB5MmAdAgi2OoFGAKshYhCbmLq2DS03CgVA=="], @@ -491,6 +495,8 @@ "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + "identifier-regex": ["identifier-regex@1.1.0", "", { "dependencies": { "reserved-identifiers": "^1.0.0" } }, "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA=="], + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], @@ -527,6 +533,8 @@ "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "is-identifier": ["is-identifier@1.1.0", "", { "dependencies": { "identifier-regex": "^1.1.0", "super-regex": "^1.1.0" } }, "sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw=="], + "is-js-type": ["is-js-type@3.0.0", "", { "dependencies": { "js-types": "^4.0.0" } }, "sha512-IbPf3g3vxm1D902xaBaYp2TUHiXZWwWRu5bM9hgKN9oAQcFaKALV6Gd13PGhXjKE5u2n8s1PhLhdke/E1fchxQ=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], @@ -583,6 +591,8 @@ "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + "make-asynchronous": ["make-asynchronous@1.1.0", "", { "dependencies": { "p-event": "^6.0.0", "type-fest": "^4.6.0", "web-worker": "^1.5.0" } }, "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg=="], + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], @@ -735,10 +745,14 @@ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "p-event": ["p-event@6.0.1", "", { "dependencies": { "p-timeout": "^6.1.2" } }, "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "package-json-validator": ["package-json-validator@1.5.2", "", { "dependencies": { "npm-package-arg": "^13.0.2", "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^7.0.0" } }, "sha512-eHXskJQU4aCiSfjhRfTVtCJ+22/EzLHgYgZv5Gj3teb3NJrnTMzq5BnKAWKvR+PLpknCO1PmOCImDuO+dX4Vaw=="], @@ -791,6 +805,8 @@ "quotation": ["quotation@2.0.3", "", {}, "sha512-yEc24TEgCFLXx7D4JHJJkK4JFVtatO8fziwUxY4nB/Jbea9o9CVS3gt22mA0W7rPYAGW2fWzYDSOtD94PwOyqA=="], + "quote-js-string": ["quote-js-string@0.1.0", "", {}, "sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA=="], + "read-package-json-fast": ["read-package-json-fast@3.0.2", "", { "dependencies": { "json-parse-even-better-errors": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" } }, "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw=="], "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -929,6 +945,8 @@ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + "reserved-identifiers": ["reserved-identifiers@1.2.0", "", {}, "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -981,6 +999,8 @@ "strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="], + "super-regex": ["super-regex@1.1.0", "", { "dependencies": { "function-timeout": "^1.0.1", "make-asynchronous": "^1.0.1", "time-span": "^5.1.0" } }, "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], @@ -989,6 +1009,8 @@ "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + "time-span": ["time-span@5.1.0", "", { "dependencies": { "convert-hrtime": "^5.0.0" } }, "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -1063,6 +1085,8 @@ "walk-up-path": ["walk-up-path@3.0.1", "", {}, "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="], + "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], @@ -1125,9 +1149,15 @@ "eslint-mdx/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + "eslint-plugin-perfectionist/@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="], + "eslint-plugin-sonarjs/globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], - "eslint-plugin-unicorn/globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], + "eslint-plugin-unicorn/browserslist": ["browserslist@4.28.5", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001800", "electron-to-chromium": "^1.5.387", "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ=="], + + "eslint-plugin-unicorn/globals": ["globals@17.7.0", "", {}, "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg=="], + + "eslint-plugin-unicorn/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "eslint-plugin-yml/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -1139,6 +1169,8 @@ "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "make-asynchronous/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "normalize-package-data/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], @@ -1193,6 +1225,20 @@ "eslint-mdx/espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], + + "eslint-plugin-unicorn/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="], + + "eslint-plugin-unicorn/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001803", "", {}, "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg=="], + + "eslint-plugin-unicorn/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.388", "", {}, "sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw=="], + + "eslint-plugin-unicorn/browserslist/node-releases": ["node-releases@2.0.50", "", {}, "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg=="], + "eslint_d/eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "eslint_d/eslint/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], @@ -1219,8 +1265,20 @@ "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "npm-pick-manifest/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], } } diff --git a/frontend/bun.lock b/frontend/bun.lock index e8570a768..a0aa825e8 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -36,9 +36,9 @@ "vite": "^8.1.3", "vite-plugin-checker": "^0.14.4", "vite-plugin-dynamic-base": "^1.4.1", - "vite-plugin-run": "^0.9.0", + "vite-plugin-run": "^0.9.1", "vite-plugin-vuetify": "^2.1.3", - "vitest": "^4.1.9", + "vitest": "^4.1.10", "vue-eslint-parser": "^10.4.1", }, }, @@ -342,19 +342,19 @@ "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.7", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg=="], - "@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="], + "@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="], - "@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="], + "@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.9", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.10", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q=="], - "@vitest/runner": ["@vitest/runner@4.1.9", "", { "dependencies": { "@vitest/utils": "4.1.9", "pathe": "^2.0.3" } }, "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg=="], + "@vitest/runner": ["@vitest/runner@4.1.10", "", { "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" } }, "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="], - "@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], + "@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="], - "@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="], + "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="], "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], @@ -880,11 +880,11 @@ "vite-plugin-dynamic-base": ["vite-plugin-dynamic-base@1.4.1", "", { "dependencies": { "@swc/core": "^1.3.61", "node-html-parser": "^5.3.3" }, "peerDependencies": { "vite": ">= 2.9.5" } }, "sha512-TvH5FNo8mp/jBZZN/agKnMIlf/OgFKr3vBPMniBAl8x5uFyIJzPQEMqIqwKQUxZUtWhVRWWBdW+9T/LU2Wg+Qg=="], - "vite-plugin-run": ["vite-plugin-run@0.9.0", "", { "dependencies": { "@antfu/utils": "^9.3.0", "debug": "^4.4.3", "es-toolkit": "^1.45.1", "minimatch": "^10.2.5", "picocolors": "^1.1.1", "tinyexec": "^1.1.1" }, "peerDependencies": { "vite": "^8.0.8" } }, "sha512-Y2jaKIPtnAiQu46LJ798JV5gHRBRzGocOBX/RspfRsDbHZts/oYsvx2vQNxFLqxd0k69j4lQokYZ8dKDAqiTgA=="], + "vite-plugin-run": ["vite-plugin-run@0.9.1", "", { "dependencies": { "@antfu/utils": "^9.3.0", "debug": "^4.4.3", "es-toolkit": "^1.45.1", "minimatch": "^10.2.5", "picocolors": "^1.1.1", "tinyexec": "^1.1.1" }, "peerDependencies": { "vite": "^8.0.8" } }, "sha512-0WtCNNwJM2iBbYcNfmQjHdQGluZuyKRIXM62MY+x3B4TVuCgA6pChMUFjzD1dxrQwgEl5f6duhY4ewweV7vQJw=="], "vite-plugin-vuetify": ["vite-plugin-vuetify@2.1.3", "", { "dependencies": { "@vuetify/loader-shared": "^2.1.2", "debug": "^4.3.3", "upath": "^2.0.1" }, "peerDependencies": { "vite": ">=5", "vue": "^3.0.0", "vuetify": ">=3" } }, "sha512-Q4SC/4TqbNvaZIFb9YsfBqkGlYHbJJJ6uU3CnRBZqLUF3s5eCMVZAaV4GkTbehIH/bhSj42lMXztOwc71u6rVw=="], - "vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="], + "vitest": ["vitest@4.1.10", "", { "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw=="], "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], @@ -1022,8 +1022,6 @@ "vite/rolldown": ["rolldown@1.1.3", "", { "dependencies": { "@oxc-project/types": "=0.137.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.3", "@rolldown/binding-darwin-arm64": "1.1.3", "@rolldown/binding-darwin-x64": "1.1.3", "@rolldown/binding-freebsd-x64": "1.1.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", "@rolldown/binding-linux-arm64-gnu": "1.1.3", "@rolldown/binding-linux-arm64-musl": "1.1.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.3", "@rolldown/binding-linux-s390x-gnu": "1.1.3", "@rolldown/binding-linux-x64-gnu": "1.1.3", "@rolldown/binding-linux-x64-musl": "1.1.3", "@rolldown/binding-openharmony-arm64": "1.1.3", "@rolldown/binding-wasm32-wasi": "1.1.3", "@rolldown/binding-win32-arm64-msvc": "1.1.3", "@rolldown/binding-win32-x64-msvc": "1.1.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g=="], - "vitest/vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="], - "vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], "vue/@vue/server-renderer": ["@vue/server-renderer@3.5.39", "", { "dependencies": { "@vue/compiler-ssr": "3.5.39", "@vue/shared": "3.5.39" }, "peerDependencies": { "vue": "3.5.39" } }, "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw=="], @@ -1148,8 +1146,6 @@ "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA=="], - "vitest/vite/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], - "vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.3", "", { "dependencies": { "@vue/devtools-shared": "^8.1.3", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA=="], "vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.39", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.39", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw=="], diff --git a/frontend/package.json b/frontend/package.json index d11db2abf..fa4eae834 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,9 +47,9 @@ "vite": "^8.1.3", "vite-plugin-checker": "^0.14.4", "vite-plugin-dynamic-base": "^1.4.1", - "vite-plugin-run": "^0.9.0", + "vite-plugin-run": "^0.9.1", "vite-plugin-vuetify": "^2.1.3", - "vitest": "^4.1.9", + "vitest": "^4.1.10", "vue-eslint-parser": "^10.4.1" } } diff --git a/package.json b/package.json index 5fd1e49ec..6c296d239 100644 --- a/package.json +++ b/package.json @@ -80,17 +80,17 @@ "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", "eslint-plugin-package-json": "^1.5.0", - "eslint-plugin-perfectionist": "^5.9.1", + "eslint-plugin-perfectionist": "^5.10.0", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-regexp": "^3.1.1", "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.1.0", "eslint-plugin-toml": "^1.4.0", - "eslint-plugin-unicorn": "^70.0.0", + "eslint-plugin-unicorn": "^71.1.0", "eslint-plugin-vue": "^10.9.2", "eslint-plugin-vue-scoped-css": "^3.1.1", - "eslint-plugin-yml": "^3.5.0", + "eslint-plugin-yml": "^3.6.0", "postcss-scss": "^4.0.9", "prettier": "^3.9.4", "prettier-plugin-nginx": "^1.0.3", diff --git a/uv.lock b/uv.lock index d62cd7156..cd2dfa415 100644 --- a/uv.lock +++ b/uv.lock @@ -387,59 +387,87 @@ wheels = [ [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] @@ -457,75 +485,63 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +version = "3.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/56/10a88e00039537d74bd420f0457c52ab8f58a1af56126e3b9f1b1c8c4724/charset_normalizer-3.4.8.tar.gz", hash = "sha256:d9bf144d6faf12c70d58e47f7512992ae2882b820031d6cef68152deb645bf2d", size = 151790, upload-time = "2026-07-06T15:27:58.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/c2/39de60ef5687662f467bed3d1e6944c67a4f0d057141d0404002b8f405ae/charset_normalizer-3.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:faac37c4904598daa00cb4c9b32f3b4cc814fb5f145d7a531ceb4a70f2114132", size = 319040, upload-time = "2026-07-06T15:26:15.854Z" }, + { url = "https://files.pythonhosted.org/packages/a7/57/a9474c3aeaa337c8a330c0dc5df266527d56da3b189c029529f6b08af2a4/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f191c19a32dc6cec0fb8079789d786254653a9ce906fcab04ccd2eed07bba233", size = 215541, upload-time = "2026-07-06T15:26:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/13/a9/be1ff7e81f6e086dced2a7a7a28b789be351d9796084ccaf6136a4ffafb3/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05811b76943d477bb90822dedb5c4565cef70148847a59d574e2b35043aeb563", size = 236913, upload-time = "2026-07-06T15:26:18.482Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/d8c5eae93da26d463f9ebe46a4937ca44434dc2937a565b92437befb3d94/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3868a3e4ec1e40b419e060d063f93eac6f046fa21426c4816421223ae7dc8ab8", size = 232815, upload-time = "2026-07-06T15:26:19.734Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/98e301ca944bcca5e6bc312406b579c8a6d81546c1b494afb3a9478495d6/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25f93d194eb6264c64416cabff46a91f6d99b97e7525a1b4f35c77a99e75cc68", size = 223995, upload-time = "2026-07-06T15:26:20.931Z" }, + { url = "https://files.pythonhosted.org/packages/fa/df/f5222366b76dcb31453a9bd922610c893540d0e729fd390439b0d3e972ee/charset_normalizer-3.4.8-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:ee6a62492f18d432cca031fabd158f400a8c25bf7b9458f50953393a2a23d97a", size = 208522, upload-time = "2026-07-06T15:26:22.214Z" }, + { url = "https://files.pythonhosted.org/packages/74/52/293220d59d8ddfb8aa56836b33bd6df58e70795d8a102a858c2984480f00/charset_normalizer-3.4.8-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c16cb4fc35e4b064f5ee78d849f15a550ada1729c3372916672e38f1f01d1d4", size = 219660, upload-time = "2026-07-06T15:26:23.493Z" }, + { url = "https://files.pythonhosted.org/packages/c3/2c/81a298e66f3d01e61bfc6f7064bbb553b067a9f1d979e5962bf00733069b/charset_normalizer-3.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2fbd0edb0426ab28e70fac9d1d4ef549eb5a64a2521f0428c441d75e4387e6c2", size = 218230, upload-time = "2026-07-06T15:26:24.629Z" }, + { url = "https://files.pythonhosted.org/packages/0d/45/f1dd2328cbc3340705f82072c09bd4c68d6e079191cde05810c1eac77eee/charset_normalizer-3.4.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccb9052771216170015f810b88065fd9e13b1e0b391f92abb9b47e0919a42aad", size = 210006, upload-time = "2026-07-06T15:26:25.837Z" }, + { url = "https://files.pythonhosted.org/packages/38/63/28697000620e117eb413424caaf60b6f98ddb1b09b2c11f7c0038d9936a7/charset_normalizer-3.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3809ba5d3cd02aca0894597f2669a825bdfe2229061515c128b0f4e5533b4ab5", size = 225771, upload-time = "2026-07-06T15:26:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/12/a7/d5844e315f5f35e7938c415f07a1df144eed1cf993f1b43cc16c980c5b46/charset_normalizer-3.4.8-cp312-cp312-win32.whl", hash = "sha256:de63c31666a049f653ada24e800192e3c019e96bc7d70fb449a000bccf26a36f", size = 150922, upload-time = "2026-07-06T15:26:28.514Z" }, + { url = "https://files.pythonhosted.org/packages/9b/82/eb8b72f184b1e4986dd9daec15d7f6d9285a6728d2b07b7f04656829f473/charset_normalizer-3.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:14a4bbe066f3fb05c6ba70e9cf9d34614b57a2fd70ea8c27cc30f34155e16a58", size = 162294, upload-time = "2026-07-06T15:26:29.745Z" }, + { url = "https://files.pythonhosted.org/packages/09/5a/ab810134aa41034a08ffe94c058102016e6ad9bce62f3cdba547b4723385/charset_normalizer-3.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:2b5b0c0dca0a02c3f816f89abf18af3d20416dedbc3d3aa5f3981045f88ae7b0", size = 152409, upload-time = "2026-07-06T15:26:31.034Z" }, + { url = "https://files.pythonhosted.org/packages/11/49/fe5a8572a70cd9cba79f80af9388ac8c5c914ed4459b956f940244e499a5/charset_normalizer-3.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:057f8609f7341618c98e5aa9a6109fa116acff2a658497d47ab3325b5e8f2b08", size = 317424, upload-time = "2026-07-06T15:26:32.23Z" }, + { url = "https://files.pythonhosted.org/packages/e5/59/d71c96616b6825425a876f79f38fa440db30b32cc1166179a839f6259150/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c591f9a82adc5b89a039b90df74e43de2b9177fb46771172bed7b80722a70db0", size = 214723, upload-time = "2026-07-06T15:26:33.635Z" }, + { url = "https://files.pythonhosted.org/packages/c6/35/dc9eeb297f19b7b6ada39709ccb74937e6c51f0947958ae601a977cedd5d/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b56f449132d9adefe55b87635d05177a914ed5d070438a74725e1d77a280002", size = 236200, upload-time = "2026-07-06T15:26:34.99Z" }, + { url = "https://files.pythonhosted.org/packages/0a/37/6775fe852b4acad8bf7e0575fbe8aa9f41b546e33251acbded3c04a6b0d9/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1004c3b5831a301dadfb9e916f38e78e2ff3e08db24a1ad7c354db8ee3dea9c3", size = 231740, upload-time = "2026-07-06T15:26:36.498Z" }, + { url = "https://files.pythonhosted.org/packages/e4/28/1bcc3f5f3bac81532384adcfcdd9362c7f46a188a19deacc1ddaf7bdaa00/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60883e22821d17c9e5b4f3ca1ef8074f766e3db28791f851b665929c515635c0", size = 222888, upload-time = "2026-07-06T15:26:38.161Z" }, + { url = "https://files.pythonhosted.org/packages/47/81/9f3993ca62ef090c58059da641e49e3129e74700a6a3beb58436cdb8d4b9/charset_normalizer-3.4.8-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:21e4dbb942c8a6342e2685f232dd2a7bc73465697bd26ead4f118271d28be383", size = 207649, upload-time = "2026-07-06T15:26:39.628Z" }, + { url = "https://files.pythonhosted.org/packages/11/f4/679f636bcbdc2d53d06b1f4039be310450dca95a9f76bbf22f09985556e8/charset_normalizer-3.4.8-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84bcae14c65e645ca66b661339183d32b8c846a17c96e3e81ab3d346e1c498d4", size = 218908, upload-time = "2026-07-06T15:26:40.911Z" }, + { url = "https://files.pythonhosted.org/packages/7e/44/96e8c81867ba8a45ff893c8e7474c2d6b9633f7aa663da7901d040214d3e/charset_normalizer-3.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b4e4d44b8287aa13a25e16e29393d494b0643b24894f7c8266c6f6788dd36337", size = 217096, upload-time = "2026-07-06T15:26:42.148Z" }, + { url = "https://files.pythonhosted.org/packages/a5/bf/4d53f04f29bdb22601701f4f9f4d038edfb27976c296fcb7400c02736a6e/charset_normalizer-3.4.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f68545d1b267dbfafd5d253b6d1cb161562c4e61ab25b5c4cdb7d9e5923e441e", size = 209355, upload-time = "2026-07-06T15:26:43.557Z" }, + { url = "https://files.pythonhosted.org/packages/ad/60/92b3f630798d777fa880ad289a3f9f2fc663e4b4beb24783c53318820254/charset_normalizer-3.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1936e48214adea74922a20c8ab41b1393ae27cc9e329eb1f0b937d3416824f36", size = 224732, upload-time = "2026-07-06T15:26:44.892Z" }, + { url = "https://files.pythonhosted.org/packages/bd/85/eafa0a3c7bb6fe9f02f4c7901f02071933cac85ee634197e17280818c6de/charset_normalizer-3.4.8-cp313-cp313-win32.whl", hash = "sha256:1f8e3521860187d597f3867d8466da225b9179ea2833bb26de1bb026144d07c3", size = 150358, upload-time = "2026-07-06T15:26:46.153Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8c/879fafff7b47bb1166d289f2d2472cb31b9922f9f4ca1f392edf85ec16be/charset_normalizer-3.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:8b654b6f52a0a9a6be38e88f3e1dc68f1093ebeb2abbadafc7c82da0786a34be", size = 161685, upload-time = "2026-07-06T15:26:47.423Z" }, + { url = "https://files.pythonhosted.org/packages/c3/46/b57c7e778a7b578f28d35fd38544687d4f8d9c019585eebc5ad936073fad/charset_normalizer-3.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:d2d5a250ee26e29468b7607d97479221b069fa8aaf6f929ac84ec0e962e15154", size = 152333, upload-time = "2026-07-06T15:26:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/1c/bc/0a8540b8cd494951cca1428606373942803f5ffcec40fe798f819c5a8adb/charset_normalizer-3.4.8-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:77e993ecf65f21ab1f82266ff5e84a7de2c879e7d9b8bc006009df83f22a1d5e", size = 316993, upload-time = "2026-07-06T15:26:49.962Z" }, + { url = "https://files.pythonhosted.org/packages/0e/99/a0868f0a1f0a045fd374d1f2cf7042d8ad5d7fb4dd1f4ac7365e319f7e32/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:524939917f17f6de502dfda30b472550965740d7f126659d4c4f8dd1569cce22", size = 215638, upload-time = "2026-07-06T15:26:51.338Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e9/43c4d09a09b5557cc5fe1d87c9d96f86a3942aec0517d2b5408cef87ca75/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a4508989ba8e2ce43ef989453d18188b261546e8188cbdd4ef451fb9e4c3b467", size = 236456, upload-time = "2026-07-06T15:26:52.531Z" }, + { url = "https://files.pythonhosted.org/packages/e2/67/492ca98b3ab785b736b5da10c1bc233e1c8fec6c0cdb29b482c38bfc52a2/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9e44127f7d11eee4548ad2cdf1f4e1b6eaaddd5cb92d15ad65f6ecc9bcf403ab", size = 232253, upload-time = "2026-07-06T15:26:53.838Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/1e6eff58c14f1aace1e26d80defbeaea2d35e075dbe4b611111ee4b47fa8/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb90317359f7e67bb6df615999a95e0980877468e617ddce8b6c2f8e7fe60d95", size = 222886, upload-time = "2026-07-06T15:26:55.009Z" }, + { url = "https://files.pythonhosted.org/packages/40/7a/90056a5326b0c4b9a3f924d337729c344c11542e5bc7191e50410db61587/charset_normalizer-3.4.8-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:35d9e7a9c960520ae89d1f4e305d1c047a74dea2e0f73a0e84f879356c2e8776", size = 206482, upload-time = "2026-07-06T15:26:56.306Z" }, + { url = "https://files.pythonhosted.org/packages/18/ff/94761d31a33878dbb5008ddbd918615061fcf5c0a612aa3075450e60f628/charset_normalizer-3.4.8-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:92e322b054c7ff886f78feab7360736bb45de2e18cf4a0ee84e8fc5a08d53a19", size = 218929, upload-time = "2026-07-06T15:26:57.422Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/00b9675acd7c4b926b9102ee3f0d1a570ce943901be73b87485001393fe1/charset_normalizer-3.4.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c0086d97094363556206dc3bcf43f7edcfc043ea7a568a46f45efea74858bd1", size = 218069, upload-time = "2026-07-06T15:26:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/04/11/94ada5a0482ee4bf688d04be4c7d6fd945d37370d04a95671040dfe2b416/charset_normalizer-3.4.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0752c849b51198267df2aba013c4de3a2955bd014a4fd70828809946c1acbc0c", size = 207146, upload-time = "2026-07-06T15:27:00.058Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f7/246bd36762207ab4752cd436b64e5d81a1668b15ddea7b5b2d0e8545e727/charset_normalizer-3.4.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2a4707e09eca11e81ece4fced600c5a0a801f568b962244f6f517bc274745fc9", size = 224896, upload-time = "2026-07-06T15:27:01.599Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f7/3510622d1fbe13b0ebf827c475e40a27e2be427140d792878b63ab6425cc/charset_normalizer-3.4.8-cp314-cp314-win32.whl", hash = "sha256:8ea67f427c073ae3da0923aa55f3715131fa613a61a7f2f8d762bde75eaf00ae", size = 150851, upload-time = "2026-07-06T15:27:02.964Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/9ce65dd21672b55cf800cca5f4433afa1586fda1d78731067ec9ec544c62/charset_normalizer-3.4.8-cp314-cp314-win_amd64.whl", hash = "sha256:ff71018850863362e5c7533769d0a9f77715c31af1502d523630ce822922f5c9", size = 162549, upload-time = "2026-07-06T15:27:04.249Z" }, + { url = "https://files.pythonhosted.org/packages/2f/34/9a5967eed666a88f31a0866884606d9ec3c2cd6091e2ccd7e0b4c4176c35/charset_normalizer-3.4.8-cp314-cp314-win_arm64.whl", hash = "sha256:44464e66f4da2f21dea7145c7693f9f60717ca4794a954dea5bf8c2c932678bd", size = 153079, upload-time = "2026-07-06T15:27:05.608Z" }, + { url = "https://files.pythonhosted.org/packages/02/4f/aa44cc81d8987f105352c74c0bf919007f8b80e9880d28bcf0393c1a816e/charset_normalizer-3.4.8-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:50a0c2e58ad2c203adb616fef28941b7e13716adbc25e0dfaeec29f5afe6382f", size = 338586, upload-time = "2026-07-06T15:27:06.86Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2b/b0392e2b235c08ff0623d905c2ee8ac820620544043c1ce92ce0b3d64c55/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1e589fdb95c76f08288bbb346230cdd8994db74903db6637b380f7b5fc9336", size = 222764, upload-time = "2026-07-06T15:27:08.23Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a1/7d466879190731f5559662c22232646f2ae2dace2323c3e5aefcf78d458a/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3d7c887444c5a7ef0d68d358d81e758a850bc626f8e639e2ca5667153272b20", size = 241331, upload-time = "2026-07-06T15:27:09.512Z" }, + { url = "https://files.pythonhosted.org/packages/70/17/8b89e797137aa28c8fb0bafbafc243246a7afe21620a13b00e37624ece1d/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:65c389b96c0cfff3a3f0458fa1c7ce554a30e23101a88a49f03997afce6a929f", size = 239323, upload-time = "2026-07-06T15:27:10.86Z" }, + { url = "https://files.pythonhosted.org/packages/d7/98/1c1940730ed22d50983be4e243c722c89d5136d6f073bd840d1128bfddcb/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:593403fc47dcdf55e2987b2e3cc2e064127e2b908929f1f18b2e4a4652cbd780", size = 229964, upload-time = "2026-07-06T15:27:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/53/2d/bb8e81b7ff603d3f77e9a8a5d1ad34fcabbf3c54d300c29d99fba581fa23/charset_normalizer-3.4.8-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:606088e9fa2b7469ab9c42d4da8e05a415622a07714edd2fcd8fed48dda4c853", size = 212405, upload-time = "2026-07-06T15:27:13.447Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1f/e52a3a53b13da591bb8f21d29e63877268eadf20686b7762351d4b89062c/charset_normalizer-3.4.8-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0317406326fed512f42a1632ad91a96228a7616c06547666a6dd79967f1bd6ca", size = 226918, upload-time = "2026-07-06T15:27:14.89Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f9/32996d79c57189af9722fe618f46d8a86b7be035ca98887b8d0c3821f141/charset_normalizer-3.4.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b67d50ee47e5c57a0064a9cb575b963a7125819dfd1fd094d44d378fff94659b", size = 225113, upload-time = "2026-07-06T15:27:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d2/9248c18e695696513774523a794cfb8b677521ce9ad7554d301cb10a9b20/charset_normalizer-3.4.8-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79e402b869f270140afa5e2b0e2ac100585358d812fe3dd093d424f7a72964e0", size = 214966, upload-time = "2026-07-06T15:27:17.418Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9d/4b19432d406179a40f924691906ee5b15ac664b408971c973295192444ea/charset_normalizer-3.4.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2970b9f7ab69ec3a0423ec6b6ac718e79fbf4a282c0bc103ef88c1ef50dfa15a", size = 231699, upload-time = "2026-07-06T15:27:19.131Z" }, + { url = "https://files.pythonhosted.org/packages/be/41/bdbdf71e8c3ccff10ef3cc2bb9467a7fdb3dc94b9a406d1a3c44afd39632/charset_normalizer-3.4.8-cp314-cp314t-win32.whl", hash = "sha256:458c2972a78043b7261c9726670029f15f722e70669bcbe961153a01968f589f", size = 155333, upload-time = "2026-07-06T15:27:20.681Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f8/e05c69323bd50091ec39f5f885385b884624b0131a6885a0c83a6217ba7a/charset_normalizer-3.4.8-cp314-cp314t-win_amd64.whl", hash = "sha256:0c926329a1df7cd56d7d8349fe354460d20aefd2e394c9e159e479d018b2b359", size = 167378, upload-time = "2026-07-06T15:27:22.042Z" }, + { url = "https://files.pythonhosted.org/packages/c2/04/cbaf1a2f5e2bbf70760e774380cbf052b10849fc35e770905df31af5cf00/charset_normalizer-3.4.8-cp314-cp314t-win_arm64.whl", hash = "sha256:2232baea80a2b01783679fed4e625ccdb19a974f44c9cf0fba21a777a4c8179c", size = 157782, upload-time = "2026-07-06T15:27:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/23/52/d5bee5b6ea81882d549b566d2545b044bbcbc33fe5fbe001008a7e745a21/charset_normalizer-3.4.8-py3-none-any.whl", hash = "sha256:b7c1fb310df524e01fbe84d43b7f95aa4f808f8eaa0dafc185f64ba395e37d54", size = 64279, upload-time = "2026-07-06T15:27:57.043Z" }, ] [[package]] @@ -1242,7 +1258,7 @@ wheels = [ [[package]] name = "drf-spectacular" -version = "0.29.0" +version = "0.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, @@ -1252,9 +1268,9 @@ dependencies = [ { name = "pyyaml" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/0e/a4f50d83e76cbe797eda88fc0083c8ca970cfa362b5586359ef06ec6f70a/drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc", size = 241722, upload-time = "2025-11-02T03:40:26.348Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/43/41d25039a6a53545420ebc98eb9f877ec9fe30c7bd03fefabcaf9b953af7/drf_spectacular-0.30.0.tar.gz", hash = "sha256:53e79e7ba00e240441b63c32273754a5368e4c2ab44a19f2595277cc1cd559c9", size = 252311, upload-time = "2026-07-06T11:29:46.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d9/502c56fc3ca960075d00956283f1c44e8cafe433dada03f9ed2821f3073b/drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a", size = 105433, upload-time = "2025-11-02T03:40:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/c3/56/74dd7b45bbde6d24494220b98d6961cb1200b63a1800332b430daa2c4551/drf_spectacular-0.30.0-py3-none-any.whl", hash = "sha256:006cf5921ebe20a9bd24f7c846261ebbf78780be5961b0d6e87afaa82afd62ff", size = 111150, upload-time = "2026-07-06T11:29:45.12Z" }, ] [[package]] @@ -2439,7 +2455,7 @@ wheels = [ [[package]] name = "picopt" -version = "6.5.2" +version = "6.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "confuse" }, @@ -2461,9 +2477,9 @@ dependencies = [ { name = "treestamps" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/d7/ea8bc07909aee3b1ad49f5102d036e4635e12c71346820c8523d3cf4fb44/picopt-6.5.2.tar.gz", hash = "sha256:e1a5ef09fee16dff008551a61adb322a3ad478a0dfb4d5c0183c2eab553601fd", size = 10321774, upload-time = "2026-05-28T23:03:41.999Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/df/011852b57fb5a0568daf6ebc8ad80ff56c24dcec0ae26b899931ce954fce/picopt-6.6.1.tar.gz", hash = "sha256:e3733489a5d8eef3a824a1daa5717a50ee9fa4f7a6f94f7f272b07171e098f02", size = 10372187, upload-time = "2026-07-06T17:21:49.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/88/816fe6e0b2f9c83999e3debd854d98bca2836d2490c7b1270442216f0652/picopt-6.5.2-py3-none-any.whl", hash = "sha256:6dda424bd9d75ba1f769803fd81c5f6e0a15181a6252844db87b392c90d5c9c3", size = 108108, upload-time = "2026-05-28T23:03:40.678Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/a72ad0c0d950a6a816d7fa0237d9be0c2387b2a835e8126c56df69fbff09/picopt-6.6.1-py3-none-any.whl", hash = "sha256:26ce77bdabd4c83fea75dbf6279a58abfc8ca4df92ee43d1d0798314cc5f2d2c", size = 131744, upload-time = "2026-07-06T17:21:47.823Z" }, ] [[package]] @@ -3953,15 +3969,15 @@ wheels = [ [[package]] name = "treestamps" -version = "4.0.2" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4a/a1/02de5cb2900dbaefed89971b3c5bc61a9ee93c13d68725b61022e0d575c8/treestamps-4.0.2.tar.gz", hash = "sha256:7ab0ae03b25ddcf45011cc0e589b9137baeb2e284f0b194f5cf7a299a8baa9bb", size = 181276, upload-time = "2026-04-29T09:52:32.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/87/31328b19121029e6db528aa4c70f992f1dd5ccc74435bb2e22874970f64d/treestamps-4.1.0.tar.gz", hash = "sha256:159652cd3a2f6365e3d7c5021defb66104ba89148c0cdd87e7ec14f713870cb3", size = 210180, upload-time = "2026-07-05T19:46:29.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/10/d467b3fc53990fb3f86a5cffd0a290d717fa076442557b585af882a2c716/treestamps-4.0.2-py3-none-any.whl", hash = "sha256:53146741ace2a8df626d2f305cfa3aac8c7719e4af26a6dcb9aa19ba270aecb1", size = 18358, upload-time = "2026-04-29T09:52:34.07Z" }, + { url = "https://files.pythonhosted.org/packages/3d/cb/508cbbb17a23ad35cea3fe2b86c8b1811d0c37fd7476c1e3892e79025be5/treestamps-4.1.0-py3-none-any.whl", hash = "sha256:f7323de89fad48030989cee9939b809c5ede15c5a20d5153c157f8d008231494", size = 32155, upload-time = "2026-07-05T19:46:28.62Z" }, ] [[package]] From f7abf7d7ae73cfdf06e94c85d798145352ed4875 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 11 Jul 2026 00:45:49 -0700 Subject: [PATCH 20/82] fix(settings): nest comicbox loglevel/delete_keys under general section An un-nested overlay is silently ignored by confuse. --- codex/settings/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codex/settings/__init__.py b/codex/settings/__init__.py index f3c82a861..0186eecb2 100644 --- a/codex/settings/__init__.py +++ b/codex/settings/__init__.py @@ -1118,11 +1118,15 @@ def _get_middleware(features: FeatureFlags) -> tuple[str, ...]: # ``delete_keys`` is typed as ``Sequence(str)`` in the comicbox confuse # template, so feed it a sorted tuple rather than the source frozenset. +# Both keys live under the ``general`` section of the comicbox config +# tree; an un-nested overlay is silently ignored by confuse. COMICBOX_CONFIG: ComicboxSettings = get_config( { "comicbox": { - "loglevel": LOGLEVEL, - "delete_keys": tuple(sorted(_COMICBOX_DELETE_KEYS)), + "general": { + "loglevel": LOGLEVEL, + "delete_keys": tuple(sorted(_COMICBOX_DELETE_KEYS)), + } } } ) From da257b06f8f9695ea838f489f2263a39bbe766e1 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 11 Jul 2026 00:53:15 -0700 Subject: [PATCH 21/82] fix(onlinetag): resolve prompts against current DB path, surface apply failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answering a deferred prompt fetched the chosen issue against the path serialized into the prompt at scan time. When an earlier write for the same comic ran with rename enabled (the comic's other source's prompt, or a stored-id prefetch), that path was stale and the apply died with an uncaught FileNotFoundError — after the prompt was already consumed, so the admin's pick vanished with no feedback. - _apply_resolution now re-reads the comic's path from the DB by pk; a missing row reports to the Tagging error panel instead of fetching a dead path. - fetch/replay failures (ComicboxError, OSError) and non-resolving explicit ids now land on the Tagging error panel instead of only the log, since the pick can no longer be re-prompted. - stored-id prefetch and tag_by_id also catch OSError so a vanished file degrades gracefully. - regression test for the COMICBOX_CONFIG general-section overlay (un-nested loglevel/delete_keys were silently ignored, letting comicfn2dict remainders like "(0000)" leak into rename targets). Co-Authored-By: Claude Fable 5 --- codex/librarian/onlinetag/session_manager.py | 76 +++++++++++-- tests/test_onlinetag_session_manager.py | 106 +++++++++++++++++++ tests/test_settings_comicbox_config.py | 28 +++++ 3 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 tests/test_settings_comicbox_config.py diff --git a/codex/librarian/onlinetag/session_manager.py b/codex/librarian/onlinetag/session_manager.py index a45df5a3d..12d600856 100644 --- a/codex/librarian/onlinetag/session_manager.py +++ b/codex/librarian/onlinetag/session_manager.py @@ -163,7 +163,7 @@ def tag_by_id(self, task: OnlineTagByIdTask) -> None: credentials, extra_ids=task.extra_ids, ) - except ComicboxError as exc: + except (ComicboxError, OSError) as exc: msg = f"Fetching {task.source} issue {task.issue_id} failed: {exc}" self.log.warning(f"Online tag by id: {msg} ({path})") add_tag_write_error(str(path), msg) @@ -473,7 +473,7 @@ def _fetch_stored_id( tags = fetch_tags_by_explicit_id( path, primary_source, primary_id, credentials, extra_ids=extra_ids ) - except ComicboxError as exc: + except (ComicboxError, OSError) as exc: self.log.warning(f"Online tag stored-id prefetch failed for {path}: {exc}") return None return (primary_source, tags) if tags else None @@ -756,6 +756,32 @@ def _explicit_resolution( return "manual", f"{source}:{issue_id}" return action, payload + def _report_apply_failure(self, path_str: str, msg: str) -> None: + """Log + surface a failed prompt apply in the admin Tagging error panel.""" + self.log.warning(f"Online tag: {msg} ({path_str})") + add_tag_write_error(path_str, msg) + self.librarian_queue.put(TAG_WRITE_ERRORS_CHANGED_TASK) + + def _refresh_comic_path(self, pk: int, prompt_path: str) -> str: + """ + Return the comic's current path, preferring the DB row over the prompt. + + A prompt's serialized path goes stale between deferral and answer — + most commonly when an earlier write for the same comic ran with + rename enabled and moved the file. The DB row follows the rename + (via the write's move import), so it is the fresher reference. A + missing row means the comic was deleted (or re-imported under a new + pk after a rename the DB never synced); surface that instead of + fetching against a dead path. + """ + comic = Comic.objects.filter(pk=pk).only("path").first() + if comic: + return comic.path + self._report_apply_failure( + prompt_path, "comic no longer in the database; answer not applied" + ) + return "" + def _apply_resolution( self, prompt: dict[str, Any], @@ -765,11 +791,14 @@ def _apply_resolution( ) -> None: """Build a fresh session, apply the chosen match, and enqueue a write.""" pk = prompt.get("pk") - path_str = prompt.get("path") or "" + prompt_path = prompt.get("path") or "" source = prompt.get("source") or "" - if pk is None or not path_str or not source: + if pk is None or not prompt_path or not source: self.log.warning("Online tag: prompt missing comic reference; skipping.") return + path_str = self._refresh_comic_path(pk, prompt_path) + if not path_str: + return credentials = self._build_credentials() if not credentials: self.log.warning("Online tag: no credentials for prompt resolution.") @@ -784,7 +813,21 @@ def _apply_resolution( explicit = self._explicit_issue_id(action, payload, source) if explicit is not None: self._apply_explicit_id(prompt, pk, explicit, path_str, credentials) - return + else: + resolution = (action, payload, chosen_volume_id) + self._apply_replayed_search(prompt, pk, path_str, credentials, resolution) + + def _apply_replayed_search( + self, + prompt: dict[str, Any], + pk: int, + path_str: str, + credentials: OnlineCredentials, + resolution: tuple[str, Any, int | None], + ) -> None: + """Re-search and apply a pick that carries no explicit issue id.""" + action, payload, chosen_volume_id = resolution + source = prompt.get("source") or "" # defer_prompts on: the bridged selector consults the preloaded # resolution; without it (or a handler) an ambiguous re-search would # fall through to comicbox's interactive CLI prompt inside the daemon. @@ -800,7 +843,13 @@ def _apply_resolution( payload=payload, chosen_volume_id=chosen_volume_id, ) - tags = self._first_tags(session, Path(path_str)) + try: + tags = self._first_tags(session, Path(path_str)) + except (ComicboxError, OSError) as exc: + self._report_apply_failure( + path_str, f"re-applying chosen match failed: {exc}" + ) + return if not tags: self._handle_unresolved(prompt, path_str, session) return @@ -832,14 +881,23 @@ def _apply_explicit_id( ) -> None: """Fetch the picked issue by id and enqueue its write (no re-search).""" src, issue_id = explicit - tags = fetch_tags_by_explicit_id(Path(path_str), src, issue_id, credentials) + try: + tags = fetch_tags_by_explicit_id(Path(path_str), src, issue_id, credentials) + except (ComicboxError, OSError) as exc: + # The prompt is already consumed, so a swallowed failure would + # silently discard the admin's pick — put it on the error panel. + self._report_apply_failure( + path_str, f"fetching chosen issue {src}:{issue_id} failed: {exc}" + ) + return if tags: self._enqueue_resolved_write(prompt, pk, tags, path_str) else: # The id itself didn't resolve (wrong/unknown issue). Don't re-queue # a fresh ambiguous prompt — the admin made an explicit choice. - msg = f"chosen issue {src}:{issue_id} did not resolve for {path_str}" - self.log.warning(f"Online tag: {msg}.") + self._report_apply_failure( + path_str, f"chosen issue {src}:{issue_id} did not resolve" + ) def _handle_unresolved( self, prompt: dict[str, Any], path_str: str, session: OnlineSession diff --git a/tests/test_onlinetag_session_manager.py b/tests/test_onlinetag_session_manager.py index 7642242cf..e589a4450 100644 --- a/tests/test_onlinetag_session_manager.py +++ b/tests/test_onlinetag_session_manager.py @@ -37,6 +37,7 @@ BulkOnlineTagTask, OnlineTagPromptResponseTask, ) +from codex.librarian.scribe.tagwrite_errors import get_tag_write_errors from codex.librarian.scribe.tasks import BulkTagWriteTask from codex.models import ( Comic, @@ -466,6 +467,111 @@ def test_resolve_choose_unresolved_id_does_not_write_or_requeue(self) -> None: assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] # The explicit choice is honored: no worse fresh prompt is re-queued. assert get_pending_prompts() == {} + # The consumed-but-unapplied pick surfaces on the admin error panel. + errors = get_tag_write_errors() + assert len(errors) == 1 + assert "did not resolve" in errors[0]["error"] + + def test_resolve_fetches_against_current_db_path_not_prompt_snapshot(self) -> None: + """ + A pick applies against the comic's current DB path, not the prompt's. + + The serialized prompt path goes stale when an earlier write for the + same comic ran with rename enabled (e.g. the comic's other source's + prompt was answered first) — the DB row follows the rename. + """ + comic = _make_comic() + stale_path = str(Path(comic.path).with_name("stale-pre-rename-name.cbz")) + set_pending_prompts( + { + "fp1": { + "fingerprint": "fp1", + "pk": comic.pk, + "path": stale_path, + "source": "metron", + "candidates": [{"issue_id": 123, "source": "metron"}], + "mode": "auto", + "formats": ["COMIC_INFO"], + "delete_original": False, + } + } + ) + captured: dict = {} + + def _fake_fetch(path, source, issue_id, _credentials, **_kwargs): + captured.update(path=str(path), source=source, issue_id=issue_id) + return {"series": "X"} + + with patch(_FETCH_TARGET, _fake_fetch): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert captured["path"] == str(comic.path) + writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + assert len(writes) == 1 + assert writes[0].comic_pks == frozenset({comic.pk}) + + def test_resolve_fetch_failure_surfaces_error_without_crashing(self) -> None: + """A fetch crash (stale file, source error) reports; the pick isn't lost silently.""" + comic = _make_comic() + comic_path = str(comic.path) + set_pending_prompts( + { + "fp1": { + "fingerprint": "fp1", + "pk": comic.pk, + "path": comic_path, + "source": "comicvine", + "candidates": [{"issue_id": 764978, "source": "comicvine"}], + "mode": "auto", + "formats": ["COMIC_INFO"], + "delete_original": False, + } + } + ) + + def _boom(*_args, **_kwargs): + reason = f"{comic_path} does not exist." + raise FileNotFoundError(reason) + + with patch(_FETCH_TARGET, _boom): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + assert get_pending_prompts() == {} + errors = get_tag_write_errors() + assert len(errors) == 1 + assert "fetching chosen issue comicvine:764978 failed" in errors[0]["error"] + + def test_resolve_missing_comic_row_reports_and_never_fetches(self) -> None: + """A prompt whose comic left the DB reports instead of fetching a dead path.""" + set_pending_prompts( + { + "fp1": { + "fingerprint": "fp1", + "pk": 999_999, + "path": "/gone/c.cbz", + "source": "metron", + "candidates": [{"issue_id": 123, "source": "metron"}], + "mode": "auto", + "formats": ["COMIC_INFO"], + "delete_original": False, + } + } + ) + called: list = [] + + def _fake_fetch(*args, **_kwargs): + called.append(args) + return {"series": "X"} + + with patch(_FETCH_TARGET, _fake_fetch): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not called + assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + errors = get_tag_write_errors() + assert len(errors) == 1 + assert "no longer in the database" in errors[0]["error"] def test_resolve_drifted_prompt_requeues_fresh_prompt(self) -> None: """A candidate with no issue id falls back to replay, which can drift.""" diff --git a/tests/test_settings_comicbox_config.py b/tests/test_settings_comicbox_config.py new file mode 100644 index 000000000..7e5c4585f --- /dev/null +++ b/tests/test_settings_comicbox_config.py @@ -0,0 +1,28 @@ +""" +COMICBOX_CONFIG overlay regression tests. + +comicbox's confuse template nests ``loglevel`` and ``delete_keys`` under the +``general`` section; a flat overlay is *silently ignored*. That regression +made renames carry comicfn2dict remainder junk from the old filename into +the new one (e.g. ``Wolverine (0000) #023.cbz`` → +``… Power Grab[(0000)].cbz``) because ``remainders`` was never deleted from +the merged metadata. +""" + +from codex.settings import COMICBOX_CONFIG, LOGLEVEL + + +def test_delete_keys_overlay_reaches_comicbox() -> None: + """The unused-field delete list must survive the config overlay.""" + keys = COMICBOX_CONFIG.general.delete_keys + # Non-empty proves the nested overlay applied at all. + assert keys + # Filename junk must not leak into rename targets. + assert "remainders" in keys + # A field codex consumes must never be deleted. + assert "series" not in keys + + +def test_loglevel_overlay_reaches_comicbox() -> None: + """Comicbox logs at codex's configured level, not its own default.""" + assert COMICBOX_CONFIG.general.loglevel == LOGLEVEL From a5f37d87a900b8610568dd7600beaa04003f4997 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 11 Jul 2026 01:16:50 -0700 Subject: [PATCH 22/82] update deps and format --- bun.lock | 46 +- frontend/bun.lock | 88 +++- frontend/package.json | 8 +- frontend/tests/unit/rename-toggle.test.js | 2 +- package.json | 8 +- uv.lock | 538 +++++++++++----------- 6 files changed, 366 insertions(+), 324 deletions(-) diff --git a/bun.lock b/bun.lock index d7db82ab8..5b1a31185 100644 --- a/bun.lock +++ b/bun.lock @@ -169,7 +169,7 @@ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.61.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.61.1", "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1" } }, "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="], "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg=="], @@ -177,7 +177,7 @@ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.61.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.61.1", "@typescript-eslint/tsconfig-utils": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.61.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w=="], @@ -225,7 +225,7 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.12.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA=="], - "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.21", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-5nu7QuW5Dg9v4I9t9ZiU1Hh91BoG0x3lOzOWYTsr1bqpIqcHYzFSQ052LvZwGtEm7Trh+Mr2heYEmZ4LkDjfaA=="], + "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.23", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-CbrXxQpIUMbeyylGlxaxTgnWYM44et4Nx58swl7+vrTyC9Ttx+hQoy8BGCregbbZ08K5qFhRHeoad1VvERUbkg=="], "@xml-tools/parser": ["@xml-tools/parser@1.0.11", "", { "dependencies": { "chevrotain": "7.1.1" } }, "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA=="], @@ -365,7 +365,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.6.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg=="], + "eslint": ["eslint@10.7.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ=="], "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], @@ -381,7 +381,7 @@ "eslint-plugin-compat": ["eslint-plugin-compat@7.0.2", "", { "dependencies": { "@mdn/browser-compat-data": "^6.1.1", "ast-metadata-inferer": "^0.8.1", "browserslist": "^4.25.2", "find-up": "^5.0.0", "globals": "^15.7.0", "lodash.memoize": "^4.1.2", "semver": "^7.6.2" }, "peerDependencies": { "eslint": "^9.0.0 || ^10.0.0" } }, "sha512-gN8hF+4NzMsHUbr4m/TYZK0FtW3DcV4g8rXpTsY2EV5xiRD8jsilUlB9lNSkGGX0veDCxMhKSWbSd+faJByQDA=="], - "eslint-plugin-de-morgan": ["eslint-plugin-de-morgan@2.1.2", "", { "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-6/MXP77FUrFGTdJ2Lny3Jv027bE3SUHDf+/VBUBckpH5TskOY5G4UX+AebqJeUpt3nXjy3S0KWBRwTItVQwgvw=="], + "eslint-plugin-de-morgan": ["eslint-plugin-de-morgan@2.1.3", "", { "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-vuU/AIPvzsRhCyAfgaWwRVy0QUGvtv2pCn3N6tV4LZRFRNKPuV+Md1mYLvLuM9tORKLe8plzx5DqS9/II/3dew=="], "eslint-plugin-depend": ["eslint-plugin-depend@1.5.0", "", { "dependencies": { "empathic": "^2.0.0", "module-replacements": "^2.10.1", "semver": "^7.6.3" }, "peerDependencies": { "eslint": ">=8.40.0" } }, "sha512-i3UeLYmclf1Icp35+6W7CR4Bp2PIpDgBuf/mpmXK5UeLkZlvYJ21VuQKKHHAIBKRTPivPGX/gZl5JGno1o9Y0A=="], @@ -783,7 +783,7 @@ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.9.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg=="], + "prettier": ["prettier@3.9.5", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg=="], "prettier-linter-helpers": ["prettier-linter-helpers@1.0.1", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg=="], @@ -1127,6 +1127,14 @@ "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + "@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], + + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + + "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -1149,8 +1157,6 @@ "eslint-mdx/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - "eslint-plugin-perfectionist/@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="], - "eslint-plugin-sonarjs/globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], "eslint-plugin-unicorn/browserslist": ["browserslist@4.28.5", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001800", "electron-to-chromium": "^1.5.387", "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ=="], @@ -1221,15 +1227,17 @@ "@npmcli/promise-spawn/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "eslint-mdx/espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + + "eslint-mdx/espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "eslint-plugin-unicorn/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="], @@ -1265,20 +1273,10 @@ "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], - - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], - - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], - - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "npm-pick-manifest/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], } } diff --git a/frontend/bun.lock b/frontend/bun.lock index a0aa825e8..60a495036 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -324,7 +324,7 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="], + "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], @@ -378,7 +378,7 @@ "@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="], - "@vue/language-core": ["@vue/language-core@3.3.6", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-LgBMZAy2sR3cQWknpyaxnI6yBkqDfLBPkbdhwRhQCvzfNJRQXPilgQIrdI/v4ytJ0sAq9bWhaPsjqBqneomJ3Q=="], + "@vue/language-core": ["@vue/language-core@3.3.7", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw=="], "@vue/reactivity": ["@vue/reactivity@3.5.39", "", { "dependencies": { "@vue/shared": "3.5.39" } }, "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog=="], @@ -392,7 +392,7 @@ "@vue/test-utils": ["@vue/test-utils@2.4.11", "", { "dependencies": { "js-beautify": "^1.14.9", "vue-component-type-helpers": "^3.0.0" }, "peerDependencies": { "@vue/compiler-dom": "3.x", "@vue/server-renderer": "3.x", "vue": "3.x" }, "optionalPeers": ["@vue/server-renderer"] }, "sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA=="], - "@vue/typescript-plugin": ["@vue/typescript-plugin@3.3.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.6", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", "vue-component-meta": "3.3.6" } }, "sha512-3jubnULC27+N2DYw2gLWMeQMdQuS5lUjeR6yzvDh76tx35zYO2c4z+LNLenJqncAOXdhqkCRLzuBgDSkTEJCqg=="], + "@vue/typescript-plugin": ["@vue/typescript-plugin@3.3.7", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.7", "@vue/shared": "^3.5.0", "path-browserify": "^1.0.1", "vue-component-meta": "3.3.7" } }, "sha512-ME/ORVBj7wy3KWhSVld8YtWy3xlPSg3ErifRI1/UTdsQxtZDDzcFF+9VszGSfNadTM5qO9kwZmnFa9hm2PFQsw=="], "@vuetify/loader-shared": ["@vuetify/loader-shared@2.1.2", "", { "dependencies": { "upath": "^2.0.1" }, "peerDependencies": { "vue": "^3.0.0", "vuetify": ">=3" } }, "sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig=="], @@ -874,7 +874,7 @@ "valibot": ["valibot@1.4.1", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g=="], - "vite": ["vite@8.1.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA=="], + "vite": ["vite@8.1.4", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.16", "rolldown": "~1.1.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ=="], "vite-plugin-checker": ["vite-plugin-checker@0.14.4", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "chokidar": "^5.0.0", "npm-run-path": "^6.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.4", "proper-lockfile": "^4.1.2", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@biomejs/biome": ">=2.4.12", "eslint": ">=9.39.4", "meow": "^13.2.0 || ^14.0.0", "optionator": "^0.9.4", "oxlint": ">=1", "stylelint": ">=16.26.1", "typescript": "*", "vite": ">=5.4.21", "vue-tsc": "~2.2.10 || ^3.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "meow", "optionator", "oxlint", "stylelint", "typescript", "vue-tsc"] }, "sha512-Tw0U9UgHIRiZ+Yoe4Gh0RrYoBiCVmO9j4tomVdYr0KUjUsqXMPhqW8ouoSWmOzGp5Iimipbl3bNXZcK7OeP7Qg=="], @@ -890,7 +890,7 @@ "vue": ["vue@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/compiler-sfc": "3.5.39", "@vue/runtime-dom": "3.5.39", "@vue/server-renderer": "3.5.39", "@vue/shared": "3.5.39" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA=="], - "vue-component-meta": ["vue-component-meta@3.3.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.6", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-kPNTwuLzqYDvsE2CiDPSbt89aypZwYoCRxtbUfbkfOgy3DV130G3Z1jJuRBct64vxB2Lii6fcih8FJba8dLoXQ=="], + "vue-component-meta": ["vue-component-meta@3.3.7", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.7", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-rqMY/EGOHGLcUbCb1VRZNBlDpUS12O0kMJL133Z3TrWpKmwtdGNLALTwEX1jBskoV+ibQp/aWEW+87a79FexCA=="], "vue-component-type-helpers": ["vue-component-type-helpers@3.3.5", "", {}, "sha512-Fe1jyPJoUGpJOYKOri44jduR7My4yYINOMJISuMAbmrs+L5LbIDUc8NTWZYY3EJLK0yPLuCmcd5zoCsE4k2/KA=="], @@ -900,7 +900,7 @@ "vue-router": ["vue-router@5.1.0", "", { "dependencies": { "@babel/generator": "^8.0.0-rc.4", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.1.2", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.16", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34", "pinia": "^3.0.4", "vite": "^7.0.0 || ^8.0.0", "vue": "^3.5.34" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g=="], - "vuetify": ["vuetify@4.1.3", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-tI6GHgICixvnoncWkSBNMKfwzjbt6H8FxEtGvUxYirOIBu00u5TUANgUbu1ly6wr9sVjvwqakCjUiAYJFsqpMw=="], + "vuetify": ["vuetify@4.1.4", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-8/H0Yl6h9t07/ExuyZFIRJqAenpKeCiVaRJzbHGfTtuZRZIs5EbtjdFYikPIu6h9thBJAx15PrU4ofa4LvdJag=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -984,6 +984,8 @@ "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], + "@vue/language-core/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + "@vue/server-renderer/@vue/shared": ["@vue/shared@3.5.38", "", {}, "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug=="], "ast-walker-scope/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], @@ -1020,7 +1022,11 @@ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/rolldown": ["rolldown@1.1.3", "", { "dependencies": { "@oxc-project/types": "=0.137.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.3", "@rolldown/binding-darwin-arm64": "1.1.3", "@rolldown/binding-darwin-x64": "1.1.3", "@rolldown/binding-freebsd-x64": "1.1.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", "@rolldown/binding-linux-arm64-gnu": "1.1.3", "@rolldown/binding-linux-arm64-musl": "1.1.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.3", "@rolldown/binding-linux-s390x-gnu": "1.1.3", "@rolldown/binding-linux-x64-gnu": "1.1.3", "@rolldown/binding-linux-x64-musl": "1.1.3", "@rolldown/binding-openharmony-arm64": "1.1.3", "@rolldown/binding-wasm32-wasi": "1.1.3", "@rolldown/binding-win32-arm64-msvc": "1.1.3", "@rolldown/binding-win32-x64-msvc": "1.1.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g=="], + "vite/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "vite/rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], + + "vitest/vite": ["vite@8.1.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA=="], "vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], @@ -1116,35 +1122,39 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g=="], + "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], - "vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw=="], + "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], - "vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw=="], + "vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], - "vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw=="], + "vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], - "vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg=="], + "vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], - "vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA=="], + "vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], - "vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w=="], + "vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], - "vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw=="], + "vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], - "vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA=="], + "vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], - "vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg=="], + "vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], - "vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g=="], + "vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], - "vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.3", "", { "os": "none", "cpu": "arm64" }, "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ=="], + "vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], - "vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.3", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg=="], + "vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], - "vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g=="], + "vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], - "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA=="], + "vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], + + "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], + + "vitest/vite/rolldown": ["rolldown@1.1.3", "", { "dependencies": { "@oxc-project/types": "=0.137.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.3", "@rolldown/binding-darwin-arm64": "1.1.3", "@rolldown/binding-darwin-x64": "1.1.3", "@rolldown/binding-freebsd-x64": "1.1.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", "@rolldown/binding-linux-arm64-gnu": "1.1.3", "@rolldown/binding-linux-arm64-musl": "1.1.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.3", "@rolldown/binding-linux-s390x-gnu": "1.1.3", "@rolldown/binding-linux-x64-gnu": "1.1.3", "@rolldown/binding-linux-x64-musl": "1.1.3", "@rolldown/binding-openharmony-arm64": "1.1.3", "@rolldown/binding-wasm32-wasi": "1.1.3", "@rolldown/binding-win32-arm64-msvc": "1.1.3", "@rolldown/binding-win32-x64-msvc": "1.1.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g=="], "vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.3", "", { "dependencies": { "@vue/devtools-shared": "^8.1.3", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA=="], @@ -1252,6 +1262,36 @@ "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "vitest/vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g=="], + + "vitest/vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw=="], + + "vitest/vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw=="], + + "vitest/vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g=="], + + "vitest/vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.3", "", { "os": "none", "cpu": "arm64" }, "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ=="], + + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.3", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg=="], + + "vitest/vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g=="], + + "vitest/vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA=="], + "vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.1.3", "", {}, "sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA=="], "vue-router/@vue/devtools-api/@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], @@ -1272,8 +1312,12 @@ "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@devframes/hub/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], "devframe/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], } } diff --git a/frontend/package.json b/frontend/package.json index fa4eae834..9613f9113 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,25 +26,25 @@ "vue": "^3.5.39", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.1.0", - "vuetify": "^4.1.3", + "vuetify": "^4.1.4", "xior": "^0.8.3" }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", - "@types/node": "^26.1.0", + "@types/node": "^26.1.1", "@unhead/bundler": "^3.1.7", "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.11", - "@vue/typescript-plugin": "^3.3.6", + "@vue/typescript-plugin": "^3.3.7", "happy-dom": "^20.10.6", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.101.0", "sass-loader": "^17.0.0", "toml": "^4.1.2", "typeface-roboto": "^1.1.13", - "vite": "^8.1.3", + "vite": "^8.1.4", "vite-plugin-checker": "^0.14.4", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.1", diff --git a/frontend/tests/unit/rename-toggle.test.js b/frontend/tests/unit/rename-toggle.test.js index 6a2182567..a57953621 100644 --- a/frontend/tests/unit/rename-toggle.test.js +++ b/frontend/tests/unit/rename-toggle.test.js @@ -114,7 +114,7 @@ describe("EditPanel — rename toggle", () => { await wrapper.vm.fetchRenamePreview(); expect(HTTP.post).toHaveBeenCalledWith( "/admin/tag-write/preflight", - // eslint-disable-next-line vitest/valid-expect -- false positive: the plugin misreads expect.any() as chai's `.any` flag chain + expect.objectContaining({ patch: expect.any(String) }), ); expect(wrapper.vm.renamePreviews).toEqual([ diff --git a/package.json b/package.json index 6c296d239..b199bc52c 100644 --- a/package.json +++ b/package.json @@ -64,13 +64,13 @@ "@fsouza/prettierd": "^0.28.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.21", - "eslint": "^10.6.0", + "@vitest/eslint-plugin": "^1.6.23", + "eslint": "^10.7.0", "eslint_d": "^15.0.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-array-func": "^5.1.1", "eslint-plugin-compat": "^7.0.2", - "eslint-plugin-de-morgan": "^2.1.2", + "eslint-plugin-de-morgan": "^2.1.3", "eslint-plugin-depend": "^1.5.0", "eslint-plugin-html": "^8.1.4", "eslint-plugin-import-x": "^4.17.1", @@ -92,7 +92,7 @@ "eslint-plugin-vue-scoped-css": "^3.1.1", "eslint-plugin-yml": "^3.6.0", "postcss-scss": "^4.0.9", - "prettier": "^3.9.4", + "prettier": "^3.9.5", "prettier-plugin-nginx": "^1.0.3", "prettier-plugin-packagejson": "^3.0.2", "prettier-plugin-toml": "^2.0.6", diff --git a/uv.lock b/uv.lock index cd2dfa415..cbb5f4841 100644 --- a/uv.lock +++ b/uv.lock @@ -485,63 +485,63 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/56/10a88e00039537d74bd420f0457c52ab8f58a1af56126e3b9f1b1c8c4724/charset_normalizer-3.4.8.tar.gz", hash = "sha256:d9bf144d6faf12c70d58e47f7512992ae2882b820031d6cef68152deb645bf2d", size = 151790, upload-time = "2026-07-06T15:27:58.477Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/c2/39de60ef5687662f467bed3d1e6944c67a4f0d057141d0404002b8f405ae/charset_normalizer-3.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:faac37c4904598daa00cb4c9b32f3b4cc814fb5f145d7a531ceb4a70f2114132", size = 319040, upload-time = "2026-07-06T15:26:15.854Z" }, - { url = "https://files.pythonhosted.org/packages/a7/57/a9474c3aeaa337c8a330c0dc5df266527d56da3b189c029529f6b08af2a4/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f191c19a32dc6cec0fb8079789d786254653a9ce906fcab04ccd2eed07bba233", size = 215541, upload-time = "2026-07-06T15:26:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/13/a9/be1ff7e81f6e086dced2a7a7a28b789be351d9796084ccaf6136a4ffafb3/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05811b76943d477bb90822dedb5c4565cef70148847a59d574e2b35043aeb563", size = 236913, upload-time = "2026-07-06T15:26:18.482Z" }, - { url = "https://files.pythonhosted.org/packages/8f/75/d8c5eae93da26d463f9ebe46a4937ca44434dc2937a565b92437befb3d94/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3868a3e4ec1e40b419e060d063f93eac6f046fa21426c4816421223ae7dc8ab8", size = 232815, upload-time = "2026-07-06T15:26:19.734Z" }, - { url = "https://files.pythonhosted.org/packages/27/0d/98e301ca944bcca5e6bc312406b579c8a6d81546c1b494afb3a9478495d6/charset_normalizer-3.4.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25f93d194eb6264c64416cabff46a91f6d99b97e7525a1b4f35c77a99e75cc68", size = 223995, upload-time = "2026-07-06T15:26:20.931Z" }, - { url = "https://files.pythonhosted.org/packages/fa/df/f5222366b76dcb31453a9bd922610c893540d0e729fd390439b0d3e972ee/charset_normalizer-3.4.8-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:ee6a62492f18d432cca031fabd158f400a8c25bf7b9458f50953393a2a23d97a", size = 208522, upload-time = "2026-07-06T15:26:22.214Z" }, - { url = "https://files.pythonhosted.org/packages/74/52/293220d59d8ddfb8aa56836b33bd6df58e70795d8a102a858c2984480f00/charset_normalizer-3.4.8-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c16cb4fc35e4b064f5ee78d849f15a550ada1729c3372916672e38f1f01d1d4", size = 219660, upload-time = "2026-07-06T15:26:23.493Z" }, - { url = "https://files.pythonhosted.org/packages/c3/2c/81a298e66f3d01e61bfc6f7064bbb553b067a9f1d979e5962bf00733069b/charset_normalizer-3.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2fbd0edb0426ab28e70fac9d1d4ef549eb5a64a2521f0428c441d75e4387e6c2", size = 218230, upload-time = "2026-07-06T15:26:24.629Z" }, - { url = "https://files.pythonhosted.org/packages/0d/45/f1dd2328cbc3340705f82072c09bd4c68d6e079191cde05810c1eac77eee/charset_normalizer-3.4.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccb9052771216170015f810b88065fd9e13b1e0b391f92abb9b47e0919a42aad", size = 210006, upload-time = "2026-07-06T15:26:25.837Z" }, - { url = "https://files.pythonhosted.org/packages/38/63/28697000620e117eb413424caaf60b6f98ddb1b09b2c11f7c0038d9936a7/charset_normalizer-3.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3809ba5d3cd02aca0894597f2669a825bdfe2229061515c128b0f4e5533b4ab5", size = 225771, upload-time = "2026-07-06T15:26:27.079Z" }, - { url = "https://files.pythonhosted.org/packages/12/a7/d5844e315f5f35e7938c415f07a1df144eed1cf993f1b43cc16c980c5b46/charset_normalizer-3.4.8-cp312-cp312-win32.whl", hash = "sha256:de63c31666a049f653ada24e800192e3c019e96bc7d70fb449a000bccf26a36f", size = 150922, upload-time = "2026-07-06T15:26:28.514Z" }, - { url = "https://files.pythonhosted.org/packages/9b/82/eb8b72f184b1e4986dd9daec15d7f6d9285a6728d2b07b7f04656829f473/charset_normalizer-3.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:14a4bbe066f3fb05c6ba70e9cf9d34614b57a2fd70ea8c27cc30f34155e16a58", size = 162294, upload-time = "2026-07-06T15:26:29.745Z" }, - { url = "https://files.pythonhosted.org/packages/09/5a/ab810134aa41034a08ffe94c058102016e6ad9bce62f3cdba547b4723385/charset_normalizer-3.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:2b5b0c0dca0a02c3f816f89abf18af3d20416dedbc3d3aa5f3981045f88ae7b0", size = 152409, upload-time = "2026-07-06T15:26:31.034Z" }, - { url = "https://files.pythonhosted.org/packages/11/49/fe5a8572a70cd9cba79f80af9388ac8c5c914ed4459b956f940244e499a5/charset_normalizer-3.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:057f8609f7341618c98e5aa9a6109fa116acff2a658497d47ab3325b5e8f2b08", size = 317424, upload-time = "2026-07-06T15:26:32.23Z" }, - { url = "https://files.pythonhosted.org/packages/e5/59/d71c96616b6825425a876f79f38fa440db30b32cc1166179a839f6259150/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c591f9a82adc5b89a039b90df74e43de2b9177fb46771172bed7b80722a70db0", size = 214723, upload-time = "2026-07-06T15:26:33.635Z" }, - { url = "https://files.pythonhosted.org/packages/c6/35/dc9eeb297f19b7b6ada39709ccb74937e6c51f0947958ae601a977cedd5d/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b56f449132d9adefe55b87635d05177a914ed5d070438a74725e1d77a280002", size = 236200, upload-time = "2026-07-06T15:26:34.99Z" }, - { url = "https://files.pythonhosted.org/packages/0a/37/6775fe852b4acad8bf7e0575fbe8aa9f41b546e33251acbded3c04a6b0d9/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1004c3b5831a301dadfb9e916f38e78e2ff3e08db24a1ad7c354db8ee3dea9c3", size = 231740, upload-time = "2026-07-06T15:26:36.498Z" }, - { url = "https://files.pythonhosted.org/packages/e4/28/1bcc3f5f3bac81532384adcfcdd9362c7f46a188a19deacc1ddaf7bdaa00/charset_normalizer-3.4.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60883e22821d17c9e5b4f3ca1ef8074f766e3db28791f851b665929c515635c0", size = 222888, upload-time = "2026-07-06T15:26:38.161Z" }, - { url = "https://files.pythonhosted.org/packages/47/81/9f3993ca62ef090c58059da641e49e3129e74700a6a3beb58436cdb8d4b9/charset_normalizer-3.4.8-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:21e4dbb942c8a6342e2685f232dd2a7bc73465697bd26ead4f118271d28be383", size = 207649, upload-time = "2026-07-06T15:26:39.628Z" }, - { url = "https://files.pythonhosted.org/packages/11/f4/679f636bcbdc2d53d06b1f4039be310450dca95a9f76bbf22f09985556e8/charset_normalizer-3.4.8-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84bcae14c65e645ca66b661339183d32b8c846a17c96e3e81ab3d346e1c498d4", size = 218908, upload-time = "2026-07-06T15:26:40.911Z" }, - { url = "https://files.pythonhosted.org/packages/7e/44/96e8c81867ba8a45ff893c8e7474c2d6b9633f7aa663da7901d040214d3e/charset_normalizer-3.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b4e4d44b8287aa13a25e16e29393d494b0643b24894f7c8266c6f6788dd36337", size = 217096, upload-time = "2026-07-06T15:26:42.148Z" }, - { url = "https://files.pythonhosted.org/packages/a5/bf/4d53f04f29bdb22601701f4f9f4d038edfb27976c296fcb7400c02736a6e/charset_normalizer-3.4.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f68545d1b267dbfafd5d253b6d1cb161562c4e61ab25b5c4cdb7d9e5923e441e", size = 209355, upload-time = "2026-07-06T15:26:43.557Z" }, - { url = "https://files.pythonhosted.org/packages/ad/60/92b3f630798d777fa880ad289a3f9f2fc663e4b4beb24783c53318820254/charset_normalizer-3.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1936e48214adea74922a20c8ab41b1393ae27cc9e329eb1f0b937d3416824f36", size = 224732, upload-time = "2026-07-06T15:26:44.892Z" }, - { url = "https://files.pythonhosted.org/packages/bd/85/eafa0a3c7bb6fe9f02f4c7901f02071933cac85ee634197e17280818c6de/charset_normalizer-3.4.8-cp313-cp313-win32.whl", hash = "sha256:1f8e3521860187d597f3867d8466da225b9179ea2833bb26de1bb026144d07c3", size = 150358, upload-time = "2026-07-06T15:26:46.153Z" }, - { url = "https://files.pythonhosted.org/packages/f5/8c/879fafff7b47bb1166d289f2d2472cb31b9922f9f4ca1f392edf85ec16be/charset_normalizer-3.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:8b654b6f52a0a9a6be38e88f3e1dc68f1093ebeb2abbadafc7c82da0786a34be", size = 161685, upload-time = "2026-07-06T15:26:47.423Z" }, - { url = "https://files.pythonhosted.org/packages/c3/46/b57c7e778a7b578f28d35fd38544687d4f8d9c019585eebc5ad936073fad/charset_normalizer-3.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:d2d5a250ee26e29468b7607d97479221b069fa8aaf6f929ac84ec0e962e15154", size = 152333, upload-time = "2026-07-06T15:26:48.689Z" }, - { url = "https://files.pythonhosted.org/packages/1c/bc/0a8540b8cd494951cca1428606373942803f5ffcec40fe798f819c5a8adb/charset_normalizer-3.4.8-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:77e993ecf65f21ab1f82266ff5e84a7de2c879e7d9b8bc006009df83f22a1d5e", size = 316993, upload-time = "2026-07-06T15:26:49.962Z" }, - { url = "https://files.pythonhosted.org/packages/0e/99/a0868f0a1f0a045fd374d1f2cf7042d8ad5d7fb4dd1f4ac7365e319f7e32/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:524939917f17f6de502dfda30b472550965740d7f126659d4c4f8dd1569cce22", size = 215638, upload-time = "2026-07-06T15:26:51.338Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e9/43c4d09a09b5557cc5fe1d87c9d96f86a3942aec0517d2b5408cef87ca75/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a4508989ba8e2ce43ef989453d18188b261546e8188cbdd4ef451fb9e4c3b467", size = 236456, upload-time = "2026-07-06T15:26:52.531Z" }, - { url = "https://files.pythonhosted.org/packages/e2/67/492ca98b3ab785b736b5da10c1bc233e1c8fec6c0cdb29b482c38bfc52a2/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9e44127f7d11eee4548ad2cdf1f4e1b6eaaddd5cb92d15ad65f6ecc9bcf403ab", size = 232253, upload-time = "2026-07-06T15:26:53.838Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/1e6eff58c14f1aace1e26d80defbeaea2d35e075dbe4b611111ee4b47fa8/charset_normalizer-3.4.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb90317359f7e67bb6df615999a95e0980877468e617ddce8b6c2f8e7fe60d95", size = 222886, upload-time = "2026-07-06T15:26:55.009Z" }, - { url = "https://files.pythonhosted.org/packages/40/7a/90056a5326b0c4b9a3f924d337729c344c11542e5bc7191e50410db61587/charset_normalizer-3.4.8-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:35d9e7a9c960520ae89d1f4e305d1c047a74dea2e0f73a0e84f879356c2e8776", size = 206482, upload-time = "2026-07-06T15:26:56.306Z" }, - { url = "https://files.pythonhosted.org/packages/18/ff/94761d31a33878dbb5008ddbd918615061fcf5c0a612aa3075450e60f628/charset_normalizer-3.4.8-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:92e322b054c7ff886f78feab7360736bb45de2e18cf4a0ee84e8fc5a08d53a19", size = 218929, upload-time = "2026-07-06T15:26:57.422Z" }, - { url = "https://files.pythonhosted.org/packages/2b/dc/00b9675acd7c4b926b9102ee3f0d1a570ce943901be73b87485001393fe1/charset_normalizer-3.4.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c0086d97094363556206dc3bcf43f7edcfc043ea7a568a46f45efea74858bd1", size = 218069, upload-time = "2026-07-06T15:26:58.719Z" }, - { url = "https://files.pythonhosted.org/packages/04/11/94ada5a0482ee4bf688d04be4c7d6fd945d37370d04a95671040dfe2b416/charset_normalizer-3.4.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0752c849b51198267df2aba013c4de3a2955bd014a4fd70828809946c1acbc0c", size = 207146, upload-time = "2026-07-06T15:27:00.058Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f7/246bd36762207ab4752cd436b64e5d81a1668b15ddea7b5b2d0e8545e727/charset_normalizer-3.4.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2a4707e09eca11e81ece4fced600c5a0a801f568b962244f6f517bc274745fc9", size = 224896, upload-time = "2026-07-06T15:27:01.599Z" }, - { url = "https://files.pythonhosted.org/packages/6f/f7/3510622d1fbe13b0ebf827c475e40a27e2be427140d792878b63ab6425cc/charset_normalizer-3.4.8-cp314-cp314-win32.whl", hash = "sha256:8ea67f427c073ae3da0923aa55f3715131fa613a61a7f2f8d762bde75eaf00ae", size = 150851, upload-time = "2026-07-06T15:27:02.964Z" }, - { url = "https://files.pythonhosted.org/packages/32/2b/9ce65dd21672b55cf800cca5f4433afa1586fda1d78731067ec9ec544c62/charset_normalizer-3.4.8-cp314-cp314-win_amd64.whl", hash = "sha256:ff71018850863362e5c7533769d0a9f77715c31af1502d523630ce822922f5c9", size = 162549, upload-time = "2026-07-06T15:27:04.249Z" }, - { url = "https://files.pythonhosted.org/packages/2f/34/9a5967eed666a88f31a0866884606d9ec3c2cd6091e2ccd7e0b4c4176c35/charset_normalizer-3.4.8-cp314-cp314-win_arm64.whl", hash = "sha256:44464e66f4da2f21dea7145c7693f9f60717ca4794a954dea5bf8c2c932678bd", size = 153079, upload-time = "2026-07-06T15:27:05.608Z" }, - { url = "https://files.pythonhosted.org/packages/02/4f/aa44cc81d8987f105352c74c0bf919007f8b80e9880d28bcf0393c1a816e/charset_normalizer-3.4.8-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:50a0c2e58ad2c203adb616fef28941b7e13716adbc25e0dfaeec29f5afe6382f", size = 338586, upload-time = "2026-07-06T15:27:06.86Z" }, - { url = "https://files.pythonhosted.org/packages/1d/2b/b0392e2b235c08ff0623d905c2ee8ac820620544043c1ce92ce0b3d64c55/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1e589fdb95c76f08288bbb346230cdd8994db74903db6637b380f7b5fc9336", size = 222764, upload-time = "2026-07-06T15:27:08.23Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a1/7d466879190731f5559662c22232646f2ae2dace2323c3e5aefcf78d458a/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3d7c887444c5a7ef0d68d358d81e758a850bc626f8e639e2ca5667153272b20", size = 241331, upload-time = "2026-07-06T15:27:09.512Z" }, - { url = "https://files.pythonhosted.org/packages/70/17/8b89e797137aa28c8fb0bafbafc243246a7afe21620a13b00e37624ece1d/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:65c389b96c0cfff3a3f0458fa1c7ce554a30e23101a88a49f03997afce6a929f", size = 239323, upload-time = "2026-07-06T15:27:10.86Z" }, - { url = "https://files.pythonhosted.org/packages/d7/98/1c1940730ed22d50983be4e243c722c89d5136d6f073bd840d1128bfddcb/charset_normalizer-3.4.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:593403fc47dcdf55e2987b2e3cc2e064127e2b908929f1f18b2e4a4652cbd780", size = 229964, upload-time = "2026-07-06T15:27:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/53/2d/bb8e81b7ff603d3f77e9a8a5d1ad34fcabbf3c54d300c29d99fba581fa23/charset_normalizer-3.4.8-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:606088e9fa2b7469ab9c42d4da8e05a415622a07714edd2fcd8fed48dda4c853", size = 212405, upload-time = "2026-07-06T15:27:13.447Z" }, - { url = "https://files.pythonhosted.org/packages/ce/1f/e52a3a53b13da591bb8f21d29e63877268eadf20686b7762351d4b89062c/charset_normalizer-3.4.8-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0317406326fed512f42a1632ad91a96228a7616c06547666a6dd79967f1bd6ca", size = 226918, upload-time = "2026-07-06T15:27:14.89Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f9/32996d79c57189af9722fe618f46d8a86b7be035ca98887b8d0c3821f141/charset_normalizer-3.4.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b67d50ee47e5c57a0064a9cb575b963a7125819dfd1fd094d44d378fff94659b", size = 225113, upload-time = "2026-07-06T15:27:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d6/d2/9248c18e695696513774523a794cfb8b677521ce9ad7554d301cb10a9b20/charset_normalizer-3.4.8-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79e402b869f270140afa5e2b0e2ac100585358d812fe3dd093d424f7a72964e0", size = 214966, upload-time = "2026-07-06T15:27:17.418Z" }, - { url = "https://files.pythonhosted.org/packages/1e/9d/4b19432d406179a40f924691906ee5b15ac664b408971c973295192444ea/charset_normalizer-3.4.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2970b9f7ab69ec3a0423ec6b6ac718e79fbf4a282c0bc103ef88c1ef50dfa15a", size = 231699, upload-time = "2026-07-06T15:27:19.131Z" }, - { url = "https://files.pythonhosted.org/packages/be/41/bdbdf71e8c3ccff10ef3cc2bb9467a7fdb3dc94b9a406d1a3c44afd39632/charset_normalizer-3.4.8-cp314-cp314t-win32.whl", hash = "sha256:458c2972a78043b7261c9726670029f15f722e70669bcbe961153a01968f589f", size = 155333, upload-time = "2026-07-06T15:27:20.681Z" }, - { url = "https://files.pythonhosted.org/packages/bd/f8/e05c69323bd50091ec39f5f885385b884624b0131a6885a0c83a6217ba7a/charset_normalizer-3.4.8-cp314-cp314t-win_amd64.whl", hash = "sha256:0c926329a1df7cd56d7d8349fe354460d20aefd2e394c9e159e479d018b2b359", size = 167378, upload-time = "2026-07-06T15:27:22.042Z" }, - { url = "https://files.pythonhosted.org/packages/c2/04/cbaf1a2f5e2bbf70760e774380cbf052b10849fc35e770905df31af5cf00/charset_normalizer-3.4.8-cp314-cp314t-win_arm64.whl", hash = "sha256:2232baea80a2b01783679fed4e625ccdb19a974f44c9cf0fba21a777a4c8179c", size = 157782, upload-time = "2026-07-06T15:27:23.312Z" }, - { url = "https://files.pythonhosted.org/packages/23/52/d5bee5b6ea81882d549b566d2545b044bbcbc33fe5fbe001008a7e745a21/charset_normalizer-3.4.8-py3-none-any.whl", hash = "sha256:b7c1fb310df524e01fbe84d43b7f95aa4f808f8eaa0dafc185f64ba395e37d54", size = 64279, upload-time = "2026-07-06T15:27:57.043Z" }, +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, ] [[package]] @@ -1060,16 +1060,16 @@ wheels = [ [[package]] name = "django" -version = "6.0.6" +version = "6.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/29/ac41e16097af67066d97a7d5775c5d8e7efc5d0284f6b0a159e07b9adb92/django-6.0.6.tar.gz", hash = "sha256:ad03916ba59523d781ae5c3f631960c23d69a9d9c43cecda52fc23b47e953713", size = 10905525, upload-time = "2026-06-03T13:02:46.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/55/664f24ff81c9ea19cb7dfc851afeae1f3c2390c7aee01d4ded68b5c1580d/django-6.0.7.tar.gz", hash = "sha256:2998503fc083124fb58037084bfa00de323c7c743f05f1b4284e77bff0ab8890", size = 10921299, upload-time = "2026-07-07T13:51:26.485Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/50/23f9dc45483419a3cc2085b498b25adfbf10642b2941c73e6d2dfaffc9ab/django-6.0.6-py3-none-any.whl", hash = "sha256:25148b1194c47c2e685e5f5e9c5d59c78b075dfd282cb9618861ba6c1708f4d2", size = 8373354, upload-time = "2026-06-03T13:02:41.72Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ec/1ce5334b6a2c52ce619c23a0be8d366a57a0e080ebb2d88266e5c849157c/django-6.0.7-py3-none-any.whl", hash = "sha256:a037427c2288443a8c02a1b02295a31c239663aa682bc50b1976afb7cf6a769e", size = 8373344, upload-time = "2026-07-07T13:51:20.007Z" }, ] [[package]] @@ -1204,7 +1204,7 @@ wheels = [ [[package]] name = "djlint" -version = "1.40.3" +version = "1.40.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1215,45 +1215,45 @@ dependencies = [ { name = "pyyaml" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/98/fdee72fa3bd20af0ca42c136d076c8a2069cd7303d66cb77c60fc901914f/djlint-1.40.3.tar.gz", hash = "sha256:4fe34e932c55f5b87c8df0daf60c07afbe8f620e1a24da1a2bde0ea2651c430e", size = 59215, upload-time = "2026-07-04T11:48:31.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/ef/c9b7728d1345ef773698983119e15cda95aedacc9a978e0776184a73c898/djlint-1.40.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e2dbb2219d9133ac5d3dfb9b6bb6a7db582ebcfe005679c16d6cbf284a8fe5", size = 577267, upload-time = "2026-07-04T11:47:46.954Z" }, - { url = "https://files.pythonhosted.org/packages/b7/97/d41a15487a18e544bb9b7c2bbc07a38cf56e13b62cae85fd78a6ffb7df50/djlint-1.40.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47b29ce89517a2ebbfdfc5de74c49f39b0f1c19a01d04aeaed4f8ddf844be367", size = 542842, upload-time = "2026-07-04T11:47:48.057Z" }, - { url = "https://files.pythonhosted.org/packages/93/5e/2dbc6d1ef05716b53db877b52fb2b6de1b0cff3ce7309cec041236563b3f/djlint-1.40.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b54bf1812e2abcff9a97d9208c6fcd0943ed311d345a0c3364fb2e8adf907eda", size = 575133, upload-time = "2026-07-04T11:47:49.585Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c7/25eac5e98b878f572bae8a4e6697b8495666f21aa1db59d0f336d26b1760/djlint-1.40.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec29ff8987808121fcc962e14c66cd929d656d63d3b5b2e04c34c435adf162fe", size = 602415, upload-time = "2026-07-04T11:47:50.674Z" }, - { url = "https://files.pythonhosted.org/packages/ac/20/78d983573e742f8df01a7942c5b0ec57e0b9b0f7fe6b533a7f003916e6ea/djlint-1.40.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62dfa80599ab3f0a1d3c43d85dffa4b42a11933626a6c5fcf03823c066109c19", size = 583668, upload-time = "2026-07-04T11:47:51.717Z" }, - { url = "https://files.pythonhosted.org/packages/80/9b/6dfa0574e980741c9bcc75168c3c790f714647dd526af88a42acfdc6b45f/djlint-1.40.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6715fb4204436013a4eb7e1e7f7edcce44eb03ec94e428ca886c5f1787540e63", size = 611619, upload-time = "2026-07-04T11:47:52.825Z" }, - { url = "https://files.pythonhosted.org/packages/a2/1b/a9f764dcf2dfa058472b95e7e148432b109aa9569e1714ca5c22787ce6d0/djlint-1.40.3-cp312-cp312-win32.whl", hash = "sha256:54e34f363278b292b274c28326542bc0f42c06a6bd99867c630864fd772b50d7", size = 437224, upload-time = "2026-07-04T11:47:53.973Z" }, - { url = "https://files.pythonhosted.org/packages/22/8e/c9d4d8f36bda28784d42fae7c067e8951e7b3ac7e3d1e3fd5f043bc42cad/djlint-1.40.3-cp312-cp312-win_amd64.whl", hash = "sha256:00e053bf26d5c9ddcad67455c597a6ef47ad8debfc59911c00d17b72c17625ab", size = 487134, upload-time = "2026-07-04T11:47:54.997Z" }, - { url = "https://files.pythonhosted.org/packages/72/38/0f665b8a24db2818b15abb459590e56a44909016cd8056906768c0790507/djlint-1.40.3-cp312-cp312-win_arm64.whl", hash = "sha256:33ca64c999010426643977eb4538e8fa790150f601cfb7af778e2cd1652d9ad8", size = 430019, upload-time = "2026-07-04T11:47:56.217Z" }, - { url = "https://files.pythonhosted.org/packages/d9/32/3b9519f888b3983ef3bfbaf220b3a493ccbf68d7a1752e05189fc9309b56/djlint-1.40.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e02a4a3cd5f5489b5dd6dc8f0807105cdb1faba1c7ff011cdc539e215b3f945d", size = 574381, upload-time = "2026-07-04T11:47:57.323Z" }, - { url = "https://files.pythonhosted.org/packages/4c/32/61d2c87fadc03044a8ec131d10087ce54196192dde864ec300a3b0494282/djlint-1.40.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ea2e27d4f23da2d397f7dd1af7a9b38b6866dc5c3697229469f31449cb8e756", size = 541643, upload-time = "2026-07-04T11:47:58.609Z" }, - { url = "https://files.pythonhosted.org/packages/66/d6/69518734dd0b266778d3800c8f6871c645dbdce64241514d4fc531f4202e/djlint-1.40.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191c439e57be56327be260e2b6d9d888f81b1016f388af5f55dc063146e6e290", size = 572995, upload-time = "2026-07-04T11:47:59.775Z" }, - { url = "https://files.pythonhosted.org/packages/01/ad/cf94cfae8f07d98b5c2f8b41f7755d20982edcad571bd1d17331af8b162f/djlint-1.40.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7704817e41e795e2d62b2e1646c8ef512678b484bae9b15d10a4968ff6cc77c", size = 600618, upload-time = "2026-07-04T11:48:00.844Z" }, - { url = "https://files.pythonhosted.org/packages/78/1a/0fae63824b1cd6f17788e474ca149bccbc79896960e1f75e8c10fb35b240/djlint-1.40.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:94a5d64dd7e0396ecdc953f7cbc3f935427330f63b5a3ee80551c3ca56a11bd2", size = 581418, upload-time = "2026-07-04T11:48:01.909Z" }, - { url = "https://files.pythonhosted.org/packages/a5/25/789fcf430e3eb5e07abfe637407cbb29ad620d1f1a186c6dd113ef5c4b7a/djlint-1.40.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bd6d14cc6ac7732a81d704276bf0865749c640c149dc7218da940c6b9a89e4c4", size = 610274, upload-time = "2026-07-04T11:48:03.196Z" }, - { url = "https://files.pythonhosted.org/packages/30/e2/8690bc27594f50b2682873affe46eab1e1b7cb6492b1c64b140981851c1c/djlint-1.40.3-cp313-cp313-win32.whl", hash = "sha256:2e858b98c5961bbdfe20a02dd3e49115de804182601a2f1c1e27b2f6a1a68f56", size = 437031, upload-time = "2026-07-04T11:48:04.354Z" }, - { url = "https://files.pythonhosted.org/packages/47/52/04c3022eb839c063a1791560e02404b2b82f1ebb486bfe9dee93f2d945ff/djlint-1.40.3-cp313-cp313-win_amd64.whl", hash = "sha256:e7db951983c33c27bf2a32153f11a05d0ec91e2f56c6d95c92c0f9a324df7676", size = 486723, upload-time = "2026-07-04T11:48:05.616Z" }, - { url = "https://files.pythonhosted.org/packages/50/ad/8460d545a0875d777d85f7ae36300d779b09b9810579a98a05ab15a83961/djlint-1.40.3-cp313-cp313-win_arm64.whl", hash = "sha256:b12581b55e2b4acf3e9d8f57ef18188618fc31a82500ea9974276428af0a32be", size = 429482, upload-time = "2026-07-04T11:48:06.77Z" }, - { url = "https://files.pythonhosted.org/packages/80/66/36c7f35408d47b88d4547a7a707c2ce4db9128b19fb549b271a53076204c/djlint-1.40.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:669a2b157d2a878eb82e7c85a65e0c57fdfc5193b2c19508d88d64ac82e83e00", size = 572309, upload-time = "2026-07-04T11:48:07.896Z" }, - { url = "https://files.pythonhosted.org/packages/5a/8f/a83bdfe5f5ea4af351755ac5923304e21fe3d12c15659608e56ecd728a0e/djlint-1.40.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85fe6a62e4759647c98f1800923ad3a186d5a34ecb027da29bf8c825c62236c8", size = 541432, upload-time = "2026-07-04T11:48:09.015Z" }, - { url = "https://files.pythonhosted.org/packages/03/75/ab7225f497acf5bbae18e381cd25d11de2beff9d7d60d058128b90adc172/djlint-1.40.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:498f66913d456da7621e3ba7a712719204d893c6f50b32d860595c1e4e565de7", size = 577447, upload-time = "2026-07-04T11:48:10.175Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7f/52cfe4727bdb2a574acf5e7c17588f851127a75c665b5380edc46a4e5eb3/djlint-1.40.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4539ac7689f4b37b813b36f88679e3c444b8372d0c76fa6d13788069ddf295c", size = 599804, upload-time = "2026-07-04T11:48:11.61Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/8305bf213c9065e3f955ad9fdbf1c88c97eee69a5f4783978b5f4ad8eac4/djlint-1.40.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:976b023f2cd2c564eb71ebc618b308d8c38eb67c5d24574bd0d29d445f2106e5", size = 584955, upload-time = "2026-07-04T11:48:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/0a/85/8b6afa4018fe3c70adecbb6ae19f6372d883d16662a5d39caa3e68da83fc/djlint-1.40.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e61c5d4ad684eeaa4134c5e51e652eb96f3d342d05f5b617eaa629accf591096", size = 609521, upload-time = "2026-07-04T11:48:14.077Z" }, - { url = "https://files.pythonhosted.org/packages/38/42/3e087f577bae675e51c47d123e38c83b249fa5ac29140740e78512b9c36c/djlint-1.40.3-cp314-cp314-win32.whl", hash = "sha256:341dbb91b1ebb96f86804a338c7c40b3a3cb860ace949c40a79da6d45272ff11", size = 442482, upload-time = "2026-07-04T11:48:15.176Z" }, - { url = "https://files.pythonhosted.org/packages/44/15/d71bd699b69f0a1d004f4451779543f778a12bac26c3e9f6ad25a910f205/djlint-1.40.3-cp314-cp314-win_amd64.whl", hash = "sha256:7917f50c96e28c5ecb48312f1696043eba8dd09a497af99f500e020ad0613d36", size = 494803, upload-time = "2026-07-04T11:48:16.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/84/2e9a86cd16d9a17c52945cec926dd4fb3db1a1e91443a5e8002b8e2e45df/djlint-1.40.3-cp314-cp314-win_arm64.whl", hash = "sha256:b09952d9cd50874287a56aaddda8caf98b33b6a94991195d717cc3b68629438a", size = 438407, upload-time = "2026-07-04T11:48:17.482Z" }, - { url = "https://files.pythonhosted.org/packages/45/2e/ae7714a5db1747c4ed14b71583baf84c53646aa6a2aa37916b55ebc46d5d/djlint-1.40.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:97c83b7497686749f7649973882c2fdd239675662f445135957f19bcc815536c", size = 623561, upload-time = "2026-07-04T11:48:18.604Z" }, - { url = "https://files.pythonhosted.org/packages/af/83/cec6eefd521993531e66e6912db85cccd5f871df65e263715a38f6b21e12/djlint-1.40.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:19969e5e5391cb97c755d1e04158456422e51a1808aacb27af624e2c47a45814", size = 593177, upload-time = "2026-07-04T11:48:19.916Z" }, - { url = "https://files.pythonhosted.org/packages/80/39/954d6465f8a072f8ef8d7468d1e33ca7c36912428bb42bb4d4d55145817a/djlint-1.40.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c02df10cf101b3908ff31128d326e23efa66e68cfaef4430e890e32c61efc129", size = 616979, upload-time = "2026-07-04T11:48:21.086Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/f1c1c0fdd7163256cc48192ce0d9bed54cc6e9a537ec66257ea721a9de5d/djlint-1.40.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e3900f481b4dcad5b4d149611b0e2c72c4955cdb1ceb5c1074ecb457cb2cb36", size = 638069, upload-time = "2026-07-04T11:48:22.463Z" }, - { url = "https://files.pythonhosted.org/packages/00/b2/834c1aba299454855555792e71610d4b128456748b1335ede89095158792/djlint-1.40.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:152b2854f92285b0ddb9b730426dfe9e7cc64603a92bde9934d51e80518a0e40", size = 623183, upload-time = "2026-07-04T11:48:23.617Z" }, - { url = "https://files.pythonhosted.org/packages/fc/e9/1858be94f1205c85505d8646ac6b85d1312791e8b8d19f94548cf54ccb5c/djlint-1.40.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:617be8d11f337cc1e4034b8a035ef1c21dcdf4578571c4029d6108439e55765c", size = 646847, upload-time = "2026-07-04T11:48:25.11Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d8/45405e2389f7e7ff74add6abf563e66cb79934809c4298e4412a1e07f216/djlint-1.40.3-cp314-cp314t-win32.whl", hash = "sha256:a44c8248569e783e9ca5eb9588374b1b265b800b93633527d8b084ff93828287", size = 465322, upload-time = "2026-07-04T11:48:26.421Z" }, - { url = "https://files.pythonhosted.org/packages/70/e8/cac6d8e0b348c1eddb37f64bf4362e0f43dde0e122de670061386d458747/djlint-1.40.3-cp314-cp314t-win_amd64.whl", hash = "sha256:af048452b63eaf329bb9c011085b554a81ef2417724f3a0f55dd26825383d85f", size = 514250, upload-time = "2026-07-04T11:48:27.886Z" }, - { url = "https://files.pythonhosted.org/packages/13/86/69541eb6079d8eda51085fefae0e7f8603390679894231dac763d90c7ca8/djlint-1.40.3-cp314-cp314t-win_arm64.whl", hash = "sha256:5e125ec3742d710b838c49e74fb5074e0efc80c3261c2bcc36a9817510da438e", size = 456540, upload-time = "2026-07-04T11:48:29.18Z" }, - { url = "https://files.pythonhosted.org/packages/17/40/f29fc555c8fb3a0cf2d12346ee05b8e8c6183ba98b82e5c8ee5453018891/djlint-1.40.3-py3-none-any.whl", hash = "sha256:d2b396a2cb288409fa83abc3f2b17c04aa98b0238bceaeb9d02c1db66e4f0e8f", size = 65272, upload-time = "2026-07-04T11:48:30.239Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/55/34/95284072186e09ea3c7a63a18930061d1ff393f7896fd693f0308fa4aef7/djlint-1.40.4.tar.gz", hash = "sha256:76949030ddfe7334bcde9bcb022133c5d7bb7d292ea7717fc8df425bad4b41c3", size = 59869, upload-time = "2026-07-07T20:50:28.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/ee/83d9e7f8a7dc037d5068efc104435ae2292f8054013d6e2a588d8b85db23/djlint-1.40.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f4f4ab65833c3388d9f0f6a7ac999910b4b3803e9b7f5a6272f19adbae0688f", size = 591323, upload-time = "2026-07-07T20:49:42.982Z" }, + { url = "https://files.pythonhosted.org/packages/70/2f/33c235765ef02bbb48f8b2385c13a93b0168f3aa708c3974a032936c8f81/djlint-1.40.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:872fec9e9c52a71951eea86a92fe49b992e69fa7d9a8acd836b9a6d7086b091b", size = 558830, upload-time = "2026-07-07T20:49:44.252Z" }, + { url = "https://files.pythonhosted.org/packages/9c/23/34a9e0681db90d033c508b7be33a36c36cfc3c37169ecd3c0ac16114978e/djlint-1.40.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cc499a61c68bb8f6ee4dc06167bbb151fc236476f18d0630a7252ba50ddc8ef", size = 591117, upload-time = "2026-07-07T20:49:45.308Z" }, + { url = "https://files.pythonhosted.org/packages/0f/90/39e3af014ce63c0bc5c3890d8ce012d7f89d053a824c6c9c6a5dec5f6410/djlint-1.40.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:451868d1009f6e84f8688c6b0788e1bb144fc89b16971e507f3e47f2cbb95e4f", size = 617193, upload-time = "2026-07-07T20:49:46.357Z" }, + { url = "https://files.pythonhosted.org/packages/8d/23/e8e465e627ccc61452c928d4af178ed0c686de74096e6248d822b38d1f5d/djlint-1.40.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab07b7cdd03b1b20f233d5e3446131e4d651f0eaa88e03c7b8952833bd402b41", size = 598865, upload-time = "2026-07-07T20:49:47.681Z" }, + { url = "https://files.pythonhosted.org/packages/9c/46/d43c57b9ac9c3adefe71343c5a705505fa05ed7caa5a197c038a24913ee3/djlint-1.40.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3656d601e6abb5111422bed75fc68c5fca3c6b709751601a8530d88309150845", size = 627900, upload-time = "2026-07-07T20:49:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/9a/fa/b035330810d3c54ab4b851e2830f1c484c4b12f8cd5449fb1635960d7c0d/djlint-1.40.4-cp312-cp312-win32.whl", hash = "sha256:4dedd0ae45b391266cbf03228e275e7d891d8243b79326b716f7570957e978b9", size = 448979, upload-time = "2026-07-07T20:49:50.103Z" }, + { url = "https://files.pythonhosted.org/packages/80/a4/c9a6ab5a416f1d9a115c61e57df6f9e39a65572ad4f13f3c9bfadec6783f/djlint-1.40.4-cp312-cp312-win_amd64.whl", hash = "sha256:41e7b9edfdb0c3a590efcbf907d747f650ac62fd93bc22fb1f0c948d81e239e7", size = 500244, upload-time = "2026-07-07T20:49:51.233Z" }, + { url = "https://files.pythonhosted.org/packages/be/37/c2a7e36bc2312136788903f24a4082e8a31cf5fd339a39df8b88374aa381/djlint-1.40.4-cp312-cp312-win_arm64.whl", hash = "sha256:d60a7493c0c5fb2f5a94e69208352e02ae9fe5f9a62cc47488234cd56b374a08", size = 441856, upload-time = "2026-07-07T20:49:52.338Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/00567d3070d86a6ea2390c7e5633be25248fe8b4af5abf9bfc665ca8d7a9/djlint-1.40.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1cd31f87b721081100f9b234c97e2e41b5d6c235d40843e576085eecaad4cbf", size = 589048, upload-time = "2026-07-07T20:49:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/8d/25/0ee438261231407ce235cc64c67e05b2ebb5dca828468b25838031ff224b/djlint-1.40.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:589db14e133bf4fec3441e3929cd97ae858e4c93a98bb974a980e71fa1924f73", size = 556757, upload-time = "2026-07-07T20:49:54.557Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fc/afb9ba2301e35a5038d78b4af6615f8302af14566fb163f692f54dbdca07/djlint-1.40.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc960f85ff848bd31d9670cd7ab6c7a9dd1adfe2d988674915be5e8920b8bf2b", size = 588960, upload-time = "2026-07-07T20:49:55.712Z" }, + { url = "https://files.pythonhosted.org/packages/aa/79/a779dde335f0d062593db0e76035826795ae27c12c0ec26b09ddb719146e/djlint-1.40.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ce6f337c431482194fca840851620bd06afb38869c6874cb37bdba016b54f70", size = 615246, upload-time = "2026-07-07T20:49:57.119Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/84ec207bfb5d63fe4f5fc1d33a5f2d59ccd97334bc8d235107fee53de83e/djlint-1.40.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cbab944cd2e615a54be76dbaccbb86047cc10b68613acf7e181716e6a6ab9769", size = 596834, upload-time = "2026-07-07T20:49:58.147Z" }, + { url = "https://files.pythonhosted.org/packages/15/9a/98f719d99bec31f96ab925c013577fc1e33fd060173cfa1c3252423da317/djlint-1.40.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:243f6d1f8243191cd9d6886371b7cb16f2fd46aa450b8501c13dcbe147eaf70a", size = 625933, upload-time = "2026-07-07T20:49:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/46/5f/fcf825020cea241f9d8fb3b2e7669101f23acb333898dacce9026aa64f64/djlint-1.40.4-cp313-cp313-win32.whl", hash = "sha256:20d7e6b772d50d197b58ffc65e89adba27b12569759dbf3dd73d0a05954ee4a3", size = 448707, upload-time = "2026-07-07T20:50:00.886Z" }, + { url = "https://files.pythonhosted.org/packages/f2/cc/304af3b90113dd365495220a377bc91d711cd2f063b48d66af1c9bcf925d/djlint-1.40.4-cp313-cp313-win_amd64.whl", hash = "sha256:400234f1c9a9e039b5300079e0cc83dde059d0c0262e57fa7da03adecff30446", size = 499762, upload-time = "2026-07-07T20:50:02.378Z" }, + { url = "https://files.pythonhosted.org/packages/9e/69/ddcb8186419f50035d1142e34c5bae5394a9fed8b9fc9aa9b6b28a652377/djlint-1.40.4-cp313-cp313-win_arm64.whl", hash = "sha256:bd6f97f22a089464d76fcecf72a7796e1a0b3eb552eb154b9a0aafc894528c22", size = 440973, upload-time = "2026-07-07T20:50:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/65/76/512638424b5e2cf8cd33c935e70ed7f1791c70dd307773895c4d680b6c3a/djlint-1.40.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:39f7227e658d665f682932decaeed39440c844bce8f4ec2a12b790fd8f053079", size = 587508, upload-time = "2026-07-07T20:50:04.67Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3c/ecf53e3ea1dee58faafa1d159415cace583b07c13635d920b7ac34dad038/djlint-1.40.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:005a90d796bb221f39bc55d9af179f5568b94eb4e29d2c1415f27cddc2f9f601", size = 556571, upload-time = "2026-07-07T20:50:05.935Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5c/0b8f47a8116291b3fa0f65fc7f7c3c283d7cfb217ca7133b9edfaca2627d/djlint-1.40.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3097d3c02ed38776e30124b0ba775a6f232f147ba052d2206d1df09e666068be", size = 593178, upload-time = "2026-07-07T20:50:07.089Z" }, + { url = "https://files.pythonhosted.org/packages/9c/78/5a0cd3ed0deba2c5410b999286e31d50d45e93eb20815bca996314bb242c/djlint-1.40.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e12dfac6257d8f3643aa4657a8fbbaeb0b8b8ad53da4ca85354133fd3465d1a", size = 613952, upload-time = "2026-07-07T20:50:08.444Z" }, + { url = "https://files.pythonhosted.org/packages/a2/06/2a6a59017bd7f35e5f3e67d9fdb76729412788e50e738e29e89cec06ad9b/djlint-1.40.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4a3e2a4ed25e179aa9fc99d907dc17b26af33b0b31a54351bf1cc17ba57d746", size = 599478, upload-time = "2026-07-07T20:50:09.538Z" }, + { url = "https://files.pythonhosted.org/packages/6d/66/8dca40d476a91128b4e96473ffc36382e9a63ef5c59065b51ea699b2d7e2/djlint-1.40.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d6258ddb3a946d47de3c465bf0e2450dea0fa9d8ec9865dc38ada3ef37fc63", size = 625083, upload-time = "2026-07-07T20:50:10.724Z" }, + { url = "https://files.pythonhosted.org/packages/25/be/9ad023536f8ca42e5f0bd730c79ca0cfbc7dc3bd1ac4653b9704bf0fea2d/djlint-1.40.4-cp314-cp314-win32.whl", hash = "sha256:905ee352144607e30be8fcf30eeb24c3b4c364f3591d79a4a0dd010597f0ee2b", size = 453854, upload-time = "2026-07-07T20:50:11.957Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a1/8367ab12ffa49cf6325d72e0e66839fcac8a763172e25b537787a313666d/djlint-1.40.4-cp314-cp314-win_amd64.whl", hash = "sha256:7932fd45f9c2287c43863ee599fb7d0967f2e7da42048218beb0d3ee8389b6bf", size = 508520, upload-time = "2026-07-07T20:50:13.178Z" }, + { url = "https://files.pythonhosted.org/packages/f8/56/0505bec0f44dac679e5d9152a0521819c60301424c54672574a7f451eeb3/djlint-1.40.4-cp314-cp314-win_arm64.whl", hash = "sha256:56035834d06a0843f806eaa31fcc0f9ecea006ffaf2710320d95779d650f7b5d", size = 450629, upload-time = "2026-07-07T20:50:14.276Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0b/ddb085d9261f63a1bd3f3369afd873c35201c4ec66f90249bdbf2cb8e632/djlint-1.40.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d06097e5a8687d154872ac57d5946640fdf1ee5f24fe942687d214a13e43e99a", size = 638620, upload-time = "2026-07-07T20:50:15.423Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/6699cfab15a0e66880335ff52387ad94ce7920bc851a390cd510af32ad4c/djlint-1.40.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ddab4dbad5a0e7fd008b3831b9cc2861896f565e1727c68053279737e85d8224", size = 605979, upload-time = "2026-07-07T20:50:16.949Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ce/cfdfb7a5d41f5dcbd3d9ec3e06ea837d5ca44a833e76a3fde9e3b6706f3b/djlint-1.40.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:861862b36413fcf118253d635774d7642d6930e3ef4f99a7a520787244c6dc1b", size = 633519, upload-time = "2026-07-07T20:50:18.094Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fe/3bb1965b5cd986daffd795a20fd0fad232a2effc518d415b52a037bce68d/djlint-1.40.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c2cf3e75df64c2e048c05afb70310dbd00caa8fd2656d21453169991af1826b", size = 655685, upload-time = "2026-07-07T20:50:19.38Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/0fd39f4465c3692c3ba5e38ad5fe6db353d69b9983fdb8c5f19c71784445/djlint-1.40.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9420dedc6806c952d09ed5841b89ac81322efb8e80678e7080339c67a9c59530", size = 639280, upload-time = "2026-07-07T20:50:20.736Z" }, + { url = "https://files.pythonhosted.org/packages/b3/00/e07c6420464d01aab6acdd7da85c0855766807f7d0289a6f41d6cd28e537/djlint-1.40.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e965f10fddac29a52fa21677d7e1d00e0880ad97febfc2aaa8476ffbade93814", size = 665379, upload-time = "2026-07-07T20:50:22.621Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ec/786a0021b7a6b4f066ccd848b8213aad9a736e5681d769d941e54b821eb5/djlint-1.40.4-cp314-cp314t-win32.whl", hash = "sha256:cc7f533fef2d13ef208009e67346e2566be685fa2758b51f66a423c7dc42fff1", size = 477438, upload-time = "2026-07-07T20:50:23.983Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d8/a2cee1f7d967daa9b010d362e26cb34813cd8033e26bf083cb596e05b698/djlint-1.40.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5cf5a6a9f8b9308c7ca70ff1ef1e2afaf103d67869ef3d96b3eea81c10d399a7", size = 528060, upload-time = "2026-07-07T20:50:25.201Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3f/9c9244eb8b530b8c8df82354b8cf4ec696e02b7b2f243b13a747890af79a/djlint-1.40.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8c9fc0260700bcece0260fb39d20ee13d72444ca5870afb7d0f2dd64aa5fb4f2", size = 468627, upload-time = "2026-07-07T20:50:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/91/56/164784f721f2fe07d769c5b51c540a7fd598aefaf3b488a0703afe03ad60/djlint-1.40.4-py3-none-any.whl", hash = "sha256:c6652630461fbd458ff3708ee2a89ce992f65fc13cbd4f1066afc7baa1b32b8f", size = 66573, upload-time = "2026-07-07T20:50:27.654Z" }, ] [[package]] @@ -2455,7 +2455,7 @@ wheels = [ [[package]] name = "picopt" -version = "6.6.1" +version = "6.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "confuse" }, @@ -2477,9 +2477,9 @@ dependencies = [ { name = "treestamps" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/df/011852b57fb5a0568daf6ebc8ad80ff56c24dcec0ae26b899931ce954fce/picopt-6.6.1.tar.gz", hash = "sha256:e3733489a5d8eef3a824a1daa5717a50ee9fa4f7a6f94f7f272b07171e098f02", size = 10372187, upload-time = "2026-07-06T17:21:49.44Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/b5/511aa39d20eef398d249715575832fbc99c9a30304ec3c95673b05ebc830/picopt-6.6.2.tar.gz", hash = "sha256:db6725fa7acc343f2ec68083bd8ef38d47e781fdd2d2236a5d17c35c3292b9cf", size = 10379632, upload-time = "2026-07-09T23:58:41.306Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/46/a72ad0c0d950a6a816d7fa0237d9be0c2387b2a835e8126c56df69fbff09/picopt-6.6.1-py3-none-any.whl", hash = "sha256:26ce77bdabd4c83fea75dbf6279a58abfc8ca4df92ee43d1d0798314cc5f2d2c", size = 131744, upload-time = "2026-07-06T17:21:47.823Z" }, + { url = "https://files.pythonhosted.org/packages/e1/10/a735e53ee81704e1acd297087fb505364d63bc87c1fc628f7be7616b681b/picopt-6.6.2-py3-none-any.whl", hash = "sha256:824344887d8d68d7e887c9b46daff7a94647b23e8835425e0fc43f5ba9cb2f69", size = 135347, upload-time = "2026-07-09T23:58:39.165Z" }, ] [[package]] @@ -2493,43 +2493,43 @@ wheels = [ [[package]] name = "pikepdf" -version = "10.9.1" +version = "10.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml" }, { name = "packaging" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/0f/a796e796e1a83e7e75b0ee061fbbe3b52889305d79ddf495a557e365ad22/pikepdf-10.9.1.tar.gz", hash = "sha256:410fcf32bc9c8a0a96d94bbd6268ba7585333b1423b93a5fa2ef3c05f4eba3da", size = 23854194, upload-time = "2026-06-19T23:53:53.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/b3/61b86a9e11a85ea579ca3d9465e6ddfea79f4baf6be17f7b244569b9bcdb/pikepdf-10.9.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cdc7520523da4966c7afabed20b6c57bc87de74e5e21d0e193b010495fb8f881", size = 4428335, upload-time = "2026-06-19T23:52:50.506Z" }, - { url = "https://files.pythonhosted.org/packages/ca/7a/01679a404d198170785211b6dd32fd5cab25f3f952e5dececd8e1525b4a6/pikepdf-10.9.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:08d2bee9fd5d7a32530cc2b0bd167600a0a296e4445823469b98f20907881bd6", size = 4748362, upload-time = "2026-06-19T23:52:52.73Z" }, - { url = "https://files.pythonhosted.org/packages/ee/46/62ecb0480f4c1002d2183609a27df290301e2161fd062152e94bb281d522/pikepdf-10.9.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea24f7f1756a7832fc7e27c9c4e1d7c98c1b847c00c3833299c5c03cf15399c", size = 2000233, upload-time = "2026-06-19T23:52:54.783Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e1/6683d947603398d4db11e6ee4fdf2323f3fd7ffa3c6ad06995f213e2b736/pikepdf-10.9.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3836fc0547af990a2b8a37cda63526ef095dd36b4599cf28cda070a9994241ef", size = 2192740, upload-time = "2026-06-19T23:52:56.947Z" }, - { url = "https://files.pythonhosted.org/packages/71/30/3489d4cf70023528e2d5968a61be7f01349ea311cf25cbd7e7d2ccf1bdb7/pikepdf-10.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9d26aeb4a5ba4bbaf03102906ef3a2333aa764785b98cc2769238635be7b4f8", size = 3638408, upload-time = "2026-06-19T23:52:59.074Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8d/df1b6f6aa9cad37d6d5545a5078b7347a60e07066fd56d267cc4ef2ba9c9/pikepdf-10.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84d2ffeac1bd20612db7346ea7b200c78d2d767cbbd6bb52a47d6e1153fa3933", size = 3836337, upload-time = "2026-06-19T23:53:01.408Z" }, - { url = "https://files.pythonhosted.org/packages/20/68/734695c9be596354e2f38e77aa1be42a919a6db669c802810e662c17df62/pikepdf-10.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:8dee389db583673a4c37036960f2980085aafc8f5b55f5843af23cd061f45fb9", size = 3296420, upload-time = "2026-06-19T23:53:03.265Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0e/f99fc15ce0150455c1203e1abe68148d13da9a6f605d94e2a499d1194bed/pikepdf-10.9.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d9ce2812bd76ef6f3549546a4ad33c81be7e64a9985997628d084e8863d157d2", size = 4428220, upload-time = "2026-06-19T23:53:05.648Z" }, - { url = "https://files.pythonhosted.org/packages/bf/2f/34deea50e306095533a8420ff4d6dd90d8c9c72ad9b46977dae63cbc4ae4/pikepdf-10.9.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:15880e09605f0c2f8afeacffb7c3b904e01569922596cc10ee3afc3777406206", size = 4748238, upload-time = "2026-06-19T23:53:07.852Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c1/f0c60cfde4116e8b01687f74c1792567c40a9bf3e1b3ee5ceab7267fedde/pikepdf-10.9.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84dfb8c7082d85106d291bec71990f03215215a07852a698bd5d84337ce31949", size = 2000169, upload-time = "2026-06-19T23:53:09.91Z" }, - { url = "https://files.pythonhosted.org/packages/a4/8e/533188d63f2b14bb6103bed6e048f2d5676622e2f902a3dff558ba4b40be/pikepdf-10.9.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:16b7edf32814256b2e50d31336a0301371a397c466bf7624bce7639e66c07b97", size = 2192691, upload-time = "2026-06-19T23:53:11.756Z" }, - { url = "https://files.pythonhosted.org/packages/97/40/317206653f2afbfc89ecf5e896bdce4ce26e11a78c87ca51a49bef73be29/pikepdf-10.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3311c44a8553f2c57bce37cc764df2a9c07ecc43a39690f7fc10c7fe081083e", size = 3638153, upload-time = "2026-06-19T23:53:13.692Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d3/cfdb8207c3e6906853f04d857eb9dbc6cc98624d17481360c0b7cca2d4db/pikepdf-10.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b7a5ff6cab6b2aed3058e362ec64d41f0c03e842eeecf9a5af30e21988ad6de0", size = 3836156, upload-time = "2026-06-19T23:53:16.093Z" }, - { url = "https://files.pythonhosted.org/packages/28/9c/4a7a588c748431b91dc617cd3dbbb79387d8a10fecc8a363151176df450e/pikepdf-10.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:6fc3096e88d8f030252a43c44c808a89c2c9905988f0d66b72b5731f8a9e3a6e", size = 3296374, upload-time = "2026-06-19T23:53:18.635Z" }, - { url = "https://files.pythonhosted.org/packages/36/a8/551a73c191c8742fd0436051ed542b245aeea517eb7ee2d91215b9a4047e/pikepdf-10.9.1-cp314-abi3-macosx_14_0_arm64.whl", hash = "sha256:3f9c79dc03ee8a44b8c4265baced60b8dd2c79bfa447a22eec67f9deade5d535", size = 4428390, upload-time = "2026-06-19T23:53:20.736Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c5/6b6d0d204bd51517e4240a349e5b3c1d83aca096aa6b115878385ed7e67c/pikepdf-10.9.1-cp314-abi3-macosx_15_0_x86_64.whl", hash = "sha256:cfb8146c396d4d8a92f20c72df0ec94a2ef61770cece29d27821b0fa2f52ce1b", size = 4748541, upload-time = "2026-06-19T23:53:23.153Z" }, - { url = "https://files.pythonhosted.org/packages/84/30/e4d13bbdbe726be9546e8ce6d81f16a650f9786e9fe8d3ba9eb8d4055455/pikepdf-10.9.1-cp314-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbeae48b65b8e9ed28419ebdd52b9cec7bf60a52c984d311b6df2458c3eee62", size = 1997375, upload-time = "2026-06-19T23:53:25.871Z" }, - { url = "https://files.pythonhosted.org/packages/cd/64/b214a90fcfbb653d8ec5c33501028d163a3ecf51e267a72f0c5c363d328d/pikepdf-10.9.1-cp314-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8d19462732f782d1ddd1c556fec5195453b3fc758f4d4c83d7294700cc496e0", size = 2190496, upload-time = "2026-06-19T23:53:27.996Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4c/627c73edf668113f837faa603cfba4140ed2836981547025abb8fc238526/pikepdf-10.9.1-cp314-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c85627cd6122d5cc734069793ebfdbeccd60679cbd2c241bc7a199797cf802d", size = 3635409, upload-time = "2026-06-19T23:53:30.268Z" }, - { url = "https://files.pythonhosted.org/packages/8a/16/83df0c82a2ee31e7b47e1cecbe8ef9de66b55642833f308bde559dd469b0/pikepdf-10.9.1-cp314-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d1d0c635368b1cd14e88e6a5a007c5271c30d1798d88452de0e88e4d6ee92335", size = 3833699, upload-time = "2026-06-19T23:53:32.46Z" }, - { url = "https://files.pythonhosted.org/packages/ec/75/4bc9beb1de126f4db1b6ac807852e3db212ba884e94b85c7552fc5735204/pikepdf-10.9.1-cp314-abi3-win_amd64.whl", hash = "sha256:cd6c12ed7a1d8327b1279d8d14890aedc10090be38114f5a0893026f217c06aa", size = 3389178, upload-time = "2026-06-19T23:53:34.67Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/2bec744893aa2843af18a9bb03475dc193b9ed392ec49420946cd3c441e9/pikepdf-10.9.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c748df16d09742e34ea3dd7e44ad64f88331d2cddf62d4d73911bc4193268f62", size = 4434676, upload-time = "2026-06-19T23:53:36.956Z" }, - { url = "https://files.pythonhosted.org/packages/dd/f2/a9b6a46b6610ed20484e1a245a4039738359448b471855de352074c972d3/pikepdf-10.9.1-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:99d1f8fe53fa5c9d78a8c7c2ea4a7aad228e1b7b0571287de7f9decf0b5561c1", size = 4753523, upload-time = "2026-06-19T23:53:39.888Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/53f7a73ad0344387254c938aa39b359a9b9979341c2dd14a7348e1ecab40/pikepdf-10.9.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee509c16b86e007d205df2083b1012ec75c825fd62fdd90aca292fd4c7a4b213", size = 2004802, upload-time = "2026-06-19T23:53:41.876Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2c/13f41886d7e73fbe9056fae291a4d883fff9f37879c98b8ca36f89b1bfe5/pikepdf-10.9.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91756746333a30ef286b8d3ead7a1382f6abb067519f3841c4a6392773059309", size = 2196628, upload-time = "2026-06-19T23:53:43.938Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f1/a8f44aa54d766a547160e29b0f3358ad1d6bc7c4b1bdfac097a470ad544a/pikepdf-10.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:06584062796a2bdc201cb76c1452a2651240c7140102ffb00cde4e4808b84a21", size = 3644233, upload-time = "2026-06-19T23:53:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/a2/1e/5a1493dc73b82f3721ec75dc0abeb8d357b8dee6a1abd6edc8ca75d874c1/pikepdf-10.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8534f300fe4d1fcf311aa6ea073284d414e4a9c0564c38c0ef020f14825fb857", size = 3840741, upload-time = "2026-06-19T23:53:48.09Z" }, - { url = "https://files.pythonhosted.org/packages/5a/03/6a736a6d4be7e45c0b662f9489a519b6298f91621afa78827a5459ee5983/pikepdf-10.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8e470a1470a809da83643c30e2e05ba629131b0193d32bc91f28c7b18cb8b23f", size = 3420520, upload-time = "2026-06-19T23:53:50.276Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/95/e7/45265a52196e7b1e4d1fdee57159d25670275784f5eeb3651327cf68e137/pikepdf-10.10.0.tar.gz", hash = "sha256:9f134806b2fe608ccb21379a664ddcefeac3f6944100d343b350299d3c69754e", size = 23886210, upload-time = "2026-07-10T05:35:11.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/94/a30157ea052c9f8c8c5fee97dceba09f374dd564ed91fa8b8d9c48cfb622/pikepdf-10.10.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ccfa2288bbc206bfe7197f8614ee20de902e511ff968da86bebf814280124156", size = 4454121, upload-time = "2026-07-10T05:34:09.054Z" }, + { url = "https://files.pythonhosted.org/packages/bc/86/313554efb00675f6e9b75bf012368e7c114174680f1c13035eba6e4a75de/pikepdf-10.10.0-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:19c14b76a240f3cd067abc77b62843268d56dce45b7787ea70fd8cceb11cc5dc", size = 4774365, upload-time = "2026-07-10T05:34:11.251Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b6/0cceaa5a1084730154e8e948b181d2eee2f7f2f3a110b919263ea29e4a0a/pikepdf-10.10.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81c938b33db9f32b7f419f06dbb97eca3e2629c57c0b18a7b719d81563c48d3f", size = 2025196, upload-time = "2026-07-10T05:34:13.727Z" }, + { url = "https://files.pythonhosted.org/packages/46/b0/fc1e0e3f0a6a652f71387c4f9061457202369e98074def130f15173a7a6c/pikepdf-10.10.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:feccd53d5620c908fbb79399b2daee9103dee27d744bc80946daca22500be90f", size = 2218460, upload-time = "2026-07-10T05:34:15.479Z" }, + { url = "https://files.pythonhosted.org/packages/7c/94/f2447204b7f63e25ec43d9641347e0736fa540b495b61315c9dceca89f03/pikepdf-10.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2333431f739205de480e547fab5dcc512668ea4af8f3f02f0d620730413d3772", size = 3663344, upload-time = "2026-07-10T05:34:18.302Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b1/d318daa40e36b1beea2faec63bc4db8d246e0945279af4b3583274edb426/pikepdf-10.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ab09282067a6e95cf0b997df020c38bb0474ed2af99edaa472ab90ef1f18929", size = 3861830, upload-time = "2026-07-10T05:34:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/4d/fb/21b6c283a216864f185fe124745bad1b72270aa0ec0912e8bb6691c16326/pikepdf-10.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:e4845031acf29026a597a207c6a07b471577e8db3b30bac172ab4ac6b4ee4be7", size = 3320968, upload-time = "2026-07-10T05:34:22.357Z" }, + { url = "https://files.pythonhosted.org/packages/da/8b/31c62f96479bb4168fed80541d8bc037bdf832e1751aef50ab62f65121ec/pikepdf-10.10.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ebfc8e36608c01271fdeec6c08e1c87148ac9c275766876e9ac1b2c91879a0db", size = 4454123, upload-time = "2026-07-10T05:34:24.573Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/fb00002df0479806408becc7ce658145269a87aa0c5355bf11371288e81d/pikepdf-10.10.0-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:e2eb3faee72fc74352e481fa174b4f0c7c2ed8516aed5bf73f9dfb7a57c3212f", size = 4774307, upload-time = "2026-07-10T05:34:26.51Z" }, + { url = "https://files.pythonhosted.org/packages/45/07/310138761c00d1fe19e6d16b81a02ec32627343a171eaca43c4f8b74054e/pikepdf-10.10.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4851b2b5c80916c0b57b9928b7fd9d1565f68c5e9c75a6304876cbd103812eed", size = 2025133, upload-time = "2026-07-10T05:34:28.536Z" }, + { url = "https://files.pythonhosted.org/packages/2d/0b/4e5271bdd40041f1b7d81cd3879003d84ece955e502f18567d4c26926e58/pikepdf-10.10.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3e725ffe276b5ab86734b2a5f7025f3e1cb3b1d26063a39adc7490834846449", size = 2218319, upload-time = "2026-07-10T05:34:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bb/361bdd9b030b974e2eee6d886ae529d3fced1378fd75b2b087ad87aa926c/pikepdf-10.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:425a76bb45ee713dbbee5b524f179f6797fd768f26d1bcf6049cc7181d479ec5", size = 3663292, upload-time = "2026-07-10T05:34:32.491Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c0/0c6e903a55cf1672921bd519823c2ea2f9ee421c5fa4b8371d2ef0b1c6c5/pikepdf-10.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d1a781fca5ad09b6ee964e2971819f82f21218636b3a5bb62fb6c89816f2b88a", size = 3861541, upload-time = "2026-07-10T05:34:34.446Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e3/6d17f19fd3c7c5d84c14d29906bba2897e739eaedff5b83e3a7cea6961d6/pikepdf-10.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:5b4460b6c610d89a1e3f52d2f48b811216b02cd18aee5c853e2cf2e8551b1ecd", size = 3320900, upload-time = "2026-07-10T05:34:36.578Z" }, + { url = "https://files.pythonhosted.org/packages/25/49/c181448d33d533003aaf7e688448355a2ffc0de132fbaa650ec6be2667b1/pikepdf-10.10.0-cp314-abi3-macosx_14_0_arm64.whl", hash = "sha256:84481f8768a9bcbd0bd490fa1b9d737bd50ef6e4c1651115b2df133c90ca229e", size = 4454276, upload-time = "2026-07-10T05:34:38.615Z" }, + { url = "https://files.pythonhosted.org/packages/8c/14/6fcaf8f95b21e167faa9095850cafeb0cbc24c8eda7e91ec288dfb1a2906/pikepdf-10.10.0-cp314-abi3-macosx_15_0_x86_64.whl", hash = "sha256:3867848e8dcca701be50464ead7e06a43a8c064ac48fb6d26faee79336e86f95", size = 4774492, upload-time = "2026-07-10T05:34:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/7a/93/5b9d3104dfa1671c6d476c161ce728176787090a4137457801d40132e6f8/pikepdf-10.10.0-cp314-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3056dbbac60d97529560d3baecd9450c03683f4d9e72e5fb4a0c5aa50f01f3cc", size = 2023029, upload-time = "2026-07-10T05:34:42.65Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a0/f55e196e18c238ec6ba88f377ca24da3af2754ad47bc284213420d101ea0/pikepdf-10.10.0-cp314-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33adc935f6e0a195e1c1aa1e0c47047d6a29835d5643d07dac9c98996a619e05", size = 2216226, upload-time = "2026-07-10T05:34:45.633Z" }, + { url = "https://files.pythonhosted.org/packages/e1/48/3724e868913669b5a715ad6bc371986d52a99e5d9747e652da29559b1b65/pikepdf-10.10.0-cp314-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc7812a9ac54442fed4cd370e61d9fc294fa05577d14c13087c310eb8cafa1c0", size = 3660978, upload-time = "2026-07-10T05:34:48.019Z" }, + { url = "https://files.pythonhosted.org/packages/de/7a/ea908e0a1daadbded49d74823b62a16bf0e2b1991f17be7c57fe269db44d/pikepdf-10.10.0-cp314-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a094ce9636652f5aff8d62c004383f13cdb4312d2d854b6096bb6e15e3c0a269", size = 3858870, upload-time = "2026-07-10T05:34:50.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/2a/5d86ec58d8ef6e295807c99196f4ef795aa89d04abde4837bcbfb4b0785f/pikepdf-10.10.0-cp314-abi3-win_amd64.whl", hash = "sha256:66567891906906e8733f1ef19cd133f46f34349cb415c858ee64db807d82413c", size = 3414578, upload-time = "2026-07-10T05:34:52.771Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2e/34a171f0789ebc029531ba4c7a54109eedd3f63ebd024ed1771309b0be3f/pikepdf-10.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:d29487188c9f4aa9dd6decd403f76b4cb23e886c701dcd4cac508c257dbeac0b", size = 4459828, upload-time = "2026-07-10T05:34:55.096Z" }, + { url = "https://files.pythonhosted.org/packages/f4/92/8eb3fa7ae994ebe6bc3628ab867b5b000f77e3480f180e68560830ce4218/pikepdf-10.10.0-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:e19fdc44c2e71cb5a150b270f0602aecabcd2432e6f9eef6f24742ff4d498b20", size = 4781116, upload-time = "2026-07-10T05:34:57.352Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/b45e3b96842053ac0c8d0d0d2cefbc3e86aa9625bddd72e66f564e79220b/pikepdf-10.10.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1d830217af7a179984f156bc6d2dee3b2f6662286f993554e53b4f73452932", size = 2029491, upload-time = "2026-07-10T05:34:59.818Z" }, + { url = "https://files.pythonhosted.org/packages/65/e2/c0661a580d52c2294a6480cf63dc48695ba38bb611ad60959359d7e46a73/pikepdf-10.10.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c2ba4bafdfbfcc417a38f817e317be905e749893104c5bfa02eb29010c3c8d5", size = 2221465, upload-time = "2026-07-10T05:35:02.264Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/ea3456ee4c2957ad8f27b315b0f215f39c2f6284ed41aa1748393296692b/pikepdf-10.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3670a7eb6e1263750a50cbf01b947f0a5fefd1a43a1a7aa09e2752cc54c7f80d", size = 3667441, upload-time = "2026-07-10T05:35:04.801Z" }, + { url = "https://files.pythonhosted.org/packages/24/f4/76332de5a6755ab0910d89cc64d1a621a09821b46383ae664afd0c5396cb/pikepdf-10.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4dd93d81e77e67937d76592ae3438ad80b2b6c9619846fbc400dd9d3e71cc689", size = 3866534, upload-time = "2026-07-10T05:35:07.01Z" }, + { url = "https://files.pythonhosted.org/packages/51/ec/a249a101d8e254b887353e6c98dad29f6133fb550e593c28cc47811ee0b4/pikepdf-10.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:616adec2b1e3e2cf0640c8dc0f479ed9a07db298e32166ee7b60fb8b33dc98af", size = 3446758, upload-time = "2026-07-10T05:35:09.106Z" }, ] [[package]] @@ -3256,11 +3256,11 @@ wheels = [ [[package]] name = "rarfile" -version = "4.2" +version = "4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/3f/3118a797444e7e30e784921c4bfafb6500fb288a0c84cb8c32ed15853c16/rarfile-4.2.tar.gz", hash = "sha256:8e1c8e72d0845ad2b32a47ab11a719bc2e41165ec101fd4d3fe9e92aa3f469ef", size = 153476, upload-time = "2024-04-03T17:10:53.798Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/80/442df102d81a10d31ec2cf73d6f411f5575e3bb7adc0aaa45cbe693a37b6/rarfile-4.3.tar.gz", hash = "sha256:0d8b5a7ffb9f8e9a7b002f2398905e4420c0cb373e799b48e48f418db9c8816a", size = 154855, upload-time = "2026-07-08T19:21:54.072Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/fc/ab37559419ca36dd8dd317c3a98395ed4dcee2beeb28bf6059b972906727/rarfile-4.2-py3-none-any.whl", hash = "sha256:8757e1e3757e32962e229cab2432efc1f15f210823cc96ccba0f6a39d17370c9", size = 29052, upload-time = "2024-04-03T17:10:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/4a161de993dab0baac89b8c5b9765f420e8cc5239c5c82e0f51cb41e5a74/rarfile-4.3-py3-none-any.whl", hash = "sha256:f9dd2f8bd811b2e30fb074aa176f972f51633aa41cf909db3f2921ed040e1128", size = 29583, upload-time = "2026-07-08T19:21:52.904Z" }, ] [[package]] @@ -3279,90 +3279,90 @@ wheels = [ [[package]] name = "regex" -version = "2026.6.28" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/05/e4f219230e11e774a6c9987d2ab0d0c6b8573e13a17e143d0015bee710ef/regex-2026.6.28.tar.gz", hash = "sha256:3cb4b6c5cb3060cc31efdc1fbb27c25fb9b29044afd87e40601a1c4d9db54342", size = 416101, upload-time = "2026-06-28T19:56:55.302Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/21/44aa415873032056c43eac21c67285deb2cf66cddb2a964c3cdc8f803efc/regex-2026.6.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:81cc5793ad33a10444445e8d29d3c73e752c8fb2e120772d70fcb6d41df40fe1", size = 490480, upload-time = "2026-06-28T19:54:05.392Z" }, - { url = "https://files.pythonhosted.org/packages/8b/5f/30d4116093c2128099f78b6990dfc1698fdbf3ee528f1e1c647378034c79/regex-2026.6.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e18225243250a1f7d7e5e5d883f3b96465cd79031acf5c6db902b7025f2125d9", size = 292137, upload-time = "2026-06-28T19:54:07.088Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/ca20a0e0de49837e6337603a91ab77556aa27033ac5b975615d98698cfb3/regex-2026.6.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd1638b1c2db1f2d01c182a4b0d3e2e88b0e99910320a745c1727ee3638ddab", size = 289623, upload-time = "2026-06-28T19:54:08.762Z" }, - { url = "https://files.pythonhosted.org/packages/50/11/c013422a7e2c59946df8ac93e792a4922c98287f2a2181341603c78a5d98/regex-2026.6.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4303ebe16b74eeb3fe2715745023266fea92fd44a23f3e7bb2fb48c7a7bbc195", size = 796756, upload-time = "2026-06-28T19:54:10.616Z" }, - { url = "https://files.pythonhosted.org/packages/b0/95/1309645a0e1ee6fb91d954501da57a0b33d50ad2a9acb313702851a7054e/regex-2026.6.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56b856b70b96c381d837f609eee442a1bd320cd2159f5c294b679552fb1a7eaf", size = 865465, upload-time = "2026-06-28T19:54:12.742Z" }, - { url = "https://files.pythonhosted.org/packages/20/06/491802db47c6f5e2904ffa2518ad3ac27fe6bbf5a66d73210a95cc080d47/regex-2026.6.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f74675ab76ab1d005ffba4dee308e53e89efc22be6e9f9fae5b539a3f81bdff2", size = 912350, upload-time = "2026-06-28T19:54:14.508Z" }, - { url = "https://files.pythonhosted.org/packages/5e/60/3ba57840bcc7e2367090360de0c15a5ba6ad22be89314251105f2e943f43/regex-2026.6.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90581684565a93f7258af1e5d3f41ef20d7d7c61f2a428183a342bcb65485e38", size = 801261, upload-time = "2026-06-28T19:54:16.432Z" }, - { url = "https://files.pythonhosted.org/packages/eb/27/af1eb74e9a78c782b3e450b611a595e44906da8a5107e1227f4a7fd0480b/regex-2026.6.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:28f9e6c28f9b90f6f784595a33240a57e181e61b6ee3dc259b25c61e356d1aa3", size = 777072, upload-time = "2026-06-28T19:54:18.128Z" }, - { url = "https://files.pythonhosted.org/packages/20/18/fdd4c883a39e3ed00d669062af1135809bfd3281bf528150849fbd68825b/regex-2026.6.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:378a71d861fc7c8806b04ac5b133d53c0e774f92f5d9663a539872d3fa2b0417", size = 785119, upload-time = "2026-06-28T19:54:20.314Z" }, - { url = "https://files.pythonhosted.org/packages/1c/79/0aabe34b8482dcadf64355f70f96e22eba5ec6c1efb33563f89654f4061c/regex-2026.6.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc199874ecd6267a49b111052250825bfe19b5101b23b2ba80f54efa3e0994e", size = 860118, upload-time = "2026-06-28T19:54:22.368Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2c/c973323306a27c9db7d160e9584eb7e0ece2a96224ccb0d39060558b31f9/regex-2026.6.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b916a10431494ef4b4d62c6c89cab6426af7873125b8cd6c15811bf5fc58eec8", size = 765786, upload-time = "2026-06-28T19:54:24.265Z" }, - { url = "https://files.pythonhosted.org/packages/e3/df/9ca3e378e352242a4cb45573a5e9162c3ee791507702a23966fa559e36b5/regex-2026.6.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e27727fba075f1e4409416d2f537d4c30fc11f012ea507f7bd74d3e19ecb57a", size = 852120, upload-time = "2026-06-28T19:54:25.972Z" }, - { url = "https://files.pythonhosted.org/packages/a2/3e/3e31e255c4971f53cbce6306b5e3c76cbd3735a54f419bb3b2f194e9f68c/regex-2026.6.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:700fc6a7844bb2c4149292ac79d1df8841a00acd4d45cd32c1ebc7bcc1fd0da8", size = 789503, upload-time = "2026-06-28T19:54:27.678Z" }, - { url = "https://files.pythonhosted.org/packages/72/01/d36561c21c3033d7eeb31d51b491916817de7861acefccc5fc9db8a5037c/regex-2026.6.28-cp312-cp312-win32.whl", hash = "sha256:03376d60b6a11aecb88a79fa2be06b40faa01c6693bc31ef69435cd4818b9463", size = 267109, upload-time = "2026-06-28T19:54:29.316Z" }, - { url = "https://files.pythonhosted.org/packages/a0/59/bbbb0591f38b18c65977cd65ce64749eba1c1996c99ac04e900fc30c0dcb/regex-2026.6.28-cp312-cp312-win_amd64.whl", hash = "sha256:fbd2ded482bf99e6651992bbfcde460272724d4bbc49ef3d6b46d9312867ec84", size = 277711, upload-time = "2026-06-28T19:54:31.143Z" }, - { url = "https://files.pythonhosted.org/packages/86/06/be4f6b337d773ae5739a1bc238f97c16926e72017243735853c030f4c628/regex-2026.6.28-cp312-cp312-win_arm64.whl", hash = "sha256:37294d3d7ddb64c7e89184b2894e0f8f0a19c514bc59513d71fe692c3a8d5fc6", size = 277022, upload-time = "2026-06-28T19:54:32.97Z" }, - { url = "https://files.pythonhosted.org/packages/b6/53/d5c1b3cc0b5a0c985563ad6fac93d73ff2b300cb84342d89f044625d6bc7/regex-2026.6.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b295a83426e0e44e9e60fde99789e181bd26788a1890ae7fe2a24c69bb6246ca", size = 490329, upload-time = "2026-06-28T19:54:35.775Z" }, - { url = "https://files.pythonhosted.org/packages/8d/9f/0c3503e819e91ca0e7a901a8e989ebf840ac7c7aea20b1fc7f31b6759f77/regex-2026.6.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c31665c0deb5c111557a1cac8c27bd5629e2f9e7fd5058900a03576c33b601c", size = 292039, upload-time = "2026-06-28T19:54:37.977Z" }, - { url = "https://files.pythonhosted.org/packages/bb/7f/cd004e13fcad23b3794a82307dfd222e6365eb7f598bd3caab148a830bff/regex-2026.6.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6bf295f2c59de77d1ea7de053607ae4dc9ceb3d57bbb6c7ec51ef4acc4ccff94", size = 289488, upload-time = "2026-06-28T19:54:39.545Z" }, - { url = "https://files.pythonhosted.org/packages/73/4c/293fb34586fbcdc47eac436069e9c11f71fae5dadfd4889b475d7d2e5f7a/regex-2026.6.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17c077586770f67e05bbffeba07fbee6b2b22244f4d4caf8d94e59d574befe04", size = 796772, upload-time = "2026-06-28T19:54:41.347Z" }, - { url = "https://files.pythonhosted.org/packages/92/fa/c0cd1a90b7d12d9dc155cfc8bdea8df9720988ea5b07e8fa1eccbd0ab2dd/regex-2026.6.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e6cb5a61486f9062397d2e189573b39d38ecfaed698fd9fb6e2756a8ebb8762", size = 865467, upload-time = "2026-06-28T19:54:43.485Z" }, - { url = "https://files.pythonhosted.org/packages/4e/db/0b479973046d005a1eaea299d5d536aeecb9488a16d9cbb8286338102e2d/regex-2026.6.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e86e91a2664f44c3a4e363a7d78fb17c27d5046882e30ea5a877f5e89b28d2ba", size = 912345, upload-time = "2026-06-28T19:54:46.091Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5b/d65adfbd02f32212431bca1f06d1e2eb763a20b12978b454bafaf23dacb7/regex-2026.6.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dfd1331c49233998d84fc5f1f4436cf7a435a7655f6cf0f490229bb5c7254e5", size = 801291, upload-time = "2026-06-28T19:54:48.3Z" }, - { url = "https://files.pythonhosted.org/packages/fc/09/2103686defaf9a0a31c1663782359d5b45f42524c64cca681f5481e44a5e/regex-2026.6.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cadea12805a1bce0b091c302b814207be26fb60a9c0e7f9ad2f9e21790a429fe", size = 777106, upload-time = "2026-06-28T19:54:50.326Z" }, - { url = "https://files.pythonhosted.org/packages/85/5a/b57593c0aa23ed269ec332fbcf07852abcb6b746e811d9464e0d09b4e25f/regex-2026.6.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f2c1682b67ad5d2376498f2a5a2a8f782fa2e4a06d0465b5e357799806e8a20", size = 785175, upload-time = "2026-06-28T19:54:52.172Z" }, - { url = "https://files.pythonhosted.org/packages/79/59/c36e756ad29bf14d7b6c6d7138952476b21f6160286cedb98ac13481c993/regex-2026.6.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:64e142eb55e84868087da1375d7c36ff97d55010951849f515322a91d5fef1b4", size = 860186, upload-time = "2026-06-28T19:54:54.11Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/49808aea0da9649c300139360708fb91b7144be1f962fcebf96755fde948/regex-2026.6.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:abb4daabe7be63273787a62dfd6164dadf8f7a63fbec3d2730e5e5e7126d858c", size = 765754, upload-time = "2026-06-28T19:54:56.04Z" }, - { url = "https://files.pythonhosted.org/packages/be/c5/52bbd436cf2200decdf48825fa38363eaaeebb77011ea9928a1ef9e0b9f2/regex-2026.6.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec2b2ad00ab8c16a2798cc8db80c53c4d5b8b3a2441f6cbaef06625f5ca25854", size = 852085, upload-time = "2026-06-28T19:54:57.988Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c3/0390b66e3019497143fe768b3ba567b64d8b24f3812d09506deb86f4a0f0/regex-2026.6.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bfc9677982c914d9085b8e1c3b3ae6e88f139fb56531c2416d6c8f338093c22b", size = 789600, upload-time = "2026-06-28T19:54:59.977Z" }, - { url = "https://files.pythonhosted.org/packages/88/fd/ab5b03653a244975069fed93d73f4f5f7484c03a84cedb238292510d7182/regex-2026.6.28-cp313-cp313-win32.whl", hash = "sha256:bf54bc693fc4e0530e666ba5ec4bcba14dbe8f66b7cfc15c27317d1a6e40b9a5", size = 267088, upload-time = "2026-06-28T19:55:02.159Z" }, - { url = "https://files.pythonhosted.org/packages/68/55/21022f7d3143210ae8d4ff905c45306237b657375cc0b97883f49db3d423/regex-2026.6.28-cp313-cp313-win_amd64.whl", hash = "sha256:e128feaf65bf3d9eb91bec92322a8f7e4835e9c798f3e9ea4b69f4def85620e3", size = 277680, upload-time = "2026-06-28T19:55:04.185Z" }, - { url = "https://files.pythonhosted.org/packages/b6/99/7f664804f1aef924542b0b233996b78b3e4d0a52d9951358aac99f129f51/regex-2026.6.28-cp313-cp313-win_arm64.whl", hash = "sha256:695873e0ea8d3815ea9e92e2c68faf039cc450e2c0a62a31afe2049eb11be767", size = 277017, upload-time = "2026-06-28T19:55:06.29Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e1/9eb83518e159d719fd681c4932dc2aaff855ce72451e1d05d69466f25a96/regex-2026.6.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:189dbf9fc4252d9f1352bf4bd1bef885edb6cc4b7341df202a65f821aaa3891c", size = 494195, upload-time = "2026-06-28T19:55:08.292Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e2/e259c5f2f7be269d0e2fb54275c1fa6a13fb47019f389c3f3ae457447825/regex-2026.6.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9277a4c6503390aa39cb4483b87ec0384faee0850a23b5cea33d008b5d8d83f1", size = 293976, upload-time = "2026-06-28T19:55:10.014Z" }, - { url = "https://files.pythonhosted.org/packages/8d/4e/9bdf444014d22b045d0c82ca114fac7e07a597b5b5331b7c4ce6328426e2/regex-2026.6.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17eddca4e8ea9af0b5739314776cdf0172a49731ab61f2e1ea66e066ddd46c97", size = 292340, upload-time = "2026-06-28T19:55:11.88Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3a/f49b11e59cbfe187ace0053a460bd72a0169b8cd52e7db9421a074ce7a43/regex-2026.6.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4466b8641e00c697aab5a73150150d2b2ea96b131c595691f42031abafd9f4d", size = 811704, upload-time = "2026-06-28T19:55:13.612Z" }, - { url = "https://files.pythonhosted.org/packages/2f/fb/ad04c39e149bf8b6cf357df5fff78341733ec366780a00c803a36735818c/regex-2026.6.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cfcd4b0bdcf768c498415c170d1ed2a25a99bf0b65fa253bbd02f68ceba6475", size = 871157, upload-time = "2026-06-28T19:55:15.797Z" }, - { url = "https://files.pythonhosted.org/packages/7f/64/0e5ba31c11eb8ef7aac19a690c1211fc9aa9990caf09565785ebb0081b9a/regex-2026.6.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:80c7adf1ef647f6b1e8aa2ca280e517174cd08bdf7a2e412cdfb68bd6a0917cb", size = 917287, upload-time = "2026-06-28T19:55:18.692Z" }, - { url = "https://files.pythonhosted.org/packages/11/75/6b78df2b858c2fcbbc4858fdc3f2975cf2703be374b2842db7d2c32591a7/regex-2026.6.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a043f5770e82283a22aed4cefef1a4e0f9dd8fd7184cb6ce0ad2e579e2134a9e", size = 816333, upload-time = "2026-06-28T19:55:20.973Z" }, - { url = "https://files.pythonhosted.org/packages/b4/01/ecfe665a3694d5eda9f3ec686c856438ada0943947b6005e90556a1e2cdf/regex-2026.6.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3bd630a8dba06b55254ea5ee862194edab52ec783100d2ef1cd15a9c512fee27", size = 785518, upload-time = "2026-06-28T19:55:23.003Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0a/88f9cd88ff1e82881605c4ffd62d77ee67d051232cfe6f8e9a64b86cf0e8/regex-2026.6.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b77207e3cee13086f1906a6a2a12b41244c577e8ad9370d4b35ae1d548d354f3", size = 801371, upload-time = "2026-06-28T19:55:24.888Z" }, - { url = "https://files.pythonhosted.org/packages/a8/97/601483732f93275482ceb9fed57813dfed7c47d3a019db6ec4a3bb6e23e0/regex-2026.6.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:6de82c268e5d101ee9e3ffd869924aa9a371e3a21e752cf4fa17b6ce50d219f7", size = 866517, upload-time = "2026-06-28T19:55:27.232Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/385c2a0351b994a693453c1d1a6e9af9eb35db3c9460d76b5078acd70c62/regex-2026.6.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b15859e3908544fb99cf47341dcf0bfd089147d258c4c4d8a29e5b087f8085cb", size = 772834, upload-time = "2026-06-28T19:55:29.154Z" }, - { url = "https://files.pythonhosted.org/packages/06/bc/bbf4a5b3b29770d7f307d3c28b5b1bca0105b0cb424be0a4eb1339bc92cf/regex-2026.6.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c91487a917edd48a1ea646fdf60d7936d304f0e686fa7ea8326e47efca51d816", size = 856606, upload-time = "2026-06-28T19:55:32.186Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/51d74fff82f682819979249f8d700267108ba5dc4eb284b0e11b9c85e4b3/regex-2026.6.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4ac65f3e3a99fd8f3a4a74e7a6610acd1ce9dfe9b8a03d346a4922380d68aeb", size = 803475, upload-time = "2026-06-28T19:55:34.328Z" }, - { url = "https://files.pythonhosted.org/packages/7c/3e/6be10cefdc813533fe604dbf5d3c77d2638e7ee658b2749ebadc113b6b2e/regex-2026.6.28-cp313-cp313t-win32.whl", hash = "sha256:3f6316f258bc7e6c9c2acbe9954947bbd397a81be3742a637a555f1855d6618d", size = 269126, upload-time = "2026-06-28T19:55:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/3c/3c/32cda905ea1a6eeeb798291c294d8ec66ee0efe0cdba28b061e248b1d396/regex-2026.6.28-cp313-cp313t-win_amd64.whl", hash = "sha256:1484bdd6fba28422df9b5ebb04055b2e1b680e8e4f08490bb21ff0f3cc50d0ab", size = 279961, upload-time = "2026-06-28T19:55:38.456Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b9/69f4e5cd6fbe0bb420cb2dbae441ca118f2495bdda522a74da75aa9829e7/regex-2026.6.28-cp313-cp313t-win_arm64.whl", hash = "sha256:3f15020f0b69cafe57baa067ff65b29acef68ff6b1670a53bef1ca11d708e02d", size = 279266, upload-time = "2026-06-28T19:55:40.62Z" }, - { url = "https://files.pythonhosted.org/packages/3b/fb/fad3b810a5bb1e09b9e5d6913fc6ba88cab738fdf283196827a3c59a4c10/regex-2026.6.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:f7c032b0c8a73739ff8ff1aaf30c281fa19c17bf7f1543256c8507390db7807c", size = 490407, upload-time = "2026-06-28T19:55:42.724Z" }, - { url = "https://files.pythonhosted.org/packages/d6/52/b8c79d12276d93e90e707e939b396034c04980caf1235312ef790f8e11fc/regex-2026.6.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f6710f512c57b84f127a23d0f59560a03b64136eff419ae1be5ab557577fe5e3", size = 291988, upload-time = "2026-06-28T19:55:44.549Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/6a911f18279daa8d7bb8b20d771ddb6ef31fabd35f5921f9d3ba21640e80/regex-2026.6.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c0013958f427bd82509a186b9ff206d66cb8d60a81fc797a4c717afd18c5b0ba", size = 289704, upload-time = "2026-06-28T19:55:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/fd/22/ad1955c47c669291a05804d53d7071cc0732dfdf166857be38003cedc2d1/regex-2026.6.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f06cdcd6421f8e194ad312ea608020381250df9b8a57661c1b57e9e5273878", size = 797017, upload-time = "2026-06-28T19:55:48.166Z" }, - { url = "https://files.pythonhosted.org/packages/e5/67/a83159ff8703ab4d0c2cf99e76ebf289b7b4a501623241d09f88f3614f80/regex-2026.6.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec9689392f7494ff4e3f8e7e8522f9158f11023f337eaaf04a64542fc45bbf26", size = 866112, upload-time = "2026-06-28T19:55:51.047Z" }, - { url = "https://files.pythonhosted.org/packages/b9/09/7bff2d6dbbd77421b3274aa51db1c887381cbc5b6eda93598c3e882ea345/regex-2026.6.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa084684e6d2078bf6139e374d1fc2af5ddc1ac7122759a2db716d68169f6fd0", size = 911554, upload-time = "2026-06-28T19:55:53.707Z" }, - { url = "https://files.pythonhosted.org/packages/29/44/ae59c3826e7ba492e56795cdf74ea2a7b5b7c5ea116afb79ee4956a5dff1/regex-2026.6.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40455e6840dc4e96a6fe50f4cedc957de2752c954d91e789812be55d49be199a", size = 800665, upload-time = "2026-06-28T19:55:55.875Z" }, - { url = "https://files.pythonhosted.org/packages/d6/19/6fd033d2ab00f35d445aaeaf3307c1e721424dcbfd48f6f65c857cb939cf/regex-2026.6.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:530b5c223b9ca5dd8370ac502e080aee0e4ded32be987c6564b425fb5523d581", size = 777243, upload-time = "2026-06-28T19:55:57.909Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9d/99730f26df4938049ab1e652ca75e967b4c6739444e18d9707bfdb8af20c/regex-2026.6.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e0ed273ecd1a89be84466c1749bfe58609cc2a32b5d5e05006c4625ba96411b", size = 785784, upload-time = "2026-06-28T19:56:00.072Z" }, - { url = "https://files.pythonhosted.org/packages/48/49/105cd57162f5fc5c04cc917a1388a060cf8427e5c14353cd9044660fbf4d/regex-2026.6.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0ab0d5344311fc8e8667078942056c3b9c9b4a4b1cc99f2eb8a5af54554f4acc", size = 860914, upload-time = "2026-06-28T19:56:02.017Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a5/788245a95b69018f58bff2f4fd27d007cacaea088cdb390979743f1b2571/regex-2026.6.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:eacb79625323d9f7e7925366b917f492b8356fad58f5dc4fa12ff8c21d8f4ca9", size = 765915, upload-time = "2026-06-28T19:56:05.021Z" }, - { url = "https://files.pythonhosted.org/packages/ca/01/292065a39a004b05e67a337b18213670a7cb919d6856ac2d7df7f1a10dbb/regex-2026.6.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20f4d87702702aa1d572721e146f301660c50eef6fd6cb596e48a22b0ace17db", size = 851404, upload-time = "2026-06-28T19:56:07.251Z" }, - { url = "https://files.pythonhosted.org/packages/98/9e/a93d865db0e13483ae1a01d81e2ce16d4a7fe2f9b9fe4aac4cc08590b136/regex-2026.6.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e693940a3b9e6d6e4dc2a54ecaa74b74934f77af1ef95f518a74261ef7cc1bc", size = 789373, upload-time = "2026-06-28T19:56:09.894Z" }, - { url = "https://files.pythonhosted.org/packages/82/0c/38b1685ad4017d78efbc8fa7dbbf96d8113b53750c8aa2d3609defd46605/regex-2026.6.28-cp314-cp314-win32.whl", hash = "sha256:234a51e20ebc18ab83b2c0600cf28f2e884560a0e00f743878f0b7d8e7c4cf03", size = 272496, upload-time = "2026-06-28T19:56:11.83Z" }, - { url = "https://files.pythonhosted.org/packages/55/50/e19f261ff9ba9b50722a529e09b1743ecf65eb348be99d0fd2cd7fcede1c/regex-2026.6.28-cp314-cp314-win_amd64.whl", hash = "sha256:7b15c437bc4604f03ceb3f8d37eae2f8930e320e1bc556b259848c639d9eec1a", size = 280754, upload-time = "2026-06-28T19:56:13.758Z" }, - { url = "https://files.pythonhosted.org/packages/36/b8/c9e68f3a9e33be73f20990b2c065b144ff2d0aa242608a950d8c4f3b56e8/regex-2026.6.28-cp314-cp314-win_arm64.whl", hash = "sha256:c6e6f790d01380a74ad564f216c533b86504afb61bf66f2b2e11e7f1a3e287a7", size = 280979, upload-time = "2026-06-28T19:56:15.928Z" }, - { url = "https://files.pythonhosted.org/packages/03/e6/21c425a37880c650d007c4171c6a80325446d830d85f5fbf335e7205b1e7/regex-2026.6.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3527a72adcbe9e3600f1553b497d397c1a371d227580d41d96c3c5964109b65c", size = 494282, upload-time = "2026-06-28T19:56:18.049Z" }, - { url = "https://files.pythonhosted.org/packages/07/50/6647a7ccf5ffff995ba955a0b7d766440f4e58ce1666549c8ee998f2b972/regex-2026.6.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a644f6408692812f5ead82519eed680e08d5d546fddbd9f7d9514e3c73899aa5", size = 293977, upload-time = "2026-06-28T19:56:20.145Z" }, - { url = "https://files.pythonhosted.org/packages/8c/dc/a3e141a4eaf125e50f63105570c01fa477c06ac5259dcfa95e9b90760e84/regex-2026.6.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8e2fae6bb883648346f84db270dc9aafc29d8e895f62b88a75ccc83b09519820", size = 292432, upload-time = "2026-06-28T19:56:22.345Z" }, - { url = "https://files.pythonhosted.org/packages/35/ee/2ac1a6b9f167f8ff69f5a789938cc103b60cff41b24a6990daced8b88e34/regex-2026.6.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debe623e09cee97ef9404575e936c610aac9bb08358c5099aaef14644a6871f2", size = 811877, upload-time = "2026-06-28T19:56:25.056Z" }, - { url = "https://files.pythonhosted.org/packages/df/7b/9a5505ee92180bcae300b1018b9ff3d3c19962436e66f2505f255e9fde35/regex-2026.6.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc579c91fb4605773483a8d940b136bcc5b854fff44fa14a1572a038f46563f1", size = 871212, upload-time = "2026-06-28T19:56:27.352Z" }, - { url = "https://files.pythonhosted.org/packages/24/4d/d61a702a9f9d1bd29b22cbef1aed6d477baa961232a7eb4d91b7775b0b3e/regex-2026.6.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7c42be203d84ecf7d487ff23f8a61ef0eb0534fa0fc317a2fce8c065d20618f", size = 917507, upload-time = "2026-06-28T19:56:29.762Z" }, - { url = "https://files.pythonhosted.org/packages/d4/60/1308066f5966b65fbb6905b99ba37e9f1cd753dd0ac08485f8257334ee92/regex-2026.6.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8184b4e2fdaf9cdfe77e38f15a4d9dc149168c9c29eb0ea17c5481d3bb80546", size = 816389, upload-time = "2026-06-28T19:56:32.043Z" }, - { url = "https://files.pythonhosted.org/packages/bd/5c/57ce2cb8d714ee0b7f11c7ee4cfe2af66df2b90f147feadcb538609a3a02/regex-2026.6.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:697f103104f5872d64078d8eeac59979960be8ee76115a2d3f31096312e2a400", size = 785890, upload-time = "2026-06-28T19:56:34.492Z" }, - { url = "https://files.pythonhosted.org/packages/ff/fd/1d5350d3a8a327bff0fccacb911732baf7b5b6f5529c0e3fa602a23e7dad/regex-2026.6.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:714d2b1aa29beef0ddfcdc72ad0771c05326551a8bb0680b0ddf74bfaad87387", size = 801451, upload-time = "2026-06-28T19:56:36.749Z" }, - { url = "https://files.pythonhosted.org/packages/f3/79/3c9e4f8a0306e030ad5a43bbbc01625fb28d58a813bc52d42fd1cc63fb2e/regex-2026.6.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0f09f62e450cc2f113018cc8412aeea3a120a04e1ca7e801a0d441583f9a3b06", size = 866504, upload-time = "2026-06-28T19:56:38.994Z" }, - { url = "https://files.pythonhosted.org/packages/65/12/f747de475b54f4709efb24dd0fbc8467c64cec91f5db0d047b079646ee78/regex-2026.6.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:731ea12d5aeb2577eaef2393d6428b995f76eb35f68a89e03e15a97719d1de19", size = 773047, upload-time = "2026-06-28T19:56:41.061Z" }, - { url = "https://files.pythonhosted.org/packages/58/3c/f02f860e0500c1b2d61a79dec7e214b37fb9656281dcddc92397edf96678/regex-2026.6.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:51e952c8783eabd4706d0f63922f219bcfc1bef9b8cb35941c0d1a0396578858", size = 856665, upload-time = "2026-06-28T19:56:43.466Z" }, - { url = "https://files.pythonhosted.org/packages/4d/6c/28b3fa222513484be9dee26b7222bda109056c43ea28aa2314262ca48816/regex-2026.6.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43248fe4c0ab8fbb223588a0795b11268940072c97bba30ea8f9b49d8cdfde34", size = 803573, upload-time = "2026-06-28T19:56:45.791Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/8f86cf1a1fd85c5ab0c503c9fe4607ad4ad48978b2d8b435d94465e134c7/regex-2026.6.28-cp314-cp314t-win32.whl", hash = "sha256:fc1eddc25ad23c0f1344ab280d961ac595ead48292d7c779497975942373f493", size = 274515, upload-time = "2026-06-28T19:56:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/0f/de/f8613c03b36786ddef2c930d28f9bcae861fcd541cc9203a870956cf1e83/regex-2026.6.28-cp314-cp314t-win_amd64.whl", hash = "sha256:ede8d8e53b6dde0a50f7eca902f0af76d87ab02a55aba7542da68ae3e5dfe83d", size = 283650, upload-time = "2026-06-28T19:56:50.614Z" }, - { url = "https://files.pythonhosted.org/packages/4d/f3/f5ec86839bbabe33b6dee649b62ff9a445d43de6b0ad780cf6b83c56f61e/regex-2026.6.28-cp314-cp314t-win_arm64.whl", hash = "sha256:4da6f6a72f8700b97a1a765e837fb7d5750bfd9f13acea7bae498f573e3a70a8", size = 283338, upload-time = "2026-06-28T19:56:52.879Z" }, +version = "2026.7.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/37/451aaddbf50922f34d744ad5ca919ae1fcfac112123885d9728f52a484b3/regex-2026.7.10.tar.gz", hash = "sha256:1050fedf0a8a92e843971120c2f57c3a99bea86c0dfa1d63a9fac053fe54b135", size = 416282, upload-time = "2026-07-10T19:49:46.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/9c/2503d4ccf3452dc323f8baa3cf3ee10406037d52735c76cfced81423f183/regex-2026.7.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7252b48b0c60100095088fbeb281fca9a4fcf678a4e04b1c520c3f8613c952c4", size = 497114, upload-time = "2026-07-10T19:47:16.22Z" }, + { url = "https://files.pythonhosted.org/packages/91/eb/04534f4263a4f658cd20a511e9d6124350044f2214eb24fee2db96acf318/regex-2026.7.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:da6ef4cb8d457aab0482b50120136ae94238aaa421863eaa7d599759742c72d6", size = 297422, upload-time = "2026-07-10T19:47:17.794Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2d/35809de392ab66ba439b58c3187ae3b8b53c883233f284b59961e5725c99/regex-2026.7.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe7ff456c22725c9d9017f7a2a7df2b51af6df77314176760b22e2d05278e181", size = 292110, upload-time = "2026-07-10T19:47:19.188Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1e/5ce0fbe9aab071893ce2b7df020d0f561f7b411ec334124302468d587884/regex-2026.7.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3463a5f26be513a49e4d497debcf1b252a2db7b92c77d89621aa90b83d2dd38", size = 796800, upload-time = "2026-07-10T19:47:20.639Z" }, + { url = "https://files.pythonhosted.org/packages/d4/67/c1ccbada395c10e334763b583e1039b1660b142303ebb941d4269130b22f/regex-2026.7.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:948dfc62683a6947b9b486c4598d8f6e3ecc542478b6767b87d52be68aeb55c6", size = 865509, upload-time = "2026-07-10T19:47:22.135Z" }, + { url = "https://files.pythonhosted.org/packages/0e/06/f0b31afc16c1208f945b66290eb2a9936ab8becdfb23bbcedb91cc5f9d9b/regex-2026.7.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c2cbd385d82f63bb35edb60b09b08abad3619bd0a4a492ae59e55afaf98e1b9d", size = 912395, upload-time = "2026-07-10T19:47:24.128Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1c/8687de3a6c3220f4f872a9bf4bcd8dc249f2a96e7dddfa93de8bd4d16399/regex-2026.7.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6222cafe00e072bb2b8f14142cd969637411fbc4dd3b1d73a90a3b817fa046f", size = 801308, upload-time = "2026-07-10T19:47:25.696Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e3/60a40ec02a2315d826414a125640aceb6f30450574c530c8f352110ece0e/regex-2026.7.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:65ee5d1ac3cd541325f5ac92625b1c1505f4d171520dd931bda7952895c5321a", size = 777120, upload-time = "2026-07-10T19:47:27.158Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9a/ec579b4f840ac59bc7c192b56e66abd4cbf385615300d59f7c94bf6863ae/regex-2026.7.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa34473fbcc108fea403074f3f45091461b18b2047d136f16ffaa4c65ad46a68", size = 785164, upload-time = "2026-07-10T19:47:28.732Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1c/60d88afd5f98d4b0fb1f8b8969270628140dc01c7ff93a939f2aa83f31a6/regex-2026.7.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d028d189d8f38d7ff292f22187c0df37f2317f554d2ed9a2908ada330af57c0", size = 860161, upload-time = "2026-07-10T19:47:30.605Z" }, + { url = "https://files.pythonhosted.org/packages/2a/40/08ae3ba45fe79e48c9a888a3389a7ee7e2d8c580d2d996da5ece02dfdcb9/regex-2026.7.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:396ea70e4ea1f19571940add3bad9fd3eb6a19dc610d0d01f692bc1ba0c10cb4", size = 765829, upload-time = "2026-07-10T19:47:32.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/e6/e613c6755d19aca9d977cdc3418a1991ffc8f386779752dd8fdfa888ea89/regex-2026.7.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ebbf0d83ed5271991d666e54bb6c90ac2c55fb2ef3a88740c6af85dc85de2402", size = 852170, upload-time = "2026-07-10T19:47:33.567Z" }, + { url = "https://files.pythonhosted.org/packages/03/33/89072f2060e6b844b4916d5bc40ef01e973640c703025707869264ec75ab/regex-2026.7.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58a4571b2a093f6f6ee4fd281faa8ebf645abcf575f758173ea2605c7a1e1ecb", size = 789550, upload-time = "2026-07-10T19:47:35.395Z" }, + { url = "https://files.pythonhosted.org/packages/e3/3c/4bc8be9a155035e63780ccac1da101f36194946fdc3f6fce90c7179fc6df/regex-2026.7.10-cp312-cp312-win32.whl", hash = "sha256:eac1207936555aa691ce32df1432b478f2729d54e6d93a1f4db9215bcd8eb47d", size = 267151, upload-time = "2026-07-10T19:47:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/9f5aade65bb98cc6e99c336e45a49a658300720c16721f3e687f8d754fec/regex-2026.7.10-cp312-cp312-win_amd64.whl", hash = "sha256:ecae626449d00db8c08f8f1fc00047a32d6d7eb5402b3976f5c3fda2b80a7a4f", size = 277751, upload-time = "2026-07-10T19:47:38.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/6f/d069dd12872ea1d50e17319d342f89e2072cae4b62f4245009a1108c74d8/regex-2026.7.10-cp312-cp312-win_arm64.whl", hash = "sha256:87794549a3f5c1c2bdfba2380c1bf87b931e375f4133d929da44f95e396bf5fe", size = 277063, upload-time = "2026-07-10T19:47:40.023Z" }, + { url = "https://files.pythonhosted.org/packages/e0/88/0c977b9f3ba9b08645516eca236388c340f56f7a87054d41a187a04e134c/regex-2026.7.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4db009b4fc533d79af3e841d6c8538730423f82ea8508e353a3713725de7901c", size = 496868, upload-time = "2026-07-10T19:47:41.675Z" }, + { url = "https://files.pythonhosted.org/packages/f6/51/600882cd5d9a3cf083fd66a4064f5b7f243ba2a7de2437d42823e286edaf/regex-2026.7.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b96341cb29a3faa5db05aff29c77d141d827414f145330e5d8846892119351c1", size = 297306, upload-time = "2026-07-10T19:47:43.521Z" }, + { url = "https://files.pythonhosted.org/packages/52/6f/48a912054ffcb756e374207bb8f4430c5c3e0ffa9627b3c7b6661844b30a/regex-2026.7.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14d27f6bd04beb01f6a25a1153d73e58c290fd45d92ba56af1bb44199fd1010d", size = 291950, upload-time = "2026-07-10T19:47:45.267Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c8/8e1c3c86ebcee7effccbd1f7fc54fe3af22aa0e9204503e2baea4a6ff001/regex-2026.7.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b6a11bf898cca3ce7bfaa17b646901107f3975677fbd5097f36e5eb5641983", size = 796817, upload-time = "2026-07-10T19:47:48.054Z" }, + { url = "https://files.pythonhosted.org/packages/65/39/3e49d9ff0e0737eb8180a00569b47aabb59b84611f48392eba4d998d91a0/regex-2026.7.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:234f8e0d65cf1df9becadae98648f74030ee85a8f12edcb5eb0f60a22a602197", size = 865513, upload-time = "2026-07-10T19:47:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/70/57/6511ad809bb3122c65bbeeffa5b750652bb03d273d29f3acb0754109b183/regex-2026.7.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:91b916d495db3e1b473c7c8e68733beec4dce8e487442db61764fff94f59740e", size = 912391, upload-time = "2026-07-10T19:47:51.776Z" }, + { url = "https://files.pythonhosted.org/packages/cc/29/a1b0c109c9e878cb04b931bfe4c54332d692b93c322e127b5ae9f25b0d9e/regex-2026.7.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f0d4ccf70b1d13711242de0ba78967db5c35d12ac408378c70e06295c3f6644", size = 801338, upload-time = "2026-07-10T19:47:53.38Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/171c3dad4d77000e1befeff2883ca88734696dfd97b2951e5e074f32e4dd/regex-2026.7.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c622f4c638a725c39abcb2e680b1bd592663c83b672a4ed350a17f806d75618e", size = 777149, upload-time = "2026-07-10T19:47:54.944Z" }, + { url = "https://files.pythonhosted.org/packages/33/61/41ab0de0e4574da1071c151f67d1eb9db3d92c43e31d64d2e6863c3d89bf/regex-2026.7.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41a47c2b28d9421e2509a4583a22510dc31d83212fcf38e1508a7013140f71a8", size = 785216, upload-time = "2026-07-10T19:47:56.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/28/372859ea693736f07cf7023247c7eca8f221d9c6df8697ff9f93371cca08/regex-2026.7.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:13fba679fe035037e9d5286620f88bbfd105df4d5fcd975942edd282ab986775", size = 860229, upload-time = "2026-07-10T19:47:58.278Z" }, + { url = "https://files.pythonhosted.org/packages/50/b1/e1d32cd944b599534ae655d35e8640d0ec790c0fa12e1fb29bf434d50f55/regex-2026.7.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8e26a075fa9945b9e44a3d02cc83d776c3b76bb1ff4b133bbfa620d5650131da", size = 765797, upload-time = "2026-07-10T19:48:00.291Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/79a2cd9556a3329351e370929743ef4f0ccc0aaff6b3dc414ae5fa4a1302/regex-2026.7.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d0834c84ae8750ae1c4cede59b0afd4d2f775be958e11b18a3eea24ed9d0d9f1", size = 852130, upload-time = "2026-07-10T19:48:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/66/58/76fec29898cf5d359ab63face50f9d4f7135cc2eca3477139227b1d09952/regex-2026.7.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64722a5031aeace7f6c8d5ea9a9b22d9368af0d6e8fa532585da8158549ea963", size = 789644, upload-time = "2026-07-10T19:48:03.748Z" }, + { url = "https://files.pythonhosted.org/packages/f6/06/3c7cec7817bda293e13c8f88aed227bbcf8b37e5990936ff6442a8fdf11a/regex-2026.7.10-cp313-cp313-win32.whl", hash = "sha256:74ae61d8573ecd51b5eeee7be2218e4c56e99c14fa8fcf97cf7519611d4be92e", size = 267130, upload-time = "2026-07-10T19:48:05.677Z" }, + { url = "https://files.pythonhosted.org/packages/88/6c/e2a6f9a6a905f923cfc912298a5949737e9504b1ca24f29eda8d04d05ece/regex-2026.7.10-cp313-cp313-win_amd64.whl", hash = "sha256:5e792367e5f9b4ffb8cad93f1beaa91837056b94da98aa5c65a0db0c1b474927", size = 277722, upload-time = "2026-07-10T19:48:07.318Z" }, + { url = "https://files.pythonhosted.org/packages/00/a6/9d8935aaa940c388496aa1a0c82669cc4b5d06291c2712d595e3f0cf16d3/regex-2026.7.10-cp313-cp313-win_arm64.whl", hash = "sha256:82ab8330e7e2e416c2d42fcec67f02c242393b8681014750d4b70b3f158e1f08", size = 277059, upload-time = "2026-07-10T19:48:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e9/26decfd3e85c09e42ff7b0d23a6f51085ca4c268db15f084928ca33459c6/regex-2026.7.10-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2b93eafd92c4128bab2f93500e8912cc9ecb3d3765f6685b902c6820d0909b6b", size = 501508, upload-time = "2026-07-10T19:48:10.668Z" }, + { url = "https://files.pythonhosted.org/packages/38/a5/5b167cebde101945690219bf34361481c9f07e858a4f46d9996b80ec1490/regex-2026.7.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3f03b92fb6ec739df042e45b06423fc717ecf0063e07ffe2897f7b2d5735e1e8", size = 299705, upload-time = "2026-07-10T19:48:12.544Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/7909be4b9f449f8c282c14b6762d59aa722aeaeebe7ee4f9bb623eeaa5e0/regex-2026.7.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bb5aab464a0c5e03a97abad5bdf54517061ebbf72340d576e99ff661a42575cc", size = 294605, upload-time = "2026-07-10T19:48:14.495Z" }, + { url = "https://files.pythonhosted.org/packages/82/88/e52550185d6fda68f549b01239698697de47320fd599f5e880b1986b7673/regex-2026.7.10-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fadb07dbe36a541283ff454b1a268afd54b077d917043f2e1e5615372cb5f200", size = 811747, upload-time = "2026-07-10T19:48:16.197Z" }, + { url = "https://files.pythonhosted.org/packages/06/98/16c255c909714de1ee04da6ae30f3ee04170f300cdc0dcf57a314ee4816a/regex-2026.7.10-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21150500b970b12202879dfd82e7fd809d8e853140fff84d08e57a90cf1e154e", size = 871203, upload-time = "2026-07-10T19:48:18.12Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/423ed27c9bae2092a453e853da2b6628a658d08bb5a6117db8d591183d85/regex-2026.7.10-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a68b637451d64ba30ed8ae125c973fa834cc2d37dfa7f154c2b479015d477ba8", size = 917334, upload-time = "2026-07-10T19:48:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/73/87/74dac8efb500db31cb000fda6bae2be45fc2fbf1fa9412f445fbb8acbe37/regex-2026.7.10-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e23458d8903e33e7d27196d7a311523dc4e2f4137a5f34e4dbd30c8d37ff33e", size = 816379, upload-time = "2026-07-10T19:48:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/1859403654e3e030b288f06d49233c6a4f889d62b84c4ef3f3a28653173d/regex-2026.7.10-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae27622c094558e519abf3242cf4272db961d12c5c9a9ffb7a1b44b2627d5c6", size = 785563, upload-time = "2026-07-10T19:48:23.643Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d8/35d30d6bdf1ef6a5430e8982607b3a6db4df1ddedbe001e43435585d88ba/regex-2026.7.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ee877b6d78f9dff1da94fef51ae8cf9cce0967e043fdcc864c40b85cf293c192", size = 801415, upload-time = "2026-07-10T19:48:25.499Z" }, + { url = "https://files.pythonhosted.org/packages/f7/22/630f31f5ea4826167b2b064d9cac2093a5b3222af380aa432cfe1a5dabcd/regex-2026.7.10-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:2c66a8a1969cfd506d1e203c0005fd0fc3fe6efc83c945606566b6f9611d4851", size = 866560, upload-time = "2026-07-10T19:48:27.789Z" }, + { url = "https://files.pythonhosted.org/packages/8d/14/f5914a6d9c5bc63b9bed8c9a1169fb0be35dbe05cdc460e17d953031a366/regex-2026.7.10-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2bc350e1c5fa250f30ab0c3e38e5cfdffcd82cb8af224df69955cab4e3003812", size = 772877, upload-time = "2026-07-10T19:48:29.563Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0f/7c13999eef3e4186f7c79d4950fa56f041bf4de107682fb82c80db605ff9/regex-2026.7.10-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:53f54993b462f3f91fea0f2076b46deb6619a5f45d70dbd1f543f789d8b900ef", size = 856648, upload-time = "2026-07-10T19:48:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/a4/71/a48e43909b6450fb48fa94e783bef2d9a37179258bc32ef2283955df7be7/regex-2026.7.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cfcec18f7da682c4e2d82112829ce906569cb8d69fa6c26f3a50dfbed5ceb682", size = 803520, upload-time = "2026-07-10T19:48:33.275Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b8/f037d1bf2c133cb24ceb6e7d81d08417080390eddab6ddfd701aa7091874/regex-2026.7.10-cp313-cp313t-win32.whl", hash = "sha256:a2d6d30be35ddd70ce0f8ee259a4c25f24d6d689a45a5ac440f03e6bcc5a21d1", size = 269168, upload-time = "2026-07-10T19:48:35.353Z" }, + { url = "https://files.pythonhosted.org/packages/b6/9c/eaac34f8452a838956e7e89852ad049678cdc1af5d14f72d3b3b658b1ea5/regex-2026.7.10-cp313-cp313t-win_amd64.whl", hash = "sha256:c57b6ad3f7a1bdd101b2966f29dc161adf49727b1e8d3e1e89db2eda8a75c344", size = 280004, upload-time = "2026-07-10T19:48:37.106Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a9/e22e997587bc1d588b0b2cd0572027d39dd3a006216e40bbf0361688c51c/regex-2026.7.10-cp313-cp313t-win_arm64.whl", hash = "sha256:3d8ef9df02c8083c7b4b855e3cb87c8e0ebbcfea088d98c7a886aaefdf88d837", size = 279308, upload-time = "2026-07-10T19:48:38.907Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a7fa3ada9bd2d2ce20d56dfceec6b2a51afeed9bf3d8286355ceec5f0628/regex-2026.7.10-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:39f81d1fdf594446495f2f4edd8e62d8eda0f7a802c77ac596dc8448ad4cc5ca", size = 497087, upload-time = "2026-07-10T19:48:40.543Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7e/ca0b1a87192e5828dbc16f16ae6caca9b67f25bf729a3348468a5ff52755/regex-2026.7.10-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:441edc66a54063f8269d1494fc8474d06605e71e8a918f4bcfd079ebda4ce042", size = 297307, upload-time = "2026-07-10T19:48:42.213Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/fb40bb34275d3cd4d7a376d5fb2ea1f0f4a96fd884fa83c0c4ae869001bf/regex-2026.7.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cfeb11990f59e59a0df26c648f0adfcbf27be77241250636f5769eb08db662be", size = 292163, upload-time = "2026-07-10T19:48:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/34cbea16c8fea9a18475a7e8f5837c70af451e738bfeb4eb5b029b7dc07a/regex-2026.7.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460176b2db044a292baaee6891106566739657877af89a251cded228689015a6", size = 797064, upload-time = "2026-07-10T19:48:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/77/f6805d97f15f5a710bdfd56a768f3468c978239daf9e1b15efd8935e1967/regex-2026.7.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9dc55698737aca028848bde418d6c51d74f2a5fd44872d3c8b56b626729adb89", size = 866155, upload-time = "2026-07-10T19:48:47.589Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e3/a2a905807bba3bcd90d6ebbb67d27af2adf7d41708175cbc6b956a0c75f1/regex-2026.7.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d3e10779f60c000213a5b53f518824bd07b3dc119333b26d70c6be1c27b5c794", size = 911596, upload-time = "2026-07-10T19:48:49.473Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ca/a3126888b2c6f33c7e29144fedf85f6d5a52a400024fa045ad8fc0550ef1/regex-2026.7.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38a5926601aaccf379512746b86eb0ac1d29121f6c776dac6ac5b31077432f2c", size = 800713, upload-time = "2026-07-10T19:48:51.452Z" }, + { url = "https://files.pythonhosted.org/packages/66/19/9d252fd969f726c8b56b4bacf910811cc70495a110907b3a7ccb96cd9cad/regex-2026.7.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a72ecf5bfd3fc8d57927f7e3ded2487e144472f39010c3acaec3f6f3ff53f361", size = 777286, upload-time = "2026-07-10T19:48:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/40/7a/5f1bf433fa446ecb3aab87bb402603dc9e171ef8052c1bb8690bb4e255a3/regex-2026.7.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d50714405845c1010c871098558cfe5718fe39d2a2fab5f95c8863caeb7a82b3", size = 785826, upload-time = "2026-07-10T19:48:55.381Z" }, + { url = "https://files.pythonhosted.org/packages/99/ca/69f3a7281d86f1b592338007f3e535cc219d771448e2b61c0b56e4f9d05b/regex-2026.7.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ec1c44cf9bd22079aac37a07cb49a29ced9050ab5bddf24e50aba298f1e34d90", size = 860957, upload-time = "2026-07-10T19:48:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/487ff55c8d515ec9dd60d7ba3c129eeaa9e527358ed9e8a054a9e9430f81/regex-2026.7.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9e9aaef25a40d1f1e1bbb1d0eb0190c4a64a7a1750f7eb67b8399bed6f4fd2a6", size = 765959, upload-time = "2026-07-10T19:49:00.27Z" }, + { url = "https://files.pythonhosted.org/packages/73/e1/fa034e6fa8896a09bd0d5e19c81fdc024411ab37980950a0401dccee8f6d/regex-2026.7.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e54e088dc64dd2766014e7cfe5f8bc45399400fd486816e494f93e3f0f55da06", size = 851447, upload-time = "2026-07-10T19:49:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/b9427ed53b0e14c540dc436d56aaf57a19fb9183c6e7abd66f4b4368fbad/regex-2026.7.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:834271b1ff2cfa1f67fcd65a48bf11d11e9ab837e21bf79ce554efb648599ae8", size = 789418, upload-time = "2026-07-10T19:49:03.949Z" }, + { url = "https://files.pythonhosted.org/packages/ba/52/aab92420c8aa845c7bcbe68dc65023d4a9e9ea785abf0beb2198f0de5ba1/regex-2026.7.10-cp314-cp314-win32.whl", hash = "sha256:f988a1cec68058f71a38471813fba9e87dffe855582682e8a10e40ece12567a2", size = 272538, upload-time = "2026-07-10T19:49:05.833Z" }, + { url = "https://files.pythonhosted.org/packages/99/16/5c7050e0ef7dd8889441924ff0a2c33b7f0587c0ccb0953fe7ca997d673b/regex-2026.7.10-cp314-cp314-win_amd64.whl", hash = "sha256:2129e4a5e86f26926982d883dff815056f2e98220fdf630e59f961b578a26c43", size = 280796, upload-time = "2026-07-10T19:49:07.593Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1a/4f6099d2ba271502fdb97e697bae2ed0213c0d87f2273fe7d21e2e401d12/regex-2026.7.10-cp314-cp314-win_arm64.whl", hash = "sha256:9cd5b6805396157b4cf993a6940cbb8663161f29b4df2458c1c9991f099299c5", size = 281017, upload-time = "2026-07-10T19:49:09.767Z" }, + { url = "https://files.pythonhosted.org/packages/19/02/4061fc71f64703e0df61e782c2894c3fbc089d277767eff6e16099581c73/regex-2026.7.10-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:103e8f3acc3dcede88c0331c8612766bdcfc47c9250c5477f0e10e0550b9da49", size = 501467, upload-time = "2026-07-10T19:49:11.952Z" }, + { url = "https://files.pythonhosted.org/packages/73/a5/8d42b2f3fd672908a05582effd0f88438bf9bb4e8e02d69a62c723e23601/regex-2026.7.10-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:538ddb143f5ca085e372def17ef3ed9d74b50ad7fc431bd85dc50a9af1a7076f", size = 299700, upload-time = "2026-07-10T19:49:14.067Z" }, + { url = "https://files.pythonhosted.org/packages/65/70/36fa4b46f73d268c0dbe77c40e62da2cd4833ee206d3b2e438c2034e1f36/regex-2026.7.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e3448e86b05ce87d4eb50f9c680860830f3b32493660b39f43957d6263e2eba", size = 294590, upload-time = "2026-07-10T19:49:15.883Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a7/b6db1823f3a233c2a46f854fdc986f4fd424a84ed557b7751f2998efb266/regex-2026.7.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eab9d3f981c423afd1a61db055cfe83553c3f6455949e334db04722469dd0a2", size = 811925, upload-time = "2026-07-10T19:49:17.97Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7d/f8bee4c210c42c7e8b952bb9fb7099dd7fb2f4bd0f33d0d65a8ab08aafc0/regex-2026.7.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:177f930af3ad72e1045f8877540e0c43a38f7d328cf05f31963d0bd5f7ecf067", size = 871257, upload-time = "2026-07-10T19:49:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/22adf72e614ba0216b996e9aaef5712c23699e360ea127bb3d5ee1a7666f/regex-2026.7.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dd3b6d97beb39afb412f2c79522b9e099463c31f4c49ab8347c5a2ca3531c478", size = 917551, upload-time = "2026-07-10T19:49:22.069Z" }, + { url = "https://files.pythonhosted.org/packages/03/f7/ebc15a39e81e6b58da5f913b91fc293a25c6700d353c14d5cd25fc85712a/regex-2026.7.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8679f0652a183d93da646fcec8da8228db0be40d1595da37e6d74c2dc8c4713c", size = 816436, upload-time = "2026-07-10T19:49:24.131Z" }, + { url = "https://files.pythonhosted.org/packages/5c/33/20bc2bdd57f7e0fcc51be37e4c4d1bca7f0b4af8dc0a148c23220e689da8/regex-2026.7.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:494b19a5805438aeb582de99f9d97603d8fd48e6f4cc74d0088bb292b4da3b70", size = 785935, upload-time = "2026-07-10T19:49:26.265Z" }, + { url = "https://files.pythonhosted.org/packages/b4/51/87ff99c849b56309c40214a72b54b0eef320d0516a8a516970cc8be1b725/regex-2026.7.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0911e34151a5429d0325dae538ba9851ec0b62426bdfd613060cda8f1c36ec7f", size = 801494, upload-time = "2026-07-10T19:49:28.493Z" }, + { url = "https://files.pythonhosted.org/packages/16/11/fde67d49083fef489b7e0f841e2e5736516795b166c9867f05956c1e494b/regex-2026.7.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b862572b7a5f5ed47d2ba5921e63bf8d9e3b682f859d8f11e0e5ca46f7e82173", size = 866549, upload-time = "2026-07-10T19:49:30.592Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b5/31a156c36acf10181d88f55a66c688d5454a344e53ccc03d49f4a48a2297/regex-2026.7.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3f361215e000d68a4aff375106637b83c80be36091d83ee5107ad3b32bd73f48", size = 773089, upload-time = "2026-07-10T19:49:32.661Z" }, + { url = "https://files.pythonhosted.org/packages/27/bb/734e978c904726664df47ae36ce5eca5065de5141185ae46efec063476a2/regex-2026.7.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4533af6099543db32ef26abc2b2f824781d4eebb309ab9296150fd1a0c7eb07d", size = 856710, upload-time = "2026-07-10T19:49:35.289Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e5/dc35cea074dbdcb9776c4b0542a3bc326ff08454af0768ef35f3fc66e7fa/regex-2026.7.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:668ab85105361d0200e3545bec198a1acfc6b0aeb5fff8897647a826e5a171be", size = 803621, upload-time = "2026-07-10T19:49:37.704Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/124564af46bc0b592785610b3985315610af0a07f4cf21fa36e06c2398dd/regex-2026.7.10-cp314-cp314t-win32.whl", hash = "sha256:dd7715817a187edd7e2a2390908757f7ba42148e59cad755fb8ee1160c628eca", size = 274558, upload-time = "2026-07-10T19:49:39.926Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/cd813ce9f3404c0443915175c1e339c5afd8fcda04310102eaf233015eef/regex-2026.7.10-cp314-cp314t-win_amd64.whl", hash = "sha256:78712d4954234df5ca24fdadb65a2ab034213f0cdfde376c272f9fc5e09866bb", size = 283687, upload-time = "2026-07-10T19:49:41.872Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d3/3dae6a6ce46144940e64425e32b8573a393a009aeaf75fa6752a35399056/regex-2026.7.10-cp314-cp314t-win_arm64.whl", hash = "sha256:749b92640e1970e881fdf22a411d74bf9d049b154f4ef7232eeb9a90dd8be7f3", size = 283377, upload-time = "2026-07-10T19:49:43.985Z" }, ] [[package]] @@ -3624,27 +3624,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, - { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, - { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, - { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, - { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, - { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, - { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, - { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +version = "0.15.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/36/6f65aa9989acdec45d417192d8f4e7921931d8a6cf87ac74bce3eed98a8e/ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500", size = 4769401, upload-time = "2026-07-09T20:01:34.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/c6/ede15cac6839f3dbce52565c8f5164a8210e669c7bc4decb03e5bdf47d0d/ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d", size = 10854342, upload-time = "2026-07-09T20:00:53.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/9d/d825b07ee7ea9e2d61df92a860033c94e06e7300d50a1c2653aac27d24fe/ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd", size = 11139539, upload-time = "2026-07-09T20:00:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/3b107712e642f063c7a9e0887c427b22cb44097de5aab36c05f2e280670c/ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646", size = 10595437, upload-time = "2026-07-09T20:01:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/b4523cc90ba239ede441447a19d0c968846a3012e5a0b0c5b62831a3d5e3/ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be", size = 10990053, upload-time = "2026-07-09T20:01:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/92/cc/c6a9872a5375f0628875481cf2f66b13d7d865bf3ca2e57f91c7e762d976/ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd", size = 10666096, upload-time = "2026-07-09T20:01:04.299Z" }, + { url = "https://files.pythonhosted.org/packages/ab/97/c621f7a17e097f1790fa3af6374138823b330b2d03fc38337945daca212c/ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41", size = 11537011, upload-time = "2026-07-09T20:01:06.771Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/d928727e476e25ccc57c6f449ffd80241a651a973ad949d39cfb2a771d28/ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86", size = 12347101, upload-time = "2026-07-09T20:01:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/1e/88/8cd62026802b16018ad06931d87997cf795ba2a6239ab659606c87d96bf0/ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67", size = 11572001, upload-time = "2026-07-09T20:01:11.092Z" }, + { url = "https://files.pythonhosted.org/packages/b2/97/f63084cf55444fc110e8cb985ebfcc592af47f597d44453d778cb81bc156/ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8", size = 11549239, upload-time = "2026-07-09T20:01:13.27Z" }, + { url = "https://files.pythonhosted.org/packages/9d/77/f107da4a2874b7715914b03f09ba9c54424de3ff8a1cc5d015d3ee2ce0ac/ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075", size = 11535340, upload-time = "2026-07-09T20:01:15.206Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e9/601deb322d3303a7bf212b0100ead6f2ee3f6a044d89c30f2f92bf83c731/ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341", size = 10964048, upload-time = "2026-07-09T20:01:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2e/0f2176d1e99c15192caea19c8c3a0a955246b4cb4de795042eeb616345cd/ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d", size = 10667055, upload-time = "2026-07-09T20:01:19.73Z" }, + { url = "https://files.pythonhosted.org/packages/48/60/abd74a02e0c4214f12a68becfd30af7165cfdcb0e661ecdc60bbb949c09a/ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233", size = 11242043, upload-time = "2026-07-09T20:01:21.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c6/583075d8ccabb4b229345edcaf1545eb3d8d6be90f686a479d7e94088bbf/ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f", size = 11648064, upload-time = "2026-07-09T20:01:24.023Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3c/37d0ecb729a7cc2d393ea7dce316fc585680f35d93b8d62139d7d0a3700c/ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd", size = 10896555, upload-time = "2026-07-09T20:01:26.941Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b8/e43466b2a6067ce91e669068f6e28d6c719a920f014b070d5c8731725de3/ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3", size = 12038772, upload-time = "2026-07-09T20:01:29.497Z" }, + { url = "https://files.pythonhosted.org/packages/dd/75/e90ab9aeece218a9fc5a5bc3ec97d0ee6bb3c4ff95869463c1de58e29a1c/ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8", size = 11375265, upload-time = "2026-07-09T20:01:31.772Z" }, ] [[package]] @@ -3969,40 +3969,40 @@ wheels = [ [[package]] name = "treestamps" -version = "4.1.0" +version = "4.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/87/31328b19121029e6db528aa4c70f992f1dd5ccc74435bb2e22874970f64d/treestamps-4.1.0.tar.gz", hash = "sha256:159652cd3a2f6365e3d7c5021defb66104ba89148c0cdd87e7ec14f713870cb3", size = 210180, upload-time = "2026-07-05T19:46:29.661Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/33/db241ab2748986e86406919bbfb273333dc226ca2656121546aa655807ed/treestamps-4.1.1.tar.gz", hash = "sha256:7c287f49c118f7b6bba35d8154846ad68acb9d15be0bc2edf1d277e1f057e124", size = 211456, upload-time = "2026-07-09T23:20:58.835Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/cb/508cbbb17a23ad35cea3fe2b86c8b1811d0c37fd7476c1e3892e79025be5/treestamps-4.1.0-py3-none-any.whl", hash = "sha256:f7323de89fad48030989cee9939b809c5ede15c5a20d5153c157f8d008231494", size = 32155, upload-time = "2026-07-05T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ea/518f3ec0a673e6606608cd0a1edbca39c3909e3ba58c5e9ccf8791822628/treestamps-4.1.1-py3-none-any.whl", hash = "sha256:af747ad8e61a655308f32b8fd1d3c022ebca5d1af76d54e6682872935ef5874a", size = 32665, upload-time = "2026-07-09T23:20:57.536Z" }, ] [[package]] name = "ty" -version = "0.0.56" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/55/07/fb29aea5235b0aa8ecfc4d1cc6ddf9fba8b863d67d96c6d345694d644c43/ty-0.0.56.tar.gz", hash = "sha256:84d114dc3796361c0fc72945016eabd74d46b9ee64f198cb0e485719704681e5", size = 6050123, upload-time = "2026-07-01T16:44:56.036Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/48/bce79e7ca5c1cc529d3e0d37ddd1121aea4b68a4f749974ad1cc77161871/ty-0.0.56-py3-none-linux_armv6l.whl", hash = "sha256:186d4a53e15747c947e1ec3d7eec8e345d8e40a1ca10e634c585db52497e87dd", size = 11643066, upload-time = "2026-07-01T16:44:18.374Z" }, - { url = "https://files.pythonhosted.org/packages/80/d1/22555d8a1d719661f10050f3865d877bbf497da908961c75fe22217dd18a/ty-0.0.56-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aae1a980fd9535da0469b7ba2b2e1b54a907743a5e0f442dd57eee9f5bfd034c", size = 11407487, upload-time = "2026-07-01T16:44:20.956Z" }, - { url = "https://files.pythonhosted.org/packages/cf/2d/b3b7a74ce8bc59ef48843ad80179bb0d9598bbd6cfc0d11d519bdf6b1352/ty-0.0.56-py3-none-macosx_11_0_arm64.whl", hash = "sha256:afd3058c0a6c5f241e814734f133008c93ee805f61c9cf4ce7412b8822b5d9ad", size = 10962270, upload-time = "2026-07-01T16:44:22.959Z" }, - { url = "https://files.pythonhosted.org/packages/64/ac/6c2fd7de0304a8a7218a756af74f7e62a5e8540fdb175e0a869e51042345/ty-0.0.56-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058b52f7a823ac13aae3cae30809dd6b5145794b64d8478f9ef38c75d79b4483", size = 11471406, upload-time = "2026-07-01T16:44:25.327Z" }, - { url = "https://files.pythonhosted.org/packages/50/b6/11d861156861c03c7726b74558f9a0e0092661aff83a4fda1279df28c425/ty-0.0.56-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c66e00c1522add1f2bbdd2e45828c953b35c306b7bef03ec9169c75a63699a0", size = 11445612, upload-time = "2026-07-01T16:44:27.531Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ba/09df108582090f3c0770ec4bc8675affed60248f6793a78d909be16211d9/ty-0.0.56-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40903d71c669a30691b5a5d5728056c7877a1bd6be4f233a38883a8b28cf34d7", size = 12093889, upload-time = "2026-07-01T16:44:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/d7/f7/dbb4b4ccb69cd64c209ae55b1ab788ace8222c2bc1f6845be9e7cbedbf25/ty-0.0.56-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63fe3947fe0c46c69a7d950e6832ee70a9ec17321fefbff3d2e3c20baf9e5bd0", size = 12666337, upload-time = "2026-07-01T16:44:31.586Z" }, - { url = "https://files.pythonhosted.org/packages/86/e9/73f903fe4a3d9ea02f26f57c1eb07e3b1029ec92b0e8c2364718893440e3/ty-0.0.56-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a0c1a72f9854532e710e119b6871ffe4542c8a65146f1f65dcd78fecd885b4", size = 12280247, upload-time = "2026-07-01T16:44:33.637Z" }, - { url = "https://files.pythonhosted.org/packages/d6/90/cebd222495832f1a00dcd321ba25f3cab804221a4991b992c2178bec68ee/ty-0.0.56-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70d1665596494e24d8ebd198438872b5a56ec3cae5f2bcf6c673be797acc4e3c", size = 11991107, upload-time = "2026-07-01T16:44:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/b7/07/8f7337a07250f42d975cdb6decf47fc5b421e6c7da5e3e7be1e85f63a7e5/ty-0.0.56-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:778f99e51558afc1dbbe48ee38ab6aae7b31390ed8c1a1ef1499b295e9f1e82f", size = 12298970, upload-time = "2026-07-01T16:44:38.243Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b9/a52cd59034a48f5f18c6b155cc2cc36861d874b6d0af204b12c898024c3d/ty-0.0.56-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:867bc5708e0066bb4ff6c7db524bd5deea2676c62bfe71d3303138b3be850af0", size = 11425683, upload-time = "2026-07-01T16:44:40.473Z" }, - { url = "https://files.pythonhosted.org/packages/1d/2e/48e42d33357d52eefb695c0c3fcfc96879b73668a7447d1d1e0ad774fedc/ty-0.0.56-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6012f4189c928edb330a37deb9930f982380bd4aa7c4b8e0428eec9651c7551", size = 11469258, upload-time = "2026-07-01T16:44:42.513Z" }, - { url = "https://files.pythonhosted.org/packages/d5/01/ad1b4138be1e3fa97863af3925aa2134f17a593240c35dc38c3429fb5ad1/ty-0.0.56-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee83de1a7ff4cc32837ec06134ce391d441bc5b35ecd8d3cfe053f120f3e4c1", size = 11758736, upload-time = "2026-07-01T16:44:44.567Z" }, - { url = "https://files.pythonhosted.org/packages/09/34/9d81967ff240eaa57e9249728ef7b7790747cf6d3c9a98ec86b2cfdcc8ee/ty-0.0.56-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:62619b3b0e2c6248ef30d3f0e2f2217ae9893040585be07f32324242f197cd6f", size = 12100242, upload-time = "2026-07-01T16:44:46.584Z" }, - { url = "https://files.pythonhosted.org/packages/c3/36/f51d4666d2de6cf33c1f3a1fcc4bb6b70b197dd6ceaa491eef71d78fe8e8/ty-0.0.56-py3-none-win32.whl", hash = "sha256:b30687bb5cd9729d34c889a289edf32770388d9bb05243e534e723fb45e0381b", size = 11093759, upload-time = "2026-07-01T16:44:49.171Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b4/8fb5d4acfa4afb152245b20fa263069a7547bd1f8e4bfca4eda280c897d7/ty-0.0.56-py3-none-win_amd64.whl", hash = "sha256:ad4c8c47b6f4e3f9ed3fc0b1a5d650088d229e17dd8f63c1826d6bbe94cc3235", size = 12100327, upload-time = "2026-07-01T16:44:51.26Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fc/6a183e71edde90d0c35c2303f23f7a45b6891d1a2c45daf7b8f869831e19/ty-0.0.56-py3-none-win_arm64.whl", hash = "sha256:57538f273d444a5f1293fa7860e967178afe3917611fc5eff16b64e1204fe0d6", size = 11538780, upload-time = "2026-07-01T16:44:53.8Z" }, +version = "0.0.58" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/4c/26c90732658903aeb1d289208f7b7b492fa21029e0c4d6c51bdd6f8f5e51/ty-0.0.58.tar.gz", hash = "sha256:8f22484174e65c630660a454bf81b80cae7a3a7e70479f19c170d6cd87949258", size = 6133665, upload-time = "2026-07-10T03:09:30.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e1/5d1aa2a75829459834689f080e4be7a9d8828ce14b939ebed69161a35811/ty-0.0.58-py3-none-linux_armv6l.whl", hash = "sha256:47412850b6fbef61c42f244f6a51aa2f2c9e91f08cfbafd2d1e3730d2419d317", size = 11706915, upload-time = "2026-07-10T03:08:51.028Z" }, + { url = "https://files.pythonhosted.org/packages/96/e1/929eda9cc72a9afe39a03c76f946a503508d37343cc8ff2e64226afda105/ty-0.0.58-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79deb7bb4e5b3a1eee6ab9abc724d6ce3559d4977982707f310a139ee11fc703", size = 11532079, upload-time = "2026-07-10T03:08:53.771Z" }, + { url = "https://files.pythonhosted.org/packages/07/43/ebc58b3fc7d86a7abba2829f1674f7d4ae3a08f9794c1f31b707950c871f/ty-0.0.58-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a28af3187e661708a386d44a4fc32896a5f589fb07b734a11ab2f516e7572b7", size = 11092983, upload-time = "2026-07-10T03:08:56.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/6e/9547dbb8e51e47749cfb721a02b4fc862f9a932fa0f66a34a4d6dc429bb1/ty-0.0.58-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21a6977e34bc362fb378add46e59d5d56331c1c36727e6904767217ca8479718", size = 11490492, upload-time = "2026-07-10T03:08:58.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/9f83b51b5e7795d6c8d76b4bb1bb7cebf4c10d609e846c02ab138e404556/ty-0.0.58-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ac23e6bf6105ceca46632debf1b10b98125aaf60202aeae02f6abfeea242b3d", size = 11503696, upload-time = "2026-07-10T03:09:00.741Z" }, + { url = "https://files.pythonhosted.org/packages/47/9d/9fd48a0696c680f74e50f31cd54e524eeb58c97ea9bb1c3b8e04230ba215/ty-0.0.58-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6df6a8c6a21894807a49851370ec7fc64aa910296c78ada31db0ef19359112", size = 12158653, upload-time = "2026-07-10T03:09:02.998Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ea/b5de845d2d8edae04d901c7585af7f04a057e18b26311f7fa4ca62b2da30/ty-0.0.58-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9df5847eebc026cde088b44420a03f7c6c169a7db747176bdf0a656eb1144713", size = 12723019, upload-time = "2026-07-10T03:09:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/17/1c/54083b23eeff1e101f50b6df6a2c7f1e14b31abe0577c91bcae9e2c9395d/ty-0.0.58-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53a331a7f1f85872c810676a0f16096ef98c1b95c8c9a573fe7fde64d0a93e7c", size = 12275715, upload-time = "2026-07-10T03:09:07.564Z" }, + { url = "https://files.pythonhosted.org/packages/22/88/16925434b06faa49d36aa7e7508a6821ec6feacb429ca2fd80a3d52716b4/ty-0.0.58-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb0b05cd479fdcedc2e6781d376ee1a33569f37ae7f58357004635f615c4374c", size = 12033075, upload-time = "2026-07-10T03:09:10.222Z" }, + { url = "https://files.pythonhosted.org/packages/58/2b/b55708dd483982ae03d14da3667e3f0346cd384e11cca3dd3674a8b598c1/ty-0.0.58-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a0012786077e5becbb6add9fe51eda1d4d36d249afd3ae6cd141d554af15ae3", size = 12367729, upload-time = "2026-07-10T03:09:12.338Z" }, + { url = "https://files.pythonhosted.org/packages/91/a3/99ad66652956408f7e9ac3db6b4a416199d773b6c073cf95b0eea126d340/ty-0.0.58-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4ede96d7f6d149156da254e784b062b817736b68d4c6d660555a3d02d96966fb", size = 11439798, upload-time = "2026-07-10T03:09:14.518Z" }, + { url = "https://files.pythonhosted.org/packages/ee/23/344ceed4fe02ed498711e1f4a47b6e311fb1e9c4fecc19d31894be7a3472/ty-0.0.58-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:47608e58f73901b989402e6f283249cda4c2314282ec368aa74ab61761c62bd5", size = 11512695, upload-time = "2026-07-10T03:09:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/55/cf/3801831812c468f3fd0b3043a80f557a9aa90e6c27375763d7c3121e03f2/ty-0.0.58-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9757de17cc17e4c6bc26e18d4e26dea52ffee53d41a10d9087c6465e6ab12e2b", size = 11812253, upload-time = "2026-07-10T03:09:19.151Z" }, + { url = "https://files.pythonhosted.org/packages/7b/80/bce4f245787b77d1ec9feec7d9161eade5e01a77dbc132e016b24df83b0d/ty-0.0.58-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f3776d54c1d935fcb8f814bd58efba402c86c555f93e1144c59d087e7aa8b906", size = 12123918, upload-time = "2026-07-10T03:09:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/bab3d6268e7e88c792bf7cdde81bd11b31aa587eaba75196502b729747c8/ty-0.0.58-py3-none-win32.whl", hash = "sha256:8f50ec0ac3b42baa4c75895dc367071dc86b00ab440d29fdc72c633286a94815", size = 11230897, upload-time = "2026-07-10T03:09:23.871Z" }, + { url = "https://files.pythonhosted.org/packages/9c/68/d9504c895864aaa84a840dce6ac7f8e681f6938be1882c8d4f60832dbe57/ty-0.0.58-py3-none-win_amd64.whl", hash = "sha256:de8847b3a65475ae4773bddd3126bfcf29f017e88967c4dcc9c75d743a4d3e5c", size = 12299376, upload-time = "2026-07-10T03:09:26.292Z" }, + { url = "https://files.pythonhosted.org/packages/26/04/c9847cb680b5fe8e1f7d7b483edd5cedcc29394496e7b8ed40d96be796ba/ty-0.0.58-py3-none-win_arm64.whl", hash = "sha256:7334bb38789878f60677f2eb9c1de4bfdf4583e2443790989c77da9b10fe0989", size = 11710495, upload-time = "2026-07-10T03:09:28.478Z" }, ] [[package]] @@ -4074,11 +4074,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] From e45ef9094bc3097c985efea27b927151152b1e4b Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 12 Jul 2026 19:25:49 -0700 Subject: [PATCH 23/82] Native OIDC single sign-on (Admin Auth tab) (#798) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(auth): native OIDC login via django-allauth Codex becomes an OIDC Relying Party (Authentik/Authelia) with a config-gated login flow: - [auth.oidc] TOML section + CODEX_AUTH_OIDC_* env overrides - allauth apps installed unconditionally; behavior gated on AUTH_OIDC_ENABLED (all OIDC paths 404 when off) - CodexSocialAccountAdapter: username linking (superusers included, documented trust boundary), optional email linking, claim-chain username mapping with sub-hash collision suffix, groups-claim sync to existing Django groups, admin-group grant/revoke, error redirects to the SPA (never an allauth template) - Branded throttled init endpoint /api/v4/auth/oidc/login; allauth login/callback mounted at /sso/ (outside the namespaced API tree so allauth's internal reverses work) - RP-initiated logout URL via cached discovery document using the spec's client_id parameter (no stored tokens needed) - /session payload gains public oidcEnabled/oidcProviderName/ oidcLoginUrl and authenticated oidcLogoutUrl - Profile username locks per-user when an OIDC identity is linked - OIDC failures reuse the failed-login log line format for fail2ban Co-Authored-By: Claude Opus 4.8 * feat(frontend): SSO login button, RP logout, and sso-error page - auth store: oidc admin flags, loginSSO() full-page navigation, logout() follows oidcLogoutUrl for RP-initiated logout - SsoLoginButton shared by the login dialog (with divider) and the unauthorized lock screen - /auth/sso-error route + page mapping backend error codes to human messages, with retry hidden for non-retryable codes Co-Authored-By: Claude Opus 4.8 * docs(auth): OIDC setup guide + complete tinyauth forward-auth recipe - README: native OIDC section (config table, redirect URI with prefix, Authentik/Authelia walkthroughs, identity-mapping and admin-linking trust warning, session-lifetime and OPDS caveats) - README: full nginx auth_request recipe for tinyauth with header override hardening, Traefik/Caddy equivalents, and a forward-auth deployment checklist (OPDS + WebSocket gating, spoof test) - schema test: allauth views stay out of the OpenAPI schema - test typing fixes surfaced by basedpyright Co-Authored-By: Claude Opus 4.8 * update deps * fix(settings): nest comicbox loglevel/delete_keys under general section An un-nested overlay is silently ignored by confuse. * fix(onlinetag): resolve prompts against current DB path, surface apply failures Answering a deferred prompt fetched the chosen issue against the path serialized into the prompt at scan time. When an earlier write for the same comic ran with rename enabled (the comic's other source's prompt, or a stored-id prefetch), that path was stale and the apply died with an uncaught FileNotFoundError — after the prompt was already consumed, so the admin's pick vanished with no feedback. - _apply_resolution now re-reads the comic's path from the DB by pk; a missing row reports to the Tagging error panel instead of fetching a dead path. - fetch/replay failures (ComicboxError, OSError) and non-resolving explicit ids now land on the Tagging error panel instead of only the log, since the pick can no longer be re-prompted. - stored-id prefetch and tag_by_id also catch OSError so a vanished file degrades gracefully. - regression test for the COMICBOX_CONFIG general-section overlay (un-nested loglevel/delete_keys were silently ignored, letting comicfn2dict remainders like "(0000)" leak into rename targets). Co-Authored-By: Claude Fable 5 * feat(auth): move OIDC config from codex.toml to the Admin UI Auth tab OIDCSettings DB singleton (EmailSettings pattern) becomes the sole config source, read at request time: - OIDCSettings model + migration 0047 (seeds pk=1, one-time courtesy import of any pre-GUI [auth.oidc] TOML values); client_secret encrypted at rest via EncryptedCharField - get_oidc_settings()/oidc_enabled() in settings.db; cachalot makes admin edits live on the next request, no restart - codex/oidc.py rewired to request-time reads; new adapter list_apps override builds an unsaved SocialApp from the row (per-app settings['scope'] wins in allauth's get_scope), so disabled state keeps allauth's own DoesNotExist -> 404 gating - RP-initiated logout and session flags read the row - AdminOIDCSettingsView GET/PUT (write-only secret + clientSecretSet mirror, discovery-cache invalidation on save) and AdminOIDCTestView (discovery-document probe) at /api/v4/admin/oidc-settings[/test] - New Admin UI Auth tab mirroring the Email tab: draft/dirty tracking, never-echoed secret with Clear Credential, redirect-URI display, Test Connection endpoint report - [auth.oidc] TOML section and CODEX_AUTH_OIDC_* env overrides removed; README updated - Tests now seed the DB row instead of patching module constants Co-Authored-By: Claude Opus 4.8 * format * update deps and fix * fix(admin): gate the OIDC enable switch on server URL + client ID The Auth tab's Enable OIDC Login checkbox is disabled until a valid server URL and a client ID are entered (it can always be unchecked so clearing a field never strands the switch). The serializer enforces the same invariant for API clients and partial updates that blank a prerequisite while enabled — previously such a save produced a silently inert enabled=true row. Co-Authored-By: Claude Opus 4.8 * feat(admin): Auth tab gains Account & Access flags, tinyauth note, name gate - Move the Account & Access flag cards (Registration, Verify New User Email, Non-Users) from the Users tab to the Auth tab — they govern how people get in, which is that tab's subject - Auth tab prose explains that forward-auth gateways like tinyauth are not OIDC providers and points them at Remote-User header auth, which coexists with OIDC - Provider name joins server URL and client ID as an enable prerequisite, in the UI switch gate and the serializer invariant Co-Authored-By: Claude Opus 4.8 * style(admin): visually nest the OIDC subsections under their header AdminSection gains a sub variant: a small uppercase overline title (h4, $text-meta) and an indented left rule, with tighter sibling rhythm than top-level sections. The Auth tab wraps the whole OIDC block — prose, Identity Provider, User Mapping, Logout, and Test Connection — in one parent 'OIDC Single Sign-On' AdminSection with the config groups as sub sections, so their subordination to the OIDC header is unmistakable next to the sibling Account & Access section. Documented in DESIGN.md. Co-Authored-By: Claude Opus 4.8 * style(admin): collapse the OIDC section when OIDC is disabled Most admins never configure OIDC, so the section body — prose, config sub-sections, and Test Connection — hides behind an AdminExpandToggle disclosure. It starts expanded only when OIDC is already enabled; otherwise a one-line hint summarizes what's inside next to a Configure toggle. The disclosure is initialized once from the saved state so saving a disable doesn't slam the panel shut mid-edit. Co-Authored-By: Claude Opus 4.8 * docs(admin): plain-English hints for PKCE and other OIDC jargon fields PKCE, Client ID, Username Claim, and Groups Claim now carry hints an admin who has never touched OIDC can act on — including what a claim is and why PKCE should stay on. Co-Authored-By: Claude Opus 4.8 * test(sso): authentik + tinyauth manual test harness in test-proxy/ Adds a docker-compose IdP stack and nginx wiring so SSO can be manually verified before release: - compose.yaml: authentik (OIDC provider, :9010) + tinyauth (forward auth, :3232), everything bound to localhost with throwaway creds - authentik/blueprints/codex-test.yaml: auto-applied fixtures — readers and codex-admins groups, testuser/testadmin, and the codex-test OIDC client with callback URIs for proxied and direct, prefixed and bare - forwardauth.conf: nginx :8081 gating Codex behind tinyauth auth_request with an overriding Remote-User header - README.md: step-by-step test matrix covering native OIDC (login, group sync, admin mapping, RP logout, linking, error page, disabled 404) and forward-auth (login, gating, spoof-proofing, coexistence) tinyauth DB path pinned to the writable /data volume (workdir is root-owned). test-proxy/ excluded from eslint: authentik !Find tags and compose healthcheck arrays require flow-style YAML the yml plugin bans. Co-Authored-By: Claude Opus 4.8 * test(sso): run the harness nginx as a compose service nginx joins authentik + tinyauth in compose.yaml, so only Codex runs on the host. The native bin/run-test-proxy.sh path still works — both share server.conf/forwardauth.conf, with the sole native-vs-container difference (backend addresses) isolated into named upstreams: - upstreams-native.conf: localhost backends (host nginx) - upstreams-docker.conf: host.docker.internal + tinyauth service name - connection-upgrade.conf: the ws-upgrade map, now shared - ssl-listen.conf / ssl-listen-none.conf: SSL/QUIC listeners split out so the container serves plain HTTP (native keeps the 8443 listeners) The compose nginx mounts these into the stock image's conf.d and reaches host Codex via host.docker.internal (extra_hosts host-gateway for Linux). Also fixes a latent harness bug that would break OIDC through the proxy: X-Forwarded-Host used $host (strips the port), so Django's build_absolute_uri produced a portless redirect_uri that couldn't match the registered callback. Now $http_host, port included. Co-Authored-By: Claude Opus 4.8 * test(sso): fix tinyauth boot crash on localhost app URL tinyauth v5 derives a cookie domain from its app URL at startup and rejects single-label hosts and IPs ('invalid app url, must be at least second level domain'), so http://localhost:3232 crash-looped. Move the forward-auth path onto *.localtest.me (all subdomains resolve to 127.0.0.1 via public DNS, every browser, no /etc/hosts): - tinyauth app url -> http://tinyauth.localtest.me:3232 - gated Codex -> http://codex.localtest.me:8081 - shared cookie -> .localtest.me (spans both) The @tinyauth_login redirect and README Test 2 follow. OIDC/authentik stay on localhost (no cross-host cookie needed there). Also documents in README Troubleshooting that the harness publishes only 9010/8080/8081/3232 and never binds Vite's 5173 — a blocked HMR is a stale vite process, and 8080/8081 clashes come from running native make dev-reverse-proxy alongside the compose nginx. Co-Authored-By: Claude Opus 4.8 * test(sso): set authentik provider grant_types; drop IPv6 host-gateway Two issues from the first live OIDC run: - 'Login with Authentik' failed with authentik logging 'Invalid grant_type for provider'. authentik 2026.x added an explicit grant_types model field that defaults to an EMPTY list, so a blueprint that omits it creates a provider allowing no grants and the authorize step returns invalid_request. Set grant_types: [authorization_code, refresh_token] on the provider. - nginx logged 'connect() to [fd..::254]:9810 Network unreachable' then fell back to IPv4. The IPv6 came from extra_hosts host-gateway (a Docker Desktop IPv6 ULA gateway Granian doesn't listen on). Comment it out — Docker Desktop provides an IPv4 host.docker.internal built-in; Linux users uncomment it. README troubleshooting covers both, including re-applying the blueprint to an already-running authentik. Co-Authored-By: Claude Opus 4.8 * fix(auth): refresh public flags when OIDC is toggled or on logout The 'Login with ' button (adminFlags.oidcEnabled) went stale after disabling OIDC: OIDCSettings is a singleton with no admin.flags.changed websocket broadcast, and logout() left adminFlags untouched, so the button lingered on the login screen until a manual page reload. - admin.updateOidcSettings now calls auth.loadAdminFlags after a save, resyncing the public OIDC flags immediately. - auth.logout now re-fetches public flags (except when doing an RP-initiated full-page redirect, which reloads anyway), so the logged-out login screen always reflects current settings. Co-Authored-By: Claude Opus 4.8 * update deps * chore(lint): clear radon CC/MI and remark warnings - codex/oidc.py: extract CodexSocialAccountAdapter._sync_admin from _sync_user (rank C -> B); keyword-only bool arg for FBT001. - tests: split the 940-line test_onlinetag_session_manager (MI rank B, pre-existing on develop) — move the TagPassRunner and stored-id-map classes into test_onlinetag_tag_pass.py, importing the shared doubles from the session-manager module (as test_opds_schema already does). Both files now MI rank A. - test-proxy/README.md: wrap the bare http://localhost autolink in <> and the [fd..::254] nginx error in backticks so remark-lint stops reading it as a link reference. Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- README.md | 129 ++++- bin/run-test-proxy.sh | 6 +- bun.lock | 109 ++-- codex/failed_login_log.py | 18 +- codex/migrations/0047_oidc_settings.py | 127 +++++ codex/models/admin.py | 54 ++ codex/oidc.py | 392 +++++++++++++ codex/serializers/admin/oidc.py | 98 ++++ codex/serializers/auth.py | 34 +- codex/settings/__init__.py | 48 +- codex/settings/codex.toml.default | 4 + codex/settings/db.py | 31 +- codex/urls/api/v4/admin.py | 11 + codex/urls/api/v4/auth.py | 2 + codex/urls/app.py | 3 + codex/urls/root.py | 4 + codex/views/admin/oidc.py | 79 +++ codex/views/session.py | 21 +- eslint.config.js | 4 + frontend/DESIGN.md | 4 +- frontend/bun.lock | 430 ++++---------- frontend/package.json | 4 +- frontend/src/api/v4/admin.js | 9 + .../components/admin/tabs/admin-section.scss | 25 + .../components/admin/tabs/admin-section.vue | 9 +- .../src/components/admin/tabs/auth-tab.vue | 537 ++++++++++++++++++ .../src/components/admin/tabs/user-tab.vue | 16 +- frontend/src/components/auth/login-dialog.vue | 13 + frontend/src/components/auth/sso-error.vue | 83 +++ .../src/components/auth/sso-login-button.vue | 34 ++ frontend/src/components/unauthorized.vue | 7 + frontend/src/plugins/router.js | 8 + frontend/src/stores/admin.js | 54 ++ frontend/src/stores/auth.js | 35 ++ frontend/tests/unit/admin-oidc-store.test.js | 158 ++++++ frontend/tests/unit/sso-auth.test.js | 164 ++++++ package.json | 5 +- pyproject.toml | 1 + test-proxy/README.md | 194 +++++++ .../authentik/blueprints/codex-test.yaml | 114 ++++ test-proxy/compose.yaml | 160 ++++++ test-proxy/connection-upgrade.conf | 10 + test-proxy/forwardauth.conf | 78 +++ test-proxy/nginx.conf | 15 +- test-proxy/server.conf | 33 +- test-proxy/ssl-listen-none.conf | 4 + test-proxy/ssl-listen.conf | 10 + test-proxy/upstreams-docker.conf | 14 + test-proxy/upstreams-native.conf | 11 + tests/test_admin_oidc_settings.py | 200 +++++++ tests/test_oidc_login.py | 422 ++++++++++++++ tests/test_oidc_settings.py | 86 +++ tests/test_onlinetag_session_manager.py | 135 ----- tests/test_onlinetag_tag_pass.py | 160 ++++++ uv.lock | 327 ++++++----- 55 files changed, 4007 insertions(+), 736 deletions(-) create mode 100644 codex/migrations/0047_oidc_settings.py create mode 100644 codex/oidc.py create mode 100644 codex/serializers/admin/oidc.py create mode 100644 codex/views/admin/oidc.py create mode 100644 frontend/src/components/admin/tabs/auth-tab.vue create mode 100644 frontend/src/components/auth/sso-error.vue create mode 100644 frontend/src/components/auth/sso-login-button.vue create mode 100644 frontend/tests/unit/admin-oidc-store.test.js create mode 100644 frontend/tests/unit/sso-auth.test.js create mode 100644 test-proxy/README.md create mode 100644 test-proxy/authentik/blueprints/codex-test.yaml create mode 100644 test-proxy/compose.yaml create mode 100644 test-proxy/connection-upgrade.conf create mode 100644 test-proxy/forwardauth.conf create mode 100644 test-proxy/ssl-listen-none.conf create mode 100644 test-proxy/ssl-listen.conf create mode 100644 test-proxy/upstreams-docker.conf create mode 100644 test-proxy/upstreams-native.conf create mode 100644 tests/test_admin_oidc_settings.py create mode 100644 tests/test_oidc_login.py create mode 100644 tests/test_oidc_settings.py create mode 100644 tests/test_onlinetag_tag_pass.py diff --git a/README.md b/README.md index 502193fd7..6b8ea1f06 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,9 @@ border-radius: 128px; ### 🔌 Integrations - **OPDS 1 & 2** syndication with streaming, search, and authentication. -- **Remote-User HTTP header SSO** for reverse-proxy single sign-on. +- **OIDC single sign-on** with identity providers like Authentik and Authelia. +- **Remote-User HTTP header SSO** for reverse-proxy single sign-on (tinyauth, + Authelia, Authentik proxy mode). - **Fail2Ban** log for IP-banning failed login attempts. ### 🪶 Operations @@ -608,6 +610,9 @@ to 2 queries per second. `1` (trust XFF), which is correct when Codex sits behind a reverse proxy. Set to `0` when Codex is exposed directly so that clients can't forge `X-Forwarded-For` to poison the log. +- OIDC single sign-on has no environment variables or TOML keys — it is + configured in the Admin UI under the **Auth** tab. See the + [OIDC Single Sign On](#oauth--oidc) section below. ### Reverse Proxy @@ -672,29 +677,133 @@ article. ##### OAuth & OIDC -Codex is not an OIDC client at this time. However the following Remote-User and -Token Authentication methods may assist other services in providing Single Sign -On. +Codex is a native OIDC client. Point it at an identity provider like +[Authentik](https://goauthentik.io/) or [Authelia](https://www.authelia.com/) +and a "Login with …" button appears on the login dialog and the unauthorized +screen. (tinyauth is not an OIDC provider — tinyauth users should use the +[Remote-User](#remote-user-authentication) method below instead.) + +Configure it in the Admin UI under the **Auth** tab — there are no TOML keys or +environment variables for OIDC. Set at minimum the **Provider Name** (the login +button label), the issuer **Server URL** (discovery is fetched from +`/.well-known/openid-configuration`), and the **Client ID**, then +flip the enable switch; the client secret is stored encrypted at rest. A **Test +Connection** button fetches the provider's discovery document and reports the +endpoints it advertises before you commit to the config. Every change takes +effect on the next request — no restart. The tab also carries the user-mapping +knobs (username claim, auto-provisioning, email linking, group sync, admin +group) and RP-initiated logout, all documented inline. + +Register this redirect URI with your identity provider (including your +`url_path_prefix` if you use one): + +```text +https://your-host[/url_path_prefix]/sso/oidc/login/callback/ +``` + +**Identity mapping.** Users are keyed on the stable OIDC `sub` claim. On first +login, Codex links the login to an existing local user with the same username +(and by email if **Link by Email** is on); otherwise it creates a new user when +**Create Users on First Login** is on. With **Sync Groups** on, the identity +provider's groups claim replaces the user's Codex groups on every login — only +existing Codex groups are matched, never created — so IdP groups can drive Codex +library access controls. **Admin Group** members are granted (and, when absent +from the claim, revoked) Codex admin rights. + +⚠️ Username linking includes admin accounts: an identity-provider user named +`admin` links to Codex's built-in `admin` account. Only enable OIDC against an +identity provider whose username namespace you control, and change the default +admin password regardless. ⚠️ + +**Provider notes.** + +- **Authentik:** create an OAuth2/OpenID provider + application. The default + `openid profile email` scope works as-is; `groups` is included in the profile + scope. RP-initiated logout works. +- **Authelia:** register Codex as a client in `identity_providers.oidc` with a + hashed client secret. Keep **Fetch Userinfo** on (Authelia serves username and + email claims only from the userinfo endpoint) and add `groups` to the scope + for group sync. Authelia does not implement RP-initiated logout; leave it off. + +**Good to know.** + +- The client secret is entered in the Admin UI and stored encrypted at rest — it + never appears in `codex.toml` or the API responses. +- Codex sessions live 60 days; disabling a user at the identity provider does + not end their existing Codex session. Deactivate the user in Codex too, or + lower `SESSION_COOKIE_AGE`-equivalent exposure by logging them out. +- OIDC-created users have no local password, so OPDS readers can't use HTTP + Basic auth for them. Use the per-user API token (User Profile in the sidebar) + for OPDS, or set a local password. +- On first start after upgrading, Codex applies a few small new database tables + for the OIDC account linkage and the settings singleton. +- OIDC login failures are recorded in the [Failed-Login Log](#failed-login-log) + when it is enabled. ##### Remote-User Authentication Remote-User authentication tells Codex to accept a username from the webserver and assume that authentication has already been done. This is very insecure if -you haven't configured an authenticating reverse proxy in front of Codex. +you haven't configured an authenticating reverse proxy in front of Codex. This +is the right method for forward-auth gateways like +[tinyauth](https://tinyauth.app/), and also works with Authelia's `auth_request` +mode and Authentik's proxy outpost. -Here's a snipped for configuring nginx with tinyauth to provide this header. -This snipped it incomplete and assumes that the rest of nginx tinyauth config -has been done: +Here's a complete nginx location for the tinyauth case, assuming the tinyauth +server itself is already configured and reachable at `tinyauth:3000`: ```nginx -auth_request_set $tinyauth_remote_user $upstream_http_remote_user; -proxy_set_header Remote-User $tiny_auth_user; +# In the http {} block (shared with the WebSocket map above). +# The internal auth endpoint nginx consults for every request. +location = /tinyauth { + internal ; + proxy_pass http://tinyauth:3000/api/auth/nginx; + proxy_set_header Host $host; + proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; +} + +location ^~ /codex { + auth_request /tinyauth; + # Pass the authenticated username to Codex, OVERRIDING anything the + # client sent. Never pass the client's own Remote-User through. + auth_request_set $remote_user $upstream_http_remote_user; + proxy_set_header Remote-User $remote_user; + proxy_pass http://codex:9810; + # ... the same proxy_set_header / WebSocket lines as the + # Reverse Proxy example above ... +} +``` + +The equivalent Traefik middleware (`forwardAuth` with +`authResponseHeaders: [Remote-User]`) or Caddy directive (`forward_auth` with +`copy_headers Remote-User`) works the same way: the proxy authenticates, then +injects `Remote-User` toward Codex. + +Then enable it in Codex with `CODEX_AUTH_REMOTE_USER=1` or: + +```toml +[auth] +remote_user = true ``` ⚠️ Only turn on the `CODEX_AUTH_REMOTE_USER` environment variable if your webserver sets the `Remote-User` header itself every time for the Codex location, overriding any malicious client that might set it themselves. ⚠️ +Checklist for a forward-auth deployment: + +- The auth gate covers the **whole** Codex location — including `/opds/` and the + WebSocket path `/api/v4/ws` — not just the HTML pages. If you exempt OPDS for + reader apps, those requests fall back to Codex's own Basic/token auth, which + is fine. +- A request with a client-forged `Remote-User` header and no proxy auth must be + rejected or have the header overwritten (test it with + `curl -H "Remote-User: admin"`). +- Remote-User and OIDC can be enabled at the same time; they are independent + paths to a normal Codex session. + ##### HTTP Token Authentication You can also configure your proxy to add token authentication to the headers. diff --git a/bin/run-test-proxy.sh b/bin/run-test-proxy.sh index c2fbf7dc8..208aec10b 100755 --- a/bin/run-test-proxy.sh +++ b/bin/run-test-proxy.sh @@ -82,9 +82,11 @@ mkdir -p "$scaffold/tmp" nginx -p "$scaffold" -c "$scaffold/nginx.conf" -t # Best-effort: surface the ports/backend the committed config actually uses. +# The SSL listeners live in ssl-listen.conf and the backend in +# upstreams-native.conf (both shared with the compose nginx service). http_port="$(awk '/^[[:space:]]*listen [0-9]+ / && !/ssl/ && !/quic/ {print $2; exit}' "$scaffold/server.conf")" -https_port="$(awk '/^[[:space:]]*listen [0-9]+ ssl/ {print $2; exit}' "$scaffold/server.conf")" -backend="$(awk '/proxy_pass[[:space:]]+http:\/\// {gsub(/.*http:\/\/|;.*/, ""); print; exit}' "$scaffold/server.conf")" +https_port="$(awk '/^[[:space:]]*listen [0-9]+ ssl/ {print $2; exit}' "$scaffold/ssl-listen.conf")" +backend="$(awk '/upstream codex_backend/{f=1;next} f&&/server/{gsub(/;/,"",$2); print $2; exit}' "$scaffold/upstreams-native.conf")" cat <=4.8.4 <6.1.0" } }, "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.61.1", "", {}, "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.61.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.61.1", "@typescript-eslint/tsconfig-utils": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="], @@ -249,17 +252,17 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.38", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "browserslist": ["browserslist@4.28.5", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001800", "electron-to-chromium": "^1.5.387", "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -267,7 +270,7 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001803", "", {}, "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -347,7 +350,7 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - "electron-to-chromium": ["electron-to-chromium@1.5.375", "", {}, "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q=="], + "electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -359,7 +362,7 @@ "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "es-toolkit": ["es-toolkit@1.47.1", "", {}, "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q=="], + "es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -369,7 +372,7 @@ "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], - "eslint-fix-utils": ["eslint-fix-utils@0.4.2", "", { "peerDependencies": { "@types/estree": ">=1", "eslint": ">=8" }, "optionalPeers": ["@types/estree"] }, "sha512-n7ZTcwwkP5scedlhvWMcqxED+O1NzXcj5Rxn/0kJQMP88k02vRcBfQ1qsk/JHb6Aw8bajFoetFCCBiNIcNCsvA=="], + "eslint-fix-utils": ["eslint-fix-utils@0.4.3", "", { "peerDependencies": { "@types/estree": ">=1", "eslint": ">=8" }, "optionalPeers": ["@types/estree"] }, "sha512-EKzNhsNavV5DdkHVltHBM9TqfsonCmiUhOm1Ra97zo03M9IAx3wtM1eC27eWGMj/D3LrALmHHNe1QlQH1P7Nxw=="], "eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "^4.10.1", "stable-hash-x": "^0.2.0" }, "peerDependencies": { "unrs-resolver": "^1.0.0" }, "optionalPeers": ["unrs-resolver"] }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="], @@ -497,7 +500,7 @@ "identifier-regex": ["identifier-regex@1.1.0", "", { "dependencies": { "reserved-identifiers": "^1.0.0" } }, "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA=="], - "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], @@ -589,7 +592,7 @@ "lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], - "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "make-asynchronous": ["make-asynchronous@1.1.0", "", { "dependencies": { "p-event": "^6.0.0", "type-fest": "^4.6.0", "web-worker": "^1.5.0" } }, "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg=="], @@ -715,7 +718,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@3.3.13", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q=="], + "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], @@ -723,7 +726,7 @@ "natural-orderby": ["natural-orderby@5.0.0", "", {}, "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg=="], - "node-releases": ["node-releases@2.0.48", "", {}, "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA=="], + "node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="], "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], @@ -769,11 +772,11 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], - "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + "postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="], "postcss-safe-parser": ["postcss-safe-parser@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A=="], @@ -959,7 +962,7 @@ "scslre": ["scslre@0.3.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.0", "regexp-ast-analysis": "^0.7.0" } }, "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ=="], - "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -1003,7 +1006,7 @@ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], - "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], + "svgo": ["svgo@4.0.2", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="], "synckit": ["synckit@0.11.13", "", { "dependencies": { "@pkgr/core": "^0.3.6" } }, "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg=="], @@ -1099,7 +1102,7 @@ "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], - "yaml-eslint-parser": ["yaml-eslint-parser@2.0.0", "", { "dependencies": { "eslint-visitor-keys": "^5.0.0", "yaml": "^2.0.0" } }, "sha512-h0uDm97wvT2bokfwwTmY6kJ1hp6YDFL0nRHwNKz8s/VD1FH/vvZjAKoMUE+un0eaYBSG7/c6h+lJTP+31tjgTw=="], + "yaml-eslint-parser": ["yaml-eslint-parser@2.1.0", "", { "dependencies": { "eslint-visitor-keys": "^5.0.0", "yaml": "^2.0.0" } }, "sha512-1zo9KRfp6vIAXBEhWAz09ex3hUwh3T+/6dGbGteHvNseA0Uk4FgQnPES55klZ6cDzSy9FpgKS0D/CoLUvCCj4w=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -1107,8 +1110,6 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@fsouza/prettierd/prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@npmcli/config/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], @@ -1127,14 +1128,6 @@ "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], - "@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], - - "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], - - "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -1157,21 +1150,15 @@ "eslint-mdx/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - "eslint-plugin-sonarjs/globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], - - "eslint-plugin-unicorn/browserslist": ["browserslist@4.28.5", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001800", "electron-to-chromium": "^1.5.387", "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ=="], + "eslint-plugin-sonarjs/globals": ["globals@17.7.0", "", {}, "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg=="], "eslint-plugin-unicorn/globals": ["globals@17.7.0", "", {}, "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg=="], - "eslint-plugin-unicorn/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - "eslint-plugin-yml/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - "eslint_d/eslint": ["eslint@10.5.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ=="], - "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "is-builtin-module/builtin-modules": ["builtin-modules@5.2.0", "", {}, "sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ=="], + "is-builtin-module/builtin-modules": ["builtin-modules@5.3.0", "", {}, "sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ=="], "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], @@ -1221,39 +1208,17 @@ "@npmcli/git/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "@npmcli/map-workspaces/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], + "@npmcli/map-workspaces/minimatch/brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], "@npmcli/package-json/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@npmcli/promise-spawn/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], - "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "eslint-mdx/espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "eslint-plugin-unicorn/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="], - - "eslint-plugin-unicorn/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001803", "", {}, "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg=="], - - "eslint-plugin-unicorn/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.388", "", {}, "sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw=="], - - "eslint-plugin-unicorn/browserslist/node-releases": ["node-releases@2.0.50", "", {}, "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg=="], - - "eslint_d/eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "eslint_d/eslint/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - - "eslint_d/eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -1273,8 +1238,6 @@ "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "npm-pick-manifest/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], diff --git a/codex/failed_login_log.py b/codex/failed_login_log.py index d6446f2d4..da5f0202d 100644 --- a/codex/failed_login_log.py +++ b/codex/failed_login_log.py @@ -67,14 +67,28 @@ def not_failed_login_filter(record) -> bool: return not record["extra"].get(_FAILED_LOGIN_KEY) +def log_failed_login(request: "HttpRequest | None", username: str = _MISSING) -> None: + """ + Emit one failed-login record in the standard format. + + Public helper for callers outside the ``user_login_failed`` signal + (e.g. OIDC callback failures in :mod:`codex.oidc`) so every failure + shares one line format for banning-tool regexes. When the failed-login + log is disabled the tagged record is dropped by the main sinks' + :func:`not_failed_login_filter` and no dedicated sink exists, so + calling this is always safe. + """ + ip = get_client_ip(request) + _failed_login_logger.warning(f"Failed login from {ip} user={username or _MISSING}") + + def on_login_failed(sender, credentials=None, request=None, **_kwargs) -> None: """Receiver for ``django.contrib.auth.signals.user_login_failed``.""" del sender if request is None: request = _current_request.get() - ip = get_client_ip(request) username = (credentials or {}).get("username") or _MISSING - _failed_login_logger.warning(f"Failed login from {ip} user={username}") + log_failed_login(request, username) class RequestContextMiddleware: diff --git a/codex/migrations/0047_oidc_settings.py b/codex/migrations/0047_oidc_settings.py new file mode 100644 index 000000000..33b6ee4a0 --- /dev/null +++ b/codex/migrations/0047_oidc_settings.py @@ -0,0 +1,127 @@ +"""Generated by Django 6.0.6 on 2026-07-11 07:49.""" + +from django.conf import settings +from django.db import migrations, models + +import codex.models.fields +from codex.settings.config import get_bool, get_str + + +def seed_oidc_settings(apps, _schema_editor): + """ + Create the OIDCSettings singleton. + + One-time courtesy import of any ``[auth.oidc]`` TOML keys from the + pre-GUI config era (the section was never in a release, but a test + deployment may have used it). Falls back to model defaults. + """ + config = getattr(settings, "CODEX_CONFIG", {}) + oidc_settings = apps.get_model("codex", "OIDCSettings") + oidc_settings.objects.get_or_create( + pk=1, + defaults={ + "enabled": get_bool(config, "auth.oidc.enabled", default=False), + "provider_name": get_str(config, "auth.oidc.provider_name", default="SSO"), + "server_url": get_str(config, "auth.oidc.server_url", default=""), + "client_id": get_str(config, "auth.oidc.client_id", default=""), + "client_secret": get_str(config, "auth.oidc.client_secret", default=""), + "scope": get_str(config, "auth.oidc.scope", default="openid profile email"), + "pkce": get_bool(config, "auth.oidc.pkce", default=True), + "token_auth_method": get_str( + config, "auth.oidc.token_auth_method", default="" + ), + "fetch_userinfo": get_bool( + config, "auth.oidc.fetch_userinfo", default=True + ), + "username_claim": get_str( + config, "auth.oidc.username_claim", default="preferred_username" + ), + "create_users": get_bool(config, "auth.oidc.create_users", default=True), + "link_by_email": get_bool(config, "auth.oidc.link_by_email", default=False), + "sync_groups": get_bool(config, "auth.oidc.sync_groups", default=False), + "groups_claim": get_str(config, "auth.oidc.groups_claim", default="groups"), + "admin_group": get_str(config, "auth.oidc.admin_group", default=""), + "rp_initiated_logout": get_bool( + config, "auth.oidc.rp_initiated_logout", default=False + ), + }, + ) + + +class Migration(migrations.Migration): + """Migrate db.""" + + dependencies = [ + ("codex", "0046_comicboxtaggingdefaults_rename_files"), + ] + + operations = [ + migrations.CreateModel( + name="OIDCSettings", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("enabled", models.BooleanField(default=False)), + ( + "provider_name", + models.CharField(blank=True, default="SSO", max_length=128), + ), + ("server_url", models.URLField(blank=True, default="", max_length=512)), + ("client_id", models.CharField(blank=True, default="", max_length=128)), + ( + "client_secret", + codex.models.fields.EncryptedCharField( + blank=True, default="", max_length=512 + ), + ), + ( + "scope", + models.CharField( + blank=True, default="openid profile email", max_length=128 + ), + ), + ("pkce", models.BooleanField(default=True)), + ( + "token_auth_method", + models.CharField(blank=True, default="", max_length=128), + ), + ("fetch_userinfo", models.BooleanField(default=True)), + ( + "username_claim", + models.CharField( + blank=True, default="preferred_username", max_length=128 + ), + ), + ("create_users", models.BooleanField(default=True)), + ("link_by_email", models.BooleanField(default=False)), + ("sync_groups", models.BooleanField(default=False)), + ( + "groups_claim", + models.CharField(blank=True, default="groups", max_length=128), + ), + ( + "admin_group", + models.CharField(blank=True, default="", max_length=128), + ), + ("rp_initiated_logout", models.BooleanField(default=False)), + ], + options={ + "verbose_name_plural": "OIDCSettings", + "get_latest_by": "updated_at", + "abstract": False, + }, + ), + migrations.RunPython( + code=seed_oidc_settings, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/codex/models/admin.py b/codex/models/admin.py index e1f55e668..ca05f4363 100644 --- a/codex/models/admin.py +++ b/codex/models/admin.py @@ -32,6 +32,7 @@ "ComicboxTaggingDefaults", "EmailSettings", "LibrarianStatus", + "OIDCSettings", "ThrottleSettings", "Timestamp", ) @@ -174,6 +175,59 @@ class Meta(BaseModel.Meta): verbose_name_plural = "EmailSettings" +class OIDCSettings(BaseModel): + """ + Singleton OIDC (SSO) client configuration. + + The sole source of OIDC config — edited in the Admin UI Auth tab and + read at request time (``codex.settings.db.get_oidc_settings``), so + enabling, disabling, or repointing the identity provider takes + effect on the next request without a restart. Toggling only adds or + removes a login method: existing sessions and local password login + are unaffected, so an admin cannot lock themselves out here. + + OIDC is effectively enabled only when ``enabled`` AND ``server_url`` + AND ``client_id`` are all set — see + :func:`codex.settings.db.oidc_enabled`. + + ``client_secret`` is encrypted at rest via :class:`EncryptedCharField` + using ``FIELD_ENCRYPTION_KEY``. + """ + + enabled = BooleanField(default=False) + provider_name = CharField(max_length=MAX_NAME_LEN, blank=True, default="SSO") + # Issuer URLs run long (e.g. Authentik's /application/o//). + server_url = URLField(max_length=512, blank=True, default="") + client_id = CharField(max_length=MAX_NAME_LEN, blank=True, default="") + client_secret = EncryptedCharField() + scope = CharField( + max_length=MAX_NAME_LEN, blank=True, default="openid profile email" + ) + pkce = BooleanField(default=True) + token_auth_method = CharField(max_length=MAX_NAME_LEN, blank=True, default="") + fetch_userinfo = BooleanField(default=True) + username_claim = CharField( + max_length=MAX_NAME_LEN, blank=True, default="preferred_username" + ) + create_users = BooleanField(default=True) + link_by_email = BooleanField(default=False) + sync_groups = BooleanField(default=False) + groups_claim = CharField(max_length=MAX_NAME_LEN, blank=True, default="groups") + admin_group = CharField(max_length=MAX_NAME_LEN, blank=True, default="") + rp_initiated_logout = BooleanField(default=False) + + @override + def save(self, *args, **kwargs): + """Enforce singleton: always use pk=1.""" + self.pk = 1 + super().save(*args, **kwargs) + + class Meta(BaseModel.Meta): + """Constraints.""" + + verbose_name_plural = "OIDCSettings" + + class ThrottleSettings(BaseModel): """ Singleton DRF rate-limit configuration. diff --git a/codex/oidc.py b/codex/oidc.py new file mode 100644 index 000000000..b1338221d --- /dev/null +++ b/codex/oidc.py @@ -0,0 +1,392 @@ +""" +Native OIDC (SSO) login via django-allauth. + +The allauth apps are always installed (see ``codex.settings``); everything +here is gated at runtime on the :class:`codex.models.admin.OIDCSettings` +DB singleton, edited in the Admin UI Auth tab. cachalot caches the row and +invalidates it on write, so enabling, disabling, or repointing the +identity provider takes effect on the next request without a restart. +The flow is: + +1. ``GET /api/v4/auth/oidc/login`` (:class:`OIDCLoginRedirectView`, throttled, + 404 when disabled) redirects to allauth's provider login view. +2. allauth redirects to the identity provider's authorize endpoint + (state + PKCE bound to the Django session). allauth discovers the + provider app through :meth:`CodexSocialAccountAdapter.list_apps`, + which builds an unsaved ``SocialApp`` from the DB singleton — when + OIDC is off there is no app, so allauth's own + ``SocialApp.DoesNotExist -> Http404`` keeps every path a clean 404. +3. The provider redirects back to ``/sso/oidc/login/callback/`` where + allauth exchanges the code and builds a ``SocialLogin``. +4. :class:`CodexSocialAccountAdapter` links or provisions the Django user + and allauth logs it in with a normal session cookie, landing on the SPA + root where ``loadSession()`` picks up the session. + +Every signup path resolves inside the adapter: failures and policy +rejections redirect to the SPA's ``/auth/sso-error`` route instead of ever +rendering an allauth template. +""" + +from hashlib import sha256 +from typing import TYPE_CHECKING, NoReturn, override +from urllib.parse import urlencode + +import requests +from allauth.account.utils import user_username +from allauth.core.exceptions import ImmediateHttpResponse +from allauth.socialaccount.adapter import DefaultSocialAccountAdapter +from django.contrib.auth.models import Group, User +from django.core.cache import cache +from django.http import Http404, HttpResponseRedirect +from django.shortcuts import redirect +from django.urls import reverse +from loguru import logger +from rest_framework.views import APIView + +from codex.failed_login_log import log_failed_login +from codex.settings import GRANIAN_URL_PATH_PREFIX +from codex.settings.db import get_oidc_settings, oidc_enabled +from codex.throttling import ScopedRateThrottle + +if TYPE_CHECKING: + from allauth.socialaccount.models import SocialApp, SocialLogin + from django.http import HttpRequest + + from codex.models.admin import OIDCSettings + +_SSO_ERROR_PATH = f"{GRANIAN_URL_PATH_PREFIX}/auth/sso-error" +_OIDC_PROVIDER_ID = "oidc" +_DEFAULT_USERNAME_CLAIM = "preferred_username" +# OAuth2 error codes passed through to the SPA error page; anything else +# collapses to server_error so the redirect never reflects attacker input. +_KNOWN_ERRORS = frozenset( + { + "access_denied", + "email_exists", + "server_error", + "signup_disabled", + "temporarily_unavailable", + } +) + + +DISCOVERY_CACHE_KEY = "codex-oidc-discovery" +_DISCOVERY_CACHE_TTL = 60 * 60 +_DISCOVERY_FAILURE_TTL = 60 * 5 +_DISCOVERY_TIMEOUT = 5 +_WELL_KNOWN = "/.well-known/openid-configuration" + + +def user_is_oidc_managed(user) -> bool: + """Return whether the user's identity is owned by the identity provider.""" + return bool( + user and user.pk and user.is_authenticated and user.socialaccount_set.exists() + ) + + +def discovery_url(server_url: str) -> str: + """Return the well-known discovery URL for an issuer URL.""" + if "/.well-known/" in server_url: + return server_url + return server_url.rstrip("/") + _WELL_KNOWN + + +def fetch_discovery_document(server_url: str) -> dict: + """ + Fetch the provider's OIDC discovery document, uncached. + + Raises ``requests.RequestException`` or ``ValueError`` on failure — + used directly by the Admin UI's Test Connection endpoint. + """ + response = requests.get(discovery_url(server_url), timeout=_DISCOVERY_TIMEOUT) + response.raise_for_status() + return response.json() + + +def _discovery_document(server_url: str) -> dict: + """ + Fetch and cache the provider's OIDC discovery document. + + Failures are negative-cached briefly so an unreachable identity + provider can't turn every session boot into a hanging request. + """ + doc = cache.get(DISCOVERY_CACHE_KEY) + if doc is not None: + return doc + try: + doc = fetch_discovery_document(server_url) + except (requests.RequestException, ValueError) as exc: + logger.warning(f"OIDC discovery fetch failed: {type(exc).__name__}") + cache.set(DISCOVERY_CACHE_KEY, {}, _DISCOVERY_FAILURE_TTL) + return {} + cache.set(DISCOVERY_CACHE_KEY, doc, _DISCOVERY_CACHE_TTL) + return doc + + +def oidc_logout_url(request: "HttpRequest") -> str: + """ + Build the RP-initiated logout URL, or "" when unavailable. + + Uses the spec's ``client_id`` + ``post_logout_redirect_uri`` + parameters — no ``id_token_hint`` because allauth does not retain the + raw ID token JWT. Authentik accepts this; Authelia has no end_session + endpoint, so its discovery document simply omits the key. + """ + row = get_oidc_settings() + if not (oidc_enabled(row) and row and row.rp_initiated_logout): + return "" + end_session = _discovery_document(row.server_url).get("end_session_endpoint") + if not end_session: + return "" + post_logout = request.build_absolute_uri(GRANIAN_URL_PATH_PREFIX or "/") + params = urlencode( + {"client_id": row.client_id, "post_logout_redirect_uri": post_logout} + ) + return f"{end_session}?{params}" + + +def _raise_sso_error(error: str) -> NoReturn: + """Abort the login flow with a redirect to the SPA's SSO error page.""" + if error not in _KNOWN_ERRORS: + error = "server_error" + raise ImmediateHttpResponse(redirect(f"{_SSO_ERROR_PATH}?error={error}")) + + +def merged_claims(sociallogin: "SocialLogin") -> dict: + """ + Merge ID-token and userinfo claims into one mapping; userinfo wins. + + allauth's openid_connect provider stores ``extra_data`` as + ``{"id_token": {...}, "userinfo": {...}}`` but its ``_pick_data`` + *prefers* userinfo rather than merging, so a claim present only in the + ID token (e.g. Authentik's ``groups``) would be invisible. Merging + tolerates both Authentik (claims in the ID token) and Authelia + (claims only in userinfo). + """ + extra = sociallogin.account.extra_data or {} + id_token = extra.get("id_token") or {} + userinfo = extra.get("userinfo") or {} + if id_token or userinfo: + return {**id_token, **userinfo} + # Pre-65.11 allauth stored a flat claims dict. + return dict(extra) + + +def _map_username(claims: dict, row: "OIDCSettings | None") -> str: + """Map claims to a Codex username: username_claim -> email -> sub.""" + claim = (row.username_claim if row else "") or _DEFAULT_USERNAME_CLAIM + username = claims.get(claim) or claims.get("email") or claims.get("sub") or "" + return str(username) + + +def _build_social_app(row: "OIDCSettings") -> "SocialApp": + """ + Build allauth's unsaved SocialApp from the DB singleton. + + The same shape allauth itself builds from + ``SOCIALACCOUNT_PROVIDERS[...]["APPS"]`` entries — per-app + ``settings["scope"]`` wins over provider-level SCOPE in + ``OAuth2Provider.get_scope``. + """ + from allauth.socialaccount.models import SocialApp + + app_settings = { + "server_url": row.server_url, + "oauth_pkce_enabled": row.pkce, + "fetch_userinfo": row.fetch_userinfo, + "scope": row.scope.split(), + } + if row.token_auth_method: + app_settings["token_auth_method"] = row.token_auth_method + return SocialApp( + provider="openid_connect", + provider_id=_OIDC_PROVIDER_ID, + name=row.provider_name or "SSO", + client_id=row.client_id, + secret=row.client_secret, + settings=app_settings, + ) + + +class CodexSocialAccountAdapter(DefaultSocialAccountAdapter): + """Link, provision, and sync Codex users from OIDC claims.""" + + @override + def list_apps(self, request, provider=None, client_id=None) -> list: + """ + Blend in the OIDC app configured by the Admin UI singleton. + + Mirrors allauth's own filter semantics for settings-configured + apps. When OIDC is disabled no app exists, so allauth's provider + views 404 on their own. + """ + apps = super().list_apps(request, provider=provider, client_id=client_id) + row = get_oidc_settings() + if not (oidc_enabled(row) and row): + return apps + app = _build_social_app(row) + if client_id and app.client_id != client_id: + return apps + if provider and provider not in (app.provider, app.provider_id): + return apps + apps.append(app) + return apps + + @override + def is_open_for_signup(self, request, sociallogin) -> bool: + """Gate auto-provisioning on the create_users config knob.""" + row = get_oidc_settings() + return bool(row and row.create_users) + + @override + def pre_social_login(self, request, sociallogin) -> None: + """ + Resolve every login path before allauth's signup machinery runs. + + Linking policy (user decision, documented in the README): an OIDC + login auto-links to an existing local user with the same username — + including superusers — and by email only when ``link_by_email`` is + on. Linking records the SocialAccount keyed on the stable ``sub`` + claim, so later logins match by sub even if the username changes. + """ + row = get_oidc_settings() + claims = merged_claims(sociallogin) + if sociallogin.is_existing: + # Known sub: sync and let allauth log the user in. + self._sync_user(sociallogin.user, claims, row) + return + if user := self._find_linkable_user(claims, row): + sociallogin.connect(request, user) + self._sync_user(user, claims, row) + return + if not (row and row.create_users): + log_failed_login(request, _map_username(claims, row)) + _raise_sso_error("signup_disabled") + if self._email_conflict(claims): + # Unlinked local user owns this email and link_by_email is + # off. Without this check allauth would render its own signup + # form into the SPA-less void. + log_failed_login(request, _map_username(claims, row)) + _raise_sso_error("email_exists") + + @override + def populate_user(self, request, sociallogin, data): + """ + Apply the configured username claim chain to new users. + + If the mapped username is already taken (an already-linked account + owns it, so pre_social_login won't link), suffix with a short hash + of the sub. Without this, allauth blanks the colliding username and + falls back to a generic "user" name. + """ + user = super().populate_user(request, sociallogin, data) + row = get_oidc_settings() + if username := _map_username(merged_claims(sociallogin), row): + if User.objects.filter(username__iexact=username).exists(): + sub_hash = sha256(str(sociallogin.account.uid).encode()).hexdigest() + username = f"{username}-{sub_hash[:8]}" + user_username(user, username) + return user + + @override + def save_user(self, request, sociallogin, form=None): + """Provision a new user, then sync groups/admin from claims.""" + user = super().save_user(request, sociallogin, form) + self._sync_user(user, merged_claims(sociallogin), get_oidc_settings()) + return user + + @override + def on_authentication_error( + self, request, provider, error=None, exception=None, extra_context=None + ) -> None: + """Log the failure and land on the SPA error page, never a template.""" + del provider, extra_context + # Same record format as password failures so fail2ban regexes and + # the dedicated-log privacy story apply unchanged. When the + # failed-login log is disabled the record is dropped by the main + # sinks' filter, so this degrades gracefully. + log_failed_login(request) + code = str(error) if error else "server_error" + if exception: # pragma: no cover - depends on provider behavior + # str(exception) may embed tokens or secrets; log type only. + log_failed_login(request, f"oidc-exception={type(exception).__name__}") + _raise_sso_error(code) + + @staticmethod + def _find_linkable_user(claims: dict, row: "OIDCSettings | None") -> User | None: + """ + Find an existing unlinked local user to attach this login to. + + Skips users that already have an OIDC link (a different sub with + the same preferred_username must not hijack their account). + """ + if username := _map_username(claims, row): + user = User.objects.filter(username__iexact=username).first() + if user and not user.socialaccount_set.exists(): # pyright: ignore[reportAttributeAccessIssue], # ty: ignore[unresolved-attribute] + return user + if row and row.link_by_email and (email := claims.get("email")): + user = User.objects.filter(email__iexact=str(email)).first() + if user and not user.socialaccount_set.exists(): # pyright: ignore[reportAttributeAccessIssue], # ty: ignore[unresolved-attribute] + return user + return None + + @staticmethod + def _email_conflict(claims: dict) -> bool: + """Return whether creating this user collides with an existing email.""" + email = claims.get("email") + return bool(email and User.objects.filter(email__iexact=str(email)).exists()) + + @staticmethod + def _sync_user(user, claims: dict, row: "OIDCSettings | None") -> None: + """ + Sync Django groups and admin flags from the groups claim. + + Runs on every OIDC login so library ACLs track the identity + provider. An absent or malformed groups claim is a no-op — Authelia + omits it entirely unless the ``groups`` scope is requested. Only + EXISTING Codex groups are matched; groups are never created because + they gate library ACLs. + """ + if not (user and row and (row.sync_groups or row.admin_group)): + return + group_names = claims.get(row.groups_claim or "groups") + if not isinstance(group_names, list): + return + names = {str(name) for name in group_names} + if row.sync_groups: + user.groups.set(Group.objects.filter(name__in=names)) + if row.admin_group: + # The identity provider is authoritative: grants AND revokes. + CodexSocialAccountAdapter._sync_admin( + user, is_admin=row.admin_group in names + ) + + @staticmethod + def _sync_admin(user, *, is_admin: bool) -> None: + """Grant or revoke staff+superuser to match the admin-group claim.""" + if user.is_staff != is_admin or user.is_superuser != is_admin: + user.is_staff = user.is_superuser = is_admin + user.save(update_fields=("is_staff", "is_superuser")) + + +class OIDCLoginRedirectView(APIView): + """ + Branded, throttled entry point for OIDC login. + + A plain GET navigation (not XHR): the SPA sets + ``window.location.href`` here and the chain of redirects ends back at + the SPA root with a session cookie. 404s when OIDC is disabled, like + RegisterView does when registration is off. + """ + + authentication_classes = () + throttle_classes = (ScopedRateThrottle,) + throttle_scope = "oidc_login" + + def get(self, request: "HttpRequest") -> HttpResponseRedirect: + """Redirect to allauth's provider login view.""" + del request + if not oidc_enabled(): + raise Http404 + url = reverse("openid_connect_login", kwargs={"provider_id": _OIDC_PROVIDER_ID}) + return HttpResponseRedirect(url) diff --git a/codex/serializers/admin/oidc.py b/codex/serializers/admin/oidc.py new file mode 100644 index 000000000..ea3880e08 --- /dev/null +++ b/codex/serializers/admin/oidc.py @@ -0,0 +1,98 @@ +"""OIDCSettings admin serializers.""" + +from typing import override + +from rest_framework.exceptions import ValidationError +from rest_framework.fields import SerializerMethodField +from rest_framework.serializers import BooleanField, CharField, Serializer, URLField + +from codex.models import OIDCSettings +from codex.serializers.models.base import BaseModelSerializer + +_ENABLE_REQUIRES_MSG = ( + "OIDC login cannot be enabled without a provider name," + " a server URL, and a client ID." +) + + +class OIDCSettingsSerializer(BaseModelSerializer): + """Serializer for the OIDCSettings singleton.""" + + # Secret is write-only — clients see whether one is set, not the value. + client_secret = CharField(write_only=True, required=False, allow_blank=True) + client_secret_set = SerializerMethodField() + + @staticmethod + def get_client_secret_set(obj) -> bool: + """Whether an OIDC client secret has been configured.""" + return bool(obj.client_secret) + + @override + def validate(self, attrs): + """ + Reject an enabled-but-unconfigured state. + + The UI disables the enable switch until a provider name, server + URL, and client ID are present; this enforces the same invariant + for API clients and for partial updates that blank a prerequisite + while leaving ``enabled`` on. Merges the incoming fields over the + saved row so partial PUTs validate against the effective result. + """ + merged = { + field: attrs.get(field, getattr(self.instance, field, "")) + for field in ("enabled", "provider_name", "server_url", "client_id") + } + if merged["enabled"] and not ( + merged["provider_name"] and merged["server_url"] and merged["client_id"] + ): + raise ValidationError({"enabled": _ENABLE_REQUIRES_MSG}) + return attrs + + class Meta(BaseModelSerializer.Meta): + """Specify model and fields.""" + + model = OIDCSettings + fields = ( + "enabled", + "provider_name", + "server_url", + "client_id", + "client_secret", + "client_secret_set", + "scope", + "pkce", + "token_auth_method", + "fetch_userinfo", + "username_claim", + "create_users", + "link_by_email", + "sync_groups", + "groups_claim", + "admin_group", + "rp_initiated_logout", + ) + read_only_fields = ("client_secret_set",) + + +class OIDCTestRequestSerializer(Serializer): + """ + Request body for the OIDC Test Connection endpoint. + + ``server_url`` is optional: when present it overrides the saved row + for this one probe, so an admin can validate an issuer URL before + saving it (the email Test Send pattern). + """ + + server_url = URLField(required=False, allow_blank=True) + + +class OIDCTestResponseSerializer(Serializer): + """Test-connection outcome: which endpoints the provider advertises.""" + + ok = BooleanField() + error = CharField(allow_null=True, required=False, default=None) + issuer = CharField(allow_null=True, required=False, default=None) + authorization_endpoint = BooleanField(required=False, default=False) + token_endpoint = BooleanField(required=False, default=False) + userinfo_endpoint = BooleanField(required=False, default=False) + end_session_endpoint = BooleanField(required=False, default=False) diff --git a/codex/serializers/auth.py b/codex/serializers/auth.py index d07e46769..391519387 100644 --- a/codex/serializers/auth.py +++ b/codex/serializers/auth.py @@ -9,10 +9,25 @@ from rest_framework.serializers import Serializer from rest_registration.api.serializers import DefaultUserProfileSerializer +from codex.oidc import user_is_oidc_managed from codex.serializers.fields.auth import TimezoneField from codex.serializers.versions import VersionsSerializer +def _username_locked(serializer: Serializer) -> bool: + """ + Return whether an external identity manages the request user's username. + + Global when ``AUTH_REMOTE_USER`` owns identity; per-user when the + account is linked to an OIDC identity provider. Renaming in either + case would desync the user from the IdP and lock them out. + """ + if settings.AUTH_REMOTE_USER: + return True + request = serializer.context.get("request") if serializer.context else None + return user_is_oidc_managed(getattr(request, "user", None)) + + class CodexProfileSerializer(DefaultUserProfileSerializer): """ Profile serializer for self-service edits of username + email. @@ -33,7 +48,7 @@ class CodexProfileSerializer(DefaultUserProfileSerializer): @override def get_fields(self): - """Make email writable; lock username under remote-user auth.""" + """Make email writable; lock username under external identity.""" fields = super().get_fields() email = fields.get("email") if email is not None: @@ -46,7 +61,7 @@ def get_fields(self): # walking the parent ``Field`` type and complaining. if hasattr(email, "allow_blank"): setattr(email, "allow_blank", True) # noqa: B010 - if settings.AUTH_REMOTE_USER and "username" in fields: + if "username" in fields and _username_locked(self): fields["username"].read_only = True return fields @@ -68,6 +83,10 @@ class AdminFlagsSerializer(Serializer): register_verification = BooleanField(read_only=True) email_enabled = BooleanField(read_only=True) remote_user_enabled = BooleanField(read_only=True) + oidc_enabled = BooleanField(read_only=True) + oidc_provider_name = CharField(read_only=True, allow_blank=True) + oidc_login_url = CharField(read_only=True, allow_blank=True) + oidc_logout_url = CharField(read_only=True, allow_blank=True) class UserSerializer(Serializer): @@ -102,9 +121,10 @@ class ProfileUpdateSerializer(Serializer): """ Writable subset of the user profile. - ``username`` is read-only when ``AUTH_REMOTE_USER`` owns identity. - Empty strings on ``email`` mean "clear it"; the - rest-registration-backed profile view accepts that semantics. + ``username`` is read-only when an external identity (remote-user + proxy or a linked OIDC provider) owns it. Empty strings on ``email`` + mean "clear it"; the rest-registration-backed profile view accepts + that semantics. """ username = CharField(required=False, allow_blank=False) @@ -113,9 +133,9 @@ class ProfileUpdateSerializer(Serializer): @override def get_fields(self): - """Lock username under remote-user auth (matches v3 profile semantics).""" + """Lock username under external identity (matches v3 profile semantics).""" fields = super().get_fields() - if settings.AUTH_REMOTE_USER and "username" in fields: + if "username" in fields and _username_locked(self): fields["username"].read_only = True return fields diff --git a/codex/settings/__init__.py b/codex/settings/__init__.py index 0186eecb2..ea1e967ba 100644 --- a/codex/settings/__init__.py +++ b/codex/settings/__init__.py @@ -521,6 +521,12 @@ def _get_installed_apps(features: FeatureFlags) -> tuple[str, ...]: "rest_framework", "rest_framework.authtoken", "rest_registration", + # allauth apps are installed unconditionally (like rest_registration); + # OIDC behavior is gated at runtime on the OIDCSettings singleton. + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.openid_connect", "corsheaders", ] if features.django_vite: @@ -566,6 +572,7 @@ def _get_middleware(features: FeatureFlags) -> tuple[str, ...]: if AUTH_FAILED_LOGIN_LOG: middleware.append("codex.failed_login_log.RequestContextMiddleware") middleware += [ + "allauth.account.middleware.AccountMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "codex.middleware.CodexMiddleware", @@ -698,11 +705,41 @@ def _get_middleware(features: FeatureFlags) -> tuple[str, ...]: # Authentication # ################## +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + # Authenticates completed OIDC logins (allauth). Inert unless a + # sociallogin flow runs, so safe to include unconditionally. + "allauth.account.auth_backends.AuthenticationBackend", +] if AUTH_REMOTE_USER: - AUTHENTICATION_BACKENDS = [ - "django.contrib.auth.backends.RemoteUserBackend", - "django.contrib.auth.backends.ModelBackend", - ] + AUTHENTICATION_BACKENDS.insert(0, "django.contrib.auth.backends.RemoteUserBackend") + +# django-allauth: native OIDC (SSO) login. Apps and URLs are always +# installed; behavior is gated at request time on the OIDCSettings DB +# singleton (no provider app when disabled => allauth 404s). +SOCIALACCOUNT_ADAPTER = "codex.oidc.CodexSocialAccountAdapter" +# The init endpoint is a plain GET navigation; state + PKCE provide the +# anti-forgery binding. +SOCIALACCOUNT_LOGIN_ON_GET = True +SOCIALACCOUNT_AUTO_SIGNUP = True +# Never gate logins on email_verified: Authentik defaults it to False. +SOCIALACCOUNT_EMAIL_VERIFICATION = "none" +ACCOUNT_EMAIL_VERIFICATION = "none" +# No use for stored access/refresh tokens: Codex never calls the +# identity provider on the user's behalf, and RP-initiated logout uses +# the spec's client_id parameter instead of id_token_hint (allauth does +# not retain the raw id_token JWT anyway). +SOCIALACCOUNT_STORE_TOKENS = False +# No SOCIALACCOUNT_PROVIDERS: the provider app is built at request time +# from the OIDCSettings DB singleton (Admin UI Auth tab) by +# CodexSocialAccountAdapter.list_apps, so config changes are live +# without a restart. +# allauth defaults this to "oidc", which would double up with the +# provider_id path segment: /sso/oidc/oidc/login/. Codex mounts the +# provider urls at "sso/" (urls/root.py), so /sso/oidc/login/. +SOCIALACCOUNT_OPENID_CONNECT_URL_PREFIX = "" +# Where the OIDC callback lands the user after login. +LOGIN_REDIRECT_URL = GRANIAN_URL_PATH_PREFIX or "/" # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ @@ -819,6 +856,9 @@ def _get_middleware(features: FeatureFlags) -> tuple[str, ...]: "reset_password": ( f"{THROTTLE_RESET_PASSWORD}/hour" if THROTTLE_RESET_PASSWORD else None ), + # The OIDC init endpoint is an unauthenticated redirect; a modest + # per-IP cap keeps it from being used to spam the identity provider. + "oidc_login": "10/min", } _RENDERER_CLASSES = [ diff --git a/codex/settings/codex.toml.default b/codex/settings/codex.toml.default index 1ab6a1b3f..fac0ed580 100644 --- a/codex/settings/codex.toml.default +++ b/codex/settings/codex.toml.default @@ -123,6 +123,10 @@ # Disable if Codex is exposed directly (otherwise clients can forge their IP). # failed_login_log_trust_forwarded_for = true +# OIDC single sign-on (Authentik, Authelia, ...) is configured in the +# Admin UI under the "Auth" tab, including the client secret (encrypted +# at rest) and a Test Connection button. There are no TOML keys for it. + # [custom_covers] # Maximum size (in megabytes) of a custom-cover upload. This setting # is also exposed in the Admin UI under "Custom Covers"; the DB value diff --git a/codex/settings/db.py b/codex/settings/db.py index c69a36944..487623845 100644 --- a/codex/settings/db.py +++ b/codex/settings/db.py @@ -25,7 +25,12 @@ from codex.choices.admin import AdminFlagChoices if TYPE_CHECKING: - from codex.models.admin import AdminFlag, EmailSettings, ThrottleSettings + from codex.models.admin import ( + AdminFlag, + EmailSettings, + OIDCSettings, + ThrottleSettings, + ) THROTTLE_SCOPE_PERIOD: dict[str, str] = { "anon": "min", @@ -97,6 +102,30 @@ def get_email_settings() -> EmailSettings | None: return None +def get_oidc_settings() -> OIDCSettings | None: + """Return the OIDCSettings singleton or None when DB isn't ready.""" + try: + from codex.models.admin import OIDCSettings + except (ImportError, RuntimeError): + return None + try: + return OIDCSettings.objects.filter(pk=1).first() + except Exception: + return None + + +def oidc_enabled(row: OIDCSettings | None = None) -> bool: + """ + Return whether OIDC login is effectively on. + + Requires the enabled switch AND a server_url AND a client_id. Pass + the row in when the caller already fetched it. + """ + if row is None: + row = get_oidc_settings() + return bool(row and row.enabled and row.server_url and row.client_id) + + def _coalesce(db_value: Any, settings_value: Any) -> Any: """Prefer ``db_value`` when truthy; otherwise fall back to ``settings_value``.""" return db_value or settings_value diff --git a/codex/urls/api/v4/admin.py b/codex/urls/api/v4/admin.py index 7b9961df0..cb095e058 100644 --- a/codex/urls/api/v4/admin.py +++ b/codex/urls/api/v4/admin.py @@ -24,6 +24,7 @@ AdminFolderListView, AdminLibraryViewSet, ) +from codex.views.admin.oidc import AdminOIDCSettingsView, AdminOIDCTestView from codex.views.admin.onlinetag import ( AdminOnlineTagAbortView, AdminOnlineTagActiveView, @@ -167,6 +168,16 @@ AdminEmailTestSendView.as_view(), name="email_settings_test", ), + path( + "oidc-settings", + AdminOIDCSettingsView.as_view(), + name="oidc_settings", + ), + path( + "oidc-settings/test", + AdminOIDCTestView.as_view(), + name="oidc_settings_test", + ), path( "tagging-defaults", AdminTaggingDefaultsView.as_view(), diff --git a/codex/urls/api/v4/auth.py b/codex/urls/api/v4/auth.py index 0935ded36..2c4be1991 100644 --- a/codex/urls/api/v4/auth.py +++ b/codex/urls/api/v4/auth.py @@ -4,6 +4,7 @@ from rest_registration.api.views.change_password import ChangePasswordView from rest_registration.api.views.login import LoginView, LogoutView +from codex.oidc import OIDCLoginRedirectView from codex.views.auth import AuthToken, CSRFView, ProfileView from codex.views.register import RegisterView from codex.views.reset_password import ResetPasswordView, SendResetPasswordLinkView @@ -14,6 +15,7 @@ path("register", RegisterView.as_view(), name="register"), path("login", LoginView.as_view(), name="login"), path("logout", LogoutView.as_view(), name="logout"), + path("oidc/login", OIDCLoginRedirectView.as_view(), name="oidc-login"), path("token", AuthToken.as_view(), name="token"), path("profile", ProfileView.as_view(), name="profile"), path( diff --git a/codex/urls/app.py b/codex/urls/app.py index 53f68b550..c161418a9 100644 --- a/codex/urls/app.py +++ b/codex/urls/app.py @@ -41,6 +41,9 @@ # so Vue Router can render the reset screen; without it the catch-all below # would redirect the link to the home page. path("auth/reset-password/", IndexView.as_view(), name="reset-password"), + # Client-side auth route reached from the OIDC adapter's error + # redirects (codex/oidc.py). Same catch-all hazard as reset-password. + path("auth/sso-error", IndexView.as_view(), name="sso-error"), path("", IndexView.as_view(), name="start"), re_path( ".*", diff --git a/codex/urls/root.py b/codex/urls/root.py index 2c964c6cb..098a49b5a 100644 --- a/codex/urls/root.py +++ b/codex/urls/root.py @@ -43,6 +43,10 @@ name="robots", ), path("api/", include("codex.urls.api.root")), + # allauth's OIDC login/callback. Mounted at the root, outside the + # namespaced api tree, because allauth reverses its own URL names + # without a namespace. The views 404 when no OIDC app is configured. + path("sso/", include("allauth.socialaccount.providers.openid_connect.urls")), path("opds/", include("codex.urls.opds.root")), path("opds", RedirectView.as_view(pattern_name="opds:v1:start")), path("health", health_check_view, name="healthcheck"), diff --git a/codex/views/admin/oidc.py b/codex/views/admin/oidc.py new file mode 100644 index 000000000..26f5edeeb --- /dev/null +++ b/codex/views/admin/oidc.py @@ -0,0 +1,79 @@ +"""Admin OIDC Settings View.""" + +from __future__ import annotations + +import requests +from django.core.cache import cache +from loguru import logger +from rest_framework.response import Response + +from codex.models import OIDCSettings +from codex.oidc import DISCOVERY_CACHE_KEY, fetch_discovery_document +from codex.serializers.admin.oidc import ( + OIDCSettingsSerializer, + OIDCTestRequestSerializer, + OIDCTestResponseSerializer, +) +from codex.views.admin.auth import AdminAPIView + + +class AdminOIDCSettingsView(AdminAPIView): + """GET/PUT for the OIDCSettings singleton.""" + + def get(self, _request): + """Return the current OIDC settings.""" + row = OIDCSettings.objects.get(pk=1) + serializer = OIDCSettingsSerializer(row) + return Response(serializer.data) + + def put(self, request): + """Update OIDC settings and drop the cached discovery document.""" + row = OIDCSettings.objects.get(pk=1) + serializer = OIDCSettingsSerializer(row, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + # The issuer may have changed; the next login or logout-URL + # build must re-discover the provider's endpoints. + cache.delete(DISCOVERY_CACHE_KEY) + return Response(OIDCSettingsSerializer(row).data) + + +class AdminOIDCTestView(AdminAPIView): + """POST a discovery-document probe against the identity provider.""" + + def post(self, request): + """Fetch the discovery document and report the advertised endpoints.""" + req = OIDCTestRequestSerializer(data=request.data) + req.is_valid(raise_exception=True) + server_url = req.validated_data.get("server_url", "") + if not server_url: + row = OIDCSettings.objects.filter(pk=1).first() + server_url = row.server_url if row else "" + if not server_url: + response = OIDCTestResponseSerializer( + {"ok": False, "error": "No server URL configured."} + ) + return Response(response.data) + + try: + doc = fetch_discovery_document(server_url) + except (requests.RequestException, ValueError) as exc: + logger.warning("Codex OIDC test connection failed: {exc}", exc=exc) + response = OIDCTestResponseSerializer( + {"ok": False, "error": str(exc) or exc.__class__.__name__} + ) + return Response(response.data) + + response = OIDCTestResponseSerializer( + { + "ok": bool( + doc.get("authorization_endpoint") and doc.get("token_endpoint") + ), + "issuer": doc.get("issuer"), + "authorization_endpoint": bool(doc.get("authorization_endpoint")), + "token_endpoint": bool(doc.get("token_endpoint")), + "userinfo_endpoint": bool(doc.get("userinfo_endpoint")), + "end_session_endpoint": bool(doc.get("end_session_endpoint")), + } + ) + return Response(response.data) diff --git a/codex/views/session.py b/codex/views/session.py index b3c916446..3e9f0dc78 100644 --- a/codex/views/session.py +++ b/codex/views/session.py @@ -16,8 +16,10 @@ from codex.choices.admin import AdminFlagChoices from codex.models import AdminFlag +from codex.oidc import oidc_logout_url from codex.serializers.auth import SessionSerializer -from codex.settings.db import email_enabled +from codex.settings import GRANIAN_URL_PATH_PREFIX +from codex.settings.db import email_enabled, get_oidc_settings, oidc_enabled from codex.views.auth import AuthGenericAPIView, user_payload from codex.views.version import version_payload @@ -56,8 +58,7 @@ class SessionView(AuthGenericAPIView): permission_classes = (AllowAny,) serializer_class = SessionSerializer - @staticmethod - def _admin_flags(*, authenticated: bool) -> dict: + def _admin_flags(self, *, authenticated: bool) -> dict: """ Build the auth-relevant admin flags + settings-derived capabilities. @@ -81,9 +82,23 @@ def _admin_flags(*, authenticated: bool) -> dict: # Public: the logged-out login dialog shows the reset-password # link when email is configured. flags["email_enabled"] = email_enabled() + # Public, unlike remote_user_enabled: the logged-out shell must + # render the "Login with " button. + oidc_row = get_oidc_settings() + oidc_on = oidc_enabled(oidc_row) + flags["oidc_enabled"] = oidc_on + if oidc_on and oidc_row: + flags["oidc_provider_name"] = oidc_row.provider_name or "SSO" + flags["oidc_login_url"] = ( + f"{GRANIAN_URL_PATH_PREFIX}/api/v4/auth/oidc/login" + ) # Private: only the authenticated profile dialog reads this. if authenticated: flags["remote_user_enabled"] = bool(settings.AUTH_REMOTE_USER) + # Computed while still authenticated because the frontend + # navigates here only after the local logout POST succeeds. + if logout_url := oidc_logout_url(self.request): + flags["oidc_logout_url"] = logout_url return flags @override diff --git a/eslint.config.js b/eslint.config.js index 5ae4006be..04fafa5da 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -19,6 +19,10 @@ export default defineConfig([ "codex/templates/**/*.html", // Handled by djlint "codex/templates/pwa/serviceworker-register.js", // removes eslint-disable that it then complains about "tasks", + // Manual SSO test harness: authentik blueprints need flow-style + // custom tags (!Find [...]) and compose needs healthcheck arrays, + // both of which the yml plugin's block-style rules reject. + "test-proxy/", // "frontend", ], }, diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 1e2e4bf6f..953bd0403 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -75,7 +75,9 @@ Prefer these over bespoke markup. Each enforces the language so it can't drift. - **`AdminSection`** (`admin-section.vue`) — a titled block. `title` prop, optional `hint` prop or `#hint` slot, optional `#actions` slot (header-right, e.g. a Stop or Add button). Replaces the `.adminGroup` + `.adminGroupHeader` + - `

` markup. + `

` markup. The `sub` boolean prop nests a section inside another + AdminSection: a small uppercase overline title (h4) and an indented left rule + make the subordination visible (see the Auth tab's OIDC block). - **`AdminActionBar`** (`action-bar.vue`) — the Save / Revert (or Save / cancel) row. Primary button is `type="submit"` `tonal`; secondary is `text`. Props `saveText`, `saving`, `saveDisabled`, `revertDisabled`; emits `revert`. diff --git a/frontend/bun.lock b/frontend/bun.lock index 60a495036..64fc71610 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -15,25 +15,25 @@ "vue": "^3.5.39", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.1.0", - "vuetify": "^4.1.3", + "vuetify": "^4.1.4", "xior": "^0.8.3", }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", - "@types/node": "^26.1.0", + "@types/node": "^26.1.1", "@unhead/bundler": "^3.1.7", "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.11", - "@vue/typescript-plugin": "^3.3.6", + "@vue/typescript-plugin": "^3.3.7", "happy-dom": "^20.10.6", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.101.0", "sass-loader": "^17.0.0", "toml": "^4.1.2", "typeface-roboto": "^1.1.13", - "vite": "^8.1.3", + "vite": "^8.1.4", "vite-plugin-checker": "^0.14.4", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.1", @@ -56,7 +56,7 @@ "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], - "@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], + "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], "@devframes/hub": ["@devframes/hub@0.5.4", "", { "dependencies": { "birpc": "^4.0.0", "nostics": "^0.2.0", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyexec": "^1.2.2" }, "peerDependencies": { "devframe": "0.5.4" } }, "sha512-NZs5RFuNb6tjQd2JBsRYQT+OqE+z16Kg9/72HG4k+uJdzK90sAGtAjOwK8bsvav/9l85KGx4agfkgxnfbl313w=="], @@ -130,89 +130,89 @@ "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.100", "", { "os": "win32", "cpu": "x64" }, "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], - "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.137.0", "", { "os": "android", "cpu": "arm" }, "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.139.0", "", { "os": "android", "cpu": "arm" }, "sha512-22EsXTA3Vc7OvrF4bfT48PFln2UbxkVgrp/Tm32qLw76Dv7SmcInfClJe6yPYamnli6HiqasnESZ5ezN+X4ybg=="], - "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.137.0", "", { "os": "android", "cpu": "arm64" }, "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw=="], + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.139.0", "", { "os": "android", "cpu": "arm64" }, "sha512-uASQkZV+CUQ6xXvoMzDQyfhd8OMusdv2FyCPfAi5ZDYptesapJlw7erhpGi1Lc+U8QjQV2JUrIh7d/3su2LzmQ=="], - "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.137.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA=="], + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.139.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vuQOv2WF5pZCkdSPEExul98o853M3MCR24DpWsGrUoPJu5KcnGE34kLXY2yFwBwZwT+eN1x40Tt4q6ZzlEdNUA=="], - "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.137.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg=="], + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.139.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-zxZB8ChS+7XactZhcyxQ292P/DNiiBaPPKYiVUlb3RC0V/hme5bokNubjcmEmbW9uTfmqLTpveAdkbe66H3pGg=="], - "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.137.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw=="], + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.139.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iw2MsoCPBQwdJqywRAGsF1jEAcGoZ+DeG2LVzt5pdUvRHqajsMF46BH0rHiWoWtMwcMSia+oQYga7fHo7u7Bcw=="], - "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.137.0", "", { "os": "linux", "cpu": "arm" }, "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q=="], + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.139.0", "", { "os": "linux", "cpu": "arm" }, "sha512-SwjD/k3Y5bwGJWOH313VyaDrAuaYg1Pe+4AAXCgUKvSO5IHkj+mZ0oFcOWmB5nTuOM3uwdT+leL8h4OnFlI5hw=="], - "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.137.0", "", { "os": "linux", "cpu": "arm" }, "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ=="], + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.139.0", "", { "os": "linux", "cpu": "arm" }, "sha512-qbeygDkcvailKFhphb2YGMMXsaZIynHlPtoOrcx4NZB/tRbUKraCCMbw8dPeFtEKasfS2qWh1VnReY+Lcys1zA=="], - "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.137.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w=="], + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.139.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-SrlL02KeImKlvx/9rCeopOLH7qKI3rqKKEguK6KBwVwEGLhV/A47dW4DNigf6/LFhrwoovaiayEQryjYAI86dQ=="], - "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.137.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ=="], + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.139.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-u9e884ChAVRmIZ1jr/m46S96FoDQnruFjISLi4Y0i6Wu/JUUmIiw7+umLyXILJsPfUuqnN5BJLe23t07+Y6+IA=="], - "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.137.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ=="], + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.139.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Z9tU2b3GJfAXOdirQmz4gZQkQkVjy53i77gf91l0733MQKa/qtk73KQQE2GzDtMqim+HyjpzvemmqzBtH2IJUA=="], - "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.137.0", "", { "os": "linux", "cpu": "none" }, "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw=="], + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.139.0", "", { "os": "linux", "cpu": "none" }, "sha512-RyUbr7hzPK84YDWKs77PRYk9VBwWbsbuYsQzQWiSmLnARXTg2zntLPGfCH/1wpfUYdmGkp/6SsqTXSsYdw9Jgw=="], - "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.137.0", "", { "os": "linux", "cpu": "none" }, "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g=="], + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.139.0", "", { "os": "linux", "cpu": "none" }, "sha512-dcQhjtcDvtR8BgkUpt03Yz5SzxdzYvTigenIJOEsiSX6G2t6yybEMxGWjp0dOuYGror0BaqcZfTyXR58amCEig=="], - "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.137.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA=="], + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.139.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-iuGrxysV4rGUymdKpn7bgZQ6Vix8Bi/6D/rp71HYIzphq6NKrsBhsGOYsSZte+uBFL43tXh7Xr7TM72sGliJNA=="], - "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.137.0", "", { "os": "linux", "cpu": "x64" }, "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q=="], + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.139.0", "", { "os": "linux", "cpu": "x64" }, "sha512-NxJdZZyaa2JLLvNfH/iJQXfCNfKcPMylwY4ObMpBVmW4Nq+RyUpVVQXrMMelcQ6rwx3nuhF1Iga8n+eoAKGCIA=="], - "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.137.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg=="], + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.139.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ePxBvvtzISmSsJ0RIj8FNikSCn58i1jtccj7XR4U9Li4iSzhkFyYlnJ51cQTUwkairz8WMTD4SpKoot8RyTnQA=="], - "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.137.0", "", { "os": "none", "cpu": "arm64" }, "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA=="], + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.139.0", "", { "os": "none", "cpu": "arm64" }, "sha512-b/c2+mPXMOxG5x16n8yf9cjor/ntQQScmYnSmLEWIWJ4rfXd5dokMxx0kliSLA+YAGq6DD3K9BWi+aFXHiiV1w=="], - "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.137.0", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.5" }, "cpu": "none" }, "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA=="], + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.139.0", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-rNU0pO+/CVPC2qZ+xtX5R2cOje9Q75q3UkfgwUCPlplqxMv6Iffd5tMPGVMCLGnPINxPlmi0WPsW94HltbYvfQ=="], - "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.137.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w=="], + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.139.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-JPEmfncZfqAFiGsAN6UZSMIBqnG8wn8buywRacFOWjP+9dZwcs03SsBp4tmyjM/4l/VoH/IuThrW1Jof3OyQUw=="], - "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.137.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA=="], + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.139.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-c06dWBwEnFHof5JN7T9/ts23uATXS9czPEBO60d4xnjH2Am29Bo7OGKdVHRmcWQnw0OBxlTg9fIEXAvtcwOBfw=="], - "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.137.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA=="], + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.139.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bI3/44urQjW/D495JPXe+c9d+4Q+ONi3DvDNnzwRZYTWHZ3hAlFdmirYK4mzhTfCZiAly02EbMYir7QGU+9O2Q=="], - "@oxc-project/types": ["@oxc-project/types@0.137.0", "", {}, "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA=="], + "@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], - "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.21.3", "", { "os": "android", "cpu": "arm" }, "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg=="], + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.23.0", "", { "os": "android", "cpu": "arm" }, "sha512-8IJyWRLVAyhTfe9/TIEbQqSQnl5rUqYJrUOS6Dkr+Mq9FGHMxDGeiEmwkBqCvDP5KckpPh/GYSgbag66O6JsCw=="], - "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.21.3", "", { "os": "android", "cpu": "arm64" }, "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw=="], + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.23.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pprVojnNhHxupwTT2gdeUlkxll6XEvWWBk3oVicOSNVWQC99OBnDhMQDoirqnzrE1bScQSMS2JgPpqdlrhz/Fg=="], - "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.21.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg=="], + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.23.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mbIrWIMAJeytyee36OyUP5XH92TP7FaKaQ2m5AjokKy7STgjrhRt7SMXqpqLjhGm6Xn721Xmsg6H3Rtd9YQETw=="], - "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.21.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg=="], + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.23.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-UnIphmZ1LazUCr9DXWaKYWtKDefPMbgLsywaoYxRqVCNHhq4MM6d2q1Nz1i9Vzxt5i+cE2nRUYpAUHr/lijNYA=="], - "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.21.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw=="], + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.23.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-aaZ/cSEYFkSxgS2hOrobT6RQcsWNviOX8dW6CEkVx2/UYkAf9MeHbjl3W0usWV53rVV//ndBdn2nb1y7jsu4lw=="], - "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3", "", { "os": "linux", "cpu": "arm" }, "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ=="], + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.23.0", "", { "os": "linux", "cpu": "arm" }, "sha512-IoJLvO5SjLSVMaq83BNTrPCb1FppvoJc1IhZ5CoUVl3PykUBku7D+LK1j0GSurhJcIc6zfjghsvaZNpq5ev6Mg=="], - "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.21.3", "", { "os": "linux", "cpu": "arm" }, "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ=="], + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.23.0", "", { "os": "linux", "cpu": "arm" }, "sha512-vskFpwg44T/LFsfjSCnVZ5ygcuqzPC1yUzVEiKa8BgHAQz0+QLQQW3EGWLPVi8EXFghzjR4EtgPBtOhCjU4jdw=="], - "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.21.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ=="], + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.23.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-//TcHVhrChyw5RYtgts6WO7KcWq9387c1Z5Zvhqpk/ktAbyaRYgBZrpSY1GDCFq50ASt6B6jhh+JxB1rB45IAg=="], - "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.21.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw=="], + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.23.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZFqlwiTf7CXLLSGyAR9tYiO33LiaeIEXW+xm42d8mnUGpDgPltyrCGYtQezyMMEXvjhOgCz1X+i7sbDTJEx+bg=="], - "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.21.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A=="], + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.23.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-oZ5LeN5+H1R19dRjTAxKrxQguH+AsemHcnthEfFxf4OjmBSty2doHLeSmMunKy3zpTHJQ3lh3Af+dNS+W6dYeA=="], - "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.21.3", "", { "os": "linux", "cpu": "none" }, "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og=="], + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.23.0", "", { "os": "linux", "cpu": "none" }, "sha512-O4ciFDyX5ebQd0qkb1bjAIg8IEfiLT03GbSeylwlwlUMK9KwBWaALwrxSbc0Msaz4U6iPj+T9eRXpD5mxBfmvA=="], - "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.21.3", "", { "os": "linux", "cpu": "none" }, "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw=="], + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.23.0", "", { "os": "linux", "cpu": "none" }, "sha512-P3o8Y9kISYjcxadmbO+94ThRwLhwGuDAbA7dcdd4+YLpfeF+mmobz8fXf4NmSdfSqjyRSkceJDBRZha9NVYkiQ=="], - "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.21.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q=="], + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.23.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oj03m1E3RmTFczKhcKJDzHaEDKJnPIsDcQFVxBJsSdXGSuIPdt5TvcM332FfMQgzI6yDJqyl4InrnFfXrmUTKQ=="], - "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.21.3", "", { "os": "linux", "cpu": "x64" }, "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q=="], + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.23.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BqJxbSC8FdP7mSuSpRePTGHm0hXWV+dfz//f7SjsteZncLaBgWTBmi/OZNv7sX6CyG/Pt/eJkPorP+DkMOhMwQ=="], - "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.21.3", "", { "os": "linux", "cpu": "x64" }, "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA=="], + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.23.0", "", { "os": "linux", "cpu": "x64" }, "sha512-utmw+VmUrW4K8LI5/6jhg4aGYKJHOIjQ9syYOOA6pF3w7haKu4r4enTe2U0C04/HbUvkq/Zif43xFsKW1Pnq9w=="], - "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.21.3", "", { "os": "none", "cpu": "arm64" }, "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg=="], + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.23.0", "", { "os": "none", "cpu": "arm64" }, "sha512-V6lbRrthHa4TbvsLjPtg+EkXT1tRY+s4I8rYLXUfiHlZzGx3sLv1EH9CEOOevjvUYHLsbe/gqCIc73XnQfPb9A=="], - "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.21.3", "", { "dependencies": { "@emnapi/core": "1.11.0", "@emnapi/runtime": "1.11.0", "@napi-rs/wasm-runtime": "^1.1.5" }, "cpu": "none" }, "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA=="], + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.23.0", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-gRoOxQPdnAmIAjxcuQNBxfihvx+wjTaQM/9/eP12xwnGNawOG/+Zz9RHN4WNSxT45b5CrscK4NB8aPh+oZQXAQ=="], - "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.21.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg=="], + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.23.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-CgTGMYsJVe1eUiCdJTpGw21svXw79ITsemN1h0hcNkiswasDbN5MoibSLY+gRMWP5syfEz5iffrjZnwEP8xeUA=="], - "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.21.3", "", { "os": "win32", "cpu": "x64" }, "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q=="], + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.23.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gUGJpr+Rn6zMxm5juApV0K3U845i8t47o8k+rbO0BHbi4PoJIfSPeQmrE2dgohQm2g5k6iviNFyXCGqvmaYUpw=="], "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], @@ -246,71 +246,71 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@swc/core": ["@swc/core@1.15.41", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.26" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.41", "@swc/core-darwin-x64": "1.15.41", "@swc/core-linux-arm-gnueabihf": "1.15.41", "@swc/core-linux-arm64-gnu": "1.15.41", "@swc/core-linux-arm64-musl": "1.15.41", "@swc/core-linux-ppc64-gnu": "1.15.41", "@swc/core-linux-s390x-gnu": "1.15.41", "@swc/core-linux-x64-gnu": "1.15.41", "@swc/core-linux-x64-musl": "1.15.41", "@swc/core-win32-arm64-msvc": "1.15.41", "@swc/core-win32-ia32-msvc": "1.15.41", "@swc/core-win32-x64-msvc": "1.15.41" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-03nQq/082QRJJiOvp3FGbgxTGyyxMxohPTjhk/W9bD2J0tk4ukITI7goOhOO2WbaHn/lsPmo/zf8+DIXhwpgYQ=="], + "@swc/core": ["@swc/core@1.15.43", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.27" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.43", "@swc/core-darwin-x64": "1.15.43", "@swc/core-linux-arm-gnueabihf": "1.15.43", "@swc/core-linux-arm64-gnu": "1.15.43", "@swc/core-linux-arm64-musl": "1.15.43", "@swc/core-linux-ppc64-gnu": "1.15.43", "@swc/core-linux-s390x-gnu": "1.15.43", "@swc/core-linux-x64-gnu": "1.15.43", "@swc/core-linux-x64-musl": "1.15.43", "@swc/core-win32-arm64-msvc": "1.15.43", "@swc/core-win32-ia32-msvc": "1.15.43", "@swc/core-win32-x64-msvc": "1.15.43" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw=="], - "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kREh6J5paQFvP3i7f/4FbqRNOJREutVFVOkder4GVyCBQ39YmER55cW/y1NNjwrchzFqgYswFn0mMDCqbqKzrw=="], + "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.43", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA=="], - "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.15.41", "", { "os": "darwin", "cpu": "x64" }, "sha512-N8B56ESFazZAWZyIkecADSPCwlLEinW7QLMEeotCpv4J7VXwfH+OLkmRL8o96UZ+1355fwHxDTS6/wK7yucvkA=="], + "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.15.43", "", { "os": "darwin", "cpu": "x64" }, "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ=="], - "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.15.41", "", { "os": "linux", "cpu": "arm" }, "sha512-6XrId2fyle0mS5xxON8rU84mPd2Cq1kDJRj+4BnQKTd7u+2kSA6Ww+JkOP0iTNqOqt9OXhPOEAjBHAuonWcdCg=="], + "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.15.43", "", { "os": "linux", "cpu": "arm" }, "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA=="], - "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.15.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-ynLIarxlkVnqHn1D0fKOVht6mNU5ks6lrH+MY3kkS+XFaGGgDxFZVjWKJlkYTKm3RCvBTfA8Ng5fLufXheMRKQ=="], + "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.15.43", "", { "os": "linux", "cpu": "arm64" }, "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw=="], - "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.15.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-dXu/5vd4gh8symyhRF+4G7gOPkjmb4pONhh7sl+6GSiW0LOKZlfu5kXmyFbTz9smOT7jgr002qY9b1nujjXt2A=="], + "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.15.43", "", { "os": "linux", "cpu": "arm64" }, "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ=="], - "@swc/core-linux-ppc64-gnu": ["@swc/core-linux-ppc64-gnu@1.15.41", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XGO6zVPXoPE0gf/XnI4jBbafNT13AYgoh6ns0JCSdOetI/kqVf0vhpz7NuNgAzZrMVCsmieqjPoTwViDgh4mOQ=="], + "@swc/core-linux-ppc64-gnu": ["@swc/core-linux-ppc64-gnu@1.15.43", "", { "os": "linux", "cpu": "ppc64" }, "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg=="], - "@swc/core-linux-s390x-gnu": ["@swc/core-linux-s390x-gnu@1.15.41", "", { "os": "linux", "cpu": "s390x" }, "sha512-0WUglRwyZtW+iMi7J3iFdrCxreZZIKf4egTwEQfIYRsqFax69A0OrFj+NIoFSE03xBT/IFRrg+S8K6f9Ky+4hA=="], + "@swc/core-linux-s390x-gnu": ["@swc/core-linux-s390x-gnu@1.15.43", "", { "os": "linux", "cpu": "s390x" }, "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA=="], - "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.15.41", "", { "os": "linux", "cpu": "x64" }, "sha512-VxkuQK59c0tHm6uJZCUrS3cyA2JhGGfdU6e41SZz0x/JS+4Sm7C1mIc97In14vkZJopEt7yXA2TouCqZDSygEA=="], + "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.15.43", "", { "os": "linux", "cpu": "x64" }, "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg=="], - "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.15.41", "", { "os": "linux", "cpu": "x64" }, "sha512-/0qXIu1ZxggLuovLb22vFfKHq2AA4n6Whw5UwmVCHk4pkw7KWnPIQpMCEqUMPsNkFJig7PPp/TSYFu8ZEb2rtQ=="], + "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.15.43", "", { "os": "linux", "cpu": "x64" }, "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ=="], - "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.15.41", "", { "os": "win32", "cpu": "arm64" }, "sha512-Y481sMNZM6rECh9VO4+y26N1lWEDAyxnBZskUf37fl90uHE946VHfmiVQWT0uMFOhyJJFovGTRuF4W82dwewUg=="], + "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.15.43", "", { "os": "win32", "cpu": "arm64" }, "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw=="], - "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.15.41", "", { "os": "win32", "cpu": "ia32" }, "sha512-BAchBD5qeUzy3hiPSLJtaaoSm4blCLyYffOF1bGE4ETcV+OisqjUAwDQMJj++4bTpvMCDzwC+Bj3PmQyBCtscw=="], + "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.15.43", "", { "os": "win32", "cpu": "ia32" }, "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g=="], - "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.15.41", "", { "os": "win32", "cpu": "x64" }, "sha512-WOkA+fJ/ViVBQDsSV9JC52NACTe5PhlurA6viASDZGb7HR3KS01ZG7RZ+Bg6SVQFIoq3gSbTsskQVe6EbHFAYw=="], + "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.15.43", "", { "os": "win32", "cpu": "x64" }, "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg=="], "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/types": ["@swc/types@0.1.27", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], @@ -332,13 +332,13 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@unhead/bundler": ["@unhead/bundler@3.1.7", "", { "dependencies": { "@vitejs/devtools-kit": "^0.3.3", "magic-string": "^0.30.21", "oxc-parser": "^0.137.0", "oxc-walker": "^1.0.0", "ufo": "^1.6.4", "unplugin": "^3.2.0" }, "peerDependencies": { "@unhead/cli": "^3.1.7", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "rolldown": ">=1.0.0-beta.0", "unhead": "^3.1.7", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "esbuild", "lightningcss", "rolldown", "vite", "webpack"] }, "sha512-yJPDwKZ3NOSmzB1a4ea5MEbeaXBq9EvOcGnav46SsPnslE7jq7QKkx42UQzxJk0q6yFPGdCZbFae2IO1HTtLPA=="], + "@unhead/bundler": ["@unhead/bundler@3.1.8", "", { "dependencies": { "@vitejs/devtools-kit": "^0.3.4", "magic-string": "^0.30.21", "oxc-parser": "^0.139.0", "oxc-walker": "^1.0.0", "ufo": "^1.6.4", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.1.8", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "rolldown": ">=1.0.0-beta.0", "unhead": "^3.1.8", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "esbuild", "lightningcss", "rolldown", "vite", "webpack"] }, "sha512-mVhM6gmvAgaE9UgENuFSvqpCghAorY8PbQgV+n3y6VyOLso/KbCOd4hm4cmI/Q/IETQZTDSZBIjVVS98mgYwdA=="], - "@unhead/vue": ["@unhead/vue@3.1.7", "", { "dependencies": { "@unhead/bundler": "3.1.7", "hookable": "^6.1.1", "unhead": "3.1.7", "unplugin": "^3.2.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-UK/hy7RT9qB4TBsUfRwXGUi5p3J6O6MtA/971emkKC3G+YpezaF7cPdxJOG9o+mFtqLIjkKd+QjRg8SiJLmBGg=="], + "@unhead/vue": ["@unhead/vue@3.1.8", "", { "dependencies": { "@unhead/bundler": "3.1.8", "hookable": "^6.1.1", "unhead": "3.1.8", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-mmcvZ2gchM+sG+M8HjOl+MAHRhyqTEBCrvsR3ymjTEPQDhHGQJqKwW1i5UJzYa2xNubj7AmSMOQF2OvMGIuuQQ=="], "@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.1", "", { "peerDependencies": { "valibot": "^1.4.0" } }, "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A=="], - "@vitejs/devtools-kit": ["@vitejs/devtools-kit@0.3.3", "", { "dependencies": { "@devframes/hub": "^0.5.4", "birpc": "^4.0.0", "devframe": "^0.5.4", "mlly": "^1.8.2", "nostics": "^0.3.0", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyexec": "^1.2.4" }, "peerDependencies": { "vite": "*" } }, "sha512-noXyK0szYxh8ALsA7PIoFANOWx13K9NoMKqk3J1pCwGMdnhxWgSqtbhki+/REoK4itbxIFUfc0EwqsoZIKiqyg=="], + "@vitejs/devtools-kit": ["@vitejs/devtools-kit@0.3.4", "", { "dependencies": { "@devframes/hub": "^0.5.4", "birpc": "^4.0.0", "devframe": "^0.5.4", "mlly": "^1.8.2", "nostics": "^1.1.4", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyexec": "^1.2.4" }, "peerDependencies": { "vite": "*" } }, "sha512-QHvb3wF0KZxvbfEplzzdGenB/dWoPBQlUnw3VVBm5qUYTdoud8rOoem9QI6h/+7a+iwy88LmLigxbSXbbv2Uyg=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.7", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg=="], @@ -364,19 +364,19 @@ "@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.38", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.38", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ=="], + "@vue/compiler-core": ["@vue/compiler-core@3.5.39", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.39", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw=="], - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.38", "", { "dependencies": { "@vue/compiler-core": "3.5.38", "@vue/shared": "3.5.38" } }, "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw=="], + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.39", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/compiler-core": "3.5.39", "@vue/compiler-dom": "3.5.39", "@vue/compiler-ssr": "3.5.39", "@vue/shared": "3.5.39", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.15", "source-map-js": "^1.2.1" } }, "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg=="], - "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.38", "", { "dependencies": { "@vue/compiler-dom": "3.5.38", "@vue/shared": "3.5.38" } }, "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA=="], + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="], - "@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="], + "@vue/devtools-api": ["@vue/devtools-api@7.7.10", "", { "dependencies": { "@vue/devtools-kit": "^7.7.10" } }, "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA=="], - "@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="], + "@vue/devtools-kit": ["@vue/devtools-kit@7.7.10", "", { "dependencies": { "@vue/devtools-shared": "^7.7.10", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw=="], - "@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="], + "@vue/devtools-shared": ["@vue/devtools-shared@7.7.10", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ=="], "@vue/language-core": ["@vue/language-core@3.3.7", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw=="], @@ -386,7 +386,7 @@ "@vue/runtime-dom": ["@vue/runtime-dom@3.5.39", "", { "dependencies": { "@vue/reactivity": "3.5.39", "@vue/runtime-core": "3.5.39", "@vue/shared": "3.5.39", "csstype": "^3.2.3" } }, "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww=="], - "@vue/server-renderer": ["@vue/server-renderer@3.5.38", "", { "dependencies": { "@vue/compiler-ssr": "3.5.38", "@vue/shared": "3.5.38" }, "peerDependencies": { "vue": "3.5.38" } }, "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw=="], + "@vue/server-renderer": ["@vue/server-renderer@3.5.39", "", { "dependencies": { "@vue/compiler-ssr": "3.5.39", "@vue/shared": "3.5.39" }, "peerDependencies": { "vue": "3.5.39" } }, "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw=="], "@vue/shared": ["@vue/shared@3.5.39", "", {}, "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA=="], @@ -428,7 +428,7 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="], "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], @@ -496,15 +496,15 @@ "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + "es-module-lexer": ["es-module-lexer@2.3.0", "", {}, "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw=="], - "es-toolkit": ["es-toolkit@1.47.1", "", {}, "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q=="], + "es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.5.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ=="], + "eslint": ["eslint@10.7.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ=="], "eslint-import-resolver-oxc": ["eslint-import-resolver-oxc@0.15.0", "", { "dependencies": { "stable-hash-x": "^0.1.1" }, "peerDependencies": { "@rspack/cli": "*", "@rspack/core": "*", "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*", "oxc-resolver": "*", "vite": "*", "webpack": "*", "webpack-cli": "*", "webpack-merge": "*" }, "optionalPeers": ["@rspack/cli", "@rspack/core", "eslint-plugin-import", "eslint-plugin-import-x", "vite", "webpack", "webpack-cli", "webpack-merge"] }, "sha512-qod5T3t3PbJdMZX3GYb8rdhD/UBK9rJCSE2b2+ttXtVdBm39CHgyCht1pMXj/1Z0ko1wNme+az4Z5chowgXD5Q=="], @@ -524,9 +524,9 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], - "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "exsolve": ["exsolve@1.1.0", "", {}, "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -568,7 +568,7 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "immutable": ["immutable@5.1.6", "", {}, "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ=="], + "immutable": ["immutable@5.1.9", "", {}, "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], @@ -594,7 +594,7 @@ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js", "js-beautify": "js/bin/js-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="], + "js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "js-beautify": "js/bin/js-beautify.js", "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="], "js-cookie": ["js-cookie@3.0.8", "", {}, "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw=="], @@ -664,7 +664,7 @@ "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], - "nanoid": ["nanoid@3.3.13", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q=="], + "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], @@ -674,7 +674,7 @@ "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], - "nostics": ["nostics@0.3.0", "", { "dependencies": { "magic-string": "^0.30.21", "oxc-parser": "^0.132.0", "unplugin": "^3.0.0" } }, "sha512-tP0hvxK4n2hZO9kK5Az7dLXIFukANDpcdtMae3tvtyRQun48gZIBVyXCJc8zk+qsdOpY7ngashH0ivQGOGzmeg=="], + "nostics": ["nostics@1.1.4", "", {}, "sha512-U4FApICSLCQ0dYiN59pxUCEZC053palQXi57yLaNdMaL6TBY4C4q3qZrJKUGcor2dGv8EhEwt8y5KvU2bo2kFg=="], "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], @@ -686,9 +686,9 @@ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "oxc-parser": ["oxc-parser@0.137.0", "", { "dependencies": { "@oxc-project/types": "^0.137.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.137.0", "@oxc-parser/binding-android-arm64": "0.137.0", "@oxc-parser/binding-darwin-arm64": "0.137.0", "@oxc-parser/binding-darwin-x64": "0.137.0", "@oxc-parser/binding-freebsd-x64": "0.137.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0", "@oxc-parser/binding-linux-arm64-gnu": "0.137.0", "@oxc-parser/binding-linux-arm64-musl": "0.137.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0", "@oxc-parser/binding-linux-riscv64-musl": "0.137.0", "@oxc-parser/binding-linux-s390x-gnu": "0.137.0", "@oxc-parser/binding-linux-x64-gnu": "0.137.0", "@oxc-parser/binding-linux-x64-musl": "0.137.0", "@oxc-parser/binding-openharmony-arm64": "0.137.0", "@oxc-parser/binding-wasm32-wasi": "0.137.0", "@oxc-parser/binding-win32-arm64-msvc": "0.137.0", "@oxc-parser/binding-win32-ia32-msvc": "0.137.0", "@oxc-parser/binding-win32-x64-msvc": "0.137.0" } }, "sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg=="], + "oxc-parser": ["oxc-parser@0.139.0", "", { "dependencies": { "@oxc-project/types": "^0.139.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.139.0", "@oxc-parser/binding-android-arm64": "0.139.0", "@oxc-parser/binding-darwin-arm64": "0.139.0", "@oxc-parser/binding-darwin-x64": "0.139.0", "@oxc-parser/binding-freebsd-x64": "0.139.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.139.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.139.0", "@oxc-parser/binding-linux-arm64-gnu": "0.139.0", "@oxc-parser/binding-linux-arm64-musl": "0.139.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.139.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.139.0", "@oxc-parser/binding-linux-riscv64-musl": "0.139.0", "@oxc-parser/binding-linux-s390x-gnu": "0.139.0", "@oxc-parser/binding-linux-x64-gnu": "0.139.0", "@oxc-parser/binding-linux-x64-musl": "0.139.0", "@oxc-parser/binding-openharmony-arm64": "0.139.0", "@oxc-parser/binding-wasm32-wasi": "0.139.0", "@oxc-parser/binding-win32-arm64-msvc": "0.139.0", "@oxc-parser/binding-win32-ia32-msvc": "0.139.0", "@oxc-parser/binding-win32-x64-msvc": "0.139.0" } }, "sha512-cf1TKZN+zc0lwqigeyXKzKVk5+vNRe99Or2+wVJsXLdlhJgC+gsIniYDfj/ZEBzCJ8Xm21ZG6YtMbR262CcS2w=="], - "oxc-resolver": ["oxc-resolver@11.21.3", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.21.3", "@oxc-resolver/binding-android-arm64": "11.21.3", "@oxc-resolver/binding-darwin-arm64": "11.21.3", "@oxc-resolver/binding-darwin-x64": "11.21.3", "@oxc-resolver/binding-freebsd-x64": "11.21.3", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", "@oxc-resolver/binding-linux-x64-musl": "11.21.3", "@oxc-resolver/binding-openharmony-arm64": "11.21.3", "@oxc-resolver/binding-wasm32-wasi": "11.21.3", "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" } }, "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA=="], + "oxc-resolver": ["oxc-resolver@11.23.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.23.0", "@oxc-resolver/binding-android-arm64": "11.23.0", "@oxc-resolver/binding-darwin-arm64": "11.23.0", "@oxc-resolver/binding-darwin-x64": "11.23.0", "@oxc-resolver/binding-freebsd-x64": "11.23.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.23.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.23.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.23.0", "@oxc-resolver/binding-linux-arm64-musl": "11.23.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.23.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.23.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.23.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.23.0", "@oxc-resolver/binding-linux-x64-gnu": "11.23.0", "@oxc-resolver/binding-linux-x64-musl": "11.23.0", "@oxc-resolver/binding-openharmony-arm64": "11.23.0", "@oxc-resolver/binding-wasm32-wasi": "11.23.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.23.0", "@oxc-resolver/binding-win32-x64-msvc": "11.23.0" } }, "sha512-f0+l598CJMOLnYPXsXxttJALH0ljtivdRMKtvHhxRuWa5FYmw5+qODARl8oYjMC/brpzKcrpdORsOBrTqhBZ9A=="], "oxc-walker": ["oxc-walker@1.0.0", "", { "dependencies": { "magic-regexp": "^0.11.0" }, "peerDependencies": { "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0" }, "optionalPeers": ["oxc-parser", "rolldown"] }, "sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ=="], @@ -714,7 +714,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], "pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="], @@ -744,7 +744,7 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], - "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], + "rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.0.1", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^18.0.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg=="], @@ -758,7 +758,7 @@ "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], - "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -774,13 +774,13 @@ "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], - "srvx": ["srvx@0.11.16", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw=="], + "srvx": ["srvx@0.11.21", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-GWTHjKMeekX8CwJf4VU9Oo6mJpSGaflGMddbCvR+Cmmh9sslRMiGbAoqqZacE0r1ncARh6buCEETr2W52F8b1w=="], "stable-hash-x": ["stable-hash-x@0.1.1", "", {}, "sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -860,19 +860,19 @@ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "unhead": ["unhead@3.1.7", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.2.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-Db1M8yQmxdzhQFMmlZ6cr/ZlpGPRabTMDPBE4GGBv69TYUNP6jV8z54H8PTU08gMsWXVtqPBs9VwCldjrqknGw=="], + "unhead": ["unhead@3.1.8", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-8YcZ88tTvVV+CxYsqKg9O2E0h/tR7PsUHQ22tc4xodVOoUcz/Pl5OHC3Ab3IKhLIy/8OcyAWoS4LCPN7kGvwSg=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], - "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], + "unplugin-utils": ["unplugin-utils@0.3.2", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.4" } }, "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA=="], "upath": ["upath@2.0.1", "", {}, "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "valibot": ["valibot@1.4.1", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g=="], + "valibot": ["valibot@1.4.2", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg=="], "vite": ["vite@8.1.4", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.16", "rolldown": "~1.1.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ=="], @@ -892,7 +892,7 @@ "vue-component-meta": ["vue-component-meta@3.3.7", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.7", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-rqMY/EGOHGLcUbCb1VRZNBlDpUS12O0kMJL133Z3TrWpKmwtdGNLALTwEX1jBskoV+ibQp/aWEW+87a79FexCA=="], - "vue-component-type-helpers": ["vue-component-type-helpers@3.3.5", "", {}, "sha512-Fe1jyPJoUGpJOYKOri44jduR7My4yYINOMJISuMAbmrs+L5LbIDUc8NTWZYY3EJLK0yPLuCmcd5zoCsE4k2/KA=="], + "vue-component-type-helpers": ["vue-component-type-helpers@3.3.7", "", {}, "sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg=="], "vue-eslint-parser": ["vue-eslint-parser@10.4.1", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", "eslint-visitor-keys": "^4.2.0 || ^5.0.0", "espree": "^10.3.0 || ^11.0.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA=="], @@ -932,11 +932,11 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "@babel/generator/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": "./bin/babel-parser.js" }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], + "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], "@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.2", "", {}, "sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA=="], + "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], "@devframes/hub/nostics": ["nostics@0.2.0", "", { "dependencies": { "magic-string": "^0.30.21", "oxc-parser": "^0.132.0", "unplugin": "^3.0.0" } }, "sha512-/WQpI46UMbqvy1okYb+V+9wW3J8/m6GJ33wm691n/tyi6YtJiZ6ssJjENAU7y4evfYrrgYN9HllKDzPvffil1w=="], @@ -946,52 +946,18 @@ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], - - "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], - - "@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - - "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - - "@types/ws/@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], - - "@vue-macros/common/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.38", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/compiler-core": "3.5.38", "@vue/compiler-dom": "3.5.38", "@vue/compiler-ssr": "3.5.38", "@vue/shared": "3.5.38", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.15", "source-map-js": "^1.2.1" } }, "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg=="], - - "@vue/compiler-core/@vue/shared": ["@vue/shared@3.5.38", "", {}, "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug=="], - "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@vue/compiler-dom/@vue/shared": ["@vue/shared@3.5.38", "", {}, "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug=="], - - "@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.39", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.39", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw=="], - - "@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], - - "@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="], - "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@vue/compiler-sfc/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], - - "@vue/compiler-ssr/@vue/shared": ["@vue/shared@3.5.38", "", {}, "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug=="], - "@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], "@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], - "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], - - "@vue/language-core/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - - "@vue/server-renderer/@vue/shared": ["@vue/shared@3.5.38", "", {}, "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug=="], - "ast-walker-scope/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "buffer-image-size/@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], - "cross-spawn/path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "devframe/nostics": ["nostics@0.2.0", "", { "dependencies": { "magic-string": "^0.30.21", "oxc-parser": "^0.132.0", "unplugin": "^3.0.0" } }, "sha512-/WQpI46UMbqvy1okYb+V+9wW3J8/m6GJ33wm691n/tyi6YtJiZ6ssJjENAU7y4evfYrrgYN9HllKDzPvffil1w=="], @@ -1004,37 +970,15 @@ "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "happy-dom/@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], - - "magic-regexp/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], - "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "nostics/oxc-parser": ["oxc-parser@0.132.0", "", { "dependencies": { "@oxc-project/types": "^0.132.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.132.0", "@oxc-parser/binding-android-arm64": "0.132.0", "@oxc-parser/binding-darwin-arm64": "0.132.0", "@oxc-parser/binding-darwin-x64": "0.132.0", "@oxc-parser/binding-freebsd-x64": "0.132.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", "@oxc-parser/binding-linux-arm64-musl": "0.132.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-musl": "0.132.0", "@oxc-parser/binding-openharmony-arm64": "0.132.0", "@oxc-parser/binding-wasm32-wasi": "0.132.0", "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", "@oxc-parser/binding-win32-x64-msvc": "0.132.0" } }, "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg=="], - - "nostics/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], - - "rolldown/@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - - "vite/rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], - - "vitest/vite": ["vite@8.1.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA=="], - - "vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], - - "vue/@vue/server-renderer": ["@vue/server-renderer@3.5.39", "", { "dependencies": { "@vue/compiler-ssr": "3.5.39", "@vue/shared": "3.5.39" }, "peerDependencies": { "vue": "3.5.39" } }, "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw=="], - - "vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.3", "", { "dependencies": { "@vue/devtools-kit": "^8.1.3" } }, "sha512-73NMCvxXh8Hyozc/jiwqTFWVcCMyi11U1zmrq4DoukQJnuo8JHt6FsNu4HdeUDa8SpIp5vb7Q22GWgIq0efsXg=="], - - "vue-router/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], + "vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.5", "", { "dependencies": { "@vue/devtools-kit": "^8.1.5" } }, "sha512-YJipMVAKe5wT5CWf5kTYCaNV7NMNjFVxJkIkJaJ4W/nCxEBzlZzrOsYKeCymdCrFZmBS/+wTWFoUs3Jf/Q6XSQ=="], "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -1046,121 +990,21 @@ "@devframes/hub/nostics/oxc-parser": ["oxc-parser@0.132.0", "", { "dependencies": { "@oxc-project/types": "^0.132.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.132.0", "@oxc-parser/binding-android-arm64": "0.132.0", "@oxc-parser/binding-darwin-arm64": "0.132.0", "@oxc-parser/binding-darwin-x64": "0.132.0", "@oxc-parser/binding-freebsd-x64": "0.132.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", "@oxc-parser/binding-linux-arm64-musl": "0.132.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-musl": "0.132.0", "@oxc-parser/binding-openharmony-arm64": "0.132.0", "@oxc-parser/binding-wasm32-wasi": "0.132.0", "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", "@oxc-parser/binding-win32-x64-msvc": "0.132.0" } }, "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg=="], - "@devframes/hub/nostics/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "@rolldown/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - - "@types/ws/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - - "@vue-macros/common/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.38", "", {}, "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug=="], - - "@vue-macros/common/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - - "@vue-macros/common/@vue/compiler-sfc/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], - - "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.39", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.39", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw=="], - "ast-walker-scope/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], - "buffer-image-size/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - "devframe/nostics/oxc-parser": ["oxc-parser@0.132.0", "", { "dependencies": { "@oxc-project/types": "^0.132.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.132.0", "@oxc-parser/binding-android-arm64": "0.132.0", "@oxc-parser/binding-darwin-arm64": "0.132.0", "@oxc-parser/binding-darwin-x64": "0.132.0", "@oxc-parser/binding-freebsd-x64": "0.132.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", "@oxc-parser/binding-linux-arm64-musl": "0.132.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-gnu": "0.132.0", "@oxc-parser/binding-linux-x64-musl": "0.132.0", "@oxc-parser/binding-openharmony-arm64": "0.132.0", "@oxc-parser/binding-wasm32-wasi": "0.132.0", "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", "@oxc-parser/binding-win32-x64-msvc": "0.132.0" } }, "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg=="], - "devframe/nostics/unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], + "editorconfig/minimatch/brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], - "editorconfig/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - - "happy-dom/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - "nostics/oxc-parser/@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.132.0", "", { "os": "android", "cpu": "arm" }, "sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw=="], - - "nostics/oxc-parser/@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.132.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg=="], - - "nostics/oxc-parser/@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.132.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ=="], - - "nostics/oxc-parser/@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.132.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg=="], - - "nostics/oxc-parser/@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.132.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.132.0", "", { "os": "linux", "cpu": "arm" }, "sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.132.0", "", { "os": "linux", "cpu": "arm" }, "sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.132.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.132.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.132.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.132.0", "", { "os": "linux", "cpu": "none" }, "sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.132.0", "", { "os": "linux", "cpu": "none" }, "sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.132.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.132.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg=="], - - "nostics/oxc-parser/@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.132.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ=="], - - "nostics/oxc-parser/@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.132.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ=="], - - "nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.132.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw=="], - - "nostics/oxc-parser/@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.132.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw=="], - - "nostics/oxc-parser/@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.132.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA=="], - - "nostics/oxc-parser/@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.132.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ=="], - - "nostics/oxc-parser/@oxc-project/types": ["@oxc-project/types@0.132.0", "", {}, "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ=="], - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], - - "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], - - "vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], - - "vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], - - "vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], - - "vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], - - "vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], - - "vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], - - "vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], - - "vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], - - "vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], - - "vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], - - "vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], - - "vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], - - "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], - - "vitest/vite/rolldown": ["rolldown@1.1.3", "", { "dependencies": { "@oxc-project/types": "=0.137.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.3", "@rolldown/binding-darwin-arm64": "1.1.3", "@rolldown/binding-darwin-x64": "1.1.3", "@rolldown/binding-freebsd-x64": "1.1.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", "@rolldown/binding-linux-arm64-gnu": "1.1.3", "@rolldown/binding-linux-arm64-musl": "1.1.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.3", "@rolldown/binding-linux-s390x-gnu": "1.1.3", "@rolldown/binding-linux-x64-gnu": "1.1.3", "@rolldown/binding-linux-x64-musl": "1.1.3", "@rolldown/binding-openharmony-arm64": "1.1.3", "@rolldown/binding-wasm32-wasi": "1.1.3", "@rolldown/binding-win32-arm64-msvc": "1.1.3", "@rolldown/binding-win32-x64-msvc": "1.1.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g=="], - - "vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.3", "", { "dependencies": { "@vue/devtools-shared": "^8.1.3", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA=="], - - "vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.39", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.39", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw=="], - - "vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="], + "vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.1.5", "", { "dependencies": { "@vue/devtools-shared": "^8.1.5", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1208,8 +1052,6 @@ "@devframes/hub/nostics/oxc-parser/@oxc-project/types": ["@oxc-project/types@0.132.0", "", {}, "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ=="], - "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "devframe/nostics/oxc-parser/@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.132.0", "", { "os": "android", "cpu": "arm" }, "sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw=="], "devframe/nostics/oxc-parser/@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.132.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg=="], @@ -1256,50 +1098,12 @@ "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - - "nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], - - "vitest/vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g=="], - - "vitest/vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw=="], - - "vitest/vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw=="], - - "vitest/vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg=="], - - "vitest/vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g=="], - - "vitest/vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.3", "", { "os": "none", "cpu": "arm64" }, "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ=="], - - "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.3", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg=="], - - "vitest/vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g=="], - - "vitest/vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA=="], - - "vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.1.3", "", {}, "sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA=="], + "vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.1.5", "", {}, "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ=="], "vue-router/@vue/devtools-api/@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "vue-router/@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], - "vue/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@devframes/hub/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@devframes/hub/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -1308,16 +1112,8 @@ "devframe/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], - - "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], - "@devframes/hub/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], "devframe/nostics/oxc-parser/@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - - "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], } } diff --git a/frontend/package.json b/frontend/package.json index 9613f9113..e6cade836 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "test:ci": "vitest run --reporter verbose --update" }, "dependencies": { - "@unhead/vue": "^3.1.7", + "@unhead/vue": "^3.1.8", "@vueuse/core": "^14.3.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", @@ -34,7 +34,7 @@ "@mdi/js": "^7.4.47", "@pinia/testing": "^1.0.3", "@types/node": "^26.1.1", - "@unhead/bundler": "^3.1.7", + "@unhead/bundler": "^3.1.8", "@vitejs/plugin-vue": "^6.0.7", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.7", diff --git a/frontend/src/api/v4/admin.js b/frontend/src/api/v4/admin.js index f8c8bb842..b23fa5a5d 100644 --- a/frontend/src/api/v4/admin.js +++ b/frontend/src/api/v4/admin.js @@ -212,6 +212,15 @@ export const updateEmailSettings = (data) => export const sendEmailTest = (data) => HTTP.post("/admin/email-settings/test", data); +export const getOidcSettings = () => + HTTP.get("/admin/oidc-settings", { params: { ts: Date.now() } }); + +export const updateOidcSettings = (data) => + HTTP.put("/admin/oidc-settings", data); + +export const testOidcConnection = (data) => + HTTP.post("/admin/oidc-settings/test", data); + export const getThrottleSettings = () => HTTP.get("/admin/throttle-settings", { params: { ts: Date.now() } }); diff --git a/frontend/src/components/admin/tabs/admin-section.scss b/frontend/src/components/admin/tabs/admin-section.scss index c26b48212..3d71893ea 100644 --- a/frontend/src/components/admin/tabs/admin-section.scss +++ b/frontend/src/components/admin/tabs/admin-section.scss @@ -34,6 +34,31 @@ font-size: d.$text-title; } +// Sub-section title (AdminSection's `sub` variant): a small overline so +// it reads unmistakably subordinate to the h3 section that contains it. +.adminGroupHeader > h4 { + margin: 0; + font-size: d.$text-meta; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgb(var(--v-theme-textSecondary)); +} + +// Nested sub-sections indent under a left rule so containment is visible +// even when scrolled past the parent header. +.adminSubGroup { + margin-top: d.$space-4; + padding-left: d.$space-4; + border-left: 2px solid rgba(var(--v-border-color), var(--v-border-opacity)); +} + +// Tighter rhythm between sibling sub-sections than between top-level +// sections (overrides the $space-6 .adminGroup sibling rule above). +.adminSubGroup + .adminSubGroup { + margin-top: d.$space-4; +} + .adminHint { margin-top: d.$space-1; font-size: d.$text-small; diff --git a/frontend/src/components/admin/tabs/admin-section.vue b/frontend/src/components/admin/tabs/admin-section.vue index 83d6f658b..032eaad1d 100644 --- a/frontend/src/components/admin/tabs/admin-section.vue +++ b/frontend/src/components/admin/tabs/admin-section.vue @@ -2,11 +2,15 @@ A titled admin block: header (h3 + optional right-aligned actions), an optional one-line hint, then its body. The unified replacement for the hand-written .adminGroup / .adminGroupHeader /

markup. See DESIGN.md §4. + + The `sub` variant nests inside another AdminSection: a smaller overline + h4 title and an indented left rule make the subordination visible (see + the Auth tab's OIDC block). --> + diff --git a/frontend/src/components/auth/sso-error.vue b/frontend/src/components/auth/sso-error.vue new file mode 100644 index 000000000..2ef5af4b7 --- /dev/null +++ b/frontend/src/components/auth/sso-error.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/frontend/src/components/auth/sso-login-button.vue b/frontend/src/components/auth/sso-login-button.vue new file mode 100644 index 000000000..2426779d1 --- /dev/null +++ b/frontend/src/components/auth/sso-login-button.vue @@ -0,0 +1,34 @@ + + + diff --git a/frontend/src/components/unauthorized.vue b/frontend/src/components/unauthorized.vue index eb37da187..2ededa773 100644 --- a/frontend/src/components/unauthorized.vue +++ b/frontend/src/components/unauthorized.vue @@ -10,6 +10,7 @@ > @@ -23,6 +24,7 @@ import { mapState } from "pinia"; import AdminBrowserLink from "@/components/admin/browser-link.vue"; import AuthMenu from "@/components/auth/auth-menu.vue"; +import SsoLoginButton from "@/components/auth/sso-login-button.vue"; import AppBanner from "@/components/banner.vue"; import EmptyState from "@/components/empty.vue"; import PlaceholderLoading from "@/components/placeholder-loading.vue"; @@ -34,6 +36,7 @@ export default { AppBanner, AdminBrowserLink, AuthMenu, + SsoLoginButton, PlaceholderLoading, EmptyState, }, @@ -83,6 +86,10 @@ export default { color: rgb(var(--v-theme-primary)); } +.ssoButton { + margin-bottom: 0.75em; +} + .login :deep(.v-list-item__prepend) { margin-right: 0.25em; } diff --git a/frontend/src/plugins/router.js b/frontend/src/plugins/router.js index 43dcc7c2f..a74e01821 100644 --- a/frontend/src/plugins/router.js +++ b/frontend/src/plugins/router.js @@ -19,8 +19,10 @@ const AdminRestoreTab = () => import("@/components/admin/tabs/restore-tab.vue"); const AdminStatsTab = () => import("@/components/admin/tabs/stats-tab.vue"); const AdminTaggingTab = () => import("@/components/admin/tabs/tagging-tab.vue"); const AdminEmailTab = () => import("@/components/admin/tabs/email-tab.vue"); +const AdminAuthTab = () => import("@/components/admin/tabs/auth-tab.vue"); const ResetPasswordConfirm = () => import("@/components/auth/reset-password-confirm.vue"); +const SsoError = () => import("@/components/auth/sso-error.vue"); // Both CODEX.LAST_ROUTE (server-injected) and the browser-defaults fallback // speak the v4 {collection, parentIds} route dialect now. @@ -78,6 +80,7 @@ const routes = [ component: AdminSettingsTab, }, { name: "admin-tagging", path: "tagging", component: AdminTaggingTab }, + { name: "admin-auth", path: "auth", component: AdminAuthTab }, { name: "admin-email", path: "email", component: AdminEmailTab }, { name: "admin-jobs", path: "jobs", component: AdminJobsTab }, { name: "admin-stats", path: "stats", component: AdminStatsTab }, @@ -89,6 +92,11 @@ const routes = [ path: "/auth/reset-password", component: ResetPasswordConfirm, }, + { + name: "sso-error", + path: "/auth/sso-error", + component: SsoError, + }, { name: "error", path: "/error/:code", component: HttpError, props: true }, { name: "404", path: "/:pathMatch(.*)*", redirect: "/error/404" }, ]; diff --git a/frontend/src/stores/admin.js b/frontend/src/stores/admin.js index b00ff9544..a3c8195df 100644 --- a/frontend/src/stores/admin.js +++ b/frontend/src/stores/admin.js @@ -28,6 +28,7 @@ const TABLE_TTL_MS = Object.freeze({ export const TABS = Object.freeze([ "Users", "Groups", + "Auth", "Email", "Libraries", "Tagging", @@ -61,6 +62,7 @@ export const useAdminStore = defineStore("admin", { stats: undefined, taggingDefaults: undefined, emailSettings: undefined, + oidcSettings: undefined, throttleSettings: undefined, apiKey: "", activeTab: "Libraries", @@ -444,6 +446,58 @@ export const useAdminStore = defineStore("admin", { return undefined; } }, + async loadOidcSettings({ force = false } = {}) { + if (this._requireAdmin()) return false; + if (!force) { + const ttl = DYNAMIC_TTL_MS; + const last = this.timestamps.OidcSettings || 0; + if (last && Date.now() - last < ttl) { + return true; + } + } + await API.getOidcSettings() + .then((response) => { + this.oidcSettings = response.data; + this.timestamps.OidcSettings = Date.now(); + return true; + }) + .catch(console.warn); + }, + async updateOidcSettings(data) { + if (this._requireAdmin()) return false; + const commonStore = useCommonStore(); + await API.updateOidcSettings(data) + .then(async (response) => { + this.oidcSettings = response.data; + this.timestamps.OidcSettings = Date.now(); + commonStore.clearErrors(); + // Keep the auth store's public OIDC flags — which drive the + // "Login with " button — in sync with the settings + // just saved, so toggling OIDC on/off is reflected without a + // manual page reload. OIDCSettings is a singleton (no + // admin.flags.changed websocket broadcast), so refresh here. + await useAuthStore().loadAdminFlags(); + return true; + }) + .catch(commonStore.setErrors); + }, + /* + * Probe the identity provider's discovery document, optionally with + * a not-yet-saved server URL override. Returns the endpoint report + * from the server; errors land on the common store too. + */ + async testOidcConnection(data) { + if (this._requireAdmin()) return undefined; + const commonStore = useCommonStore(); + try { + const response = await API.testOidcConnection(data); + commonStore.clearErrors(); + return response.data; + } catch (error) { + commonStore.setErrors(error); + return undefined; + } + }, async loadThrottleSettings({ force = false } = {}) { if (this._requireAdmin()) return false; if (!force) { diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index 104290c78..199dbd71a 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -18,6 +18,10 @@ export const useAuthStore = defineStore("auth", { lazyImportMetadata: undefined, emailEnabled: undefined, remoteUserEnabled: undefined, + oidcEnabled: undefined, + oidcProviderName: undefined, + oidcLoginUrl: undefined, + oidcLogoutUrl: undefined, }, user: undefined, /* @@ -116,6 +120,18 @@ export const useAuthStore = defineStore("auth", { }) .catch(commonStore.setErrors); }, + /* + * Full-page navigation, not an XHR: the identity provider's login + * page must render in the browser, and the chain of redirects + * ends back at the SPA root with a fresh session cookie. The URL + * arrives prefix-qualified from the ``/session`` payload. + */ + loginSSO() { + const url = this.adminFlags.oidcLoginUrl; + if (url) { + globalThis.location.assign(url); + } + }, async logout() { /* * The user clicked "log out" — clear client-side state @@ -129,6 +145,12 @@ export const useAuthStore = defineStore("auth", { * and-forget, but the menu's UI feedback would benefit from * knowing when the call resolves). */ + /* + * Captured before the local clear: RP-initiated logout also ends + * the identity-provider session. The URL only exists on payloads + * for authenticated OIDC-configured sessions. + */ + const oidcLogoutUrl = this.adminFlags.oidcLogoutUrl; try { await API.logout(); } catch (error) { @@ -140,6 +162,19 @@ export const useAuthStore = defineStore("auth", { * signing in next doesn't see the previous user's stars. */ useFavoritesStore().clear(); + if (oidcLogoutUrl) { + // Full-page redirect to end the IdP session; the ensuing + // reload refreshes the flags on its own. + globalThis.location.assign(oidcLogoutUrl); + } else { + /* + * Refresh the public admin flags so the logged-out login + * screen reflects anything changed during the session — e.g. + * OIDC toggled off hides the "Login with " button + * without a manual page reload. + */ + await this.loadAdminFlags(); + } } }, async changePassword(credentials) { diff --git a/frontend/tests/unit/admin-oidc-store.test.js b/frontend/tests/unit/admin-oidc-store.test.js new file mode 100644 index 000000000..2949872ad --- /dev/null +++ b/frontend/tests/unit/admin-oidc-store.test.js @@ -0,0 +1,158 @@ +/* + * Unit tests for the admin store's OIDC settings actions: + * loadOidcSettings (TTL-gated), updateOidcSettings, testOidcConnection. + * HTTP layer mocked; admin gate driven through the auth store. + */ +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock(import("@/api/v4/admin"), async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getOidcSettings: vi.fn(), + updateOidcSettings: vi.fn(), + testOidcConnection: vi.fn(), + }; +}); + +import { createTestingPinia } from "@pinia/testing"; +import { mount } from "@vue/test-utils"; + +import * as API from "@/api/v4/admin"; +import AuthTab, { canEnableOidc } from "@/components/admin/tabs/auth-tab.vue"; +import vuetify from "@/plugins/vuetify"; +import { useAdminStore } from "@/stores/admin"; +import { useAuthStore } from "@/stores/auth"; + +const SETTINGS = Object.freeze({ + enabled: true, + providerName: "Authentik", + serverUrl: "https://idp.example.com", + clientId: "codex", + clientSecretSet: true, +}); + +function adminStore() { + const authStore = useAuthStore(); + authStore.user = { id: 1, username: "admin", isStaff: true }; + return useAdminStore(); +} + +beforeEach(() => { + setActivePinia(createPinia()); + for (const fn of Object.values(API)) { + if (typeof fn?.mockReset === "function") { + fn.mockReset(); + } + } +}); + +describe("canEnableOidc — enable-switch gate", () => { + const complete = Object.freeze({ + providerName: "Authentik", + serverUrl: "https://idp.example.com", + clientId: "codex", + }); + + it("requires a provider name, a valid server URL, and a client ID", () => { + expect(canEnableOidc({})).toBe(false); + expect(canEnableOidc({ ...complete, providerName: "" })).toBe(false); + expect(canEnableOidc({ ...complete, serverUrl: "" })).toBe(false); + expect(canEnableOidc({ ...complete, clientId: "" })).toBe(false); + expect(canEnableOidc({ ...complete, serverUrl: "not a url" })).toBe(false); + expect(canEnableOidc(complete)).toBe(true); + }); +}); + +describe("AuthTab — OIDC disclosure", () => { + function mountTab(oidcSettings) { + const pinia = createTestingPinia({ + initialState: { + auth: { user: { id: 1, username: "admin", isStaff: true } }, + admin: { oidcSettings, flags: [] }, + }, + }); + return mount(AuthTab, { global: { plugins: [vuetify, pinia] } }); + } + + it("starts collapsed when OIDC is disabled", () => { + const wrapper = mountTab({ ...SETTINGS, enabled: false }); + expect(wrapper.find("form").exists()).toBe(false); + expect(wrapper.find(".adminExpandToggle").text()).toContain("Configure"); + expect(wrapper.text()).toContain("Not enabled"); + }); + + it("starts expanded when OIDC is enabled", () => { + const wrapper = mountTab(SETTINGS); + expect(wrapper.find("form").exists()).toBe(true); + expect(wrapper.find(".adminExpandToggle").text()).toContain("Hide"); + }); + + it("expands on toggle click", async () => { + const wrapper = mountTab({ ...SETTINGS, enabled: false }); + await wrapper.find(".adminExpandToggle").trigger("click"); + expect(wrapper.find("form").exists()).toBe(true); + }); +}); + +describe("useAdminStore — OIDC settings", () => { + it("loads settings into state", async () => { + API.getOidcSettings.mockResolvedValue({ data: SETTINGS }); + const store = adminStore(); + await store.loadOidcSettings(); + expect(store.oidcSettings).toEqual(SETTINGS); + }); + + it("respects the TTL and refetches with force", async () => { + API.getOidcSettings.mockResolvedValue({ data: SETTINGS }); + const store = adminStore(); + await store.loadOidcSettings(); + await store.loadOidcSettings(); + expect(API.getOidcSettings).toHaveBeenCalledTimes(1); + await store.loadOidcSettings({ force: true }); + expect(API.getOidcSettings).toHaveBeenCalledTimes(2); + }); + + it("denies non-admins", async () => { + useAuthStore().user = { id: 2, username: "reader" }; + const store = useAdminStore(); + await store.loadOidcSettings(); + expect(API.getOidcSettings).not.toHaveBeenCalled(); + }); + + it("update round-trips server state and refreshes auth public flags", async () => { + API.updateOidcSettings.mockResolvedValue({ + data: { ...SETTINGS, providerName: "Renamed" }, + }); + const store = adminStore(); + // Saving OIDC settings must resync the auth store's public flags so the + // login button reflects the change without a page reload. + const refresh = vi + .spyOn(useAuthStore(), "loadAdminFlags") + .mockResolvedValue(true); + await store.updateOidcSettings({ providerName: "Renamed" }); + expect(API.updateOidcSettings).toHaveBeenCalledWith({ + providerName: "Renamed", + }); + expect(store.oidcSettings.providerName).toBe("Renamed"); + expect(refresh).toHaveBeenCalled(); + }); + + it("test connection returns the endpoint report", async () => { + const report = { ok: true, issuer: "https://idp.example.com" }; + API.testOidcConnection.mockResolvedValue({ data: report }); + const store = adminStore(); + const result = await store.testOidcConnection({ + serverUrl: "https://idp.example.com", + }); + expect(result).toEqual(report); + }); + + it("test connection surfaces errors as undefined", async () => { + API.testOidcConnection.mockRejectedValue(new Error("boom")); + const store = adminStore(); + const result = await store.testOidcConnection({}); + expect(result).toBeUndefined(); + }); +}); diff --git a/frontend/tests/unit/sso-auth.test.js b/frontend/tests/unit/sso-auth.test.js new file mode 100644 index 000000000..d9be654ed --- /dev/null +++ b/frontend/tests/unit/sso-auth.test.js @@ -0,0 +1,164 @@ +/* + * Unit tests for the SSO (OIDC) auth-store actions and components. + * + * ``loginSSO`` and the RP-initiated-logout leg of ``logout`` perform + * full-page navigations via ``globalThis.location.assign`` — spied here, + * never executed. The SSO button and error page render against real + * Vuetify with a testing pinia. + */ +import { createTestingPinia } from "@pinia/testing"; +import { mount } from "@vue/test-utils"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@/api/v4/auth", () => ({ + getSession: vi.fn(), + register: vi.fn(), + login: vi.fn(), + getProfile: vi.fn(), + updateProfile: vi.fn(), + logout: vi.fn(), + updatePassword: vi.fn(), + sendResetPasswordLink: vi.fn(), + resetPassword: vi.fn(), + updateTimezone: vi.fn(), + getToken: vi.fn(), + updateToken: vi.fn(), +})); + +import * as API from "@/api/v4/auth"; +import SsoError from "@/components/auth/sso-error.vue"; +import SsoLoginButton from "@/components/auth/sso-login-button.vue"; +import vuetify from "@/plugins/vuetify"; +import { useAuthStore } from "@/stores/auth"; + +const LOGIN_URL = "/codex/api/v4/auth/oidc/login"; + +describe("useAuthStore — SSO actions", () => { + let assign; + + beforeEach(() => { + setActivePinia(createPinia()); + // spyOn returns the same spy on re-entry; clear its call history. + assign = vi + .spyOn(globalThis.location, "assign") + .mockImplementation(() => {}); + assign.mockClear(); + for (const fn of Object.values(API)) { + if (typeof fn?.mockReset === "function") { + fn.mockReset(); + } + } + }); + + it("loginSSO navigates to the prefix-qualified login URL", () => { + const store = useAuthStore(); + store.adminFlags.oidcLoginUrl = LOGIN_URL; + store.loginSSO(); + expect(assign).toHaveBeenCalledWith(LOGIN_URL); + }); + + it("loginSSO is a no-op when OIDC is not configured", () => { + const store = useAuthStore(); + store.loginSSO(); + expect(assign).not.toHaveBeenCalled(); + }); + + it("logout navigates to the RP-initiated logout URL when present", async () => { + API.logout.mockResolvedValue({}); + const store = useAuthStore(); + store.adminFlags.oidcLogoutUrl = + "https://idp.example.com/end-session/?client_id=codex"; + await store.logout(); + expect(store.user).toBeUndefined(); + expect(assign).toHaveBeenCalledWith( + "https://idp.example.com/end-session/?client_id=codex", + ); + }); + + it("logout stays local and refreshes public flags without an oidcLogoutUrl", async () => { + API.logout.mockResolvedValue({}); + // The logged-out /session reflects OIDC now disabled. + API.getSession.mockResolvedValue({ + data: { adminFlags: { oidcEnabled: false } }, + }); + const store = useAuthStore(); + store.adminFlags.oidcEnabled = true; // stale from the just-ended session + await store.logout(); + expect(assign).not.toHaveBeenCalled(); + expect(API.getSession).toHaveBeenCalled(); + expect(store.adminFlags.oidcEnabled).toBe(false); + }); + + it("logout skips the flag refresh when doing an RP-initiated redirect", async () => { + API.logout.mockResolvedValue({}); + const store = useAuthStore(); + store.adminFlags.oidcLogoutUrl = "https://idp.example.com/end-session/"; + await store.logout(); + expect(API.getSession).not.toHaveBeenCalled(); + }); +}); + +function mountWithFlags(component, adminFlags, mocks = {}) { + const pinia = createTestingPinia({ + initialState: { auth: { adminFlags } }, + }); + return mount(component, { + global: { + plugins: [vuetify, pinia], + mocks, + }, + }); +} + +describe("SsoLoginButton", () => { + it("renders the provider name when OIDC is enabled", () => { + const wrapper = mountWithFlags(SsoLoginButton, { + oidcEnabled: true, + oidcProviderName: "Authentik", + }); + const button = wrapper.find("#ssoLoginButton"); + expect(button.exists()).toBe(true); + expect(button.text()).toContain("Login with Authentik"); + }); + + it("renders nothing when OIDC is disabled", () => { + const wrapper = mountWithFlags(SsoLoginButton, { oidcEnabled: false }); + expect(wrapper.find("#ssoLoginButton").exists()).toBe(false); + }); +}); + +describe("SsoError", () => { + function mountError(error) { + return mountWithFlags( + SsoError, + { oidcEnabled: true, oidcProviderName: "SSO" }, + { + $route: { query: error ? { error } : {} }, + $router: { push: vi.fn() }, + }, + ); + } + + it("maps access_denied to a human message and offers retry", () => { + const wrapper = mountError("access_denied"); + expect(wrapper.text()).toContain("denied the login request"); + expect(wrapper.find("#ssoLoginButton").exists()).toBe(true); + }); + + it("hides retry for signup_disabled", () => { + const wrapper = mountError("signup_disabled"); + expect(wrapper.text()).toContain("Automatic account creation is disabled"); + expect(wrapper.find("#ssoLoginButton").exists()).toBe(false); + }); + + it("collapses unknown errors to the server_error message", () => { + const wrapper = mountError("bogus_code"); + expect(wrapper.text()).toContain("could not be reached"); + }); + + it("defaults to server_error with no query param", () => { + const wrapper = mountError(); + expect(wrapper.text()).toContain("could not be reached"); + }); +}); diff --git a/package.json b/package.json index b199bc52c..70ff5b4a6 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,9 @@ "preset-prettier" ] }, + "overrides": { + "typescript": "~6.0.3" + }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", @@ -102,6 +105,6 @@ "remark-preset-lint-markdown-style-guide": "^6.0.1", "remark-preset-lint-recommended": "^7.0.1", "remark-preset-prettier": "^2.0.2", - "svgo": "^4.0.1" + "svgo": "^4.0.2" } } diff --git a/pyproject.toml b/pyproject.toml index 1d4a6dd25..b9e461f71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "comicbox[pdf]~=4.1.0", "cryptography>=48.0.0", "dateparser~=1.2", + "django-allauth[socialaccount]~=65.13", "django-cachalot~=2.8", "django-cors-headers~=4.0", "django-rest-registration~=0.9", diff --git a/test-proxy/README.md b/test-proxy/README.md new file mode 100644 index 000000000..40aa8c794 --- /dev/null +++ b/test-proxy/README.md @@ -0,0 +1,194 @@ +# Codex SSO manual test harness + +Everything needed to manually test Codex authentication before a release: an +**authentik** OIDC identity provider, a **tinyauth** forward-auth gateway, and +an **nginx** reverse proxy — all as `docker compose` services. Only Codex itself +runs on the host (`make dev`). + +Every credential in this directory is public throwaway test data. + +> **Why authentik and not Authelia?** Authelia refuses to serve OIDC over plain +> HTTP, which would force self-signed-TLS trust into Codex's discovery/token +> fetches. authentik runs happily on and also supports +> RP-initiated logout, so it exercises every Codex OIDC feature. tinyauth is not +> an OIDC provider — it tests the Remote-User forward-auth path instead. + +## Pieces + +| Piece | Where | URL | +| ---------------- | -------------------------------------------------------------- | ----------------------------------- | +| authentik | `compose.yaml` | | +| tinyauth | `compose.yaml` | | +| nginx plain | `compose.yaml` + `server.conf` | | +| nginx gated | `compose.yaml` + `forwardauth.conf` | | +| authentik config | `authentik/blueprints/codex-test.yaml` (applied automatically) | + +`*.localtest.me` all resolve to `127.0.0.1` via public DNS — used for the +tinyauth path because tinyauth v5 refuses to run on a bare `localhost` app URL +(it sets its session cookie for a parent domain). Everything else stays on +`localhost`. + +The nginx service and `bin/run-test-proxy.sh` (native) share `server.conf` and +`forwardauth.conf`; only the backend addresses differ (`upstreams-docker.conf` +vs `upstreams-native.conf`). + +Test identities (all passwords `insecure-test-password` unless noted): + +| Account | Where | Groups | Notes | +| ----------- | --------- | --------------------- | ------------------------------ | +| `akadmin` | authentik | — | password `admin`; authentik UI | +| `testuser` | authentik | readers | ordinary OIDC login | +| `testadmin` | authentik | readers, codex-admins | for admin-group mapping | +| `test` | tinyauth | — | forward-auth login | + +## Start everything + +```sh +# 1. The whole harness — authentik, tinyauth, AND nginx (plain on 8080, +# tinyauth-gated on 8081). First start pulls images; authentik takes a +# minute or two to migrate + apply the blueprint. +docker compose -f test-proxy/compose.yaml up -d + +# 2. Codex, as usual (the harness assumes url_path_prefix = "/codex"). +# nginx reaches it on the host via host.docker.internal. +make dev +``` + +Sanity checks: + +```sh +curl -s http://localhost:9010/application/o/codex-test/.well-known/openid-configuration | head -c 200 +curl -s -o /dev/null -w '%{http_code}\n' http://tinyauth.localtest.me:3232/api/auth/nginx # 401 +curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/codex/ # Codex via nginx +``` + +> **Native nginx alternative.** If you'd rather run nginx on the host (e.g. to +> also exercise the 8443 TLS/HTTP3 listeners the container path omits), skip the +> `nginx` service and run `make dev-reverse-proxy` instead — it uses the same +> `server.conf` / `forwardauth.conf` with localhost backends. Either comment out +> the `nginx` service or +> `docker compose ... up -d authentik-server authentik-worker tinyauth postgresql redis` +> to avoid the 8080/8081 port clash. + +## Test 1 — native OIDC login (authentik) + +1. Browse , log in as your local admin, open + **Admin → Auth**, expand **OIDC Single Sign-On**, and enter: + - Provider Name: `Authentik` + - Server URL: `http://localhost:9010/application/o/codex-test/` + - Client ID: `codex-test` + - Client Secret: `insecure-codex-test-secret` + - (scope stays `openid profile email` — authentik ships groups in the + profile scope) +2. **Test Connection** → expect issuer + authorization/token/userinfo ✓ and + end-session ✓. +3. Enable OIDC, save, log out. +4. The lock screen / login dialog now shows **Login with Authentik**. Click it, + sign in as `testuser`, land back in Codex logged in as `testuser` (check + Admin → Users: the account was auto-created). +5. **Group sync:** create a Codex group named `readers` (Admin → Groups), turn + on _Sync Groups_ in the Auth tab, log in as `testuser` again → the user is in + `readers`. Groups that don't exist in Codex (`codex-admins` before you create + it) are ignored, never created. +6. **Admin group:** set _Admin Group_ to `codex-admins`, log in as `testadmin` → + account gets staff+superuser; remove `testadmin` from the group in authentik + (or change _Admin Group_) and log in again → revoked. +7. **RP-initiated logout:** enable _Also Log Out of the Identity Provider_, log + in via SSO, then log out of Codex → you land on authentik's end-session page + and the authentik session is gone. +8. **Linking:** in authentik create a user whose username matches an existing + local Codex user; SSO login links to (not duplicates) it — including the + admin account, per the documented trust model. +9. **Error page:** stop the compose stack and click the SSO button → + `/auth/sso-error` shows the human-readable failure, not a stack trace. +10. **Disabled = 404:** turn OIDC off, then + `curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/codex/sso/oidc/login/` + → `404`. + +## Test 2 — tinyauth forward-auth (Remote-User) + +1. Restart Codex with header auth enabled: + + ```sh + CODEX_AUTH_REMOTE_USER=1 make dev + ``` + +2. Browse → redirected to the tinyauth + login (`tinyauth.localtest.me:3232`) → sign in `test` / + `insecure-test-password` → back in Codex, logged in as `test` (auto-created). +3. **Gating:** confirm + and the WebSocket (`/codex/api/v4/ws`, watch devtools) also bounce to login + when logged out of tinyauth. +4. **Spoof-proofing (gated port):** + + ```sh + curl -s -o /dev/null -w '%{http_code}\n' \ + -H "Remote-User: admin" http://codex.localtest.me:8081/codex/api/v4/session + ``` + + → `302` to the tinyauth login: the gate overrides the client header. + +5. **The misconfiguration demo (ungated port):** the same curl against + `http://localhost:8080/...` **authenticates as admin** — port 8080 does not + strip `Remote-User`. This is exactly why the README warns to enable + `CODEX_AUTH_REMOTE_USER` only when every route to Codex sets the header + itself. Stop Codex and restart without the env var when done. +6. **Coexistence:** with both OIDC enabled and `CODEX_AUTH_REMOTE_USER=1`, both + login paths work at once (8081 header-auths you; 8080 still offers the SSO + button). + +## Troubleshooting + +**"Login with Authentik" fails with "could not be reached", authentik logs +"Invalid grant_type for provider".** authentik 2026.x added a `grant_types` +field that defaults to empty in blueprints, so the provider allows no grants. +The blueprint now sets `grant_types: [authorization_code, refresh_token]`. If +you brought the stack up before this fix, authentik still has the old provider — +re-apply the blueprint: + +```sh +docker compose -f test-proxy/compose.yaml restart authentik-worker +# then wait ~30s, or edit the blueprint file to retrigger the watcher. +``` + +Verify in the authentik UI (Applications → Providers → codex-test-provider → +Edit → Advanced protocol settings → the flow allows the authorization_code +grant). + +**nginx logs `connect() to [fd..::254]:9810 failed (Network unreachable)`.** +Harmless — nginx tries the IPv6 `host-gateway` (nothing listens there; Granian +is IPv4) and immediately retries IPv4, which succeeds. It only appears if the +`extra_hosts` line in the `nginx` service is uncommented on Docker Desktop; +leave it commented there (built-in `host.docker.internal` is IPv4). On Linux, +uncomment it — the name doesn't exist otherwise. + +**Vite HMR won't start / "port 5173".** The harness does not use 5173 — it +publishes only `9010`, `8080`, `8081`, and `3232` (check with +`docker compose -f test-proxy/compose.yaml config | grep published`). A blocked +Vite HMR is almost always a leftover `vite` from a previous `make dev` that +didn't exit cleanly: + +```sh +lsof -i :5173 # find whatever holds 5173 +pkill -f vite # or kill the specific PID, then re-run make dev +``` + +**"address already in use" on 8080/8081.** The compose `nginx` service and the +native `make dev-reverse-proxy` both bind 8080/8081 — run one or the other, not +both. + +**tinyauth restart loop: "invalid app url, must be at least second level +domain".** That's tinyauth v5 rejecting a bare `localhost` app URL. The current +`compose.yaml` uses `tinyauth.localtest.me` to satisfy it; if you still see +this, you're on an older copy — `git pull` / re-read `compose.yaml` and +`docker compose ... up -d --force-recreate tinyauth`. + +## Reset + +```sh +docker compose -f test-proxy/compose.yaml down -v # wipe IdP state +``` + +Codex-side cleanup between runs: delete the auto-created users (`testuser`, +`testadmin`, `test`) in Admin → Users, and disable OIDC in the Auth tab (or +restore your config dir from backup). diff --git a/test-proxy/authentik/blueprints/codex-test.yaml b/test-proxy/authentik/blueprints/codex-test.yaml new file mode 100644 index 000000000..f3cca635c --- /dev/null +++ b/test-proxy/authentik/blueprints/codex-test.yaml @@ -0,0 +1,114 @@ +# authentik blueprint: everything the Codex OIDC manual test needs. +# +# Mounted into the authentik containers at /blueprints/custom/ (see +# ../../compose.yaml); authentik discovers and applies it automatically at +# startup and re-applies on file change (and every 60 minutes). +# +# Creates: +# groups codex-admins, readers +# users testuser / insecure-test-password (readers) +# testadmin / insecure-test-password (readers, codex-admins) +# OIDC client client id codex-test +# secret insecure-codex-test-secret +# issuer http://localhost:9010/application/o/codex-test/ +# +# Redirect URIs cover Codex behind the test-proxy nginx (8080) and direct +# (9810), each with and without a /codex url_path_prefix. +# +# All credentials here are public throwaway test data for this local +# harness only. +version: 1 +metadata: + name: codex-test + labels: + blueprints.goauthentik.io/description: Codex SSO manual-test fixtures +entries: + - model: authentik_core.group + id: group-codex-admins + identifiers: + name: codex-admins + state: present + - model: authentik_core.group + id: group-readers + identifiers: + name: readers + state: present + + - model: authentik_core.user + identifiers: + username: testuser + state: present + attrs: + name: Test User + email: testuser@example.com + password: insecure-test-password + is_active: true + groups: + - !KeyOf group-readers + + - model: authentik_core.user + identifiers: + username: testadmin + state: present + attrs: + name: Test Admin + email: testadmin@example.com + password: insecure-test-password + is_active: true + groups: + - !KeyOf group-readers + - !KeyOf group-codex-admins + + - model: authentik_providers_oauth2.oauth2provider + id: codex-test-provider + identifiers: + name: codex-test-provider + state: present + attrs: + name: codex-test-provider + client_type: confidential + client_id: codex-test + client_secret: insecure-codex-test-secret + # authentik 2026.x added an explicit grant_types field that defaults + # to an EMPTY list in blueprints — without this the authorize step + # fails with "Invalid grant_type for provider" and the callback comes + # back as error=invalid_request. authorization_code is what Codex + # uses; refresh_token matches the authentik UI default. + grant_types: + - authorization_code + - refresh_token + authorization_flow: + !Find [ + authentik_flows.flow, + [slug, default-provider-authorization-implicit-consent], + ] + invalidation_flow: + !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + signing_key: + !Find [ + authentik_crypto.certificatekeypair, + [name, authentik Self-signed Certificate], + ] + property_mappings: + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]] + redirect_uris: + - matching_mode: strict + url: http://localhost:8080/codex/sso/oidc/login/callback/ + - matching_mode: strict + url: http://localhost:8080/sso/oidc/login/callback/ + - matching_mode: strict + url: http://localhost:9810/codex/sso/oidc/login/callback/ + - matching_mode: strict + url: http://localhost:9810/sso/oidc/login/callback/ + + - model: authentik_core.application + identifiers: + slug: codex-test + state: present + attrs: + name: Codex Test + slug: codex-test + provider: !KeyOf codex-test-provider + policy_engine_mode: any diff --git a/test-proxy/compose.yaml b/test-proxy/compose.yaml new file mode 100644 index 000000000..1d8a6d559 --- /dev/null +++ b/test-proxy/compose.yaml @@ -0,0 +1,160 @@ +# Identity-provider stack for manually testing Codex SSO before release. +# +# docker compose -f test-proxy/compose.yaml up -d +# +# Services (everything binds to 127.0.0.1 only): +# authentik http://localhost:9010 OIDC identity provider +# admin UI login: akadmin / admin +# test users (from authentik/blueprints/codex-test.yaml): +# testuser / insecure-test-password groups: readers +# testadmin / insecure-test-password groups: readers, codex-admins +# OIDC client for the Codex Auth tab: +# server url http://localhost:9010/application/o/codex-test/ +# client id codex-test +# client secret insecure-codex-test-secret +# tinyauth http://localhost:3232 forward-auth gateway (nginx auth_request) +# login: test / insecure-test-password +# +# Every credential in this file is intentionally public throwaway test data. +# See test-proxy/README.md for the full manual test matrix. + +services: + postgresql: + image: docker.io/library/postgres:16-alpine + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -d authentik -U authentik"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 5s + environment: + POSTGRES_PASSWORD: insecure-test-pg-password + POSTGRES_USER: authentik + POSTGRES_DB: authentik + volumes: + - authentik_pg:/var/lib/postgresql/data + + redis: + image: docker.io/library/redis:alpine + command: --save 60 1 --loglevel warning + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 3s + volumes: + - authentik_redis:/data + + authentik-server: + image: ghcr.io/goauthentik/server:2026.5 + restart: unless-stopped + command: server + environment: &authentik_env + AUTHENTIK_SECRET_KEY: insecure-test-secret-key-for-local-harness-only + AUTHENTIK_ERROR_REPORTING__ENABLED: "false" + AUTHENTIK_DISABLE_UPDATE_CHECK: "true" + AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true" + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: insecure-test-pg-password + # Applied on first start only: makes akadmin usable immediately. + AUTHENTIK_BOOTSTRAP_PASSWORD: admin + AUTHENTIK_BOOTSTRAP_EMAIL: akadmin@example.com + ports: + - "127.0.0.1:9010:9000" + volumes: + - ./authentik/blueprints:/blueprints/custom:ro + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + + authentik-worker: + image: ghcr.io/goauthentik/server:2026.5 + restart: unless-stopped + command: worker + environment: *authentik_env + volumes: + - ./authentik/blueprints:/blueprints/custom:ro + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + + tinyauth: + image: ghcr.io/steveiliop56/tinyauth:v5 + restart: unless-stopped + environment: + # tinyauth v5 derives a cookie domain from this URL and rejects + # single-label hosts ("localhost") and IPs — it must be a dotted + # name. localtest.me and all its subdomains resolve to 127.0.0.1 via + # public DNS (works in every browser, no /etc/hosts), so the session + # cookie set for `.localtest.me` is shared with the gated Codex host + # (codex.localtest.me:8081). See forwardauth.conf and README Test 2. + TINYAUTH_APPURL: http://tinyauth.localtest.me:3232 + # test / insecure-test-password (bcrypt). Compose interpolates $, so + # every $ in the hash is doubled. Regenerate with: + # docker run -i -t --rm ghcr.io/steveiliop56/tinyauth:v5 \ + # user create --interactive + TINYAUTH_AUTH_USERS: "test:$$2a$$05$$3iNEElgG4O.dR2RbvTmEQONRN7Zl3DcMYnz1cf9esxnCdeTWB/b2u" + # The image runs as a non-root user with WORKDIR /tinyauth (root-owned) + # and declares VOLUME /data — so the sqlite DB must live under /data, + # not the default ./tinyauth.db in the unwritable workdir. + TINYAUTH_DATABASE_PATH: /data/tinyauth.db + # The harness runs over plain http. + TINYAUTH_AUTH_SECURECOOKIE: "false" + ports: + - "127.0.0.1:3232:3000" + volumes: + - tinyauth_data:/data + + # The reverse proxy in front of Codex — the same nginx that + # bin/run-test-proxy.sh runs natively, containerized so nothing has to run + # on the host but Codex itself. Serves the plain proxy on :8080 and the + # tinyauth-gated proxy on :8081 (plain HTTP; the SSL/QUIC listeners are + # native-only). Reuses the shared server.conf / forwardauth.conf; only the + # backend upstreams differ (upstreams-docker.conf). + nginx: + image: docker.io/library/nginx:stable-alpine + restart: unless-stopped + # Codex runs natively on the host via `make dev`; nginx reaches it at + # host.docker.internal:9810 (upstreams-docker.conf). + # + # Docker Desktop (macOS/Windows) provides host.docker.internal built-in + # as an IPv4 address — leave the extra_hosts below commented there. + # Uncommenting it maps the name to the `host-gateway`, which on + # IPv6-enabled Docker Desktop is a ULA (fdXX::254) that Granian doesn't + # listen on, so nginx logs "connect()... Network unreachable" and only + # then falls back to IPv4. On LINUX (no built-in), UNCOMMENT it: + # extra_hosts: + # - "host.docker.internal:host-gateway" + ports: + - "127.0.0.1:8080:8080" + - "127.0.0.1:8081:8081" + volumes: + # Numeric prefixes fix include order under the stock image's + # `include /etc/nginx/conf.d/*.conf` (inside its http{} block). + - ./upstreams-docker.conf:/etc/nginx/conf.d/00-upstreams.conf:ro + - ./connection-upgrade.conf:/etc/nginx/conf.d/01-connection-upgrade.conf:ro + - ./server.conf:/etc/nginx/conf.d/10-server.conf:ro + - ./forwardauth.conf:/etc/nginx/conf.d/20-forwardauth.conf:ro + # server.conf's `include ssl-listen.conf` resolves against the /etc/nginx + # prefix here; the empty override keeps this path plain-HTTP. + - ./ssl-listen-none.conf:/etc/nginx/ssl-listen.conf:ro + depends_on: + # nginx resolves the `tinyauth` upstream hostname at startup, so the + # container must already exist. + tinyauth: + condition: service_started + +volumes: + authentik_pg: + authentik_redis: + tinyauth_data: diff --git a/test-proxy/connection-upgrade.conf b/test-proxy/connection-upgrade.conf new file mode 100644 index 000000000..39bd224c2 --- /dev/null +++ b/test-proxy/connection-upgrade.conf @@ -0,0 +1,10 @@ +# WebSocket upgrade map, shared by the native and container nginx paths. +# Connection is "upgrade" only when the client sent an Upgrade header, else +# "close" — so normal requests aren't proxied as malformed upgrades. +# server.conf and forwardauth.conf reference $connection_upgrade. +# http{} context (native: included from nginx.conf; container: mounted into +# conf.d, which the stock image includes inside http{}). +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} diff --git a/test-proxy/forwardauth.conf b/test-proxy/forwardauth.conf new file mode 100644 index 000000000..8302ad18e --- /dev/null +++ b/test-proxy/forwardauth.conf @@ -0,0 +1,78 @@ +# nginx server block: Codex behind tinyauth forward-auth (Remote-User SSO). +# Shared by the native (bin/run-test-proxy.sh) and container (compose.yaml +# `nginx` service) run paths; the backends differ only in the +# codex_backend / tinyauth_backend upstreams (upstreams-*.conf). +# +# Listens on 8081. Every request is authorized against tinyauth via +# auth_request; unauthenticated browsers are redirected to the tinyauth +# login page and come back with a session cookie. +# +# Hostnames matter here: tinyauth v5 sets its session cookie for a parent +# DOMAIN (it won't run on bare "localhost"). So the gated Codex must live +# on a sibling subdomain of tinyauth under a shared parent. We use +# localtest.me (all subdomains resolve to 127.0.0.1 via public DNS): +# gated Codex http://codex.localtest.me:8081/codex/ +# tinyauth login http://tinyauth.localtest.me:3232/ +# shared cookie .localtest.me +# +# Run Codex with CODEX_AUTH_REMOTE_USER=1 to accept the injected header, +# then browse http://codex.localtest.me:8081/codex/ +# +# Port 8080 (server.conf) stays ungated on purpose: with remote_user +# enabled it demonstrates exactly the spoofable misconfiguration the +# README warns about — see test-proxy/README.md. + +server { + listen 8081; + listen [::]:8081; + + server_name _; + + access_log /dev/stdout; + error_log /dev/stderr; + + # The internal authorization subrequest nginx makes for every request. + location = /tinyauth-auth { + internal; + proxy_pass http://tinyauth_backend/api/auth/nginx; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header x-forwarded-proto $scheme; + proxy_set_header x-forwarded-host $http_host; + proxy_set_header x-forwarded-uri $request_uri; + } + + # Browsers that fail the subrequest get bounced to the login page. + # tinyauth.localtest.me:3232 is the published tinyauth port; the browser + # resolves it (and $http_host = codex.localtest.me:8081) to 127.0.0.1. + location @tinyauth_login { + return 302 http://tinyauth.localtest.me:3232/login?redirect_uri=$scheme://$http_host$request_uri; + } + + location / { + auth_request /tinyauth-auth; + error_page 401 = @tinyauth_login; + auth_request_set $tinyauth_user $upstream_http_remote_user; + + # NOTE: nginx drops server-level proxy_set_header inheritance the + # moment a location defines its own, so EVERY header lives here. + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # Include the port (see server.conf) so Django builds correct + # absolute URLs behind this non-standard-port proxy. + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + # The authenticated identity — OVERRIDES anything the client sent, + # which is what makes header auth safe behind this gate. + proxy_set_header Remote-User $tinyauth_user; + # WebSockets (Codex live updates); the auth subrequest still runs. + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_pass http://codex_backend; + } +} diff --git a/test-proxy/nginx.conf b/test-proxy/nginx.conf index 2bb546dcc..132a3cc5b 100644 --- a/test-proxy/nginx.conf +++ b/test-proxy/nginx.conf @@ -8,17 +8,18 @@ events { worker_connections 1024; } http { default_type application/octet-stream; access_log /dev/stdout; - # WebSocket upgrade: Connection is "upgrade" only when the client sent an - # Upgrade header, else "close" — so normal requests aren't proxied as - # malformed upgrades. server.conf's proxy_set_header references this. - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } + # WebSocket upgrade map, shared with the container path. + include connection-upgrade.conf; + # Native backends (localhost); the container path defines these same + # upstream names differently in upstreams-docker.conf. + include upstreams-native.conf; client_body_temp_path tmp/client; proxy_temp_path tmp/proxy; fastcgi_temp_path tmp/fastcgi; uwsgi_temp_path tmp/uwsgi; scgi_temp_path tmp/scgi; include server.conf; + # Codex gated behind tinyauth forward-auth on :8081 (SSO testing; + # needs the compose stack from compose.yaml running). + include forwardauth.conf; } diff --git a/test-proxy/server.conf b/test-proxy/server.conf index 9730c6874..266e65d6c 100644 --- a/test-proxy/server.conf +++ b/test-proxy/server.conf @@ -1,30 +1,33 @@ # nginx server block for local reverse-proxy testing of a url_path_prefix -# (subpath) deployment. Run via bin/run-test-proxy.sh. Edit ports, proxy_pass, -# or routing here. Adapted from the linuxserver.io nginx default.conf sample; -# ports are remapped to 8080/8443 to run unprivileged and the SSL include -# points at this dir's ssl.conf instead of the container's /config path. +# (subpath) deployment. Shared by BOTH run paths: +# - native: bin/run-test-proxy.sh (nginx -p test-proxy) +# - container: the `nginx` service in compose.yaml +# The only native-vs-container difference — where the Codex backend lives — +# is abstracted into the `codex_backend` upstream, defined in +# upstreams-native.conf / upstreams-docker.conf. Edit ports or routing here +# and both paths pick it up. Adapted from the linuxserver.io nginx sample; +# ports are remapped to 8080/8443 to run unprivileged. server { listen 8080 default_server; listen [::]:8080 default_server; - listen 8443 ssl default_server; - listen [::]:8443 ssl default_server; - listen 8443 quic reuseport default_server; - listen [::]:8443 quic reuseport default_server; + # SSL/QUIC listeners: present natively (certs on disk), empty in the + # container (the SSO harness is plain-HTTP only). See ssl-listen.conf. + include ssl-listen.conf; server_name _; - include ssl.conf; - access_log /dev/stdout; error_log /dev/stderr; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # $host (the requested host), not $server_name — server_name is the "_" - # catch-all here, and codex has USE_X_FORWARDED_HOST=True, so forwarding - # "_" makes Django reject every dynamic request with DisallowedHost. - proxy_set_header X-Forwarded-Host $host; + # $http_host (the requested host INCLUDING port), not $server_name (the + # "_" catch-all → DisallowedHost) and not $host (which strips the port). + # codex has USE_X_FORWARDED_HOST=True, so this is what Django's + # build_absolute_uri uses — the port must be present or the OIDC + # redirect_uri comes out as http://localhost/... and fails to match. + proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Proto $scheme; # proxy_set_header X-Forwarded-Ssl on; @@ -46,6 +49,6 @@ server { # nginx drop every proxy_set_header inherited from the server block above # (incl. the WebSocket Upgrade/Connection headers), breaking ws proxying. location / { - proxy_pass http://127.0.0.1:9810; + proxy_pass http://codex_backend; } } diff --git a/test-proxy/ssl-listen-none.conf b/test-proxy/ssl-listen-none.conf new file mode 100644 index 000000000..97536e9ec --- /dev/null +++ b/test-proxy/ssl-listen-none.conf @@ -0,0 +1,4 @@ +# Intentionally empty. Mounted over server.conf's `include ssl-listen.conf` +# in the container path (compose.yaml `nginx` service) so it serves plain +# HTTP only. The native path uses ssl-listen.conf (with the 8443 SSL/QUIC +# listeners) instead. diff --git a/test-proxy/ssl-listen.conf b/test-proxy/ssl-listen.conf new file mode 100644 index 000000000..6d9103548 --- /dev/null +++ b/test-proxy/ssl-listen.conf @@ -0,0 +1,10 @@ +# SSL + HTTP/3 listeners for the NATIVE run path (bin/run-test-proxy.sh), +# included from server.conf's server{} block. Kept for the original subpath +# TLS/HTTP3 testing this directory was built for. The container path mounts +# ssl-listen-none.conf over this include instead — the SSO harness needs no +# TLS, and QUIC/cert wiring inside a container is avoidable complexity. +listen 8443 ssl default_server; +listen [::]:8443 ssl default_server; +listen 8443 quic reuseport default_server; +listen [::]:8443 quic reuseport default_server; +include ssl.conf; diff --git a/test-proxy/upstreams-docker.conf b/test-proxy/upstreams-docker.conf new file mode 100644 index 000000000..6ac7ae517 --- /dev/null +++ b/test-proxy/upstreams-docker.conf @@ -0,0 +1,14 @@ +# Backends for the CONTAINER run path (the `nginx` service in compose.yaml). +# From inside the compose network: +# - Codex runs natively on the host -> host.docker.internal (needs the +# extra_hosts host-gateway mapping the compose service sets for Linux; +# automatic on Docker Desktop). +# - tinyauth is a sibling service -> its service name + INTERNAL port +# 3000 (not the published 3232). +# The native path uses upstreams-native.conf instead. +upstream codex_backend { + server host.docker.internal:9810; +} +upstream tinyauth_backend { + server tinyauth:3000; +} diff --git a/test-proxy/upstreams-native.conf b/test-proxy/upstreams-native.conf new file mode 100644 index 000000000..610663376 --- /dev/null +++ b/test-proxy/upstreams-native.conf @@ -0,0 +1,11 @@ +# Backends for the NATIVE run path (bin/run-test-proxy.sh). nginx runs on +# the host, so both backends are reachable on localhost: Codex from +# `make dev`, tinyauth on its published compose port. The container path +# uses upstreams-docker.conf instead — that is the only difference between +# the two. +upstream codex_backend { + server 127.0.0.1:9810; +} +upstream tinyauth_backend { + server 127.0.0.1:3232; +} diff --git a/tests/test_admin_oidc_settings.py b/tests/test_admin_oidc_settings.py new file mode 100644 index 000000000..286e73334 --- /dev/null +++ b/tests/test_admin_oidc_settings.py @@ -0,0 +1,200 @@ +"""Tests for the admin OIDC settings endpoints.""" + +from typing import Final, override +from unittest.mock import patch + +from django.contrib.auth.models import User +from django.core.cache import cache +from django.test import TestCase + +from codex.models.admin import OIDCSettings +from codex.oidc import DISCOVERY_CACHE_KEY + +_URL: Final = "/api/v4/admin/oidc-settings" +_TEST_URL: Final = "/api/v4/admin/oidc-settings/test" +_HTTP_OK: Final = 200 +_HTTP_BAD_REQUEST: Final = 400 +_HTTP_FORBIDDEN: Final = 403 +_SECRET: Final = "hush-test-secret" # noqa: S105 + + +def _data(response) -> dict: + return response.json()["data"] + + +class AdminOIDCPermissionTests(TestCase): + """Only admins may read or write OIDC settings.""" + + def test_anonymous_denied(self) -> None: + assert self.client.get(_URL).status_code == _HTTP_FORBIDDEN + assert ( + self.client.put(_URL, {}, content_type="application/json").status_code + == _HTTP_FORBIDDEN + ) + assert self.client.post(_TEST_URL, {}).status_code == _HTTP_FORBIDDEN + + def test_non_staff_denied(self) -> None: + user = User.objects.create_user("reader") + self.client.force_login(user) + assert self.client.get(_URL).status_code == _HTTP_FORBIDDEN + + +class AdminOIDCSettingsViewTests(TestCase): + """GET/PUT round trip with a write-only secret.""" + + @override + def setUp(self) -> None: + admin = User.objects.create_superuser("admin", "", "admin") + self.client.force_login(admin) + + def test_get_returns_settings_without_secret(self) -> None: + OIDCSettings.objects.filter(pk=1).update(client_secret="") + data = _data(self.client.get(_URL)) + assert data["enabled"] is False + assert data["providerName"] == "SSO" + assert data["clientSecretSet"] is False + assert "clientSecret" not in data + + def test_put_round_trip_and_secret_mirror(self) -> None: + payload = { + "enabled": True, + "providerName": "Authentik", + "serverUrl": "https://auth.example.com/application/o/codex/", + "clientId": "codex", + "clientSecret": _SECRET, + "scope": "openid profile email groups", + "syncGroups": True, + } + data = _data(self.client.put(_URL, payload, content_type="application/json")) + assert data["enabled"] is True + assert data["providerName"] == "Authentik" + assert data["clientSecretSet"] is True + assert "clientSecret" not in data + row = OIDCSettings.objects.get(pk=1) + assert row.client_secret == _SECRET + assert row.sync_groups is True + + def test_put_without_secret_keeps_existing_secret(self) -> None: + OIDCSettings.objects.filter(pk=1).update(client_secret=_SECRET) + self.client.put( + _URL, {"providerName": "Renamed"}, content_type="application/json" + ) + row = OIDCSettings.objects.get(pk=1) + assert row.client_secret == _SECRET + assert row.provider_name == "Renamed" + + def test_put_blank_secret_clears_it(self) -> None: + OIDCSettings.objects.filter(pk=1).update(client_secret=_SECRET) + data = _data( + self.client.put(_URL, {"clientSecret": ""}, content_type="application/json") + ) + assert data["clientSecretSet"] is False + assert OIDCSettings.objects.get(pk=1).client_secret == "" + + def test_put_enabled_without_prerequisites_is_rejected(self) -> None: + """enabled=true requires a server URL and client ID (UI mirror).""" + response = self.client.put( + _URL, {"enabled": True}, content_type="application/json" + ) + assert response.status_code == _HTTP_BAD_REQUEST + assert OIDCSettings.objects.get(pk=1).enabled is False + + def test_put_blanking_prerequisite_while_enabled_is_rejected(self) -> None: + OIDCSettings.objects.filter(pk=1).update( + enabled=True, server_url="https://idp.example.com", client_id="codex" + ) + response = self.client.put( + _URL, {"serverUrl": ""}, content_type="application/json" + ) + assert response.status_code == _HTTP_BAD_REQUEST + assert OIDCSettings.objects.get(pk=1).server_url == "https://idp.example.com" + + def test_put_blanking_provider_name_while_enabled_is_rejected(self) -> None: + OIDCSettings.objects.filter(pk=1).update( + enabled=True, server_url="https://idp.example.com", client_id="codex" + ) + response = self.client.put( + _URL, {"providerName": ""}, content_type="application/json" + ) + assert response.status_code == _HTTP_BAD_REQUEST + assert OIDCSettings.objects.get(pk=1).provider_name == "SSO" + + def test_put_disable_and_blank_together_is_allowed(self) -> None: + OIDCSettings.objects.filter(pk=1).update( + enabled=True, server_url="https://idp.example.com", client_id="codex" + ) + response = self.client.put( + _URL, + {"enabled": False, "serverUrl": "", "clientId": ""}, + content_type="application/json", + ) + assert response.status_code == _HTTP_OK + row = OIDCSettings.objects.get(pk=1) + assert row.enabled is False + assert row.server_url == "" + + def test_put_invalidates_discovery_cache(self) -> None: + cache.set(DISCOVERY_CACHE_KEY, {"issuer": "https://stale.example.com"}) + self.client.put( + _URL, + {"serverUrl": "https://new.example.com"}, + content_type="application/json", + ) + assert cache.get(DISCOVERY_CACHE_KEY) is None + + +class AdminOIDCTestViewTests(TestCase): + """The Test Connection endpoint probes the discovery document.""" + + @override + def setUp(self) -> None: + admin = User.objects.create_superuser("admin", "", "admin") + self.client.force_login(admin) + + def _post(self, payload: dict) -> dict: + return _data( + self.client.post(_TEST_URL, payload, content_type="application/json") + ) + + def test_reports_discovered_endpoints(self) -> None: + doc = { + "issuer": "https://idp.example.com", + "authorization_endpoint": "https://idp.example.com/authorize", + "token_endpoint": "https://idp.example.com/token", + "userinfo_endpoint": "https://idp.example.com/userinfo", + } + with patch("codex.views.admin.oidc.fetch_discovery_document", return_value=doc): + data = self._post({"serverUrl": "https://idp.example.com"}) + assert data["ok"] is True + assert data["issuer"] == "https://idp.example.com" + assert data["authorizationEndpoint"] is True + assert data["tokenEndpoint"] is True + assert data["userinfoEndpoint"] is True + # Authelia case: no end_session advertised. + assert data["endSessionEndpoint"] is False + + def test_reports_fetch_error(self) -> None: + import requests + + with patch( + "codex.views.admin.oidc.fetch_discovery_document", + side_effect=requests.ConnectionError("boom"), + ): + data = self._post({"serverUrl": "https://unreachable.example.com"}) + assert data["ok"] is False + assert "boom" in data["error"] + + def test_falls_back_to_saved_server_url(self) -> None: + OIDCSettings.objects.filter(pk=1).update(server_url="https://saved.example.com") + with patch( + "codex.views.admin.oidc.fetch_discovery_document", return_value={} + ) as mock_fetch: + data = self._post({}) + mock_fetch.assert_called_once_with("https://saved.example.com") + assert data["ok"] is False + + def test_no_server_url_anywhere(self) -> None: + OIDCSettings.objects.filter(pk=1).update(server_url="") + data = self._post({}) + assert data["ok"] is False + assert "No server URL" in data["error"] diff --git a/tests/test_oidc_login.py b/tests/test_oidc_login.py new file mode 100644 index 000000000..d963f7170 --- /dev/null +++ b/tests/test_oidc_login.py @@ -0,0 +1,422 @@ +""" +Tests for native OIDC login: gating, adapter linking, and claim sync. + +The OAuth2 handshake itself is allauth's tested territory; these tests +drive ``complete_social_login`` with prebuilt ``SocialLogin`` objects +(what the callback view produces after the token exchange) so every +Codex policy branch is exercised against the real signup/login flows. +""" + +from typing import Final, override +from unittest.mock import patch + +from allauth.core import context +from allauth.core.exceptions import ImmediateHttpResponse +from allauth.socialaccount.helpers import complete_social_login +from allauth.socialaccount.models import SocialAccount, SocialLogin +from django.contrib.auth.models import AnonymousUser, Group, User +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from django.core.cache import cache +from django.test import RequestFactory, TestCase +from django.urls import resolve + +from codex.models.admin import OIDCSettings +from codex.oidc import ( + DISCOVERY_CACHE_KEY, + CodexSocialAccountAdapter, + merged_claims, + oidc_logout_url, +) +from codex.serializers.auth import ProfileUpdateSerializer +from codex.settings import GRANIAN_URL_PATH_PREFIX + +# The test environment configures a url_path_prefix, which conveniently +# proves the error redirects are prefix-qualified. +_SSO_ERROR: Final = f"{GRANIAN_URL_PATH_PREFIX}/auth/sso-error" +_SUB: Final = "sub-1234" +_HTTP_OK: Final = 200 +_HTTP_NOT_FOUND: Final = 404 +_HTTP_FOUND: Final = 302 + + +def _seed_oidc(**overrides) -> OIDCSettings: + """Seed the OIDCSettings singleton as a working test deployment.""" + defaults = { + "enabled": True, + "provider_name": "Test SSO", + "server_url": "https://idp.example.com", + "client_id": "codex-test", + "client_secret": "test-secret-hush", + **overrides, + } + row, _ = OIDCSettings.objects.update_or_create(pk=1, defaults=defaults) + return row + + +def _sociallogin(id_token=None, userinfo=None, sub=_SUB) -> SocialLogin: + """Build the SocialLogin the callback produces after token exchange.""" + extra_data = {} + if id_token is not None: + extra_data["id_token"] = id_token + if userinfo is not None: + extra_data["userinfo"] = userinfo + account = SocialAccount(provider="oidc", uid=sub, extra_data=extra_data) + sociallogin = SocialLogin(user=User(), account=account) + data = merged_claims(sociallogin) + adapter = CodexSocialAccountAdapter() + request = RequestFactory().get("/sso/oidc/login/callback/") + adapter.populate_user(request, sociallogin, {"email": data.get("email")}) + return sociallogin + + +def _request(rf: RequestFactory): + """Build a request with working session and messages, like the callback's.""" + request = rf.get("/sso/oidc/login/callback/") + SessionMiddleware(lambda _: None).process_request(request) # pyright: ignore[reportArgumentType], # ty: ignore[invalid-argument-type] + request.session.save() + MessageMiddleware(lambda _: None).process_request(request) # pyright: ignore[reportArgumentType], # ty: ignore[invalid-argument-type] + request.user = AnonymousUser() + return request + + +def _complete_social_login(request, sociallogin: SocialLogin): + """Run the real login/signup flow inside allauth's request context.""" + with context.request_context(request): + response = complete_social_login(request, sociallogin) + # Every Codex path ends in a redirect; narrows the Optional for + # the type checkers as a bonus. + assert response is not None + return response + + +class OIDCDisabledTests(TestCase): + """Every OIDC path must 404 when the feature is off (the default).""" + + def test_init_endpoint_404(self) -> None: + response = self.client.get("/api/v4/auth/oidc/login") + assert response.status_code == _HTTP_NOT_FOUND + + def test_allauth_login_path_404(self) -> None: + """Allauth's own view 404s: no SocialApp exists when APPS is empty.""" + response = self.client.get("/sso/oidc/login/") + assert response.status_code == _HTTP_NOT_FOUND + + def test_allauth_callback_path_404(self) -> None: + response = self.client.get("/sso/oidc/login/callback/") + assert response.status_code == _HTTP_NOT_FOUND + + +class OIDCInitTests(TestCase): + """The branded init endpoint redirects into allauth when enabled.""" + + def test_init_redirects_to_allauth_login(self) -> None: + _seed_oidc() + response = self.client.get("/api/v4/auth/oidc/login") + assert response.status_code == _HTTP_FOUND + assert response["Location"] == "/sso/oidc/login/" + + +class OIDCProvisionTests(TestCase): + """User provisioning and username mapping through the real signup flow.""" + + @override + def setUp(self) -> None: + _seed_oidc() + self.rf = RequestFactory() # pyright: ignore[reportUninitializedInstanceVariable] + + def _complete(self, sociallogin: SocialLogin): + return _complete_social_login(_request(self.rf), sociallogin) + + def test_creates_user_from_preferred_username(self) -> None: + sociallogin = _sociallogin( + id_token={"sub": _SUB}, + userinfo={"preferred_username": "aj", "email": "aj@example.com"}, + ) + response = self._complete(sociallogin) + assert response.status_code == _HTTP_FOUND + user = User.objects.get(username="aj") + assert user.email == "aj@example.com" + assert not user.has_usable_password() + assert SocialAccount.objects.get(user=user).uid == _SUB + + def test_username_falls_back_to_email_then_sub(self) -> None: + sociallogin = _sociallogin( + id_token={"sub": _SUB}, userinfo={"email": "fallback@example.com"} + ) + self._complete(sociallogin) + assert User.objects.filter(username="fallback@example.com").exists() + + sociallogin = _sociallogin(id_token={"sub": "sub-bare"}, sub="sub-bare") + self._complete(sociallogin) + assert User.objects.filter(username="sub-bare").exists() + + def test_claims_merged_id_token_and_userinfo(self) -> None: + """A claim present only in the ID token (Authentik groups) is seen.""" + sociallogin = _sociallogin( + id_token={"sub": _SUB, "groups": ["readers"]}, + userinfo={"preferred_username": "aj"}, + ) + claims = merged_claims(sociallogin) + assert claims["groups"] == ["readers"] + assert claims["preferred_username"] == "aj" + + def test_create_users_off_redirects_to_error(self) -> None: + _seed_oidc(create_users=False) + sociallogin = _sociallogin( + id_token={"sub": _SUB}, userinfo={"preferred_username": "nobody"} + ) + response = self._complete(sociallogin) + assert response.status_code == _HTTP_FOUND + assert response["Location"] == f"{_SSO_ERROR}?error=signup_disabled" + assert not User.objects.filter(username="nobody").exists() + + +class OIDCLinkingTests(TestCase): + """Account-linking policy: username always, superusers included.""" + + @override + def setUp(self) -> None: + _seed_oidc() + self.rf = RequestFactory() # pyright: ignore[reportUninitializedInstanceVariable] + + def _complete(self, sociallogin: SocialLogin): + return _complete_social_login(_request(self.rf), sociallogin) + + def test_links_existing_user_by_username(self) -> None: + local = User.objects.create_user("aj", "old@example.com") + sociallogin = _sociallogin( + id_token={"sub": _SUB}, userinfo={"preferred_username": "AJ"} + ) + response = self._complete(sociallogin) + assert response.status_code == _HTTP_FOUND + account = SocialAccount.objects.get(uid=_SUB) + assert account.user == local + assert User.objects.count() == 1 + + def test_links_superuser_by_username(self) -> None: + """Chosen behavior, not an accident: superusers auto-link too.""" + admin = User.objects.create_superuser("admin", "", "admin") + sociallogin = _sociallogin( + id_token={"sub": _SUB}, userinfo={"preferred_username": "admin"} + ) + self._complete(sociallogin) + assert SocialAccount.objects.get(uid=_SUB).user == admin + + def test_different_sub_does_not_hijack_linked_user(self) -> None: + """Same preferred_username, different sub: new user, no hijack.""" + local = User.objects.create_user("aj") + SocialAccount.objects.create(user=local, provider="oidc", uid=_SUB) + sociallogin = _sociallogin( + id_token={"sub": "sub-other"}, + userinfo={"preferred_username": "aj"}, + sub="sub-other", + ) + self._complete(sociallogin) + other = SocialAccount.objects.get(uid="sub-other").user + assert other != local + # Collision resolved with a sub-hash suffix, keeping the identity. + assert other.username.startswith("aj-") + + def test_email_conflict_redirects_when_link_by_email_off(self) -> None: + User.objects.create_user("existing", "aj@example.com") + sociallogin = _sociallogin( + id_token={"sub": _SUB}, + userinfo={"preferred_username": "newname", "email": "aj@example.com"}, + ) + response = self._complete(sociallogin) + assert response.status_code == _HTTP_FOUND + assert response["Location"] == f"{_SSO_ERROR}?error=email_exists" + assert not User.objects.filter(username="newname").exists() + + def test_links_by_email_when_enabled(self) -> None: + local = User.objects.create_user("existing", "aj@example.com") + _seed_oidc(link_by_email=True) + sociallogin = _sociallogin( + id_token={"sub": _SUB}, + userinfo={"preferred_username": "newname", "email": "AJ@example.com"}, + ) + response = self._complete(sociallogin) + assert response.status_code == _HTTP_FOUND + assert SocialAccount.objects.get(uid=_SUB).user == local + + +class OIDCGroupSyncTests(TestCase): + """Group and admin mapping from the groups claim.""" + + @override + def setUp(self) -> None: + _seed_oidc(sync_groups=True) + self.rf = RequestFactory() # pyright: ignore[reportUninitializedInstanceVariable] + self.readers = Group.objects.create(name="readers") # pyright: ignore[reportUninitializedInstanceVariable] + self.user = User.objects.create_user("aj") # pyright: ignore[reportUninitializedInstanceVariable] + SocialAccount.objects.create(user=self.user, provider="oidc", uid=_SUB) + + def _login_with_groups(self, groups) -> None: + sociallogin = _sociallogin( + id_token={"sub": _SUB, "groups": groups}, + userinfo={"preferred_username": "aj"}, + ) + _complete_social_login(_request(self.rf), sociallogin) + self.user.refresh_from_db() + + def test_sync_sets_existing_groups_only(self) -> None: + self._login_with_groups(["readers", "not-a-codex-group"]) + assert list(self.user.groups.all()) == [self.readers] + assert not Group.objects.filter(name="not-a-codex-group").exists() + + def test_sync_removes_dropped_groups(self) -> None: + self.user.groups.add(self.readers) + self._login_with_groups([]) + assert not self.user.groups.exists() + + def test_absent_groups_claim_is_noop(self) -> None: + self.user.groups.add(self.readers) + sociallogin = _sociallogin( + id_token={"sub": _SUB}, userinfo={"preferred_username": "aj"} + ) + _complete_social_login(_request(self.rf), sociallogin) + self.user.refresh_from_db() + assert list(self.user.groups.all()) == [self.readers] + + def test_admin_group_grants_and_revokes(self) -> None: + _seed_oidc(sync_groups=True, admin_group="codex-admins") + self._login_with_groups(["codex-admins"]) + assert self.user.is_staff + assert self.user.is_superuser + self._login_with_groups(["readers"]) + assert not self.user.is_staff + assert not self.user.is_superuser + + +class OIDCSessionFlagTests(TestCase): + """The /session payload advertises OIDC state to the SPA.""" + + @override + def setUp(self) -> None: + """Seed the AdminFlag + Timestamp rows /session requires.""" + from codex.startup import init_admin_flags, init_timestamps + + init_admin_flags() + init_timestamps() + + def test_sso_error_route_resolves_to_spa_index(self) -> None: + """ + ``/auth/sso-error`` serves the SPA, not the catch-all redirect. + + The adapter's error redirects land here; without the explicit app + urlconf entry the catch-all would 302 the error page to home. + """ + match = resolve("/auth/sso-error") + assert match.view_name == "app:sso-error" + + def test_disabled_flags_logged_out(self) -> None: + data = self.client.get("/api/v4/session").json()["data"] + flags = data["adminFlags"] + assert flags["oidcEnabled"] is False + assert "oidcProviderName" not in flags + assert "oidcLoginUrl" not in flags + assert "oidcLogoutUrl" not in flags + + def test_enabled_flags_are_public_and_prefixed(self) -> None: + _seed_oidc(provider_name="Authentik") + data = self.client.get("/api/v4/session").json()["data"] + flags = data["adminFlags"] + assert flags["oidcEnabled"] is True + assert flags["oidcProviderName"] == "Authentik" + assert flags["oidcLoginUrl"] == ( + f"{GRANIAN_URL_PATH_PREFIX}/api/v4/auth/oidc/login" + ) + # Logged out: no logout URL even when rp logout is configured. + assert "oidcLogoutUrl" not in flags + + +class OIDCLogoutURLTests(TestCase): + """RP-initiated logout URL construction from the discovery document.""" + + @override + def setUp(self) -> None: + cache.delete(DISCOVERY_CACHE_KEY) + self.rf = RequestFactory() # pyright: ignore[reportUninitializedInstanceVariable] + + def _logout_url(self, discovery: dict) -> str: + _seed_oidc(rp_initiated_logout=True) + request = self.rf.get("/api/v4/session") + with patch("codex.oidc._discovery_document", return_value=discovery): + return oidc_logout_url(request) + + def test_builds_end_session_url(self) -> None: + url = self._logout_url( + {"end_session_endpoint": "https://idp.example.com/end-session/"} + ) + assert url.startswith("https://idp.example.com/end-session/?") + assert "client_id=codex-test" in url + assert "post_logout_redirect_uri=" in url + + def test_empty_when_provider_has_no_end_session(self) -> None: + """Authelia's discovery document omits end_session_endpoint.""" + assert self._logout_url({}) == "" + + def test_empty_when_disabled(self) -> None: + request = self.rf.get("/api/v4/session") + assert oidc_logout_url(request) == "" + + +class OIDCUsernameLockTests(TestCase): + """Linked accounts get a read-only username in profile serializers.""" + + def _username_read_only(self, user) -> bool: + request = RequestFactory().get("/api/v4/auth/profile") + request.user = user + serializer = ProfileUpdateSerializer(context={"request": request}) + return serializer.fields["username"].read_only + + def test_locked_when_oidc_linked(self) -> None: + user = User.objects.create_user("aj") + SocialAccount.objects.create(user=user, provider="oidc", uid=_SUB) + assert self._username_read_only(user) is True + + def test_unlocked_for_local_user(self) -> None: + user = User.objects.create_user("aj") + assert self._username_read_only(user) is False + + +class OIDCSchemaTests(TestCase): + """allauth's plain Django views must not leak into the API schema.""" + + def test_allauth_views_absent_from_schema(self) -> None: + admin = User.objects.create_superuser("admin", "", "admin") + self.client.force_login(admin) + response = self.client.get("/api/v4/schema") + assert response.status_code == _HTTP_OK + paths = response.data["paths"] # pyright: ignore[reportAttributeAccessIssue], # ty: ignore[unresolved-attribute] + sso_paths = [path for path in paths if "/sso/" in path] + assert sso_paths == [] + # The DRF init endpoint is a documented part of the API. + assert any(path.endswith("/auth/oidc/login") for path in paths) + + +class OIDCErrorTests(TestCase): + """Authentication errors land on the SPA error page, never a template.""" + + def _error_location(self, error) -> str: + adapter = CodexSocialAccountAdapter() + request = RequestFactory().get("/sso/oidc/login/callback/") + try: + adapter.on_authentication_error(request, provider=None, error=error) + except ImmediateHttpResponse as exc: + return exc.response["Location"] + msg = "on_authentication_error did not raise" + raise AssertionError(msg) + + def test_known_error_passes_through(self) -> None: + assert self._error_location("access_denied") == ( + f"{_SSO_ERROR}?error=access_denied" + ) + + def test_unknown_error_collapses_to_server_error(self) -> None: + """Never reflect arbitrary provider input into the redirect.""" + assert self._error_location(" diff --git a/frontend/tests/unit/stats-tab.test.js b/frontend/tests/unit/stats-tab.test.js new file mode 100644 index 000000000..79f2434f8 --- /dev/null +++ b/frontend/tests/unit/stats-tab.test.js @@ -0,0 +1,178 @@ +/* + * Tests for the Admin Stats tab. + * + * The tab is what an administrator sees of the anonymous stats report, so + * behavior locked in here: + * - Every section the API returns is rendered under a titled table. + * - Toggle booleans read as Yes/No, and "have you configured this" + * booleans read as Set/Not set, so nobody mistakes one for the other. + * - The API key is never rendered, even though the payload carries it. + */ +import { createTestingPinia } from "@pinia/testing"; +import { mount } from "@vue/test-utils"; +import { describe, expect, test } from "vitest"; + +import StatsTab from "@/components/admin/tabs/stats-tab.vue"; +import vuetify from "@/plugins/vuetify"; + +const STATS = { + platform: { + docker: false, + machine: "arm64", + cores: 10, + system: { name: "Darwin", release: "25.5.0" }, + pythonVersion: "3.14.4", + codexVersion: "2.2.3", + }, + config: { + libraryCount: 2, + sessionCount: 1, + userAnonymousCount: 0, + userRegisteredCount: 2, + authGroupCount: 0, + libraryReadOnlyCount: 1, + libraryPollCount: 2, + libraryEventsCount: 2, + libraryGroupAclCount: 0, + customCoverCount: 3, + customCoverUploadedCount: 2, + customCoverDirCount: 1, + failedImportCount: 0, + apiKey: "s3cr3t-api-key", + }, + sessions: { + topCollection: { publishers: 2 }, + orderBy: { sort_name: 2 }, + dynamicCovers: { true: 2 }, + finishOnLastPage: { true: 1 }, + fitTo: { W: 1 }, + readingDirection: { ltr: 1 }, + viewMode: { cover: 2, table: 1 }, + tableCoverSize: { sm: 1 }, + customCovers: { true: 2 }, + multiSortCount: 0, + }, + collections: { + publisherCount: 17, + imprintCount: 20, + seriesCount: 56, + volumeCount: 60, + issueCount: 154, + folderCount: 70, + storyArcCount: 23, + }, + fileTypes: { cbz: 100, cbr: 50, cb7: 2, pdf: 3, unknown: 1 }, + metadata: { characterCount: 609, storyCount: 151 }, + usage: { + bookmarkCount: 1, + favoriteCount: 4, + favoriteUserCount: 1, + favorites: { comics: 4 }, + }, + identifiers: { "metron:comic": 85, "comicvine:comic": 126 }, + adminFlags: { + autoUpdate: false, + folderView: true, + sendTelemetry: true, + apiKeySet: true, + bannerTextSet: false, + browserDefaultCollection: "publishers", + browserMaxObjPerPage: 100, + ageRatingDefault: "Everyone", + }, + tagging: { + defaultMatchMode: "auto", + mergeAllSources: false, + defaultSources: { metron: 1, comicvine: 1 }, + hasMetronCredentials: true, + hasComicvineCredentials: false, + metronUrlSet: false, + }, + auth: { + oidcEnabled: false, + oidcPkce: true, + oidcScopeCustom: false, + oidcTokenAuthMethod: "", + userAgeCeilingCount: 0, + }, + email: { smtpConfigured: false, smtpUseTls: true, smtpUseSsl: false }, + throttle: { throttleAnon: 0, throttleResetPassword: 5 }, + deployment: { + remoteUserAuth: false, + failedLoginLog: true, + urlPathPrefixSet: true, + }, +}; + +const SECTION_TITLES = [ + "Platform", + "Config", + "File Types", + "User Settings", + "Browser Collections", + "Tags", + "Reading", + "Identifiers", + "Admin Flags", + "Online Tagging", + "Authentication", + "Email", + "Rate Limits", + "Deployment", +]; + +function mountTab(stats = STATS) { + const pinia = createTestingPinia({ initialState: { admin: { stats } } }); + return mount(StatsTab, { + global: { plugins: [pinia, vuetify] }, + }); +} + +describe("AdminStatsTab", () => { + test("renders a table for every stats section", () => { + const text = mountTab().text(); + for (const title of SECTION_TITLES) { + expect(text).toContain(title); + } + }); + + test("never renders the api key", () => { + const text = mountTab().text(); + expect(text).not.toContain("s3cr3t-api-key"); + // Its presence is still reported, without the value. + expect(text).toContain("API Key"); + }); + + test("toggles read as Yes/No", () => { + const text = mountTab().text(); + expect(text).toContain("Folder View"); + expect(text).toContain("Yes"); + expect(text).toContain("No"); + }); + + test("configured-or-not booleans read as Set/Not set", () => { + const text = mountTab().text(); + expect(text).toContain("Set"); + expect(text).toContain("Not set"); + }); + + test("renders new v2 sections with readable labels", () => { + const text = mountTab().text(); + expect(text).toContain("Read Only"); + expect(text).toContain("Bookmarks"); + expect(text).toContain("Single Sign On"); + expect(text).toContain("Reverse Proxy Subpath"); + expect(text).toContain("Metron Credentials"); + }); + + test("renders identifier buckets as source and type", () => { + expect(mountTab().text()).toContain("Metron: Comic"); + }); + + test("survives a params-filtered response with sections missing", () => { + // /admin/stats?platform=... returns only the requested sections. + const wrapper = mountTab({ platform: STATS.platform }); + expect(wrapper.text()).toContain("arm64"); + expect(wrapper.text()).not.toContain("Bookmarks"); + }); +}); diff --git a/pyproject.toml b/pyproject.toml index eb3ed0f5a..48329a513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "adrf~=0.1.12", "bidict~=0.23", "channels~=4.2", - "comicbox[pdf]~=4.5.1", + "comicbox[pdf]~=4.6.0", "cryptography>=48.0.0", "dateparser~=1.2", "django-allauth[socialaccount]~=65.13", @@ -49,7 +49,7 @@ readme = "README.md" requires-python = ">=3.12" license = "GPL-3.0-only" name = "codex" -version = "2.2.2" +version = "2.2.3" [[project.authors]] name = "AJ Slater" email = "aj@slater.net" diff --git a/tests/test_telemeter.py b/tests/test_telemeter.py new file mode 100644 index 000000000..76a7f0beb --- /dev/null +++ b/tests/test_telemeter.py @@ -0,0 +1,173 @@ +"""Tests for the anonymous stats payload and how it is transmitted.""" + +import json +from email.message import Message +from lzma import decompress +from typing import override +from unittest.mock import patch +from urllib.error import HTTPError +from urllib.parse import urlsplit + +import pytest +from django.http import QueryDict +from django.test import TestCase + +from codex.librarian.telemeter import telemeter +from codex.librarian.telemeter.stats import CodexStats +from codex.serializers.admin.stats import ( + AdminStatsRequestSerializer, + StatsSerializer, +) + +EXPECTED_SECTIONS = ( + "platform", + "config", + "sessions", + "collections", + "file_types", + "metadata", + "usage", + "identifiers", + "admin_flags", + "tagging", + "auth", + "email", + "throttle", + "deployment", +) + + +class _FakeResponse: + """Stand in for http.client.HTTPResponse.""" + + def __init__(self, status: int = 200) -> None: + self.status = status + + def __enter__(self): + return self + + def __exit__(self, *_args) -> bool: + return False + + +class TelemeterTransportTestCase(TestCase): + """The url and headers the telemeter actually puts on the wire.""" + + def test_posts_to_wire_version_two(self) -> None: + """Codex 2.2.3 and later report the expanded payload as version 2.""" + assert telemeter._VERSION == "2" # noqa: SLF001 + assert telemeter._POST.endswith("/stats/codex/2") # noqa: SLF001 + + def test_credentials_are_not_in_the_url(self) -> None: + """ + Userinfo in the url makes http.client read the password as a port. + + That raised InvalidURL on every send, silently, for months. + """ + netloc = urlsplit(telemeter._POST).netloc # noqa: SLF001 + assert "@" not in netloc + assert netloc + + def test_credentials_move_to_an_authorization_header(self) -> None: + """The credentials taken out of the url are still sent.""" + headers = telemeter._new_headers() # noqa: SLF001 + assert headers["Content-Type"] == "application/xz" + assert headers.get("Authorization", "").startswith("Basic ") + + def test_posts_compressed_json(self) -> None: + """The body is the lzma-compressed payload, and the url is reachable.""" + payload = {"uuid": "abc", "stats": {"platform": {"codex_version": "2.2.3"}}} + with patch.object(telemeter, "urlopen") as mock_urlopen: + mock_urlopen.return_value = _FakeResponse() + telemeter._post_stats(payload) # noqa: SLF001 + + request = mock_urlopen.call_args.args[0] + assert request.get_method() == "POST" + assert request.host == urlsplit(telemeter._POST).netloc # noqa: SLF001 + assert json.loads(decompress(request.data).decode()) == payload + + def test_http_error_reaches_the_caller(self) -> None: + """A rejected post must not look like a successful one.""" + error = HTTPError(telemeter._POST, 500, "boom", Message(), None) # noqa: SLF001 + with ( + patch.object(telemeter, "urlopen", side_effect=error), + pytest.raises(HTTPError), + ): + telemeter._post_stats({"uuid": "abc"}) # noqa: SLF001 + + def test_send_telemetry_survives_a_failed_post(self) -> None: + """A dead stats server must never break the install.""" + logged = [] + + class _Log: + @staticmethod + def warning(msg) -> None: + logged.append(msg) + + error = HTTPError(telemeter._POST, 500, "boom", Message(), None) # noqa: SLF001 + with patch.object(telemeter, "urlopen", side_effect=error): + telemeter.send_telemetry(_Log()) + assert logged + + +class TelemeterStatsTestCase(TestCase): + """The shape of the payload the telemeter builds.""" + + @override + def setUp(self) -> None: + from codex.startup import init_admin_flags, init_timestamps + + init_admin_flags() + init_timestamps() + + def test_every_section_present(self) -> None: + """A full payload carries every section, in payload order.""" + stats = CodexStats().get() + assert tuple(stats) == EXPECTED_SECTIONS + + def test_serializer_reports_every_payload_field(self) -> None: + """ + The admin Stats tab is the visible contract for what is collected. + + Anything the telemeter sends must also be rendered there, or the page + understates what leaves the install. + """ + stats = CodexStats().get() + data = StatsSerializer(stats).data + assert set(stats) == set(data) + for section, values in stats.items(): + if not isinstance(values, dict) or not isinstance(data[section], dict): + continue + assert not set(values) - set(data[section]), ( + f"{section} fields missing from StatsSerializer" + ) + + def test_params_select_sections(self) -> None: + """The admin endpoint can ask for a subset.""" + stats = CodexStats({"platform": {}, "throttle": {}}).get() + assert set(stats) == {"platform", "throttle"} + + def test_default_admin_request_returns_every_section(self) -> None: + """ + A section missing from the request serializer vanishes from the tab. + + AdminStatsRequestSerializer fills in every declared section, so params + is always truthy and the section gate drops anything undeclared. Adding + a section to StatsSerializer without adding it here renders an empty + table with no error, which is how the identifiers section was lost. + """ + request_serializer = AdminStatsRequestSerializer(data=QueryDict("ts=1")) + assert request_serializer.is_valid(), request_serializer.errors + params = dict(request_serializer.validated_data) + assert tuple(CodexStats(params).get()) == EXPECTED_SECTIONS + + def test_new_sections_have_content(self) -> None: + """The seeded singletons and flags produce real values.""" + stats = CodexStats().get() + assert stats["admin_flags"]["send_telemetry"] in (True, False) + assert "url_path_prefix_set" in stats["deployment"] + assert "throttle_anon" in stats["throttle"] + assert "bookmark_count" in stats["usage"] + assert "library_read_only_count" in stats["config"] + assert "multi_sort_count" in stats["sessions"] + assert "comic_community_rating_count" in stats["metadata"] diff --git a/tests/test_telemeter_privacy.py b/tests/test_telemeter_privacy.py new file mode 100644 index 000000000..6a775b696 --- /dev/null +++ b/tests/test_telemeter_privacy.py @@ -0,0 +1,281 @@ +""" +The anonymous stats payload must never carry private information. + +Seeds a sentinel into every admin-authored string codex stores -- credentials, +urls, claim names, group names, banner text, api key, library paths -- and +asserts none of them reach the wire. Then walks the payload and requires every +leaf to be a number, a boolean, or a string drawn from a closed vocabulary. + +If a new stat legitimately needs a new kind of string, add its vocabulary to +``_vocabularies`` deliberately. Do not widen the walk. +""" + +import json +from typing import Final, override +from uuid import uuid4 + +from django.test import TestCase + +from codex.choices.admin import AdminFlagChoices +from codex.collection import Collection +from codex.librarian.telemeter.stats import CodexStats +from codex.models.admin import ( + AdminFlag, + ComicboxTaggingDefaults, + EmailSettings, + OIDCSettings, +) +from codex.models.identifier import Identifier, IdentifierSource, IdentifierType +from codex.models.library import Library + +# Anything containing this must never appear in the payload. +SENTINEL: Final = "XXSENTINELXX" + +# Keys whose values are free-form by nature and safe: version strings and the +# platform description, which carry no user or install identity. +_PLATFORM_KEYS: Final = frozenset( + {"machine", "name", "release", "python_version", "codex_version"} +) + +# The only payload dicts whose *keys* come from data rather than from code. +# Everywhere else a key is a field name chosen in codex's source, so checking +# it proves nothing. These are the ones that must stay closed-vocabulary. +_BUCKET_PATHS: Final = frozenset( + { + "stats.file_types", + "stats.identifiers", + "stats.usage.favorites", + "stats.tagging.default_sources", + "stats.sessions.top_collection", + "stats.sessions.order_by", + "stats.sessions.dynamic_covers", + "stats.sessions.view_mode", + "stats.sessions.table_cover_size", + "stats.sessions.custom_covers", + "stats.sessions.finish_on_last_page", + "stats.sessions.fit_to", + "stats.sessions.reading_direction", + } +) + + +def _vocabularies() -> frozenset[str]: + """Every closed vocabulary a payload string may be drawn from.""" + from codex.choices.browser import ( + BROWSER_ORDER_BY_CHOICES, + BROWSER_TABLE_COVER_SIZE_CHOICES, + BROWSER_VIEW_MODE_CHOICES, + ) + from codex.choices.reader import READER_CHOICES + from codex.models.age_rating import AgeRatingMetron + from codex.models.choices import FileTypeChoices + + values: set[str] = {"", "other", "true", "false", "unknown"} + values |= {member.value for member in Collection} + values |= {member.value.lower() for member in FileTypeChoices} + values |= {member.value for member in IdentifierType} + values |= set(BROWSER_ORDER_BY_CHOICES) + values |= set(BROWSER_VIEW_MODE_CHOICES) + values |= set(BROWSER_TABLE_COVER_SIZE_CHOICES) + for reader_choices in READER_CHOICES.values(): + values |= {value.lower() for value in reader_choices} + values |= {mode.value for mode in ComicboxTaggingDefaults.MatchModeChoices} + values |= {mode.value for mode in ComicboxTaggingDefaults.PromptsModeChoices} + values |= set(AgeRatingMetron.objects.values_list("name", flat=True)) + # Identifier buckets are ":", both closed vocabularies. + from comicbox.enums.maps.identifiers import ID_SOURCE_NAME_MAP + + sources = {source.value for source in ID_SOURCE_NAME_MAP} | {"other"} + kinds = {member.value for member in IdentifierType} | {"other"} + values |= {f"{source}:{kind}" for source in sources for kind in kinds} + # comicbox online source names, used as tagging bucket keys. + from comicbox.formats.base.online import SOURCE_NAMES + + values |= set(SOURCE_NAMES) + # OIDC client authentication methods. + values |= { + "client_secret_basic", + "client_secret_post", + "client_secret_jwt", + "private_key_jwt", + "none", + } + return frozenset(values) + + +class TelemeterPrivacyTestCase(TestCase): + """Assert the stats payload leaks nothing an administrator typed.""" + + @override + def setUp(self) -> None: + from codex.startup import init_admin_flags, init_timestamps + + init_admin_flags() + init_timestamps() + self._seed_secrets() + + @staticmethod + def _set_singleton(model, **fields) -> None: + """Update the singleton row, which migrations may already have made.""" + row = model.objects.first() or model() + for name, value in fields.items(): + setattr(row, name, value) + row.save() + + @classmethod + def _seed_secrets(cls) -> None: + """Put a sentinel in every admin-authored string codex stores.""" + cls._set_singleton( + ComicboxTaggingDefaults, + metron_user=f"{SENTINEL}-metron-user", + metron_password=f"{SENTINEL}-metron-password", + metron_url=f"https://{SENTINEL}-metron.example.com", + comicvine_key=f"{SENTINEL}-comicvine-key", + comicvine_url=f"https://{SENTINEL}-comicvine.example.com", + default_sources=["metron", f"{SENTINEL}-source"], + ) + cls._set_singleton( + OIDCSettings, + enabled=True, + provider_name=f"{SENTINEL}-provider", + server_url=f"https://{SENTINEL}-idp.example.com", + client_id=f"{SENTINEL}-client-id", + client_secret=f"{SENTINEL}-client-secret", + scope=f"openid {SENTINEL}-scope", + username_claim=f"{SENTINEL}-claim", + groups_claim=f"{SENTINEL}-groups", + admin_group=f"{SENTINEL}-admins", + token_auth_method=f"{SENTINEL}-auth-method", + ) + cls._set_singleton( + EmailSettings, + host=f"smtp.{SENTINEL}.example.com", + user=f"{SENTINEL}-smtp-user", + password=f"{SENTINEL}-smtp-password", + from_address=f"{SENTINEL}@example.com", + subject_prefix=f"[{SENTINEL}] ", + ) + AdminFlag.objects.filter(key=AdminFlagChoices.BANNER_TEXT.value).update( + value=f"{SENTINEL}-banner" + ) + AdminFlag.objects.filter(key=AdminFlagChoices.API_KEY.value).update( + value=f"{SENTINEL}-api-key" + ) + Library.objects.create(path=f"/{SENTINEL}-library", read_only=True) + source = IdentifierSource.objects.create(name=f"{SENTINEL}-source") + Identifier.objects.create( + source=source, + id_type=IdentifierType.ISSUE.value, + key=f"{SENTINEL}-id", + url=f"https://{SENTINEL}.example.com/1", + ) + + @staticmethod + def _walk(node, path: str, leaves: list[tuple[str, object]]) -> None: + """Collect every value, plus the keys of data-derived count buckets.""" + if isinstance(node, dict): + in_bucket = path in _BUCKET_PATHS + for key, value in node.items(): + if in_bucket: + leaves.append((f"{path}[key]", key)) + TelemeterPrivacyTestCase._walk(value, f"{path}.{key}", leaves) + elif isinstance(node, list): + for index, value in enumerate(node): + TelemeterPrivacyTestCase._walk(value, f"{path}[{index}]", leaves) + else: + leaves.append((path, node)) + + def test_no_sentinel_in_payload(self) -> None: + """No admin-authored string reaches the serialized payload.""" + payload = json.dumps( + {"stats": CodexStats().get(), "uuid": str(uuid4())}, default=str + ) + assert SENTINEL not in payload + + def test_unknown_identifier_source_collapsed(self) -> None: + """A source name from a comic file is replaced by "other".""" + identifiers = CodexStats().get()["identifiers"] + assert identifiers + for key in identifiers: + assert SENTINEL not in key + assert f"other:{IdentifierType.ISSUE.value}" in identifiers + + def test_every_leaf_is_a_number_bool_or_closed_vocabulary(self) -> None: + """Walk the payload; no value may be an open-ended string.""" + vocabularies = {value.lower() for value in _vocabularies()} + leaves: list[tuple[str, object]] = [] + self._walk(CodexStats().get(), "stats", leaves) + assert leaves + + unexpected = [] + for path, value in leaves: + if isinstance(value, bool | int | float) or value is None: + continue + if not isinstance(value, str): + unexpected.append((path, value)) + continue + if path.rsplit(".", 1)[-1] in _PLATFORM_KEYS: + continue + if value.lower() not in vocabularies: + unexpected.append((path, value)) + assert not unexpected, f"open-ended strings in payload: {unexpected}" + + @staticmethod + def _declared_field_names() -> frozenset[str]: + """Every key name StatsSerializer declares, at any nesting depth.""" + from rest_framework.serializers import Serializer + + from codex.serializers.admin.stats import StatsSerializer + + names: set[str] = set() + + def walk(serializer) -> None: + for name, field in serializer.get_fields().items(): + names.add(name) + if isinstance(field, Serializer): + walk(field) + + walk(StatsSerializer()) + return frozenset(names) + + @classmethod + def _undeclared_keys(cls, node, path: str, declared, found: list) -> None: + """Collect dict keys that are neither declared fields nor bucket keys.""" + if not isinstance(node, dict): + return + if path in _BUCKET_PATHS: + # Registered bucket: its keys are data, and the vocabulary walk + # checks them. + return + for key, value in node.items(): + if key not in declared: + found.append(f"{path}.{key}") + cls._undeclared_keys(value, f"{path}.{key}", declared, found) + + def test_no_unregistered_data_keyed_bucket(self) -> None: + """ + A count bucket missing from _BUCKET_PATHS is checked by nothing. + + Its keys never become leaf values, so the vocabulary walk skips them, + and the sentinel test only seeds admin config -- not publisher, series + or user rows. So instead of trusting _BUCKET_PATHS to describe itself, + require every key in the payload to be either a field name declared in + StatsSerializer or a key inside a registered bucket. A new bucket of + library-sourced names satisfies neither. + """ + declared = self._declared_field_names() + found: list[str] = [] + self._undeclared_keys(CodexStats().get(), "stats", declared, found) + assert not found, ( + "payload keys that are neither declared serializer fields nor " + f"registered bucket keys: {sorted(found)}" + ) + + def test_the_bucket_check_catches_an_unregistered_bucket(self) -> None: + """Prove the check above can actually fail.""" + payload = CodexStats().get() + payload["usage"]["top_publishers"] = {"Marvel Comics": 10} + found: list[str] = [] + self._undeclared_keys(payload, "stats", self._declared_field_names(), found) + assert "stats.usage.top_publishers" in found + assert "stats.usage.top_publishers.Marvel Comics" in found diff --git a/uv.lock b/uv.lock index cda057d5c..310dfda23 100644 --- a/uv.lock +++ b/uv.lock @@ -567,7 +567,7 @@ wheels = [ [[package]] name = "codex" -version = "2.2.2" +version = "2.2.3" source = { editable = "." } dependencies = [ { name = "adrf" }, @@ -653,7 +653,7 @@ requires-dist = [ { name = "adrf", specifier = "~=0.1.12" }, { name = "bidict", specifier = "~=0.23" }, { name = "channels", specifier = "~=4.2" }, - { name = "comicbox", extras = ["pdf"], specifier = "~=4.5.1" }, + { name = "comicbox", extras = ["pdf"], specifier = "~=4.6.0" }, { name = "cryptography", specifier = ">=48.0.0" }, { name = "dateparser", specifier = "~=1.2" }, { name = "django", specifier = "~=6.0" }, @@ -739,7 +739,7 @@ wheels = [ [[package]] name = "comicbox" -version = "4.5.1" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, @@ -776,9 +776,9 @@ dependencies = [ { name = "xmltodict" }, { name = "zipremove" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/55/0a69c9e807b7c18b39cf4aa3da8280ef9dddb5b64377381999bca6490a1e/comicbox-4.5.1.tar.gz", hash = "sha256:e90c58dfbe9e234fe89c8ad2684ff5414f5045439eca2bcbe771e6b80e9770ff", size = 1009771, upload-time = "2026-07-24T20:15:28.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/99/df7bde0961d78e5ca8f5a5a06442cf11c44de180e839c799aa93478aa74a/comicbox-4.6.0.tar.gz", hash = "sha256:deceb8baaa382cb736dd9501aa2e6fec53024fa1c5b4a973e6d401a4e1ff65a2", size = 1013136, upload-time = "2026-07-26T00:41:14.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/d0/e14bf8b03fbc139c75855996fa9e7f28b9291beb4ed79f58bba40079e7bd/comicbox-4.5.1-py3-none-any.whl", hash = "sha256:e7d188e0520330eae76298b3f3cfa1a43fa164dd22ea9fb771e1597dd3f6503c", size = 340303, upload-time = "2026-07-24T20:15:26.84Z" }, + { url = "https://files.pythonhosted.org/packages/43/d8/199aa718d77f0eaaf71f7ef94c38eccfbc34cde420e1bb5a4064b507fb4b/comicbox-4.6.0-py3-none-any.whl", hash = "sha256:2298e6ff2fd39b5e814825405e80edd4ccff9f461bfcf4844f20f201f9c34c60", size = 341933, upload-time = "2026-07-26T00:41:13.182Z" }, ] [package.optional-dependencies] From 9b102cda39ea1f486d0e6ffa3a81b2863d6353f0 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 25 Jul 2026 18:01:02 -0700 Subject: [PATCH 44/82] update deps --- frontend/bun.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/bun.lock b/frontend/bun.lock index d439d65ed..cc4b7b983 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -29,7 +29,7 @@ "@vue/typescript-plugin": "^3.3.8", "happy-dom": "^20.11.1", "rollup-plugin-visualizer": "^7.0.1", - "sass": "^1.101.7", + "sass": "^1.102.0", "sass-loader": "^17.0.0", "toml": "^5.0.0", "typeface-roboto": "^1.1.13", From a92236d7a5fda94d3a757eeb43b2d26464f15714 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 01:08:44 -0700 Subject: [PATCH 45/82] Support comicbox 4.6.0 series alternative names comicbox 4.6.0 records a series' localized and variant titles as reprints: from MetronInfo AlternativeNames and Reprints tags, and now from Metron and Comic Vine online tagging. Codex dropped the field entirely -- reprints sat outside USED_COMICBOX_FIELDS, so the read config told comicbox to skip parsing it and the aggregator popped it as a backstop. Import them into a new denormalized Reprint model keyed on series name, volume number, issue and language. Series and Volume are browse collections, so reusing them would have hung phantom rows off the browser for every alternate title. A reprint may carry only a series sort_name, which stands in when the name is absent; one with neither is dropped. Alternate names reach the metadata panel, the tag editor, a sortable browser column and filter, and a distinct alternate_series FTS column, so a comic filed under a localized title is findable by it. The column is distinct rather than folded into series to keep the series: token exact; unqualified searches match either. Online matching now recognizes Comic Vine volume aliases, which codex inherits by delegating matching to comicbox. The match prompt shows each candidate's alternative names, since a comic filed under a localized title matching its canonical volume otherwise reads as a wrong match, and passes the chosen candidate's volume id, which was plumbed end to end but always null. Drop the auto_threshold setting. It was parsed, serialized and round-tripped through resume, but never reached OnlineSession, which accepts no threshold. Reconcile sidecar columns on open. schema.sql is all CREATE TABLE IF NOT EXISTS, so a sidecar predating a release never gained its new settings_filters columns and the next dump failed with "no such column". The wanted columns derive from the schema file rather than a list that would rot the same way. This also repairs the column the community_rating rename added. Already-imported comics pick alternate names up on a Force Update Tags; adding a field to the whitelist does not invalidate metadata_mtime. Co-Authored-By: Claude Fable 5 --- NEWS.md | 16 ++ codex/choices/browser.py | 15 +- codex/choices/search.py | 3 + codex/choices/tagging.py | 19 +- codex/librarian/onlinetag/session_manager.py | 1 - codex/librarian/onlinetag/session_state.py | 6 + codex/librarian/onlinetag/tasks.py | 1 - codex/librarian/scribe/importer/const.py | 29 ++- .../librarian/scribe/importer/create/const.py | 14 ++ codex/librarian/scribe/importer/link/const.py | 2 + .../librarian/scribe/importer/link/delete.py | 4 +- .../scribe/importer/read/many_to_many.py | 79 +++++- .../scribe/importer/search/prepare.py | 5 + codex/librarian/scribe/janitor/cleanup.py | 4 +- codex/librarian/scribe/search/const.py | 1 + codex/librarian/scribe/search/prepare.py | 1 + codex/librarian/scribe/search/sync.py | 1 + codex/librarian/scribe/tag_writer.py | 2 +- codex/migrations/0049_reprints.py | 202 +++++++++++++++ codex/models/comic.py | 16 +- codex/models/identifier.py | 2 +- codex/models/named.py | 59 ++++- codex/models/settings.py | 2 + codex/serializers/admin/stats.py | 1 + codex/serializers/admin/tagging.py | 1 - codex/serializers/browser/choices.py | 23 ++ codex/serializers/browser/filters.py | 1 + codex/serializers/models/comic.py | 2 + codex/serializers/models/named.py | 25 ++ codex/settings/__init__.py | 9 +- codex/user_data/identifiers.py | 13 + codex/user_data/restore.py | 14 +- codex/user_data/schema.sql | 1 + codex/user_data/serializers.py | 7 +- codex/user_data/store.py | 48 ++++ codex/views/admin/onlinetag.py | 5 +- codex/views/browser/choices.py | 9 +- codex/views/browser/columns.py | 36 ++- codex/views/browser/const.py | 1 + codex/views/browser/filters/filter.py | 1 + .../browser/filters/search/field/column.py | 1 + codex/views/browser/intersections.py | 120 +++++++-- codex/views/browser/metadata/const.py | 7 + codex/views/browser/saved_settings.py | 9 +- codex/views/const.py | 2 + .../browser/table/browser-table-cell.vue | 1 + .../table/browser-table-column-picker.vue | 1 + .../browser/toolbars/top/filter-sub-menu.vue | 3 + .../metadata/edit-mode/edit-panel.vue | 132 ++++++++++ .../components/online-tag/prompt-popup.vue | 26 +- frontend/src/stores/metadata.js | 15 +- .../tests/unit/browser-table-cell.test.js | 14 ++ .../unit/browser-table-column-picker.test.js | 6 +- .../tests/unit/edit-panel-reprints.test.js | 228 +++++++++++++++++ frontend/tests/unit/metadata-reprints.test.js | 88 +++++++ frontend/tests/unit/prompt-popup.test.js | 130 ++++++++++ tests/importer/test_basic.py | 71 +++++- tests/importer/test_move_timestamps.py | 21 +- tests/importer/test_update_all.py | 9 + tests/test_browser_reprints_column.py | 230 ++++++++++++++++++ tests/test_browser_table_response.py | 4 +- tests/test_metadata_reprints.py | 159 ++++++++++++ tests/test_onlinetag_resume_endpoints.py | 14 +- tests/test_reprints.py | 133 ++++++++++ tests/test_search_alternate_series.py | 133 ++++++++++ tests/test_user_data_restore.py | 37 +++ tests/test_user_data_store.py | 35 +++ 67 files changed, 2211 insertions(+), 99 deletions(-) create mode 100644 codex/migrations/0049_reprints.py create mode 100644 frontend/tests/unit/edit-panel-reprints.test.js create mode 100644 frontend/tests/unit/metadata-reprints.test.js create mode 100644 frontend/tests/unit/prompt-popup.test.js create mode 100644 tests/test_browser_reprints_column.py create mode 100644 tests/test_metadata_reprints.py create mode 100644 tests/test_reprints.py create mode 100644 tests/test_search_alternate_series.py diff --git a/NEWS.md b/NEWS.md index 10ccb9dcb..45dea6e1e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,22 @@ border-radius: 128px; ## v2.2.3 +- Features + - Alternate series names: the localized and variant series titles comics + carry in MetronInfo AlternativeNames and Reprints tags (and that Metron + and Comic Vine online tagging now record) are imported, shown in the + metadata panel, editable in the tag editor, and browsable — sort, table + column, filter, and `alternate_series:` search (comicbox 4.6.0). Comics + imported or tagged online from now on pick them up automatically; + already-imported comics need a Force Update Tags. + - Online tagging matches a comic filed under a localized or variant series + title to the right volume, and the match prompt shows each candidate's + other known names (comicbox 4.6.0). + +- Fixes + - User data backups no longer skip every saved filter set after an upgrade + that adds a new filter. + ## v2.2.2 - Fixes diff --git a/codex/choices/browser.py b/codex/choices/browser.py index df216e949..d570642c8 100644 --- a/codex/choices/browser.py +++ b/codex/choices/browser.py @@ -18,6 +18,7 @@ { "created_at": "Added Time", "age_rating": "Age Rating", + "reprints": "Alternate Series", "characters": "Characters", "child_count": "Child Count", "community_rating": "Community Rating", @@ -528,6 +529,17 @@ def admin_default_route_for(top_collection: str) -> dict: "editable": False, "edit_widget": None, }, + "reprints": { + # Alternate & localized series names (comicbox ``reprints``). + # "Reprints" reads as reprint editions to users, so the + # column, order-by entry and filter all say "Alternate + # Series"; only the ORM key stays ``reprints``. + "label": "Alternate Series", + "sort_key": "reprints", + "m2m": True, + "editable": False, + "edit_widget": None, + }, "series_groups": { "label": "Series Groups", "sort_key": "series_groups", @@ -602,7 +614,7 @@ def admin_default_route_for(top_collection: str) -> dict: # shape). Display is one query for the whole page; sort scales # with the filtered collection count when the user clicks the header. # - ``high``: composite-M2M columns (``credits`` / ``identifiers`` -# / ``universes`` / ``story_arcs``). Display issues its own +# / ``reprints`` / ``universes`` / ``story_arcs``). Display issues its own # per-column query (composite display strings can't share the # simple-M2M union shape); sort runs a per-outer-row correlated # subquery that JOINs the bespoke composite expression. Both @@ -624,6 +636,7 @@ def admin_default_route_for(top_collection: str) -> dict: # Composite M2M — display + sort both per-column / per-row. "credits": "high", "identifiers": "high", + "reprints": "high", "story_arcs": "high", "universes": "high", } diff --git a/codex/choices/search.py b/codex/choices/search.py index ff1489e30..cd1f1ceee 100644 --- a/codex/choices/search.py +++ b/codex/choices/search.py @@ -62,6 +62,9 @@ def _get_fieldmap_values(*args) -> tuple: "age", "age_rating", "age_rating_metron" ), "age_rating_tagged": (), + "alternate_series": _get_fieldmap_values( + "alternate_series", "alt_series", "reprints" + ), "characters": _get_fieldmap_values("category", "categories", "characters"), "collection_title": ("collection",), "country": (), diff --git a/codex/choices/tagging.py b/codex/choices/tagging.py index fcee03559..1e6413db7 100644 --- a/codex/choices/tagging.py +++ b/codex/choices/tagging.py @@ -78,8 +78,8 @@ def _vuetify_choices(pairs: Iterable[tuple[str, str]]) -> tuple[MappingProxyType # most fields; the splits/renames are codex's relational model (issue split into # number + suffix so issues sort numerically, comicbox "arcs" stored as # story_arcs, ComicInfo's "manga"/"title" surfaced as reading_direction/stories). -# Canonical keys absent here (bookmark, date, pages, reprints, page_count, -# prices, updated_at, ...) are not tag-editor fields and are dropped. +# Canonical keys absent here (bookmark, date, pages, page_count, prices, +# updated_at, ...) are not tag-editor fields and are dropped. _CANONICAL_TO_EDITOR: MappingProxyType[str, tuple[str, ...]] = MappingProxyType( { "publisher": ("publisher",), @@ -113,6 +113,9 @@ def _vuetify_choices(pairs: Iterable[tuple[str, str]]) -> tuple[MappingProxyType "community_rating": ("community_rating",), "protagonist": ("protagonist",), "identifiers": ("identifiers",), + # The series name and issue only; the volume number and language are + # MetronInfo-exclusive — see _EXTRA_FORMAT_FIELDS. + "reprints": ("reprints",), "country": ("country",), "universes": ("universes",), } @@ -121,10 +124,18 @@ def _vuetify_choices(pairs: Iterable[tuple[str, str]]) -> tuple[MappingProxyType # Editor sub-fields comicbox stores only for some formats and which its # transform specs don't surface at the SPECS_TO top level (the # hand-maintained support data): MetronInfo's Series carries a volume -# count, and only MetronInfo's CommunityRating persists a rating count. +# count, only MetronInfo's CommunityRating persists a rating count, and +# only MetronInfo encodes a reprint's volume number (in Reprints) and +# language (in Series/AlternativeNames) — ComicInfo's AlternateSeries / +# AlternateNumber / AlternateCount carry neither. _EXTRA_FORMAT_FIELDS: MappingProxyType[str, tuple[str, ...]] = MappingProxyType( { - "METRON_INFO": ("volume_count", "community_rating_count"), + "METRON_INFO": ( + "volume_count", + "community_rating_count", + "reprint_volume", + "reprint_language", + ), } ) diff --git a/codex/librarian/onlinetag/session_manager.py b/codex/librarian/onlinetag/session_manager.py index 12d600856..a8a8c92ed 100644 --- a/codex/librarian/onlinetag/session_manager.py +++ b/codex/librarian/onlinetag/session_manager.py @@ -606,7 +606,6 @@ def run_session(self, task: BulkOnlineTagTask) -> None: "sources": list(task.sources), "mode": task.mode, "prompts_mode": task.prompts_mode, - "auto_threshold": task.auto_threshold, "delete_original": task.delete_original, "merge_all_sources": task.merge_all_sources, "rename": task.rename, diff --git a/codex/librarian/onlinetag/session_state.py b/codex/librarian/onlinetag/session_state.py index 5e4a94370..e55093202 100644 --- a/codex/librarian/onlinetag/session_state.py +++ b/codex/librarian/onlinetag/session_state.py @@ -79,7 +79,13 @@ def serialize_candidate(c) -> dict[str, Any]: "year": getattr(summary, "year", None), "publisher": getattr(summary, "publisher", ""), "cover_url": getattr(summary, "cover_url", ""), + # Alternative series names comicbox scored this candidate on. + # Empty for sources whose search results don't carry them. + "alt_series": list(getattr(summary, "alt_series", ())), }, "score": c.score, "url": getattr(c, "url", ""), + # The candidate's parent container id (CV volume, Metron series). + # None for sources that don't expose it. + "volume_id": getattr(c, "volume_id", None), } diff --git a/codex/librarian/onlinetag/tasks.py b/codex/librarian/onlinetag/tasks.py index 4e916fcb8..4a4cab4b4 100644 --- a/codex/librarian/onlinetag/tasks.py +++ b/codex/librarian/onlinetag/tasks.py @@ -21,7 +21,6 @@ class BulkOnlineTagTask(OnlineTagTask): sources: tuple[str, ...] = SOURCE_NAMES mode: str = "auto" prompts_mode: str = "ask" - auto_threshold: float = 0.85 delete_original: bool = False dry_run: bool = False # Query every source per comic and merge (comicbox first_wins=False) diff --git a/codex/librarian/scribe/importer/const.py b/codex/librarian/scribe/importer/const.py index 069e81ab7..a5fa4c7cc 100644 --- a/codex/librarian/scribe/importer/const.py +++ b/codex/librarian/scribe/importer/const.py @@ -32,6 +32,7 @@ Language, Location, OriginalFormat, + Reprint, ScanInfo, SeriesGroup, Story, @@ -172,12 +173,12 @@ def _metron_fts_values(values: tuple) -> tuple: ), ) COMIC_M2M_FIELD_NAMES: tuple[str, ...] = tuple(field.name for field in COMIC_M2M_FIELDS) -COMPLEX_M2M_MODELS = (Credit, Identifier, StoryArcNumber) +COMPLEX_M2M_MODELS = (Credit, Identifier, Reprint, StoryArcNumber) ######################## # COMPLEX M2M METADATA # ######################## -DictModelType = Credit | Identifier | StoryArcNumber +DictModelType = Credit | Identifier | Reprint | StoryArcNumber CREDITS_FIELD_NAME = "credits" CREDIT_PERSON_FIELD_NAME = "person" CREDIT_ROLE_FIELD_NAME = "role" @@ -188,6 +189,13 @@ def _metron_fts_values(values: tuple) -> tuple: IDENTIFIER_TYPE_FIELD_NAME = "id_type" IDENTIFIER_ID_KEY_FIELD_NAME = "key" IDENTIFIER_URL_FIELD_NAME = "url" +REPRINTS_FIELD_NAME = "reprints" +REPRINT_SERIES_NAME_FIELD_NAME = "series_name" +REPRINT_VOLUME_NUMBER_FIELD_NAME = "volume_number" +REPRINT_ISSUE_FIELD_NAME = "issue" +REPRINT_LANGUAGE_FIELD_NAME = "language" +# Reprints index only their series names, under their own ComicFTS column. +ALTERNATE_SERIES_FTS_FIELD_NAME = "alternate_series" UNIVERSES_FIELD_NAME = "universes" NAME_FIELD_NAME = "name" NUMBER_TO_FIELD_NAME = "number_to" @@ -293,6 +301,15 @@ def _metron_fts_values(values: tuple) -> tuple: ), "", ), + Reprint: ( + ( + REPRINT_SERIES_NAME_FIELD_NAME, + REPRINT_VOLUME_NUMBER_FIELD_NAME, + REPRINT_ISSUE_FIELD_NAME, + REPRINT_LANGUAGE_FIELD_NAME, + ), + _IDENTIFIER_RELS, + ), StoryArcNumber: ( (f"{STORY_ARC_FIELD_NAME}__name", NUMBER_FIELD_NAME), "", @@ -311,6 +328,7 @@ def _metron_fts_values(values: tuple) -> tuple: Series: ("publisher", "imprint"), Volume: ("publisher", "imprint", "series"), Credit: (CREDIT_PERSON_FIELD_NAME, CREDIT_ROLE_FIELD_NAME), + Reprint: _IDENTIFIED_SELECT_RELATED, StoryArcNumber: (STORY_ARC_FIELD_NAME,), Universe: _IDENTIFIED_SELECT_RELATED, } @@ -350,6 +368,12 @@ def _metron_fts_values(values: tuple) -> tuple: *_NAMED_MODEL_ATTRS, ), CREDITS_FIELD_NAME: (CREDIT_PERSON_FIELD_NAME, CREDIT_ROLE_FIELD_NAME), + REPRINTS_FIELD_NAME: ( + REPRINT_SERIES_NAME_FIELD_NAME, + REPRINT_VOLUME_NUMBER_FIELD_NAME, + REPRINT_ISSUE_FIELD_NAME, + REPRINT_LANGUAGE_FIELD_NAME, + ), STORY_ARC_NUMBERS_FIELD_NAME: (STORY_ARC_FIELD_NAME, NUMBER_FIELD_NAME), } ) @@ -368,6 +392,7 @@ def _metron_fts_values(values: tuple) -> tuple: Identifier: IDENTIFIER_ID_KEY_FIELD_NAME, Imprint: NAME_FIELD_NAME, Publisher: NAME_FIELD_NAME, + Reprint: REPRINT_SERIES_NAME_FIELD_NAME, Series: NAME_FIELD_NAME, StoryArc: NAME_FIELD_NAME, StoryArcNumber: f"{STORY_ARC_FIELD_NAME}__name", diff --git a/codex/librarian/scribe/importer/create/const.py b/codex/librarian/scribe/importer/create/const.py index 50215d0fb..437a60e1f 100644 --- a/codex/librarian/scribe/importer/create/const.py +++ b/codex/librarian/scribe/importer/create/const.py @@ -20,12 +20,17 @@ NUMBER_FIELD_NAME, NUMBER_TO_FIELD_NAME, PUBLISHER_FIELD_NAME, + REPRINT_ISSUE_FIELD_NAME, + REPRINT_LANGUAGE_FIELD_NAME, + REPRINT_SERIES_NAME_FIELD_NAME, + REPRINT_VOLUME_NUMBER_FIELD_NAME, SERIES_FIELD_NAME, STORY_ARC_FIELD_NAME, VOLUME_COUNT_FIELD_NAME, ) from codex.models import ( Credit, + Reprint, StoryArc, StoryArcNumber, Volume, @@ -117,6 +122,15 @@ }, {}, ), + Reprint: ( + { + REPRINT_SERIES_NAME_FIELD_NAME: None, + REPRINT_VOLUME_NUMBER_FIELD_NAME: None, + REPRINT_ISSUE_FIELD_NAME: None, + REPRINT_LANGUAGE_FIELD_NAME: None, + }, + {IDENTIFIER_FIELD_NAME: Identifier}, + ), StoryArcNumber: ({STORY_ARC_FIELD_NAME: StoryArc, NUMBER_FIELD_NAME: None}, {}), Universe: ( { diff --git a/codex/librarian/scribe/importer/link/const.py b/codex/librarian/scribe/importer/link/const.py index 49ed000b7..ed2391191 100644 --- a/codex/librarian/scribe/importer/link/const.py +++ b/codex/librarian/scribe/importer/link/const.py @@ -5,6 +5,7 @@ from codex.librarian.scribe.importer.const import ( CREDITS_FIELD_NAME, IDENTIFIERS_FIELD_NAME, + REPRINTS_FIELD_NAME, STORY_ARC_NUMBERS_FIELD_NAME, ) from codex.models.base import BaseModel @@ -12,6 +13,7 @@ COMPLEX_MODEL_FIELD_NAMES = ( CREDITS_FIELD_NAME, + REPRINTS_FIELD_NAME, STORY_ARC_NUMBERS_FIELD_NAME, IDENTIFIERS_FIELD_NAME, ) diff --git a/codex/librarian/scribe/importer/link/delete.py b/codex/librarian/scribe/importer/link/delete.py index f525954cc..40f54c95f 100644 --- a/codex/librarian/scribe/importer/link/delete.py +++ b/codex/librarian/scribe/importer/link/delete.py @@ -38,9 +38,7 @@ def _delete_m2m_field_batch( return count def _delete_m2m_fts_entries(self, field_name: str, comic_ids: set[int]) -> None: - fts_field_name = ( - "story_arcs" if field_name == "story_arc_numbers" else field_name - ) + fts_field_name, _ = self.minify_complex_link_to_fts_tuple(field_name, ()) for comic_id in comic_ids: if not self.metadata.get(FTS_UPDATE, {}).get(comic_id, {}).get( fts_field_name diff --git a/codex/librarian/scribe/importer/read/many_to_many.py b/codex/librarian/scribe/importer/read/many_to_many.py index b1f432dea..668e5c0f4 100644 --- a/codex/librarian/scribe/importer/read/many_to_many.py +++ b/codex/librarian/scribe/importer/read/many_to_many.py @@ -1,10 +1,20 @@ """Aggregate ManyToMany Metadata.""" -from collections.abc import Mapping +from collections.abc import Iterable, Mapping from types import MappingProxyType from typing import TYPE_CHECKING, cast -from comicbox.formats.comicbox.schema import IDENTIFIERS_KEY, NUMBER_KEY, ROLES_KEY +from comicbox.formats.comicbox.schema import ( + IDENTIFIERS_KEY, + ISSUE_KEY, + LANGUAGE_KEY, + NAME_KEY, + NUMBER_KEY, + ROLES_KEY, + SERIES_KEY, + SERIES_SORT_NAME_KEY, + VOLUME_KEY, +) from django.db.models import CharField, Field from django.db.models.fields.related import ManyToManyField @@ -13,6 +23,11 @@ CREDITS_FIELD_NAME, IDENTIFIERS_FIELD_NAME, LINK_M2MS, + REPRINT_ISSUE_FIELD_NAME, + REPRINT_LANGUAGE_FIELD_NAME, + REPRINT_SERIES_NAME_FIELD_NAME, + REPRINT_VOLUME_NUMBER_FIELD_NAME, + REPRINTS_FIELD_NAME, STORY_ARC_NUMBERS_FIELD_NAME, get_key_index, ) @@ -27,11 +42,16 @@ from codex.models.collections import Folder from codex.models.comic import Comic from codex.models.identifier import IdentifierSource -from codex.models.named import CreditRole +from codex.models.named import CreditRole, Reprint if TYPE_CHECKING: from codex.models.base import BaseModel +_REPRINT_SERIES_NAME_FIELD = Reprint._meta.get_field(REPRINT_SERIES_NAME_FIELD_NAME) +_REPRINT_VOLUME_NUMBER_FIELD = Reprint._meta.get_field(REPRINT_VOLUME_NUMBER_FIELD_NAME) +_REPRINT_ISSUE_FIELD = Reprint._meta.get_field(REPRINT_ISSUE_FIELD_NAME) +_REPRINT_LANGUAGE_FIELD = Reprint._meta.get_field(REPRINT_LANGUAGE_FIELD_NAME) + class AggregateManyToManyMetadataImporter(AggregateForeignKeyMetadataImporter): """Aggregate ManyToMany Metadata.""" @@ -222,7 +242,44 @@ def _get_m2m_metadata_dict_model_aggregate_sub_values( field, roles_or_numbers, clean_sub_key, clean_sub_values ) - def _get_m2m_metadata_dict_model( + @staticmethod + def _clean_reprint_key(reprint: Mapping) -> tuple | None: + """ + Flatten one comicbox reprint tree into a Reprint key tuple. + + MetronInfo AlternativeNames supply only a series ``sort_name``, + so that stands in when there's no ``series.name``. A reprint + with neither names nothing, so it's dropped. + """ + series = reprint.get(SERIES_KEY) or {} + series_name = _REPRINT_SERIES_NAME_FIELD.get_prep_value( + series.get(NAME_KEY) or series.get(SERIES_SORT_NAME_KEY) + ) + if not series_name: + return None + volume = reprint.get(VOLUME_KEY) or {} + return ( + series_name, + _REPRINT_VOLUME_NUMBER_FIELD.get_prep_value(volume.get(NUMBER_KEY)), + _REPRINT_ISSUE_FIELD.get_prep_value(reprint.get(ISSUE_KEY) or ""), + _REPRINT_LANGUAGE_FIELD.get_prep_value(reprint.get(LANGUAGE_KEY) or ""), + ) + + def _get_m2m_metadata_reprints(self, values: Iterable[Mapping]) -> dict: + """ + Aggregate reprints, the only m2m keyed by a nested comicbox tree. + + Every other complex m2m arrives as a mapping keyed by a name, a + shape that cannot express the four part reprint key. + """ + clean_values_map: dict[tuple, frozenset[tuple]] = {} + for reprint in values: + if clean_key := self._clean_reprint_key(reprint): + identifier_tuple = self.get_identifier_tuple(Reprint, reprint) + clean_values_map[clean_key] = frozenset({(identifier_tuple,)}) + return clean_values_map + + def _get_m2m_metadata_named_dict_model( self, md_key: str, field: ManyToManyField, @@ -248,7 +305,21 @@ def _get_m2m_metadata_dict_model( sub_value, ) clean_values_map.update(clean_sub_map) + return clean_values_map + def _get_m2m_metadata_dict_model( + self, + md_key: str, + field: ManyToManyField, + values: Mapping[str, Mapping | None] | list | tuple | set | frozenset, + ) -> dict: + # ``values`` is typed as the union every m2m field can present; + # comicbox always hands reprints over as a list of trees. + clean_values_map = ( + self._get_m2m_metadata_reprints(cast("Iterable[Mapping]", values)) + if field.name == REPRINTS_FIELD_NAME + else self._get_m2m_metadata_named_dict_model(md_key, field, values) + ) related_model: type[BaseModel] = field.related_model if related_model != Folder: for key, value in clean_values_map.items(): diff --git a/codex/librarian/scribe/importer/search/prepare.py b/codex/librarian/scribe/importer/search/prepare.py index 8f6388434..4c69e5502 100644 --- a/codex/librarian/scribe/importer/search/prepare.py +++ b/codex/librarian/scribe/importer/search/prepare.py @@ -1,6 +1,7 @@ """Prepare FTS update methods used in earlier import steps.""" from codex.librarian.scribe.importer.const import ( + ALTERNATE_SERIES_FTS_FIELD_NAME, CREDITS_FIELD_NAME, FTS_CREATE, FTS_CREATED_M2MS, @@ -8,6 +9,7 @@ FTS_FIELD_TARGETS, FTS_UPDATE, NON_FTS_FIELDS, + REPRINTS_FIELD_NAME, STORY_ARC_FIELD_NAME, STORY_ARC_NUMBERS_FIELD_NAME, ) @@ -27,6 +29,9 @@ def minify_complex_link_to_fts_tuple( """Only store the fts relevant parts of complex links.""" if field_name == CREDITS_FIELD_NAME: values = tuple(subvalues[0] for subvalues in values) + elif field_name == REPRINTS_FIELD_NAME: + field_name = ALTERNATE_SERIES_FTS_FIELD_NAME + values = tuple(subvalues[0] for subvalues in values) elif field_name == STORY_ARC_NUMBERS_FIELD_NAME: field_name = STORY_ARC_FIELD_NAME + "s" return field_name, tuple(values) diff --git a/codex/librarian/scribe/janitor/cleanup.py b/codex/librarian/scribe/janitor/cleanup.py index e61b6537c..60da1eb33 100644 --- a/codex/librarian/scribe/janitor/cleanup.py +++ b/codex/librarian/scribe/janitor/cleanup.py @@ -50,6 +50,7 @@ Location, OriginalFormat, Publisher, + Reprint, ScanInfo, Series, SeriesGroup, @@ -88,6 +89,7 @@ Imprint, OriginalFormat, Publisher, + Reprint, Series, SeriesGroup, ScanInfo, @@ -222,7 +224,7 @@ def cleanup_fks(self) -> None: Wrapped in ``transaction.atomic`` so the multi-pass convergence is all-or-nothing: an exception mid-pass rolls back to pre- cleanup state rather than leaving a partially-cleaned graph. - The 25 per-model deletes also coalesce into one fsync. + The per-model deletes also coalesce into one fsync. """ self.abort_event.clear() status = JanitorCleanupTagsStatus(0) diff --git a/codex/librarian/scribe/search/const.py b/codex/librarian/scribe/search/const.py index 950552366..812d40e84 100644 --- a/codex/librarian/scribe/search/const.py +++ b/codex/librarian/scribe/search/const.py @@ -20,6 +20,7 @@ "tagger", ) _COMICFTS_M2MS = ( + "alternate_series", "characters", "credits", "genres", diff --git a/codex/librarian/scribe/search/prepare.py b/codex/librarian/scribe/search/prepare.py index f7a10b16a..449007b72 100644 --- a/codex/librarian/scribe/search/prepare.py +++ b/codex/librarian/scribe/search/prepare.py @@ -29,6 +29,7 @@ "fts_original_format", "fts_scan_info", "fts_tagger", + "fts_alternate_series", "fts_characters", "fts_credits", "fts_country", diff --git a/codex/librarian/scribe/search/sync.py b/codex/librarian/scribe/search/sync.py index 857568b21..c5d6817be 100644 --- a/codex/librarian/scribe/search/sync.py +++ b/codex/librarian/scribe/search/sync.py @@ -69,6 +69,7 @@ # story_arcs) ended up empty for sync-built entries. _M2M_FTS_REL_MAP = MappingProxyType( { + "alternate_series": "reprints__series_name", "characters": "characters__name", "credits": "credits__person__name", "genres": "genres__name", diff --git a/codex/librarian/scribe/tag_writer.py b/codex/librarian/scribe/tag_writer.py index ec27063f4..46f24ca0e 100644 --- a/codex/librarian/scribe/tag_writer.py +++ b/codex/librarian/scribe/tag_writer.py @@ -121,7 +121,7 @@ def _build_base_config(task: BulkTagWriteTask): Never pass codex's read-side ``COMICBOX_CONFIG`` here. Comicbox unions a write's ``delete_keys`` with the base config's, and that config carries the big parse-skip set of schema fields codex - doesn't consume (pages, reprints, cover_image, ...). Using it as a + doesn't consume (pages, cover_image, prices, ...). Using it as a write base would strip every one of those from the user's archive. """ if not task.delete_original: diff --git a/codex/migrations/0049_reprints.py b/codex/migrations/0049_reprints.py new file mode 100644 index 000000000..a35110b46 --- /dev/null +++ b/codex/migrations/0049_reprints.py @@ -0,0 +1,202 @@ +# Generated by Django 6.0.7 on 2026-07-26 05:53 +""" +comicbox 4.6.0: reprints (alternate & localized series names). + +comicbox 4.6.0 emits a ``reprints`` list from MetronInfo +``Series/AlternativeNames`` + ``Reprints`` and from Metron / ComicVine +online tagging. Codex stores them in a denormalized :class:`Reprint` +table rather than reusing Series/Volume — those are browse collections +and alternate names would surface as phantom browser rows. + +``Reprint`` rows carry an ``Identifier``, so ``IdentifierType`` gains +its long-reserved ``reprint`` member. Reprints are also a browser order +key. Both are choices-only churn on existing columns, hence the two +``AlterField`` operations below. + +The FTS5 rebuild adds the ``alternate_series`` column. ``ComicFTS`` is +unmanaged, so makemigrations never sees it — the column list lives in +SQL and has to be dropped and recreated by hand, exactly as 0039 did +for the age-rating columns. The table comes back empty; the librarian's +regular ``SearchIndexSyncTask`` repopulates it. +""" + +import django.db.models.deletion +from django.db import migrations, models + +import codex.models.fields + +# 0039's column list plus ``alternate_series``. +_NEW_FTS_SQL = ( + "CREATE VIRTUAL TABLE codex_comicfts USING fts5(" + "comic_id UNINDEXED, created_at UNINDEXED, updated_at UNINDEXED, " + "publisher, imprint, series, name, collection_title, " + "age_rating_tagged, age_rating_metron, country, language, " + "original_format, review, scan_info, summary, tagger, " + "alternate_series, characters, " + "credits, genres, locations, roles, series_groups, stories, " + "story_arcs, tags, teams, universes, sources" + ")" +) + + +class Migration(migrations.Migration): + """Reprint model, comic m2m, filter column, and FTS alternate_series.""" + + dependencies = [ + ("codex", "0048_community_rating_alternative_issue"), + ] + + operations = [ + migrations.AddField( + model_name="settingsbrowserfilters", + name="reprints", + field=models.JSONField(default=list), + ), + migrations.AlterField( + model_name="identifier", + name="id_type", + field=models.CharField( + choices=[ + ("storyarc", "Arc"), + ("character", "Character"), + ("genre", "Genre"), + ("imprint", "Imprint"), + ("comic", "Issue"), + ("location", "Location"), + ("publisher", "Publisher"), + ("reprint", "Reprint"), + ("series", "Series"), + ("story", "Story"), + ("tag", "Tag"), + ("team", "Team"), + ("universe", "Universe"), + ("creditrole", "Role"), + ("creditperson", "Creator"), + ], + db_index=True, + max_length=16, + ), + ), + migrations.CreateModel( + name="Reprint", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "series_name", + codex.models.fields.CleaningCharField( + db_index=True, max_length=128 + ), + ), + ( + "volume_number", + codex.models.fields.CoercingPositiveSmallIntegerField( + default=None, null=True + ), + ), + ( + "issue", + codex.models.fields.CleaningCharField(default="", max_length=32), + ), + ( + "language", + codex.models.fields.CleaningCharField(default="", max_length=32), + ), + ( + "identifier", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="codex.identifier", + ), + ), + ], + options={ + "get_latest_by": "updated_at", + "abstract": False, + "unique_together": { + ("series_name", "volume_number", "issue", "language") + }, + }, + ), + migrations.AddField( + model_name="comic", + name="reprints", + field=models.ManyToManyField(to="codex.reprint"), + ), + migrations.AlterField( + model_name="settingsbrowser", + name="order_by", + field=models.CharField( + choices=[ + ("created_at", "Added Time"), + ("age_rating", "Age Rating"), + ("reprints", "Alternate Series"), + ("characters", "Characters"), + ("child_count", "Child Count"), + ("community_rating", "Community Rating"), + ("country", "Country"), + ("credits", "Credits"), + ("day", "Day"), + ("favorite", "Favorite"), + ("filename", "Filename"), + ("size", "File Size"), + ("file_type", "File Type"), + ("original_format", "Format"), + ("genres", "Genres"), + ("identifiers", "Identifiers"), + ("imprint_name", "Imprint"), + ("issue", "Issue"), + ("language", "Language"), + ("bookmark_updated_at", "Last Read"), + ("locations", "Locations"), + ("main_character", "Main Character"), + ("main_team", "Main Team"), + ("metadata_mtime", "Tags Updated"), + ("month", "Month"), + ("monochrome", "Monochrome"), + ("sort_name", "Name"), + ("page_count", "Page Count"), + ("publisher_name", "Publisher"), + ("date", "Publish Date"), + ("reading_direction", "Reading Direction"), + ("scan_info", "Scan Info"), + ("search_score", "Search Score"), + ("series_name", "Series"), + ("series_groups", "Series Groups"), + ("stories", "Stories"), + ("story_arc_number", "Story Arc Number"), + ("story_arcs", "Story Arcs"), + ("tags", "Tags"), + ("tagger", "Tagger"), + ("teams", "Teams"), + ("universes", "Universes"), + ("updated_at", "Updated Time"), + ("volume_name", "Volume"), + ("year", "Year"), + ], + default="", + max_length=32, + ), + ), + # ComicFTS is unmanaged; its Django state tracks only (comic, + # created_at, updated_at). The FTS columns are a SQL-level concern — + # no state_operations are needed, only a raw DROP + CREATE. + migrations.RunSQL( + sql="DROP TABLE IF EXISTS codex_comicfts;", + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql=_NEW_FTS_SQL, + reverse_sql="DROP TABLE IF EXISTS codex_comicfts;", + ), + ] diff --git a/codex/models/comic.py b/codex/models/comic.py index ba9f93986..dc7d2e239 100644 --- a/codex/models/comic.py +++ b/codex/models/comic.py @@ -59,6 +59,7 @@ Language, Location, OriginalFormat, + Reprint, ScanInfo, SeriesGroup, Story, @@ -209,6 +210,7 @@ class Comic(WatchedPathBrowserCollection): genres = ManyToManyField(Genre) identifiers = ManyToManyField(Identifier) locations = ManyToManyField(Location) + reprints = ManyToManyField(Reprint) series_groups = ManyToManyField(SeriesGroup) stories = ManyToManyField(Story) story_arc_numbers = ManyToManyField(StoryArcNumber) @@ -218,13 +220,16 @@ class Comic(WatchedPathBrowserCollection): ##################### # Comicbox Ignored: - # alternate_issue - # alternate_volumes + # alternate_images + # bookmark # cover_image - # is_version_of - # last_mark + # credit_primaries + # critical_rating + # identifier_primary_source # manga - # price + # pages + # prices + # remainders # rights # codex only @@ -419,6 +424,7 @@ class ComicFTS(BaseModel): scan_info = CharField(db_collation="nocase", max_length=MAX_NAME_LEN) tagger = CharField(db_collation="nocase", max_length=MAX_NAME_LEN) # M2M + alternate_series = CharField(db_collation="nocase", max_length=MAX_NAME_LEN) characters = CharField(db_collation="nocase", max_length=MAX_NAME_LEN) credits = CharField(db_collation="nocase", max_length=MAX_NAME_LEN) genres = CharField(db_collation="nocase", max_length=MAX_NAME_LEN) diff --git a/codex/models/identifier.py b/codex/models/identifier.py index 0197e1709..48af5dcaa 100644 --- a/codex/models/identifier.py +++ b/codex/models/identifier.py @@ -36,7 +36,7 @@ class IdentifierType(TextChoices): ISSUE = "comic" LOCATION = "location" PUBLISHER = "publisher" - # REPRINT = "reprint" not yet implemented + REPRINT = "reprint" SERIES = "series" STORY = "story" TAG = "tag" diff --git a/codex/models/named.py b/codex/models/named.py index 582f35050..fc7c860c9 100644 --- a/codex/models/named.py +++ b/codex/models/named.py @@ -4,11 +4,12 @@ from django.db.models import ( CASCADE, + SET_NULL, ForeignKey, ) -from codex.models.base import MAX_NAME_LEN, BaseModel, NamedModel -from codex.models.collections import BrowserCollectionModel +from codex.models.base import MAX_FIELD_LEN, MAX_NAME_LEN, BaseModel, NamedModel +from codex.models.collections import BrowserCollectionModel, Volume from codex.models.fields import CleaningCharField, CoercingPositiveSmallIntegerField from codex.models.identifier import Identifier @@ -22,6 +23,7 @@ "Language", "Location", "OriginalFormat", + "Reprint", "ScanInfo", "SeriesGroup", "Story", @@ -111,6 +113,59 @@ class OriginalFormat(NamedModel): """The original published format.""" +class Reprint(BaseModel): + """ + An alternate or localized edition of this issue. + + Denormalized on purpose: alternate series names must not become + Series/Volume rows or they'd appear as phantom browser collections. + ``series_name`` absorbs comicbox's ``series.sort_name`` when the + reprint carries no ``series.name`` (MetronInfo AlternativeNames do + this), so this is the only series string stored. + """ + + series_name = CleaningCharField(db_index=True, max_length=MAX_NAME_LEN) + volume_number = CoercingPositiveSmallIntegerField(null=True, default=None) + issue = CleaningCharField(max_length=MAX_FIELD_LEN, default="") + language = CleaningCharField(max_length=MAX_FIELD_LEN, default="") + identifier = ForeignKey(Identifier, on_delete=SET_NULL, null=True) + + class Meta(BaseModel.Meta): + """Declare constraints and indexes.""" + + unique_together = ("series_name", "volume_number", "issue", "language") + + @staticmethod + def compose_name( + series_name: str, + volume_number: int | None = None, + issue: str = "", + language: str = "", + ) -> str: + """ + Compose a display name to imitate a NamedModel. + + Callers that only have the columns (browser filter choices, the + table-view intersection) format through here so every label + matches the metadata panel's. + """ + parts = [series_name] + if volume_number is not None: + parts.append(Volume.to_str(volume_number, None)) + if issue: + parts.append(f"#{issue}") + if language: + parts.append(f"({language})") + return " ".join(parts) + + @property + def name(self) -> str: + """Compose a display name to imitate a NamedModel.""" + return self.compose_name( + self.series_name, self.volume_number, self.issue, self.language + ) + + class ScanInfo(NamedModel): """Whomever scanned the comic.""" diff --git a/codex/models/settings.py b/codex/models/settings.py index 48922bd3c..2b39a4c1b 100644 --- a/codex/models/settings.py +++ b/codex/models/settings.py @@ -237,6 +237,7 @@ class SettingsBrowserFilters(BaseModel): monochrome = JSONField(default=list) original_format = JSONField(default=list) reading_direction = JSONField(default=list) + reprints = JSONField(default=list) series_groups = JSONField(default=list) stories = JSONField(default=list) story_arcs = JSONField(default=list) @@ -265,6 +266,7 @@ class SettingsBrowserFilters(BaseModel): "monochrome", "original_format", "reading_direction", + "reprints", "series_groups", "stories", "story_arcs", diff --git a/codex/serializers/admin/stats.py b/codex/serializers/admin/stats.py index f7437d1e7..44139664b 100644 --- a/codex/serializers/admin/stats.py +++ b/codex/serializers/admin/stats.py @@ -116,6 +116,7 @@ class StatsComicMetadataSerializer(Serializer): language_count = IntegerField(required=False, read_only=True) location_count = IntegerField(required=False, read_only=True) original_format_count = IntegerField(required=False, read_only=True) + reprint_count = IntegerField(required=False, read_only=True) series_group_count = IntegerField(required=False, read_only=True) scan_info_count = IntegerField(required=False, read_only=True) story_arc_count = IntegerField(required=False) diff --git a/codex/serializers/admin/tagging.py b/codex/serializers/admin/tagging.py index e1fe27735..122c9aca6 100644 --- a/codex/serializers/admin/tagging.py +++ b/codex/serializers/admin/tagging.py @@ -61,7 +61,6 @@ class OnlineTagStartSerializer(Serializer): sources = ListField(child=CharField(), required=False, default=list(SOURCE_NAMES)) mode = CharField(required=False, default="auto") prompts_mode = CharField(required=False, default="ask") - auto_threshold = CharField(required=False, default="0.85") dry_run = CharField(required=False, default="false") delete_original = BooleanField(required=False, default=None) # None falls back to the admin ComicboxTaggingDefaults default. diff --git a/codex/serializers/browser/choices.py b/codex/serializers/browser/choices.py index 31c04a320..c7504a739 100644 --- a/codex/serializers/browser/choices.py +++ b/codex/serializers/browser/choices.py @@ -10,6 +10,7 @@ ) from rest_framework.serializers import Serializer +from codex.models.named import Reprint from codex.serializers.fields import ( VuetifyBooleanField, VuetifyCharField, @@ -44,6 +45,7 @@ class BrowserFilterChoicesSerializer(Serializer): locations = BooleanField(read_only=True) original_format = BooleanField(read_only=True) reading_direction = BooleanField(read_only=True) + reprints = BooleanField(read_only=True) series_groups = BooleanField(read_only=True) stories = BooleanField(read_only=True) story_arcs = BooleanField(read_only=True) @@ -78,6 +80,7 @@ class BrowserSettingsFilterSerializer(Serializer): reading_direction = VuetifyReadOnlyListField( child=VuetifyReadingDirectionChoiceField ) + reprints = VuetifyReadOnlyListField() series_groups = VuetifyReadOnlyListField() stories = VuetifyReadOnlyListField() story_arcs = VuetifyReadOnlyListField() @@ -113,6 +116,25 @@ class BrowserChoicesUniversePkSerializer(Serializer): designation = CharField(read_only=True) +# ``Reprint`` columns the label composes from, in +# :meth:`Reprint.compose_name` argument order. The choices view projects +# exactly these instead of the ``name`` every other tag model has. +REPRINT_LABEL_FIELDS = ("series_name", "volume_number", "issue", "language") + + +class BrowserChoicesReprintPkSerializer(BrowserChoicesIntegerPkSerializer): + """Reprints have no ``name`` column; compose the label from their four.""" + + name = SerializerMethodField(read_only=True) + + def get_name(self, obj) -> str: + """Compose the same label the metadata panel shows.""" + # The null sentinel row the view prepends already has a name. + if name := obj.get("name"): + return name + return Reprint.compose_name(*(obj[field] for field in REPRINT_LABEL_FIELDS)) + + class BrowserChoicesCharPkSerializer(BrowserChoicesIntegerPkSerializer): """Named Model Serailizer with pk = char hack for languages & countries.""" @@ -134,6 +156,7 @@ class BrowserChoicesDecimalPkSerializer(BrowserChoicesIntegerPkSerializer): "community_rating": BrowserChoicesDecimalPkSerializer, "file_type": BrowserChoicesCharPkSerializer, "language": LanguageSerializer, + "reprints": BrowserChoicesReprintPkSerializer, "universe": BrowserChoicesUniversePkSerializer, } ) diff --git a/codex/serializers/browser/filters.py b/codex/serializers/browser/filters.py index f5666bf5f..233011d8a 100644 --- a/codex/serializers/browser/filters.py +++ b/codex/serializers/browser/filters.py @@ -38,6 +38,7 @@ class BrowserSettingsFilterInputSerializer(Serializer): monochrome = VuetifyListField(child=VuetifyBooleanField) original_format = VuetifyListField() reading_direction = VuetifyListField(child=VuetifyReadingDirectionChoiceField) + reprints = VuetifyListField() series_groups = VuetifyListField() stories = VuetifyListField() story_arcs = VuetifyListField() diff --git a/codex/serializers/models/comic.py b/codex/serializers/models/comic.py index 0dce27115..fb0006ead 100644 --- a/codex/serializers/models/comic.py +++ b/codex/serializers/models/comic.py @@ -20,6 +20,7 @@ IdentifierSerializer, LocationSerializer, OriginalFormatSerializer, + ReprintSerializer, ScanInfoSerializer, SeriesGroupSerializer, StoryArcNumberSerializer, @@ -66,6 +67,7 @@ class ComicSerializer(BaseModelSerializer): genres = GenreSerializer(many=True, allow_null=True) identifiers = IdentifierSerializer(many=True, allow_null=True) locations = LocationSerializer(many=True, allow_null=True) + reprints = ReprintSerializer(many=True, allow_null=True) series_groups = SeriesGroupSerializer(many=True, allow_null=True) stories = StorySerializer(many=True, allow_null=True) story_arc_numbers = StoryArcNumberSerializer( diff --git a/codex/serializers/models/named.py b/codex/serializers/models/named.py index 6968cc4ec..4d70ccf30 100644 --- a/codex/serializers/models/named.py +++ b/codex/serializers/models/named.py @@ -13,6 +13,7 @@ Genre, Location, OriginalFormat, + Reprint, ScanInfo, SeriesGroup, Story, @@ -146,6 +147,30 @@ class Meta(URLNamedModelSerializer.Meta): model = Location +class ReprintSerializer(BaseModelSerializer): + """Reprint model.""" + + name = CharField(read_only=True) + url = URLField(read_only=True, source="identifier.url") + + class Meta(BaseModelSerializer.Meta): + """Configure model.""" + + model = Reprint + # The columns ride along with the composed name so the tag editor + # can seed its rows without parsing the label back apart. + fields = ( + "pk", + "name", + "series_name", + "volume_number", + "issue", + "language", + "url", + ) + depth = 1 + + class SeriesGroupSerializer(NamedModelSerializer): """SeriesGroup model.""" diff --git a/codex/settings/__init__.py b/codex/settings/__init__.py index 16c7b4336..1b161d39f 100644 --- a/codex/settings/__init__.py +++ b/codex/settings/__init__.py @@ -1125,6 +1125,7 @@ def _get_middleware(features: FeatureFlags) -> tuple[str, ...]: "protagonist", "publisher", "reading_direction", + "reprints", "review", "scan_info", "series", @@ -1141,10 +1142,10 @@ def _get_middleware(features: FeatureFlags) -> tuple[str, ...]: ) # Tell comicbox to skip parse work for every top-level field codex -# does not consume. Pages and reprints are short-circuited inside -# their respective computed actions; the rest are excluded at schema -# parse time and dropped post-merge as a backstop. Derived from the -# live comicbox schema rather than hand-curated so a comicbox release +# does not consume. Pages are short-circuited inside their computed +# action; the rest are excluded at schema parse time and dropped +# post-merge as a backstop. Derived from the live comicbox schema +# rather than hand-curated so a comicbox release # that adds a new top-level field is auto-deleted (codex maintainer # adds it to ``USED_COMICBOX_FIELDS`` if it's needed). Closes the # pre-existing ``cover_image`` gap. diff --git a/codex/user_data/identifiers.py b/codex/user_data/identifiers.py index cdcf419a5..e5315f6a7 100644 --- a/codex/user_data/identifiers.py +++ b/codex/user_data/identifiers.py @@ -56,6 +56,7 @@ "language": "codex.Language", "locations": "codex.Location", "original_format": "codex.OriginalFormat", + "reprints": "codex.Reprint", "series_groups": "codex.SeriesGroup", "stories": "codex.Story", "story_arcs": "codex.StoryArc", @@ -66,6 +67,13 @@ } ) FILTER_TAG_COLUMNS: Final[frozenset[str]] = frozenset(_FILTER_TAG_MODEL_REFS) +# Tag models that don't inherit ``NamedModel.name``. ``Reprint`` keys on +# ``series_name``, which is not unique on its own, so a restore may +# resolve more PKs than the dump held when a series has several +# alternate-name variants. +_FILTER_TAG_NAME_FIELDS: Final[MappingProxyType[str, str]] = MappingProxyType( + {"reprints": "series_name"} +) def tag_model_for_filter(column: str) -> type[Model] | None: @@ -79,6 +87,11 @@ def tag_model_for_filter(column: str) -> type[Model] | None: return apps.get_model(app_label, model_name) +def tag_name_field_for_filter(column: str) -> str: + """Return the name-bearing field of a filter column's tag model.""" + return _FILTER_TAG_NAME_FIELDS.get(column, "name") + + def encode_identifier(collection: str, parts: list[Any]) -> str: """JSON-encode an identifier tuple for use as a sidecar primary-key column.""" # ``separators`` produces compact output so equal tuples encode identically. diff --git a/codex/user_data/restore.py b/codex/user_data/restore.py index ed96e1794..37255575f 100644 --- a/codex/user_data/restore.py +++ b/codex/user_data/restore.py @@ -629,13 +629,21 @@ def _restore_one_settings_browser( def _resolve_filter_tags(column: str, raw: list, browser, report: RestoreReport): """Resolve a tag-name list back to PKs, reporting unmatched names.""" - from codex.user_data.identifiers import tag_model_for_filter + from codex.user_data.identifiers import ( + tag_model_for_filter, + tag_name_field_for_filter, + ) model = tag_model_for_filter(column) if model is None: return [] - pks = list(model.objects.filter(name__in=raw).values_list("pk", flat=True)) - if len(pks) != len(raw): + name_field = tag_name_field_for_filter(column) + pks = list( + model.objects.filter(**{f"{name_field}__in": raw}).values_list("pk", flat=True) + ) + # A non-unique name field (``Reprint.series_name``) can resolve to + # more rows than the dump held; only a shortfall is a real drop. + if len(pks) < len(raw): dropped = len(raw) - len(pks) report.note_skipped( "settings_filters", diff --git a/codex/user_data/schema.sql b/codex/user_data/schema.sql index 07a2916ea..6b47b1348 100644 --- a/codex/user_data/schema.sql +++ b/codex/user_data/schema.sql @@ -126,6 +126,7 @@ CREATE TABLE IF NOT EXISTS settings_filters ( monochrome TEXT NOT NULL DEFAULT '[]', original_format TEXT NOT NULL DEFAULT '[]', reading_direction TEXT NOT NULL DEFAULT '[]', + reprints TEXT NOT NULL DEFAULT '[]', series_groups TEXT NOT NULL DEFAULT '[]', stories TEXT NOT NULL DEFAULT '[]', story_arcs TEXT NOT NULL DEFAULT '[]', diff --git a/codex/user_data/serializers.py b/codex/user_data/serializers.py index 37bec6d4e..2e56b846e 100644 --- a/codex/user_data/serializers.py +++ b/codex/user_data/serializers.py @@ -27,6 +27,7 @@ encode_identifier, identifier_for_browse_collection, tag_model_for_filter, + tag_name_field_for_filter, ) @@ -237,9 +238,9 @@ def _rewrite_filter_value(column: str, value: Any) -> Any: model = tag_model_for_filter(column) if model is None: return value - # Every tag model inherits ``NamedModel.name``; missing PKs drop - # silently rather than raise. - return list(model.objects.filter(pk__in=value).values_list("name", flat=True)) + # Missing PKs drop silently rather than raise. + name_field = tag_name_field_for_filter(column) + return list(model.objects.filter(pk__in=value).values_list(name_field, flat=True)) def serialize_settings_filters( diff --git a/codex/user_data/store.py b/codex/user_data/store.py index 2b3ca83e1..72975fccf 100644 --- a/codex/user_data/store.py +++ b/codex/user_data/store.py @@ -42,6 +42,19 @@ SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'schema_version' """ +_TABLE_NAMES_SQL: Final[str] = """\ +SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' +""" + + +def _add_column_clause(column: sqlite3.Row) -> str: + """Render one ``PRAGMA table_info`` row as an ADD COLUMN clause.""" + clause = f"{column['name']} {column['type']}" + if column["notnull"]: + clause += " NOT NULL" + if column["dflt_value"] is not None: + clause += f" DEFAULT {column['dflt_value']}" + return clause class SidecarStore: @@ -93,6 +106,39 @@ def _open_connection(self) -> sqlite3.Connection: conn.execute(pragma) return conn + @staticmethod + def _reconcile_columns(conn: sqlite3.Connection, ddl: str) -> None: + """ + Add columns a release introduced to a sidecar that predates them. + + ``schema.sql`` is entirely ``CREATE TABLE IF NOT EXISTS``, so + re-running it against an existing file adds nothing and every + later upsert naming a new column fails with "no such column". + The wanted columns are read back out of a throwaway database + built from the current schema so they derive from the schema + file instead of a hand-kept list that would rot the same way. + """ + reference = sqlite3.connect(":memory:") + reference.row_factory = sqlite3.Row + try: + reference.executescript(ddl) + for table_row in reference.execute(_TABLE_NAMES_SQL).fetchall(): + table = table_row["name"] + # PRAGMA takes no bind parameters; the table names come + # from the schema file, not from user input. + have = { + column["name"] + for column in conn.execute(f"PRAGMA table_info({table})") + } + for column in reference.execute(f"PRAGMA table_info({table})"): + if column["name"] in have: + continue + clause = _add_column_clause(column) + conn.execute(f"ALTER TABLE {table} ADD COLUMN {clause}") + logger.info(f"Sidecar schema: added {table}.{column['name']}") + finally: + reference.close() + def _ensure_schema(self, conn: sqlite3.Connection) -> None: """Apply ``schema.sql`` and stamp the version row once per process.""" if self._schema_applied: @@ -103,6 +149,8 @@ def _ensure_schema(self, conn: sqlite3.Connection) -> None: ddl = _SCHEMA_PATH.read_text(encoding="utf-8") with conn: conn.executescript(ddl) + if not self._is_memory: + self._reconcile_columns(conn, ddl) conn.execute( "INSERT OR IGNORE INTO schema_version (version) VALUES (?)", (SCHEMA_VERSION,), diff --git a/codex/views/admin/onlinetag.py b/codex/views/admin/onlinetag.py index c0b0bce32..ad2c84fe3 100644 --- a/codex/views/admin/onlinetag.py +++ b/codex/views/admin/onlinetag.py @@ -100,7 +100,6 @@ def post(self, request): sources=tuple(data["sources"]), mode=data["mode"], prompts_mode=data["prompts_mode"], - auto_threshold=float(data.get("auto_threshold", 0.85)), delete_original=delete_original, merge_all_sources=merge_all_sources, rename=rename, @@ -137,8 +136,8 @@ def _resume_task_params(resume: dict) -> dict: ``sources`` round-trips through JSON as a list; the task wants a tuple. A resume descriptor persists to a file-based cache, so one written by an older Codex may carry keys a task field no longer accepts (e.g. the removed - ``effort`` knob) — drop unknown keys so resuming across an upgrade rebuilds - the task instead of raising ``TypeError``. + ``auto_threshold`` knob) — drop unknown keys so resuming across an upgrade + rebuilds the task instead of raising ``TypeError``. """ params = dict(resume.get("params") or {}) params["sources"] = tuple(params.get("sources") or ()) diff --git a/codex/views/browser/choices.py b/codex/views/browser/choices.py index 6fda131ba..8f5ed6b64 100644 --- a/codex/views/browser/choices.py +++ b/codex/views/browser/choices.py @@ -18,8 +18,9 @@ ) from codex.models.age_rating import AgeRating, AgeRatingMetron from codex.models.identifier import IdentifierSource -from codex.models.named import Universe +from codex.models.named import Reprint, Universe from codex.serializers.browser.choices import ( + REPRINT_LABEL_FIELDS, BrowserChoicesFilterSerializer, BrowserFilterChoicesSerializer, ) @@ -267,7 +268,11 @@ def _get_m2m_field_choices(self, model, comic_qs, rel): # Choices qs = self.get_m2m_field_query(model, comic_qs) values = ["pk", "name"] - if qs.model == Universe: + if qs.model == Reprint: + # No ``name`` column at all — BrowserChoicesReprintPkSerializer + # composes the label from these. + values = ["pk", *REPRINT_LABEL_FIELDS] + elif qs.model == Universe: values.append("designation") elif qs.model == AgeRatingMetron: # AgeRatingMetron.Meta.ordering = ("index",), but ``.distinct()`` diff --git a/codex/views/browser/columns.py b/codex/views/browser/columns.py index b8d5aa1c8..a37a705ec 100644 --- a/codex/views/browser/columns.py +++ b/codex/views/browser/columns.py @@ -19,12 +19,13 @@ When, ) from django.db.models.fields import CharField -from django.db.models.functions import Concat +from django.db.models.functions import Cast, Concat from codex.choices.browser import ( BROWSER_TABLE_COLUMNS, BROWSER_TABLE_DEFAULT_COLUMNS, ) +from codex.models.collections import Volume from codex.models.favorite import FAVORITE_MODEL_COLLECTIONS, Favorite from codex.models.functions import JsonGroupArray @@ -71,6 +72,37 @@ output_field=CharField(), ) +# Volume numbers this wide are rendered as a year rather than a volume. +VOLUME_YEAR_RANGE = (10 ** (Volume.YEAR_LEN - 1), 10**Volume.YEAR_LEN - 1) +_REPRINT_VOLUME_STR = Cast("reprints__volume_number", output_field=CharField()) +_M2M_REPRINT_EXPR = Concat( + # Mirror of ``Reprint.name`` in SQL: only the columns the reprint + # carries contribute. ``volume_number`` renders through + # ``Volume.to_str``'s rule — four digits are a year "(1999)", + # anything else a volume "v3". + F("reprints__series_name"), + Case( + When(reprints__volume_number__isnull=True, then=Value("")), + When( + reprints__volume_number__range=VOLUME_YEAR_RANGE, + then=Concat(Value(" ("), _REPRINT_VOLUME_STR, Value(")")), + ), + default=Concat(Value(" v"), _REPRINT_VOLUME_STR), + output_field=CharField(), + ), + Case( + When(reprints__issue="", then=Value("")), + default=Concat(Value(" #"), F("reprints__issue")), + output_field=CharField(), + ), + Case( + When(reprints__language="", then=Value("")), + default=Concat(Value(" ("), F("reprints__language"), Value(")")), + output_field=CharField(), + ), + output_field=CharField(), +) + _M2M_COLUMN_PATHS = MappingProxyType( { "characters": "characters__name", @@ -78,6 +110,7 @@ "genres": "genres__name", "identifiers": _M2M_IDENTIFIER_EXPR, "locations": "locations__name", + "reprints": _M2M_REPRINT_EXPR, "series_groups": "series_groups__name", "stories": "stories__name", "story_arcs": "story_arc_numbers__story_arc__name", @@ -98,6 +131,7 @@ { "credits": Q(credits__person__name__gt=""), "identifiers": Q(identifiers__id_type__gt="") | Q(identifiers__key__gt=""), + "reprints": Q(reprints__series_name__gt=""), } ) diff --git a/codex/views/browser/const.py b/codex/views/browser/const.py index 0e711d110..29f64ab5d 100644 --- a/codex/views/browser/const.py +++ b/codex/views/browser/const.py @@ -16,6 +16,7 @@ "monochrome", "original_format", "reading_direction", + "reprints", "series_groups", "stories", "story_arcs", diff --git a/codex/views/browser/filters/filter.py b/codex/views/browser/filters/filter.py index 669e5f7ed..67a5fa618 100644 --- a/codex/views/browser/filters/filter.py +++ b/codex/views/browser/filters/filter.py @@ -24,6 +24,7 @@ "genres", "identifier_source", "locations", + "reprints", "series_groups", "stories", "story_arcs", diff --git a/codex/views/browser/filters/search/field/column.py b/codex/views/browser/filters/search/field/column.py index c1d4ddc1b..7f8b47489 100644 --- a/codex/views/browser/filters/search/field/column.py +++ b/codex/views/browser/filters/search/field/column.py @@ -9,6 +9,7 @@ _FIELD_TO_REL_SPAN_MAP = MappingProxyType( { + "alternate_series": "reprints__series_name", "role": "credits__role__name", "credits": "credits__person__name", "identifiers": "identifiers__key", diff --git a/codex/views/browser/intersections.py b/codex/views/browser/intersections.py index 1c1552ac5..c724d6f8e 100644 --- a/codex/views/browser/intersections.py +++ b/codex/views/browser/intersections.py @@ -35,7 +35,12 @@ Series, Volume, ) -from codex.views.browser.columns import fk_name_columns, m2m_columns +from codex.models.named import Reprint +from codex.views.browser.columns import ( + VOLUME_YEAR_RANGE, + fk_name_columns, + m2m_columns, +) from codex.views.const import MODEL_REL_MAP # Comic FK column → collection model. Used to correlate the intersection @@ -88,6 +93,15 @@ def _format_identifier(source_name: str | None, id_type: str, key: str) -> str: return f"{id_type}:{key}" +# Positional order matches :meth:`Reprint.compose_name`'s signature. +_REPRINT_VALUE_RELS = ( + "reprints__series_name", + "reprints__volume_number", + "reprints__issue", + "reprints__language", +) + + # Comic field paths for scalar / FK-name columns that participate in # collection-row intersection. Keys mirror the column registry; values are # Django ORM paths from Comic. The intersection path runs only for @@ -202,8 +216,8 @@ def compute_collection_intersections( ) # Simple-M2M columns share one UNION-ALL query (metadata-view - # pattern). Composite shapes (credits / identifiers / universes / - # story_arcs) keep their per-column helpers. + # pattern). Composite shapes (credits / identifiers / reprints / + # universes / story_arcs) keep their per-column helpers. _compute_simple_m2m_intersections_batched( m2m_cols, collection_pks, comic_to_collection, counts, result ) @@ -347,9 +361,10 @@ def _compute_batched_scalars( # the target model's ``name`` field. Eligible for the through-table # batched union (see ``_compute_simple_m2m_intersections_batched``). # ``universes`` (composite ``name:designation``), ``credits`` -# (``Person (Role)``), ``identifiers`` (``[source:]type:key``), and -# ``story_arcs`` (StoryArcNumber → StoryArc indirection) need bespoke -# SQL and stay on per-column helpers. +# (``Person (Role)``), ``identifiers`` (``[source:]type:key``), +# ``reprints`` (no ``name`` column at all — the label composes from +# four), and ``story_arcs`` (StoryArcNumber → StoryArc indirection) +# need bespoke SQL and stay on per-column helpers. _SIMPLE_M2M_BATCH_FIELDS: frozenset[str] = frozenset( { "characters", @@ -474,7 +489,7 @@ def _compute_m2m_intersection( Simple M2M columns route through ``_compute_simple_m2m_intersections_batched`` instead — this helper covers the bespoke shapes (credits / identifiers / - universes / story_arcs). + reprints / universes / story_arcs). """ if col == "credits": _compute_credits_intersection( @@ -486,6 +501,11 @@ def _compute_m2m_intersection( collection_pks, comic_to_collection, counts, result ) return + if col == "reprints": + _compute_reprints_intersection( + collection_pks, comic_to_collection, counts, result + ) + return rel = _M2M_NAME_RELATIONS.get(col) if rel is None: @@ -741,6 +761,38 @@ def _build_identifiers_intersection_sort_sql( return _IntersectionSortRawSQL(sql, []) +def _build_reprints_intersection_sort_sql( + collection_model: type[BrowserCollectionModel], +) -> RawSQL | None: + """Reprints render the same composed label the table cell shows.""" + correlation = _comic_correlation_sql(collection_model) + if correlation is None: + return None + # Mirror of ``Reprint.compose_name``: only the columns the reprint + # carries contribute, and a four-digit volume number is a year. The + # year bounds are the only bound parameters any of these + # intersection subqueries take. + inner = """ + SELECT + r.id AS target_id, + r.series_name + || CASE + WHEN r.volume_number IS NULL THEN '' + WHEN r.volume_number BETWEEN %s AND %s + THEN ' (' || r.volume_number || ')' + ELSE ' v' || r.volume_number + END + || CASE WHEN r.issue = '' THEN '' ELSE ' #' || r.issue END + || CASE WHEN r.language = '' THEN '' ELSE ' (' || r.language || ')' END + AS display_name + FROM codex_reprint r + INNER JOIN codex_comic_reprints th ON th.reprint_id = r.id + INNER JOIN codex_comic c ON c.id = th.comic_id + """ + sql = _wrap_intersection_sort(inner, correlation) + return _IntersectionSortRawSQL(sql, list(VOLUME_YEAR_RANGE)) + + def _build_story_arcs_intersection_sort_sql( collection_model: type[BrowserCollectionModel], ) -> RawSQL | None: @@ -893,6 +945,18 @@ def scalar_intersection_sort_expr( return _IntersectionSortRawSQL(sql, []) +# Composite-M2M columns whose display string needs its own SELECT. +_COMPOSITE_M2M_SORT_BUILDERS = MappingProxyType( + { + "credits": _build_credits_intersection_sort_sql, + "identifiers": _build_identifiers_intersection_sort_sql, + "reprints": _build_reprints_intersection_sort_sql, + "story_arcs": _build_story_arcs_intersection_sort_sql, + "universes": _build_universes_intersection_sort_sql, + } +) + + def m2m_intersection_sort_expr( collection_model: type[BrowserCollectionModel], column: str ) -> RawSQL | None: @@ -904,15 +968,8 @@ def m2m_intersection_sort_expr( """ if column in _SIMPLE_M2M_FIELDS: return _build_simple_m2m_intersection_sort_sql(collection_model, column) - if column == "universes": - return _build_universes_intersection_sort_sql(collection_model) - if column == "credits": - return _build_credits_intersection_sort_sql(collection_model) - if column == "identifiers": - return _build_identifiers_intersection_sort_sql(collection_model) - if column == "story_arcs": - return _build_story_arcs_intersection_sort_sql(collection_model) - return None + builder = _COMPOSITE_M2M_SORT_BUILDERS.get(column) + return builder(collection_model) if builder else None def _compute_credits_intersection( @@ -995,3 +1052,34 @@ def _compute_identifiers_intersection( if cnt == total ) result[gpk]["identifiers"] = names + + +def _compute_reprints_intersection( + collection_pks: list[int], + comic_to_collection: str, + counts: dict[int, int], + result: dict[int, dict[str, Any]], +) -> None: + """Reprints have no ``name`` column; compose the label from their four.""" + rows = ( + Comic.objects.filter(**{f"{comic_to_collection}__in": collection_pks}) + .filter(reprints__series_name__gt="") + .values(comic_to_collection, *_REPRINT_VALUE_RELS) + .annotate(cnt=Count("pk", distinct=True)) + ) + per_collection: dict[int, list[tuple[tuple, int]]] = defaultdict(list) + for row in rows: + per_collection[row[comic_to_collection]].append( + (tuple(row[rel] for rel in _REPRINT_VALUE_RELS), row["cnt"]) + ) + for gpk in collection_pks: + total = counts.get(gpk, 0) + if not total: + result[gpk]["reprints"] = [] + continue + names = sorted( + Reprint.compose_name(*parts) + for parts, cnt in per_collection.get(gpk, ()) + if cnt == total + ) + result[gpk]["reprints"] = names diff --git a/codex/views/browser/metadata/const.py b/codex/views/browser/metadata/const.py index 3fa20f499..cbda90372 100644 --- a/codex/views/browser/metadata/const.py +++ b/codex/views/browser/metadata/const.py @@ -10,6 +10,7 @@ from codex.models.named import ( Character, Credit, + Reprint, SeriesGroup, StoryArcNumber, Team, @@ -75,6 +76,12 @@ "select": ("source",), "only": ("source", "key", "url"), }, + # Reprint.name is a property composed from all four columns; the + # default ``only=("name", ...)`` raises FieldDoesNotExist. + Reprint: { + "select": ("identifier",), + "only": ("series_name", "volume_number", "issue", "language", "identifier"), + }, Universe: {"only": ("name", "designation", "identifier")}, SeriesGroup: { "select": (), diff --git a/codex/views/browser/saved_settings.py b/codex/views/browser/saved_settings.py index 5bd68e726..a7a56f245 100644 --- a/codex/views/browser/saved_settings.py +++ b/codex/views/browser/saved_settings.py @@ -7,7 +7,7 @@ from rest_framework.serializers import BaseSerializer from codex.models.age_rating import AgeRating, AgeRatingMetron -from codex.models.base import NamedModel +from codex.models.base import BaseModel from codex.models.identifier import IdentifierSource from codex.models.named import ( Character, @@ -17,6 +17,7 @@ Language, Location, OriginalFormat, + Reprint, SeriesGroup, Story, StoryArc, @@ -44,7 +45,8 @@ SettingsBaseView, ) -# Map filter field names to the model whose PKs they store. +# Map filter field names to the model whose PKs they store. Most are +# NamedModels; ``Reprint`` is not, so the validator only leans on the pk. _FILTER_FK_MODEL_MAP = MappingProxyType( { "age_rating_metron": AgeRatingMetron, @@ -57,6 +59,7 @@ "language": Language, "locations": Location, "original_format": OriginalFormat, + "reprints": Reprint, "series_groups": SeriesGroup, "stories": Story, "story_arcs": StoryArc, @@ -69,7 +72,7 @@ def _validate_filter_field( - filters_data: dict, field: str, model: type[NamedModel], warnings: list[str] + filters_data: dict, field: str, model: type[BaseModel], warnings: list[str] ): """Validate one filter field by existing models.""" pk_list = filters_data.get(field) diff --git a/codex/views/const.py b/codex/views/const.py index 525b5a104..55bfbe901 100644 --- a/codex/views/const.py +++ b/codex/views/const.py @@ -31,6 +31,7 @@ Location, OriginalFormat, Publisher, + Reprint, ScanInfo, Series, SeriesGroup, @@ -166,6 +167,7 @@ Language, Location, OriginalFormat, + Reprint, SeriesGroup, ScanInfo, StoryArc, diff --git a/frontend/src/components/browser/table/browser-table-cell.vue b/frontend/src/components/browser/table/browser-table-cell.vue index a5e362134..b3dbabd54 100644 --- a/frontend/src/components/browser/table/browser-table-cell.vue +++ b/frontend/src/components/browser/table/browser-table-cell.vue @@ -66,6 +66,7 @@ const M2M_COLUMNS = new Set([ "genres", "identifiers", "locations", + "reprints", "series_groups", "stories", "story_arcs", diff --git a/frontend/src/components/browser/table/browser-table-column-picker.vue b/frontend/src/components/browser/table/browser-table-column-picker.vue index 895de0d79..1ca47b4ef 100644 --- a/frontend/src/components/browser/table/browser-table-column-picker.vue +++ b/frontend/src/components/browser/table/browser-table-column-picker.vue @@ -126,6 +126,7 @@ const _CATEGORIES = Object.freeze([ "publisher_name", "imprint_name", "series_name", + "reprints", "volume_name", "issue", "name", diff --git a/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue b/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue index cce61a184..f36443b60 100644 --- a/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue +++ b/frontend/src/components/browser/toolbars/top/filter-sub-menu.vue @@ -126,6 +126,9 @@ import { useBrowserStore } from "@/stores/browser"; const FILTER_TITLE_OVERRIDES = { ageRatingMetron: "Age Rating", + // "Reprints" reads as reprint editions; these are alternate and + // localized series names. + reprints: "Alternate Series", }; // Tab values are the filter keys; "Standardized" is the default tab. const AGE_RATING_DEFAULT_TAB = "ageRatingMetron"; diff --git a/frontend/src/components/metadata/edit-mode/edit-panel.vue b/frontend/src/components/metadata/edit-mode/edit-panel.vue index d18b3fac6..d1af776da 100644 --- a/frontend/src/components/metadata/edit-mode/edit-panel.vue +++ b/frontend/src/components/metadata/edit-mode/edit-panel.vue @@ -847,6 +847,93 @@ +
Alternate Series
+ + + + + + + + + + + + + + + + + + × + + + + + +
+
+ + + Add Alternate Series + +
+ + + Clear All + +
+
Identifiers
@@ -1150,6 +1237,7 @@ export default { creditsByRole: {}, storyArcNames: [], universes: [], + reprints: [], identifiers: [], patch: { publisher: "", @@ -1270,6 +1358,7 @@ export default { credits: this.creditsSerialized, storyArcs: JSON.stringify(this.storyArcNames), universes: JSON.stringify(this.universes), + reprints: JSON.stringify(this.reprints), identifiers: JSON.stringify(this.identifiers), }; }, @@ -1303,6 +1392,12 @@ export default { ) { changed.add("universes"); } + if ( + this.clearedFields.has("reprints") || + cur.reprints !== orig.reprints + ) { + changed.add("reprints"); + } if ( this.clearedFields.has("identifiers") || cur.identifiers !== orig.identifiers @@ -1593,6 +1688,8 @@ export default { this.storyArcNames = []; } else if (field === "universes") { this.universes = []; + } else if (field === "reprints") { + this.reprints = []; } else if (field === "identifiers") { this.identifiers = []; } @@ -1703,6 +1800,18 @@ export default { })); } + // Alternate series — the panel renders the composed `name`, but the + // editor rebuilds comicbox's nested reprint from the flat columns. + if (this.md.reprints?.length) { + this.reprints = this.md.reprints.map((reprint) => ({ + series_name: reprint.seriesName || "", + volume: + reprint.volumeNumber == null ? "" : String(reprint.volumeNumber), + issue: reprint.issue || "", + language: reprint.language || null, + })); + } + /* Identifiers — shaped as {pk, source, type, code, displayName, url} */ if (this.md.identifiers?.length) { this.identifiers = this.md.identifiers.map((id) => ({ @@ -1732,6 +1841,22 @@ export default { this.origSnapshot = { ...this.currentSnapshot }; }, + buildReprints() { + // comicbox nests a reprint's parts under series/volume; codex stores + // them flat. A row without a series name names nothing, so it's + // dropped rather than written as an empty alternate. + const reprints = []; + for (const row of this.reprints) { + if (!row.series_name) continue; + const reprint = { series: { name: row.series_name } }; + const number = parseInt(row.volume, 10); + if (!isNaN(number)) reprint.volume = { number }; + if (row.issue) reprint.issue = row.issue; + if (row.language) reprint.language = row.language; + reprints.push(reprint); + } + return reprints; + }, buildPatch() { // Returns { patch, deleteKeys }. A merge write can only add or replace // values — comicbox prunes empty patch values on schema load — so @@ -1846,6 +1971,13 @@ export default { } } + // Alternate series — only include if changed + if (changed.has("reprints")) { + const reprints = this.buildReprints(); + if (reprints.length) cbPatch.reprints = reprints; + else deleteKeys.push("reprints"); + } + // Identifiers — only include if changed if (changed.has("identifiers")) { if (cleared.has("identifiers") || this.identifiers.length === 0) { diff --git a/frontend/src/components/online-tag/prompt-popup.vue b/frontend/src/components/online-tag/prompt-popup.vue index 5d425c0bb..6a56cba6b 100644 --- a/frontend/src/components/online-tag/prompt-popup.vue +++ b/frontend/src/components/online-tag/prompt-popup.vue @@ -60,6 +60,15 @@ {{ Math.round(candidate.score * 100) }}% + +
+ a.k.a. {{ candidate.summary.altSeries.join(", ") }} +
({ md: undefined, @@ -107,13 +114,7 @@ export const useMetadataStore = defineStore("metadata", { this.md = undefined; }, getTagName(key) { - var tagName; - if (key === "storyArcNumbers") { - tagName = "Story Arcs"; - } else { - tagName = capitalCase(key); - } - return tagName; + return TAG_NAMES[key] || capitalCase(key); }, mapTag(tagSource, keys, filter = undefined) { const tagMap = {}; diff --git a/frontend/tests/unit/browser-table-cell.test.js b/frontend/tests/unit/browser-table-cell.test.js index 1fc036fc3..6517051dd 100644 --- a/frontend/tests/unit/browser-table-cell.test.js +++ b/frontend/tests/unit/browser-table-cell.test.js @@ -87,6 +87,20 @@ describe("BrowserTableCell — list / M2M", () => { }); expect(wrapper.find(".tableListCell").text()).toBe(""); }); + + it("joins the composed alternate-series labels", () => { + /* + * ``reprints`` has to be listed in ``M2M_COLUMNS`` or the array + * falls through to the text cell and stringifies badly. + */ + const wrapper = mountCell({ + column: "reprints", + row: { reprints: ["Kapitän Wissenschaft (de)", "Capitan Sciencia v1"] }, + }); + expect(wrapper.find(".tableListCell").text()).toBe( + "Kapitän Wissenschaft (de), Capitan Sciencia v1", + ); + }); }); describe("BrowserTableCell — bool", () => { diff --git a/frontend/tests/unit/browser-table-column-picker.test.js b/frontend/tests/unit/browser-table-column-picker.test.js index 8f0f192cf..461d989ce 100644 --- a/frontend/tests/unit/browser-table-column-picker.test.js +++ b/frontend/tests/unit/browser-table-column-picker.test.js @@ -17,9 +17,9 @@ import { smartInsertIndex } from "@/components/browser/table/browser-table-colum * Canonical-rank reference for the cases below — kept here so the * test reads as a story rather than an opaque number puzzle: * cover=0, favorite=1, publisher_name=2, imprint_name=3, - * series_name=4, volume_name=5, issue=6, name=7, child_count=8, - * file_name=9, size=10, page_count=11, file_type=12, - * year=13, … + * series_name=4, reprints=5, volume_name=6, issue=7, name=8, + * child_count=9, file_name=10, size=11, page_count=12, + * file_type=13, year=14, … */ describe("smartInsertIndex — canonical-order drafts", () => { diff --git a/frontend/tests/unit/edit-panel-reprints.test.js b/frontend/tests/unit/edit-panel-reprints.test.js new file mode 100644 index 000000000..ccd582ae3 --- /dev/null +++ b/frontend/tests/unit/edit-panel-reprints.test.js @@ -0,0 +1,228 @@ +/* + * Tests for the tag edit panel's Alternate Series section. + * + * The metadata endpoint ships each reprint's flat codex columns + * ({pk, name, seriesName, volumeNumber, issue, language, url}) alongside the + * composed chip `name`, so the editor seeds its rows from the parts instead + * of parsing the label back apart. On save the parts have to be re-nested + * into comicbox's reprint shape ({series: {name}, volume: {number}, issue, + * language}), and clearing the section has to travel as the "reprints" + * delete key — a patch can only add or replace. + * + * Volume and language are MetronInfo-only: ComicInfo's AlternateSeries / + * AlternateNumber / AlternateCount carry neither. + */ +import { createTestingPinia } from "@pinia/testing"; +import { flushPromises, mount } from "@vue/test-utils"; +import { describe, expect, test } from "vitest"; + +import EditPanel from "@/components/metadata/edit-mode/edit-panel.vue"; +import vuetify from "@/plugins/vuetify"; + +const DISABLED_TIP = "Not supported by selected metadata formats"; + +// Absent parts arrive as the model's own empty defaults: "" for the +// strings, null for the volume number. +const SHAPED_REPRINTS = Object.freeze([ + { + pk: 7, + name: "Kapitän Wissenschaft (de)", + seriesName: "Kapitän Wissenschaft", + volumeNumber: null, + issue: "", + language: "de", + }, + { + pk: 8, + name: "Capitan Sciencia v1 #3", + seriesName: "Capitan Sciencia", + volumeNumber: 1, + issue: "3", + language: "", + }, +]); + +async function mountPanel({ formats = ["METRON_INFO"], md = {} } = {}) { + const pinia = createTestingPinia({ + initialState: { + metadata: { md }, + admin: { taggingDefaults: { defaultFormats: formats } }, + browser: { settings: { twentyFourHourTime: false } }, + }, + }); + const wrapper = mount(EditPanel, { + props: { book: { pk: 1, collection: "comics", ids: [1] } }, + global: { plugins: [pinia, vuetify] }, + }); + await flushPromises(); + return wrapper; +} + +function findButton(wrapper, label) { + return wrapper.findAll("button").find((b) => b.text().includes(label)); +} + +describe("EditPanel alternate series rows", () => { + test("seeds one row per reprint from the flat columns", async () => { + const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); + + expect(wrapper.vm.reprints).toStrictEqual([ + { + series_name: "Kapitän Wissenschaft", + volume: "", + issue: "", + language: "de", + }, + { + series_name: "Capitan Sciencia", + volume: "1", + issue: "3", + language: null, + }, + ]); + expect(wrapper.vm.hasChanges).toBe(false); + }); + + test("leaves the rows empty when the comic has no reprints", async () => { + const wrapper = await mountPanel({ md: { reprints: [] } }); + expect(wrapper.vm.reprints).toStrictEqual([]); + }); + + test("the add button appends a blank row", async () => { + const wrapper = await mountPanel(); + await findButton(wrapper, "Add Alternate Series").trigger("click"); + + expect(wrapper.vm.reprints).toStrictEqual([ + { series_name: "", volume: "", issue: "", language: null }, + ]); + }); +}); + +describe("EditPanel alternate series patch", () => { + test("re-nests the parts into comicbox reprints", async () => { + const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); + wrapper.vm.reprints[0].issue = "1"; + await flushPromises(); + + const { patch, deleteKeys } = wrapper.vm.buildPatch(); + expect(patch).toStrictEqual({ + reprints: [ + { + series: { name: "Kapitän Wissenschaft" }, + issue: "1", + language: "de", + }, + { + series: { name: "Capitan Sciencia" }, + volume: { number: 1 }, + issue: "3", + }, + ], + }); + expect(deleteKeys).toStrictEqual([]); + }); + + test("an untouched section stays out of the patch entirely", async () => { + const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); + const { patch, deleteKeys } = wrapper.vm.buildPatch(); + + expect(patch).not.toHaveProperty("reprints"); + expect(deleteKeys).not.toContain("reprints"); + }); + + test("a newly added row travels with only the parts it carries", async () => { + const wrapper = await mountPanel(); + wrapper.vm.reprints.push({ + series_name: "Capitaine Science", + volume: "", + issue: "", + language: null, + }); + await flushPromises(); + + const { patch } = wrapper.vm.buildPatch(); + expect(patch.reprints).toStrictEqual([ + { series: { name: "Capitaine Science" } }, + ]); + }); + + test("removing a row drops it from the patch", async () => { + const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); + wrapper.vm.reprints.splice(0, 1); + await flushPromises(); + + const { patch } = wrapper.vm.buildPatch(); + expect(patch.reprints).toHaveLength(1); + expect(patch.reprints[0].series.name).toBe("Capitan Sciencia"); + }); + + test("a row with no series name names nothing and is dropped", async () => { + const wrapper = await mountPanel(); + wrapper.vm.reprints.push({ + series_name: "", + volume: "2", + issue: "", + language: "fr", + }); + await flushPromises(); + + const { patch, deleteKeys } = wrapper.vm.buildPatch(); + expect(patch).not.toHaveProperty("reprints"); + expect(deleteKeys).toStrictEqual(["reprints"]); + }); + + test("clearing the section deletes the comicbox reprints key", async () => { + const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); + wrapper.vm.clearField("reprints"); + await flushPromises(); + + const { patch, deleteKeys } = wrapper.vm.buildPatch(); + expect(wrapper.vm.reprints).toStrictEqual([]); + expect(deleteKeys).toStrictEqual(["reprints"]); + expect(patch).not.toHaveProperty("reprints"); + }); + + test("emptying every row also deletes rather than patching nothing", async () => { + const wrapper = await mountPanel({ md: { reprints: SHAPED_REPRINTS } }); + wrapper.vm.reprints = []; + await flushPromises(); + + const { patch, deleteKeys } = wrapper.vm.buildPatch(); + expect(deleteKeys).toStrictEqual(["reprints"]); + expect(patch).not.toHaveProperty("reprints"); + }); +}); + +describe("EditPanel alternate series format support", () => { + test("ComicInfo disables the MetronInfo-only volume and language", async () => { + const wrapper = await mountPanel({ + formats: ["COMIC_INFO"], + md: { reprints: SHAPED_REPRINTS }, + }); + const volume = wrapper + .findAll("input") + .find((i) => i.element.value === "1"); + + expect(volume.element.disabled).toBe(true); + expect(volume.element.closest("td").getAttribute("title")).toBe( + DISABLED_TIP, + ); + expect(wrapper.vm.isFieldDisabled("reprint_language")).toBe(true); + // The series name and issue do persist to ComicInfo's Alternates. + expect(wrapper.vm.isFieldDisabled("reprints")).toBe(false); + }); + + test("MetronInfo enables every part", async () => { + const wrapper = await mountPanel({ + formats: ["METRON_INFO"], + md: { reprints: SHAPED_REPRINTS }, + }); + const btn = findButton(wrapper, "Add Alternate Series"); + + expect(btn.element.disabled).toBe(false); + expect(wrapper.vm.isFieldDisabled("reprint_volume")).toBe(false); + expect(wrapper.vm.isFieldDisabled("reprint_language")).toBe(false); + }); +}); + +export default {}; diff --git a/frontend/tests/unit/metadata-reprints.test.js b/frontend/tests/unit/metadata-reprints.test.js new file mode 100644 index 000000000..3e41be1b9 --- /dev/null +++ b/frontend/tests/unit/metadata-reprints.test.js @@ -0,0 +1,88 @@ +/* + * The metadata pane's alternate-series row. + * + * The backend composes ``Reprint.name`` from whichever of series name, + * volume, issue and language the reprint carries, so the client just + * renders it like any other ``{pk, name, url}`` tag row. Only the row + * label differs from the capital-cased key: "Alternate Series". + * + * Chips route through the ``reprints`` browser filter, the key phase E + * registers. + */ +import { createTestingPinia } from "@pinia/testing"; +import { mount } from "@vue/test-utils"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, test } from "vitest"; + +import MetadataTags from "@/components/metadata/metadata-tags.vue"; +import vuetify from "@/plugins/vuetify"; +import { useBrowserStore } from "@/stores/browser"; +import { useMetadataStore } from "@/stores/metadata"; + +const REPRINTS = Object.freeze([ + { pk: 7, name: "Kapitän Wissenschaft" }, + { pk: 8, name: "Capitan Sciencia v1 (es)" }, +]); + +describe("metadata store alternate series row", () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + + test("labels the reprints row Alternate Series", () => { + const store = useMetadataStore(); + // Copied: the tags getter sorts the row in place. + store.md = { reprints: [...REPRINTS] }; + + const row = store.tags["Alternate Series"]; + expect(row.filter).toBe("reprints"); + expect(row.tags.map((tag) => tag.name)).toStrictEqual([ + "Capitan Sciencia v1 (es)", + "Kapitän Wissenschaft", + ]); + expect(store.tags.Reprints).toBeUndefined(); + }); + + test("omits the row when there are no reprints", () => { + const store = useMetadataStore(); + store.md = { reprints: [] }; + + expect(store.tags["Alternate Series"]).toBeUndefined(); + }); +}); + +describe("alternate series chips", () => { + function mountTags() { + const pinia = createTestingPinia({ + initialState: { + browser: { + settings: { show: {}, filters: {}, topCollection: "publishers" }, + }, + metadata: { md: { collection: "comics", ids: [1] } }, + }, + }); + const wrapper = mount(MetadataTags, { + global: { plugins: [pinia, vuetify] }, + props: { label: "", filter: "reprints", values: REPRINTS }, + }); + return { wrapper, browserStore: useBrowserStore() }; + } + + test("renders the composed names", () => { + const { wrapper } = mountTags(); + const text = wrapper.text(); + expect(text).toContain("Kapitän Wissenschaft"); + expect(text).toContain("Capitan Sciencia v1 (es)"); + }); + + test("clicking filters the browser by the reprint pk", async () => { + const { wrapper, browserStore } = mountTags(); + + await wrapper.findAll(".v-chip")[0].trigger("click"); + + expect(browserStore.routeWithSettings).toHaveBeenCalledWith( + { filters: { reprints: [7] } }, + { name: "browser", params: { collection: "root", pks: "0", page: 1 } }, + ); + }); +}); diff --git a/frontend/tests/unit/prompt-popup.test.js b/frontend/tests/unit/prompt-popup.test.js new file mode 100644 index 000000000..3e4c1887a --- /dev/null +++ b/frontend/tests/unit/prompt-popup.test.js @@ -0,0 +1,130 @@ +/* + * Tests for the Online Tagging match-review popup. + * + * Comicbox scores candidates against alternate series names, so a comic filed + * under a localized title matches a canonical series that looks nothing like + * its filename. The popup shows those aliases so the match is explicable, and + * a pick carries the candidate's volume id so the apply replay can narrow to + * that volume. + */ +import { createTestingPinia } from "@pinia/testing"; +import { mount } from "@vue/test-utils"; +import { describe, expect, test } from "vitest"; + +import OnlineTagPromptPopup from "@/components/online-tag/prompt-popup.vue"; +import vuetify from "@/plugins/vuetify"; +import { useOnlineTagStore } from "@/stores/online-tag"; + +// Render the dialog body inline instead of through v-dialog's overlay/teleport +// (which need browser APIs happy-dom lacks). +const VDialogStub = { name: "VDialog", template: "
" }; + +function candidate(overrides = {}) { + return { + source: "comicvine", + issueId: 42, + summary: { + series: "Captain Science", + issue: "1", + year: 1950, + publisher: "Youthful", + coverUrl: "", + altSeries: [], + }, + score: 0.91, + url: "", + volumeId: null, + ...overrides, + }; +} + +function mountPopup(candidates) { + const pinia = createTestingPinia(); + const wrapper = mount(OnlineTagPromptPopup, { + global: { + plugins: [vuetify, pinia], + stubs: { VDialog: VDialogStub }, + }, + }); + const store = useOnlineTagStore(); + store.pendingPrompts = [ + { + fingerprint: "fp1", + pk: 7, + path: "/comics/kapitan.cbz", + source: "comicvine", + candidates, + }, + ]; + store.promptDialogOpen = true; + return { wrapper, store }; +} + +describe("OnlineTagPromptPopup", () => { + describe("alternate series names", () => { + test("shows the aliases that explain an off-filename match", async () => { + const { wrapper } = mountPopup([ + candidate({ + summary: { + ...candidate().summary, + altSeries: ["Kapitän Wissenschaft", "Capitan Sciencia"], + }, + }), + ]); + await wrapper.vm.$nextTick(); + + expect(wrapper.text()).toContain( + "a.k.a. Kapitän Wissenschaft, Capitan Sciencia", + ); + }); + + test("omits the line when the source carries no aliases", async () => { + const { wrapper } = mountPopup([candidate()]); + await wrapper.vm.$nextTick(); + + expect(wrapper.text()).not.toContain("a.k.a."); + }); + + test("omits the line for a prompt cached before aliases existed", async () => { + const summary = { ...candidate().summary }; + delete summary.altSeries; + const { wrapper } = mountPopup([candidate({ summary })]); + await wrapper.vm.$nextTick(); + + expect(wrapper.text()).not.toContain("a.k.a."); + }); + }); + + describe("pick", () => { + test("passes the chosen candidate's volume id", () => { + const { wrapper, store } = mountPopup([ + candidate(), + candidate({ volumeId: 9876 }), + ]); + + wrapper.vm.pick(store.pendingPrompts[0], 1); + + expect(store.resolvePrompt).toHaveBeenCalledWith( + "fp1", + "choose", + 1, + 9876, + ); + }); + + test("passes null when the source exposes no volume id", () => { + const { wrapper, store } = mountPopup([candidate()]); + + wrapper.vm.pick(store.pendingPrompts[0], 0); + + expect(store.resolvePrompt).toHaveBeenCalledWith( + "fp1", + "choose", + 0, + null, + ); + }); + }); +}); + +export default {}; diff --git a/tests/importer/test_basic.py b/tests/importer/test_basic.py index b13b924c2..5de1d5d6a 100644 --- a/tests/importer/test_basic.py +++ b/tests/importer/test_basic.py @@ -58,6 +58,7 @@ Location, OriginalFormat, Publisher, + Reprint, ScanInfo, Series, SeriesGroup, @@ -81,7 +82,9 @@ PATH = str(LIBRARY_PATH / "test.cbz") PATH_PARENTS = {(str(Path(PATH).parent),)} PATH_PARENTS_QUERY = {(str(Path(PATH).parent),): set()} -COMPLEX_FIELD_NAMES = frozenset({"credits", "story_arc_numbers", "identifiers"}) +COMPLEX_FIELD_NAMES = frozenset( + {"credits", "reprints", "story_arc_numbers", "identifiers"} +) AGGREGATED = MappingProxyType( { FIS: {}, @@ -118,6 +121,10 @@ }, Tagger: {("comicbox dev",): set()}, Publisher: {("Youthful Adventure Stories",): {(None,)}}, + Reprint: { + ("Capitan Sciencia", 1, "", "es"): {(None,)}, + ("Kapitän Wissenschaft", None, "", "de"): {(None,)}, + }, Imprint: { ( "Youthful Adventure Stories", @@ -254,7 +261,10 @@ ) }, "locations": {("The Moon",)}, - # reprints add + "reprints": { + ("Capitan Sciencia", 1, "", "es"), + ("Kapitän Wissenschaft", None, "", "de"), + }, "series_groups": {("science comics",)}, "stories": {("The Beginning",)}, "story_arc_numbers": { @@ -357,6 +367,10 @@ Universe: {("Young Adult Silly Universe", None, "4242")}, Folder: deepcopy(PATH_PARENTS), Publisher: {("Youthful Adventure Stories", None)}, + Reprint: { + ("Capitan Sciencia", 1, "", "es", None), + ("Kapitän Wissenschaft", None, "", "de", None), + }, Imprint: {("Youthful Adventure Stories", "TestImprint", None)}, Series: { ( @@ -377,7 +391,7 @@ 7, ) }, - TOTAL: 41, + TOTAL: 43, }, UPDATE_FKS: { TOTAL: 0, @@ -415,6 +429,10 @@ "genres": {("Science Fiction",)}, "identifiers": {("comicvine", "comic", "145269")}, "locations": {("The Moon",)}, + "reprints": { + ("Capitan Sciencia", 1, "", "es"), + ("Kapitän Wissenschaft", None, "", "de"), + }, "series_groups": {("science comics",)}, "stories": {("The Beginning",)}, "story_arc_numbers": {("c", None), ("d", 1), ("e", 3), ("f", 5)}, @@ -491,6 +509,10 @@ "genres": {("Science Fiction",)}, "identifiers": {("comicvine", "comic", "145269")}, "locations": {("The Moon",)}, + "reprints": { + ("Capitan Sciencia", 1, "", "es"), + ("Kapitän Wissenschaft", None, "", "de"), + }, "series_groups": {("science comics",)}, "stories": {("The Beginning",)}, "story_arc_numbers": {("c", None), ("d", 1), ("e", 3), ("f", 5)}, @@ -516,6 +538,10 @@ "genres": {("Science Fiction",)}, "identifiers": {("comicvine", "comic", "145269")}, "locations": {("The Moon",)}, + "reprints": { + ("Capitan Sciencia", 1, "", "es"), + ("Kapitän Wissenschaft", None, "", "de"), + }, "series_groups": {("science comics",)}, "stories": {("The Beginning",)}, "story_arc_numbers": {("c", None), ("d", 1), ("e", 3), ("f", 5)}, @@ -553,6 +579,7 @@ FIS: {}, FTS_CREATE: { 1: { + "alternate_series": ("Capitan Sciencia", "Kapitän Wissenschaft"), "characters": ("Boy Empirical", "Captain Science"), "collection_title": ("The Big Omnibus",), "country": ("US",), @@ -593,6 +620,7 @@ { FTS_CREATE: { 1: { + "alternate_series": ("Capitan Sciencia", "Kapitän Wissenschaft"), "characters": ("Boy Empirical", "Captain Science"), "collection_title": ("The Big Omnibus",), "country": ("US",), @@ -632,6 +660,7 @@ { FTS_CREATE: { 1: { + "alternate_series": ("Capitan Sciencia", "Kapitän Wissenschaft"), "characters": ("Boy Empirical", "Captain Science"), "collection_title": ("The Big Omnibus",), "country": ("US",), @@ -673,7 +702,7 @@ "volume": -2, } ) -_COMPLEX_KEYS = frozenset({"credits", "identifiers", "story_arc_numbers"}) +_COMPLEX_KEYS = frozenset({"credits", "identifiers", "reprints", "story_arc_numbers"}) _COMICFTS_IGNORE_KEYS = ("comic_id", "updated_at", "created_at") @@ -763,6 +792,18 @@ def _test_comic_creation_field_complex(field_name: str, value): for subval in value.all() ) ) + elif field_name == "reprints": + value = tuple( + sorted( + ( + subval.series_name, + subval.volume_number, + subval.issue, + subval.language, + ) + for subval in value.all() + ) + ) return value @@ -808,6 +849,28 @@ def export_test_fts_creation(values_const: MappingProxyType, comic: Comic): diff_assert(fts_values, comicfts, "COMIC_FTS") +def run_full_import(importer) -> None: + """ + Run every importer phase in order. + + ``ComicImporter.apply()`` wraps the same sequence in + ``importer_pragmas``, whose exit-time ``PRAGMA wal_checkpoint`` raises + ``database table is locked`` inside a ``TestCase``'s wrapping + transaction. Drive the phases directly instead. + """ + importer.read() + importer.query() + importer.create_all_fks() + importer.update_all_fks() + importer.prepare_fk_link_instance_maps() + importer.update_comics() + importer.create_comics() + importer.link() + importer.fail_imports() + importer.delete() + importer.full_text_search() + + class BaseTestImporter(SerializeMixin, TestCase, ABC): lockfile = __file__ diff --git a/tests/importer/test_move_timestamps.py b/tests/importer/test_move_timestamps.py index 7243d1962..0e023b265 100644 --- a/tests/importer/test_move_timestamps.py +++ b/tests/importer/test_move_timestamps.py @@ -32,27 +32,12 @@ StoryArcNumber, Volume, ) -from tests.importer.test_basic import PATH, BaseTestImporter +from tests.importer.test_basic import PATH, BaseTestImporter, run_full_import _FILE_PUBLISHER = "Youthful Adventure Stories" _SOURCE_PUBLISHER = "Throwaway Source Pub" -def _run_full_import(importer) -> None: - """Run every importer phase in order (apply() without pragmas/finish).""" - importer.read() - importer.query() - importer.create_all_fks() - importer.update_all_fks() - importer.prepare_fk_link_instance_maps() - importer.update_comics() - importer.create_comics() - importer.link() - importer.fail_imports() - importer.delete() - importer.full_text_search() - - class TestImporterMoveRestampsSource(BaseTestImporter): """A publisher change re-stamps the collection the comic left.""" @@ -90,7 +75,7 @@ def test_publisher_move_restamps_source(self) -> None: check_metadata_mtime=False, ) importer = ComicImporter(task, logger, LIBRARIAN_QUEUE, Lock(), Event()) - _run_full_import(importer) + run_full_import(importer) # The comic actually moved to the file's publisher... comic = Comic.objects.get(path=PATH) @@ -142,7 +127,7 @@ def test_storyarc_removal_restamps_source(self) -> None: check_metadata_mtime=False, ) importer = ComicImporter(task, logger, LIBRARIAN_QUEUE, Lock(), Event()) - _run_full_import(importer) + run_full_import(importer) # The comic moved to the file's arcs; "Removed Arc" lost it... comic = Comic.objects.get(path=PATH) diff --git a/tests/importer/test_update_all.py b/tests/importer/test_update_all.py index 1ad9c82e2..27966e4e2 100644 --- a/tests/importer/test_update_all.py +++ b/tests/importer/test_update_all.py @@ -40,6 +40,7 @@ Location, OriginalFormat, Publisher, + Reprint, ScanInfo, Series, SeriesGroup, @@ -130,6 +131,10 @@ ("metron", "comic", "999"), }, "locations": {("Mars",)}, + "reprints": { + ("Capitan Sciencia", 1, "", "es"), + ("Kapitän Wissenschaft", None, "", "de"), + }, "series_groups": {("adult comics",)}, "stories": {("The Beginning",), ("The End",)}, "story_arc_numbers": {("c", None), ("d", 1), ("e", 3), ("g", 5)}, @@ -186,6 +191,10 @@ (("metron", "publisher", "111"),), } }, + Reprint: { + ("Capitan Sciencia", 1, "", "es"): {(None,)}, + ("Kapitän Wissenschaft", None, "", "de"): {(None,)}, + }, Imprint: { ("Youthful Adventure Stories", "TestImprint"): { (("metron", "imprint", "123"),), diff --git a/tests/test_browser_reprints_column.py b/tests/test_browser_reprints_column.py new file mode 100644 index 000000000..62875e296 --- /dev/null +++ b/tests/test_browser_reprints_column.py @@ -0,0 +1,230 @@ +""" +The ``reprints`` browser table column and filter. + +``Reprint`` is the only tag model without a ``name`` column — the label +composes from four — so both the table cell (Comic rows and collection-row +intersections) and the filter drawer need their own label paths. These +tests pin all of them to :meth:`Reprint.compose_name`'s output. +""" + +import json +import shutil +from pathlib import Path +from typing import Final, override + +from django.contrib.auth.models import User +from django.core.cache import cache +from django.test import Client, TestCase + +from codex.choices.browser import DUMMY_NULL_NAME, VUETIFY_NULL_CODE +from codex.models import Comic, Imprint, Library, Publisher, Series, Volume +from codex.models.named import Reprint +from codex.startup import init_admin_flags + +_TEST_PASSWORD: Final = "test-pw-hush-S106" # noqa: S105 +_HTTP_OK: Final = 200 +TMP_DIR = Path("/tmp/codex.tests.browser_reprints_column") # noqa: S108 +_SETTINGS_URL: Final = "/api/v4/browse/publishers/settings" + + +def _v4(response): + """Unwrap the v4 ``{data, meta, errors}`` envelope and return ``data``.""" + body = response.json() + if isinstance(body, dict) and "data" in body and "meta" in body: + return body["data"] + return body + + +class _ReprintsFixtureTestCase(TestCase): + """One publisher / imprint / series / volume and one comic to tag.""" + + @override + def setUp(self) -> None: + cache.clear() + init_admin_flags() + TMP_DIR.mkdir(exist_ok=True, parents=True) + self.library = Library.objects.create(path=str(TMP_DIR)) # pyright: ignore[reportUninitializedInstanceVariable] + self.publisher = Publisher.objects.create(name="Pub") # pyright: ignore[reportUninitializedInstanceVariable] + self.imprint = Imprint.objects.create(name="Imp", publisher=self.publisher) # pyright: ignore[reportUninitializedInstanceVariable] + self.series = self._create_series("Ser") # pyright: ignore[reportUninitializedInstanceVariable] + self.comic = self._create_comic("C1", 1) # pyright: ignore[reportUninitializedInstanceVariable] + user = User.objects.create_user( + username="reprints_column_test", password=_TEST_PASSWORD + ) + self.client = Client() + self.client.force_login(user) + + @override + def tearDown(self) -> None: + shutil.rmtree(TMP_DIR, ignore_errors=True) + + def _create_series(self, name: str) -> Series: + return Series.objects.create( + name=name, imprint=self.imprint, publisher=self.publisher + ) + + def _create_comic( + self, name: str, issue_number: int, series: Series | None = None + ) -> Comic: + series = series or self.series + volume, _ = Volume.objects.get_or_create( + name=2024, series=series, imprint=self.imprint, publisher=self.publisher + ) + path = TMP_DIR / f"{name.lower()}.cbz" + path.touch() + return Comic.objects.create( + library=self.library, + path=path, + issue_number=issue_number, + name=name, + publisher=self.publisher, + imprint=self.imprint, + series=series, + volume=volume, + size=42 + issue_number, + year=2024, + page_count=20, + ) + + def _set_view_mode_table(self) -> None: + response = self.client.patch( + _SETTINGS_URL, + data=json.dumps({"viewMode": "table"}), + content_type="application/json", + ) + assert response.status_code == _HTTP_OK, response.content + + def _browse(self, url: str) -> dict: + response = self.client.get(url) + assert response.status_code == _HTTP_OK, response.content + return _v4(response) + + def _browse_comics(self, columns: str = "cover,name,reprints") -> dict: + return self._browse( + f"/api/v4/browse/series/{self.series.pk}?page=1&columns={columns}" + ) + + def _browse_series_rows(self, columns: str = "cover,name,reprints") -> dict: + return self._browse( + f"/api/v4/browse/publishers/{self.publisher.pk}?page=1&columns={columns}" + ) + + +class BrowserReprintsColumnTestCase(_ReprintsFixtureTestCase): + """Table-cell display and sort for the composed alternate-series label.""" + + def test_comic_row_composes_every_part(self) -> None: + """The SQL expression renders the same label as ``Reprint.compose_name``.""" + reprints = ( + Reprint.objects.create(series_name="Kapitän Wissenschaft"), + Reprint.objects.create(series_name="Capitan Sciencia", volume_number=1), + Reprint.objects.create(series_name="Yearly", volume_number=1999), + Reprint.objects.create( + series_name="Full", volume_number=3, issue="2a", language="es" + ), + ) + self.comic.reprints.set(reprints) + + self._set_view_mode_table() + row = self._browse_comics()["rows"][0] + assert sorted(row["reprints"]) == sorted( + reprint.name for reprint in reprints + ), row + # Spell the expectations out so a change to either the SQL or + # the Python composer fails loudly instead of agreeing wrongly. + # The aggregate sorts its own elements — identical reprint sets + # must render identical JSON for the M2M sort to cluster them. + assert row["reprints"] == [ + "Capitan Sciencia v1", + "Full v3 #2a (es)", + "Kapitän Wissenschaft", + "Yearly (1999)", + ] + + def test_collection_row_shows_only_shared_reprints(self) -> None: + """A series row intersects its children's reprints.""" + shared = Reprint.objects.create(series_name="Shared", language="de") + lonely = Reprint.objects.create(series_name="Lonely") + self.comic.reprints.set((shared, lonely)) + sibling = self._create_comic("C2", 2) + sibling.reprints.set((shared,)) + + self._set_view_mode_table() + row = self._browse_series_rows()["rows"][0] + assert row["reprints"] == ["Shared (de)"], row + + def test_collection_row_without_reprints_is_empty(self) -> None: + """Collections whose children carry no reprints render an empty cell.""" + self._set_view_mode_table() + row = self._browse_series_rows()["rows"][0] + assert row["reprints"] == [], row + + def test_collection_row_sort_by_reprints(self) -> None: + """Sorting collection rows by the column uses its intersection SQL.""" + self.comic.reprints.add( + Reprint.objects.create(series_name="Zulu", volume_number=2) + ) + # "Zzz" has no reprints, so ascending by the column puts it + # first — the opposite of the ``sort_name`` order the sort + # falls back to when the intersection SQL isn't wired. + self._create_comic("C2", 1, series=self._create_series("Zzz")) + + self._set_view_mode_table() + response = self.client.patch( + _SETTINGS_URL, + data=json.dumps({"orderBy": "reprints", "orderReverse": False}), + content_type="application/json", + ) + assert response.status_code == _HTTP_OK, response.content + rows = self._browse_series_rows()["rows"] + assert [(row["name"], row["reprints"]) for row in rows] == [ + ("Zzz", []), + ("Ser", ["Zulu v2"]), + ], rows + + +class BrowserReprintsFilterTestCase(_ReprintsFixtureTestCase): + """The filter drawer's choices and the resulting narrowing.""" + + def test_choices_compose_labels(self) -> None: + """The choices endpoint labels reprints like the metadata panel does.""" + reprint = Reprint.objects.create( + series_name="Kapitän Wissenschaft", language="de" + ) + self.comic.reprints.add(reprint) + # A reprint-less sibling makes the view prepend the null + # sentinel, which already carries a name of its own. + self._create_comic("C2", 2) + + body = self._browse(f"/api/v4/browse/series/{self.series.pk}/choices/reprints") + choices = {choice["pk"]: choice["name"] for choice in body["choices"]} + assert choices[reprint.pk] == "Kapitän Wissenschaft (de)", choices + assert choices[VUETIFY_NULL_CODE] == DUMMY_NULL_NAME, choices + + def test_choices_availability(self) -> None: + """``reprints`` only offers a sub-menu once a value and a null exist.""" + body = self._browse(f"/api/v4/browse/series/{self.series.pk}/choices") + assert body["reprints"] is False, body + + self.comic.reprints.add(Reprint.objects.create(series_name="Alt")) + self._create_comic("C2", 2) + cache.clear() + body = self._browse(f"/api/v4/browse/series/{self.series.pk}/choices") + assert body["reprints"] is True, body + + def test_filter_narrows_to_tagged_comics(self) -> None: + """Selecting a reprint pk filters the browse to comics carrying it.""" + reprint = Reprint.objects.create(series_name="Alt", volume_number=1) + self.comic.reprints.add(reprint) + self._create_comic("C2", 2) + + response = self.client.patch( + _SETTINGS_URL, + data=json.dumps({"filters": {"reprints": [reprint.pk]}}), + content_type="application/json", + ) + assert response.status_code == _HTTP_OK, response.content + cache.clear() + body = self._browse(f"/api/v4/browse/series/{self.series.pk}?page=1") + names = [book["name"] for book in body["books"]] + assert names == ["C1"], body diff --git a/tests/test_browser_table_response.py b/tests/test_browser_table_response.py index a34b30392..871be9d5f 100644 --- a/tests/test_browser_table_response.py +++ b/tests/test_browser_table_response.py @@ -632,8 +632,8 @@ def test_table_view_simple_m2m_intersections_share_one_union_query( metadata-view pattern (``query_intersections``'s ``_query_m2m_intersections``) and unions per-through-table sub-queries into a single SQL transmission. Composite M2M - columns (credits / identifiers / universes / story_arcs) - still issue their own queries. + columns (credits / identifiers / reprints / universes / + story_arcs) still issue their own queries. """ from django.db import connection from django.test.utils import CaptureQueriesContext diff --git a/tests/test_metadata_reprints.py b/tests/test_metadata_reprints.py new file mode 100644 index 000000000..30fbd0a5b --- /dev/null +++ b/tests/test_metadata_reprints.py @@ -0,0 +1,159 @@ +""" +The metadata pane serves alternate series names as ready-to-render chips. + +``Reprint.name`` is a property composed from four columns, so the +metadata endpoint has to hydrate all of them plus the optional +identifier in one query. Without the ``M2M_QUERY_OPTIMIZERS`` entry the +default ``only=("name", "identifier")`` raises FieldDoesNotExist. +""" + +import shutil +from pathlib import Path +from typing import Final, override + +from django.contrib.auth.models import User +from django.db import connection +from django.test import Client, TestCase +from django.test.utils import CaptureQueriesContext + +from codex.models import ( + Comic, + Imprint, + Library, + Publisher, + Reprint, + Series, + Volume, +) +from codex.models.identifier import Identifier, IdentifierSource, IdentifierType +from codex.startup import init_admin_flags + +TMP_DIR = Path("/tmp/codex.tests.metadata_reprints") # noqa: S108 +_TEST_PASSWORD: Final = "test-pw-hush-S106" # noqa: S105 +_HTTP_OK: Final = 200 +_REPRINT_TABLE: Final = 'FROM "codex_reprint"' +_REPRINT_COUNT: Final = 3 + + +def _v4(response): + """Unwrap the v4 ``{data, meta, errors}`` envelope and return ``data``.""" + body = response.json() + if isinstance(body, dict) and "data" in body and "meta" in body: + return body["data"] + return body + + +class MetadataReprintsTestCase(TestCase): + """The reprints payload shape and its query cost.""" + + @override + def setUp(self) -> None: + """Seed one comic with three reprints of varying completeness.""" + init_admin_flags() + TMP_DIR.mkdir(exist_ok=True, parents=True) + library = Library.objects.create(path=str(TMP_DIR)) + publisher = Publisher.objects.create(name="Pub") + imprint = Imprint.objects.create(name="Imp", publisher=publisher) + self.series = Series.objects.create( # pyright: ignore[reportUninitializedInstanceVariable] + name="Ser", imprint=imprint, publisher=publisher + ) + volume = Volume.objects.create( + name="2024", series=self.series, imprint=imprint, publisher=publisher + ) + path = TMP_DIR / "c1.cbz" + path.touch() + self.comic = Comic.objects.create( # pyright: ignore[reportUninitializedInstanceVariable] + library=library, + path=path, + issue_number=1, + name="C1", + publisher=publisher, + imprint=imprint, + series=self.series, + volume=volume, + size=42, + ) + source = IdentifierSource.objects.create(name="metron") + identifier = Identifier.objects.create( + source=source, + id_type=IdentifierType.REPRINT.value, + key="12345", + url="https://metron.cloud/series/12345", + ) + self.comic.reprints.set( + [ + Reprint.objects.create(series_name="Kapitän Wissenschaft"), + Reprint.objects.create( + series_name="Capitan Sciencia", volume_number=1, language="es" + ), + Reprint.objects.create( + series_name="Captain Science", + issue="3", + identifier=identifier, + ), + ] + ) + + user = User.objects.create_user( + username="reprint_reader", password=_TEST_PASSWORD, is_staff=True + ) + self.client = Client() + self.client.force_login(user) + + @override + def tearDown(self) -> None: + """Remove the temp comic tree.""" + shutil.rmtree(TMP_DIR, ignore_errors=True) + + def _get_metadata(self, url: str = ""): + url = url or f"/api/v4/browse/comics/{self.comic.pk}/metadata" + with CaptureQueriesContext(connection) as ctx: + response = self.client.get(url) + assert response.status_code == _HTTP_OK, response.content + return _v4(response), ctx.captured_queries + + def test_reprint_names_compose_present_parts(self) -> None: + """Each chip name composes only the columns the reprint carries.""" + data, _ = self._get_metadata() + names = sorted(reprint["name"] for reprint in data["reprints"]) + assert names == [ + "Capitan Sciencia v1 (es)", + "Captain Science #3", + "Kapitän Wissenschaft", + ], names + + def test_reprint_columns_ship_beside_the_composed_name(self) -> None: + """The tag editor seeds its rows from the parts, not the label.""" + data, _ = self._get_metadata() + parts = { + reprint["seriesName"]: ( + reprint["volumeNumber"], + reprint["issue"], + reprint["language"], + ) + for reprint in data["reprints"] + } + assert parts == { + "Kapitän Wissenschaft": (None, "", ""), + "Capitan Sciencia": (1, "", "es"), + "Captain Science": (None, "3", ""), + }, parts + + def test_reprint_identifier_url_ships_when_set(self) -> None: + """The identifier url rides along for the chip's external link.""" + data, _ = self._get_metadata() + urls = {reprint["name"]: reprint.get("url", "") for reprint in data["reprints"]} + assert urls["Captain Science #3"] == "https://metron.cloud/series/12345" + assert not urls["Kapitän Wissenschaft"] + + def test_collection_selection_serves_the_intersection(self) -> None: + """Collections take the plain-attribute branch, not the prefetch cache.""" + url = f"/api/v4/browse/series/{self.series.pk}/metadata" + data, _ = self._get_metadata(url) + assert len(data["reprints"]) == _REPRINT_COUNT, data["reprints"] + + def test_reprints_hydrate_in_one_query(self) -> None: + """No per-reprint query for the deferred columns or the identifier.""" + _, queries = self._get_metadata() + reprint_queries = [q for q in queries if _REPRINT_TABLE in q["sql"]] + assert len(reprint_queries) == 1, reprint_queries diff --git a/tests/test_onlinetag_resume_endpoints.py b/tests/test_onlinetag_resume_endpoints.py index 265c5acd4..e78ce6f6d 100644 --- a/tests/test_onlinetag_resume_endpoints.py +++ b/tests/test_onlinetag_resume_endpoints.py @@ -26,7 +26,6 @@ "sources": ["metron", "comicvine"], "mode": "auto", "prompts_mode": "ask", - "auto_threshold": 0.85, "delete_original": False, "merge_all_sources": False, "dry_run": False, @@ -87,6 +86,19 @@ def test_resume_enqueues_task_over_remaining_pks(self) -> None: assert task.mode == "auto" assert task.prompts_mode == "ask" + def test_resume_drops_params_from_an_older_codex(self) -> None: + # A descriptor written before a knob was removed (auto_threshold) must + # still resume instead of raising TypeError on the task constructor. + set_resume_state({**_PARAMS, "auto_threshold": 0.85}, [11]) + + with patch(_QUEUE_TARGET) as mocked_queue: + response = self.client.post(_RESUME_URL) + + assert response.status_code == HTTPStatus.ACCEPTED + task = mocked_queue.put.call_args.args[0] + assert isinstance(task, BulkOnlineTagTask) + assert task.comic_pks == frozenset([11]) + def test_resume_conflict_when_scan_active(self) -> None: set_resume_state(_PARAMS, [11]) set_active_scan_id("live-scan") diff --git a/tests/test_reprints.py b/tests/test_reprints.py new file mode 100644 index 000000000..33f0887cf --- /dev/null +++ b/tests/test_reprints.py @@ -0,0 +1,133 @@ +""" +Alternate series names, from the archive to the browser and back out. + +``tests/files/comicbox-2-example.cbz`` carries two MetronInfo alternate +names, one of which supplies only a series ``sort_name``. Importing it +is the only place the whole chain — comicbox whitelist, four-column key +flattening, m2m link, display label and the nightly orphan sweep — +meets end to end. The search half of the chain lives in +``tests/test_search_alternate_series.py``. +""" + +from http import HTTPStatus +from multiprocessing import Event as ProcessEvent +from threading import Lock +from typing import Final, override + +from django.contrib.auth.models import User +from django.test import Client, SimpleTestCase +from loguru import logger + +from codex.librarian.mp_queue import LIBRARIAN_QUEUE +from codex.librarian.scribe.importer.read.many_to_many import ( + AggregateManyToManyMetadataImporter, +) +from codex.librarian.scribe.janitor.janitor import Janitor +from codex.models import Comic, Reprint, Series, Volume +from tests.importer.test_basic import PATH, BaseTestImporter, run_full_import + +_TEST_PASSWORD: Final = "test-pw-hush-S106" # noqa: S105 +# The fixture's two alternate names, as ``Reprint`` natural keys. +_FIXTURE_KEYS: Final = { + ("Capitan Sciencia", 1, "", "es"), + ("Kapitän Wissenschaft", None, "", "de"), +} +_SORT_NAME_ONLY: Final = "Capitan Sciencia" + + +def _reprint_keys() -> set[tuple]: + """Every Reprint row in the database, as natural-key tuples.""" + return set( + Reprint.objects.values_list("series_name", "volume_number", "issue", "language") + ) + + +def _make_janitor() -> Janitor: + """Build a Janitor detached from the librarian's thread graph.""" + return Janitor(logger, LIBRARIAN_QUEUE, Lock(), ProcessEvent()) + + +class ReprintImportTestCase(BaseTestImporter): + """Import the example fixture and follow its reprints downstream.""" + + @override + def setUp(self) -> None: + super().setUp() + run_full_import(self.importer) + self.comic = Comic.objects.get(path=PATH) + + def test_import_creates_rows_keyed_on_all_four_columns(self) -> None: + """Every part comicbox supplied lands in its own column.""" + assert _reprint_keys() == _FIXTURE_KEYS + linked = self.comic.reprints.values_list( + "series_name", "volume_number", "issue", "language" + ) + assert set(linked) == _FIXTURE_KEYS + + def test_sort_name_stands_in_for_a_missing_series_name(self) -> None: + """ + One AlternativeNames entry carries no ``series.name`` at all. + + It must still import, and — the reason ``Reprint`` denormalizes + instead of reusing Series/Volume — it must not become a + browsable collection row. + """ + assert Reprint.objects.filter(series_name=_SORT_NAME_ONLY).exists() + assert not Series.objects.filter(name=_SORT_NAME_ONLY).exists() + assert not Volume.objects.filter(series__name=_SORT_NAME_ONLY).exists() + + def test_metadata_endpoint_serves_imported_reprints(self) -> None: + """Imported rows reach the panel as composed display labels.""" + user = User.objects.create_user( + username="reprint_round_trip", password=_TEST_PASSWORD + ) + client = Client() + client.force_login(user) + response = client.get(f"/api/v4/browse/comics/{self.comic.pk}/metadata") + assert response.status_code == HTTPStatus.OK, response.content + names = sorted( + reprint["name"] for reprint in response.json()["data"]["reprints"] + ) + assert names == ["Capitan Sciencia v1 (es)", "Kapitän Wissenschaft (de)"] + + def test_cleanup_fks_keeps_linked_reprints(self) -> None: + """The nightly sweep leaves reprints a comic still points at.""" + _make_janitor().cleanup_fks() + assert _reprint_keys() == _FIXTURE_KEYS + + def test_cleanup_fks_deletes_orphaned_reprints(self) -> None: + """Deleting the last comic that referenced them sweeps them away.""" + self.comic.delete() + _make_janitor().cleanup_fks() + assert not Reprint.objects.exists() + + +class ReprintKeyFlatteningTestCase(SimpleTestCase): + """The nested comicbox tree collapses to the four-column key.""" + + @staticmethod + def _clean(reprint: dict) -> tuple | None: + return AggregateManyToManyMetadataImporter._clean_reprint_key(reprint) # noqa: SLF001 + + def test_series_name_wins_over_sort_name(self) -> None: + """``sort_name`` only fills in for a reprint that has no name.""" + key = self._clean({"series": {"name": "Real", "sort_name": "Sorted"}}) + assert key == ("Real", None, "", "") + + def test_every_part_flattens_to_its_column(self) -> None: + """Volume number, issue and language come out of their subtrees.""" + key = self._clean( + { + "series": {"name": "Kapitän Wissenschaft"}, + "volume": {"number": 2}, + "issue": "3", + "language": "de", + } + ) + assert key == ("Kapitän Wissenschaft", 2, "3", "de") + + def test_reprint_without_any_series_name_is_dropped(self) -> None: + """A reprint naming nothing can't key a row, so it never becomes one.""" + assert self._clean({"volume": {"number": 1}, "language": "de"}) is None + assert self._clean({"series": {}}) is None + assert self._clean({"series": {"name": " "}}) is None diff --git a/tests/test_search_alternate_series.py b/tests/test_search_alternate_series.py new file mode 100644 index 000000000..91cc53889 --- /dev/null +++ b/tests/test_search_alternate_series.py @@ -0,0 +1,133 @@ +""" +Alternate series names are searchable. + +comicbox ``reprints`` become :class:`codex.models.named.Reprint` rows and +are indexed into a dedicated ``ComicFTS.alternate_series`` column rather +than being folded into ``series`` — a ``series:`` token has to keep +meaning the canonical series. + +Two independent code paths fill that column: the importer builds FTS +entries inline, and :class:`SearchIndexerSync` rebuilds them from the +database. Each keeps its own registry of FTS columns and drops values +*silently* when the registries disagree (the historical credits / +sources / story_arcs bug documented in ``scribe/search/sync.py``), so +both are asserted here end to end. +""" + +from datetime import UTC, datetime +from http import HTTPStatus +from pathlib import Path +from threading import Event, Lock +from typing import Final, override +from urllib.parse import urlencode + +from django.contrib.auth.models import User +from django.test import Client +from loguru import logger + +from codex.librarian.mp_queue import LIBRARIAN_QUEUE +from codex.librarian.scribe.search.sync import SearchIndexerSync +from codex.models import Comic +from codex.models.comic import ComicFTS +from tests.importer.test_basic import PATH, BaseTestImporter, run_full_import + +_TEST_PASSWORD: Final = "test-pw-hush-S106" # noqa: S105 +# ``tests/files/comicbox-2-example.cbz`` carries two MetronInfo +# alternate names. The second has only a ``series.sort_name``. +_ALTERNATE_SERIES: Final = "Capitan Sciencia,Kapitän Wissenschaft" +# Comfortably past any index watermark the run just wrote. +_FUTURE: Final = datetime(2100, 1, 1, tzinfo=UTC) + + +class AlternateSeriesSearchTestCase(BaseTestImporter): + """Import the example fixture, then find it by its alternate names.""" + + @override + def setUp(self) -> None: + super().setUp() + run_full_import(self.importer) + self.comic = Comic.objects.get(path=PATH) + self._create_sibling() + user = User.objects.create_user( + username="alt_series_search_test", password=_TEST_PASSWORD + ) + self.client = Client() + self.client.force_login(user) + + def _create_sibling(self) -> None: + """ + Add a reprint-less comic to the same series. + + Without it every positive assertion below would also pass when + the search filter is dropped entirely — the single imported + comic is the whole result set either way. + """ + path = Path(PATH).with_name("sibling.cbz") + path.touch() + Comic.objects.create( + library=self.comic.library, + path=path, + issue_number=2, + name="Sibling", + publisher=self.comic.publisher, + imprint=self.comic.imprint, + series=self.comic.series, + volume=self.comic.volume, + size=1, + page_count=1, + ) + + @staticmethod + def _sync(*, rebuild: bool) -> None: + indexer = SearchIndexerSync(logger, LIBRARIAN_QUEUE, Lock(), Event()) + indexer.update_search_index(rebuild=rebuild) + + def _alternate_series_column(self) -> str: + return ComicFTS.objects.get(comic_id=self.comic.pk).alternate_series + + def _search_book_pks(self, query: str) -> list[int]: + """Browse the comic's series with a search query and return book pks.""" + params = urlencode({"page": 1, "q": query}) + url = f"/api/v4/browse/series/{self.comic.series.pk}?{params}" + response = self.client.get(url) + assert response.status_code == HTTPStatus.OK, response.content + return [book["pk"] for book in response.json()["data"]["books"]] + + def test_import_indexes_alternate_series(self) -> None: + """The importer writes reprint series names to their own FTS column.""" + assert self._alternate_series_column() == _ALTERNATE_SERIES + + def test_unqualified_search_finds_alternate_series(self) -> None: + """A bare token matches every FTS column, alternate names included.""" + assert self._search_book_pks("Wissenschaft") == [self.comic.pk] + assert self._search_book_pks("Sciencia") == [self.comic.pk] + assert self._search_book_pks("Zzyzx") == [] + + def test_field_alias_search_finds_alternate_series(self) -> None: + """Every registered alias resolves to the alternate_series column.""" + for alias in ("alternate_series", "alt_series", "alternateseries", "reprints"): + assert self._search_book_pks(f"{alias}:Sciencia") == [self.comic.pk], alias + # An unregistered column is silently discarded from the query + # rather than erroring, so the miss has to be asserted too. + assert self._search_book_pks(f"{alias}:Zzyzx") == [], alias + + def test_series_token_excludes_alternate_series(self) -> None: + """The canonical series column stays free of alternate names.""" + assert self._search_book_pks("series:Sciencia") == [] + assert self._search_book_pks("series:Captain") == [self.comic.pk] + + def test_search_index_sync_populates_alternate_series(self) -> None: + """A full index rebuild fills the column the importer filled.""" + self._sync(rebuild=True) + assert self._alternate_series_column() == _ALTERNATE_SERIES + assert self._search_book_pks("alt_series:Wissenschaft") == [self.comic.pk] + + def test_search_index_sync_updates_alternate_series(self) -> None: + """An incremental sync carries the column through ``bulk_update``.""" + self._sync(rebuild=True) + self.comic.reprints.clear() + # ``updated_at`` is auto_now, so ``.update()`` is the only way to + # push the comic past the index watermark without a save(). + Comic.objects.filter(pk=self.comic.pk).update(updated_at=_FUTURE) + self._sync(rebuild=False) + assert self._alternate_series_column() == "" diff --git a/tests/test_user_data_restore.py b/tests/test_user_data_restore.py index e9fd7bf51..ce2abb4f6 100644 --- a/tests/test_user_data_restore.py +++ b/tests/test_user_data_restore.py @@ -242,6 +242,43 @@ def test_round_trip_restores_settings_browser_show(self) -> None: assert browser.show.series is False assert browser.show.volumes is True + def test_round_trip_restores_reprints_filter(self) -> None: + """ + A reprints filter survives a rebuild that renumbers its PKs. + + The sidecar stores tag filters by name, and ``Reprint`` is the + only tag model whose name lives on ``series_name`` rather than + ``name`` — a dump that reached for ``name`` would raise. + """ + from codex.models.named import Reprint + from codex.models.settings import ( + SettingsBrowser, + SettingsBrowserFilters, + SettingsBrowserShow, + ) + + user = User.objects.create_user(username="alice", password=_TEST_PASSWORD) + show, _ = SettingsBrowserShow.objects.get_or_create() + browser = SettingsBrowser.objects.create(user=user, show=show) + reprint = Reprint.objects.create( + series_name="Kapitän Wissenschaft", language="de" + ) + SettingsBrowserFilters.objects.create(browser=browser, reprints=[reprint.pk]) + snapshot = self._snapshot_sidecar() + + # Rebuilding the library re-creates the tag row under a new PK, + # which is the whole reason the sidecar keys on names. + SettingsBrowser.objects.all().delete() + reprint.delete() + rebuilt = Reprint.objects.create( + series_name="Kapitän Wissenschaft", language="de" + ) + assert rebuilt.pk != reprint.pk + + restore(sidecar_path=snapshot) + filters = SettingsBrowserFilters.objects.get(browser__user=user) + assert filters.reprints == [rebuilt.pk] + def test_restore_is_idempotent(self) -> None: self._seed_main_db() snapshot = self._snapshot_sidecar() diff --git a/tests/test_user_data_store.py b/tests/test_user_data_store.py index 8b0e2a270..ca9b0aedb 100644 --- a/tests/test_user_data_store.py +++ b/tests/test_user_data_store.py @@ -114,6 +114,41 @@ def test_is_empty_tracks_inserts(self) -> None: self.store.upsert("users", ("username",), {"username": "alice"}) assert not self.store.is_empty() + def test_schema_gains_columns_added_by_a_release(self) -> None: + """ + A sidecar written before a new filter column still accepts it. + + ``schema.sql`` is all ``CREATE TABLE IF NOT EXISTS``, so an + upgraded install re-runs it against a file that predates the + column and nothing happens. Simulate that by dropping the + column from a freshly created sidecar and reconnecting. + """ + conn = self.store.connection() + conn.execute("ALTER TABLE settings_filters DROP COLUMN reprints") + columns = { + column["name"] + for column in conn.execute("PRAGMA table_info(settings_filters)") + } + assert "reprints" not in columns + self.store.close() + + store = SidecarStore(self.sidecar_path) + try: + store.upsert( + "settings_filters", + ("username", "client", "name"), + { + "username": "alice", + "client": "web", + "name": "", + "reprints": '["Kapitän Wissenschaft"]', + }, + ) + rows = store.fetchall("settings_filters") + finally: + store.close() + assert rows[0]["reprints"] == '["Kapitän Wissenschaft"]' + def test_upsert_empty_data_raises(self) -> None: """``upsert`` with no columns is a programming error.""" with pytest.raises(ValueError, match="no columns"): From 265f7fa44b840ee4c90e503f378d150ff0df6be2 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 14:29:59 -0700 Subject: [PATCH 46/82] update deps and devenv --- bin/fix-django.sh | 10 ++++++++++ bin/lint-django.sh | 10 ++++++++++ bun.lock | 44 ++++++++++++++++++++++++++++++++------------ cfg/django.mk | 4 ++-- package.json | 2 +- pyproject.toml | 2 +- uv.lock | 14 +++++++------- 7 files changed, 63 insertions(+), 23 deletions(-) create mode 100755 bin/fix-django.sh create mode 100755 bin/lint-django.sh diff --git a/bin/fix-django.sh b/bin/fix-django.sh new file mode 100755 index 000000000..828426977 --- /dev/null +++ b/bin/fix-django.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Fix django template lint errors +set -euxo pipefail + +mapfile -t templates < <(find . -mindepth 1 -name '.*' -prune -o -path '*/templates/*' -name '*.html' -print) +if [ ${#templates[@]} -eq 0 ]; then + echo "No django template files found. Nothing fixed." + exit 0 +fi +uv run --group lint djlint --reformat "${templates[@]}" diff --git a/bin/lint-django.sh b/bin/lint-django.sh new file mode 100755 index 000000000..32cefb014 --- /dev/null +++ b/bin/lint-django.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Lint django templates +set -euxo pipefail + +mapfile -t templates < <(find . -mindepth 1 -name '.*' -prune -o -path '*/templates/*' -name '*.html' -print) +if [ ${#templates[@]} -eq 0 ]; then + echo "No django template files found. Nothing linted." + exit 0 +fi +uv run --group lint djlint --lint "${templates[@]}" diff --git a/bun.lock b/bun.lock index 95ad979cc..5ef28b452 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", "@eslint/json": "^2.0.1", - "@fsouza/prettierd": "^0.28.0", + "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", "@vitest/eslint-plugin": "^1.6.24", @@ -60,13 +60,13 @@ "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], - "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], - "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], @@ -96,7 +96,7 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], - "@fsouza/prettierd": ["@fsouza/prettierd@0.28.0", "", { "dependencies": { "core_d": "^6.1.1", "prettier": "^3.8.3" }, "optionalDependencies": { "@babel/parser": "^7.29.7", "@typescript-eslint/typescript-estree": "^8.60.0" }, "bin": { "prettierd": "bin/prettierd" } }, "sha512-XkRkIp2en2Lcm7Qc+zqNm8ClI3RArLCBNG2A4M2xzTw43S/vYSEDQ4NcfW+6ZsUyQeoVqrFcMU5H1yAKCVkv6A=="], + "@fsouza/prettierd": ["@fsouza/prettierd@0.29.0", "", { "dependencies": { "core_d": "^6.1.1", "prettier": "^3.9.5" }, "optionalDependencies": { "@babel/parser": "^8.0.4", "@typescript-eslint/typescript-estree": "^8.64.0" }, "bin": { "prettierd": "bin/prettierd" } }, "sha512-UMBNFAVAP2Vfd8RrsH4bzchtyvqfzFp3COBYSTYmtrrU6fwK+JQLz9s6g9JNRVY6MDBWkUWNpPO63vPD/6KTFg=="], "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], @@ -172,19 +172,19 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.65.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.65.0", "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0" } }, "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.65.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.63.0", "", {}, "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.65.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.65.0", "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A=="], "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="], @@ -1110,9 +1110,9 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@fsouza/prettierd/prettier": ["prettier@3.9.5", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -1132,6 +1132,16 @@ "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], + + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + + "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], + + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -1222,6 +1232,14 @@ "@npmcli/promise-spawn/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "eslint-mdx/espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], @@ -1254,6 +1272,8 @@ "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "npm-pick-manifest/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], diff --git a/cfg/django.mk b/cfg/django.mk index e5605d5fd..2652dc38b 100644 --- a/cfg/django.mk +++ b/cfg/django.mk @@ -5,13 +5,13 @@ export DEVENV_DJANGO ## Fix django lint errors in templates ## @category Fix fix:: - uv run --group lint djlint --reformat **/templates/**/*.html + bin/fix-django.sh .PHONY: lint ## Lint django templates ## @category Lint lint:: - uv run --group lint djlint --lint **/templates/**/*.html + bin/lint-django.sh .PHONY: django-check ## Django check diff --git a/package.json b/package.json index b9ac59b29..2462b593f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", "@eslint/json": "^2.0.1", - "@fsouza/prettierd": "^0.28.0", + "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", "@vitest/eslint-plugin": "^1.6.24", diff --git a/pyproject.toml b/pyproject.toml index 48329a513..d826bb306 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "adrf~=0.1.12", "bidict~=0.23", "channels~=4.2", - "comicbox[pdf]~=4.6.0", + "comicbox[pdf]~=4.6.1", "cryptography>=48.0.0", "dateparser~=1.2", "django-allauth[socialaccount]~=65.13", diff --git a/uv.lock b/uv.lock index 310dfda23..ca8d5f7b2 100644 --- a/uv.lock +++ b/uv.lock @@ -653,7 +653,7 @@ requires-dist = [ { name = "adrf", specifier = "~=0.1.12" }, { name = "bidict", specifier = "~=0.23" }, { name = "channels", specifier = "~=4.2" }, - { name = "comicbox", extras = ["pdf"], specifier = "~=4.6.0" }, + { name = "comicbox", extras = ["pdf"], specifier = "~=4.6.1" }, { name = "cryptography", specifier = ">=48.0.0" }, { name = "dateparser", specifier = "~=1.2" }, { name = "django", specifier = "~=6.0" }, @@ -739,7 +739,7 @@ wheels = [ [[package]] name = "comicbox" -version = "4.6.0" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, @@ -776,9 +776,9 @@ dependencies = [ { name = "xmltodict" }, { name = "zipremove" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/99/df7bde0961d78e5ca8f5a5a06442cf11c44de180e839c799aa93478aa74a/comicbox-4.6.0.tar.gz", hash = "sha256:deceb8baaa382cb736dd9501aa2e6fec53024fa1c5b4a973e6d401a4e1ff65a2", size = 1013136, upload-time = "2026-07-26T00:41:14.701Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/05/1c2369de5b000c963fa119c2515d3fd1c64e55fa518eca2b691a86315c26/comicbox-4.6.1.tar.gz", hash = "sha256:4a4f50df0143c21853356a8defa767335434d870da6cdc6c56fef9a77c1f8c87", size = 1015362, upload-time = "2026-07-26T21:26:50.629Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/d8/199aa718d77f0eaaf71f7ef94c38eccfbc34cde420e1bb5a4064b507fb4b/comicbox-4.6.0-py3-none-any.whl", hash = "sha256:2298e6ff2fd39b5e814825405e80edd4ccff9f461bfcf4844f20f201f9c34c60", size = 341933, upload-time = "2026-07-26T00:41:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/cf/24/6acdbbc6c3ef7bb9023bbeb586d442649218f72177e3013b26a0b1dadc3e/comicbox-4.6.1-py3-none-any.whl", hash = "sha256:8d4c288f775063858620679dd0e4b61291ef14180fbc24767adf5b15684208f7", size = 342787, upload-time = "2026-07-26T21:26:49.016Z" }, ] [package.optional-dependencies] @@ -2654,14 +2654,14 @@ wheels = [ [[package]] name = "prompt-toolkit" -version = "3.0.52" +version = "3.0.53" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, ] [[package]] From 5d5b3bd6c033cd21f958733dad487a3e0f769bd5 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 15:10:46 -0700 Subject: [PATCH 47/82] bump news for comicbox 4.6.1 --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 45dea6e1e..96adfa131 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,6 +21,15 @@ border-radius: 128px; other known names (comicbox 4.6.0). - Fixes + - Identifier links point at the right page. Hand-tagged keys with a type + prefix like `series:178012`, Comic Vine long codes like `4050-160294`, and + `source:type:key` strings made broken issue-shaped URLs for every source + (comicbox 4.6.1). + - Every identifier URN in a comic's notes is imported; only the first was + read before (comicbox 4.6.1). + - Story arcs tagged online from Metron or Comic Vine get web links (comicbox + 4.6.1). These identifier fixes reach already-imported comics on their next + re-import, or use Force Update Tags. - User data backups no longer skip every saved filter set after an upgrade that adds a new filter. From 3b500ae25bbd4f165c37b824707698a9a18e0963 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 16:31:25 -0700 Subject: [PATCH 48/82] update devenv --- bin/manage.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/bin/manage.py b/bin/manage.py index ecd4f2a24..c9dfd2a2e 100755 --- a/bin/manage.py +++ b/bin/manage.py @@ -3,11 +3,52 @@ import os import sys +import tomllib +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +PRUNE = frozenset({"build", "dist", "docs", "node_modules", "test", "tests"}) + + +def _from_pyproject() -> str | None: + """Read an explicitly configured settings module.""" + pyproject = ROOT / "pyproject.toml" + if not pyproject.is_file(): + return None + with pyproject.open("rb") as f: + config = tomllib.load(f) + return config.get("tool", {}).get("devenv", {}).get("django-settings-module") + + +def _is_settings_package(pkg: Path) -> bool: + """Test if a dir is a package that holds a settings module or package.""" + if ( + not pkg.is_dir() + or pkg.name.startswith((".", "_")) + or pkg.name in PRUNE + or not (pkg / "__init__.py").is_file() + ): + return False + return (pkg / "settings.py").is_file() or ( + pkg / "settings" / "__init__.py" + ).is_file() + + +def _discover() -> str: + """Find the sole /settings module or package in the project root.""" + candidates = sorted(pkg.name for pkg in ROOT.iterdir() if _is_settings_package(pkg)) + if len(candidates) == 1: + return f"{candidates[0]}.settings" + reason = ( + f"Found {len(candidates)} django settings modules in {ROOT}: {candidates}. " + "Set [tool.devenv] django-settings-module in pyproject.toml." + ) + raise RuntimeError(reason) def main(): """Run the server.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "codex.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", _from_pyproject() or _discover()) try: from django.core.management import ( execute_from_command_line, From 646fe385e4fa6571e38679d0769fced1de09de77 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 17:45:34 -0700 Subject: [PATCH 49/82] fix cron double enqueue of telemeter task --- codex/librarian/cron/crond.py | 79 ++++++++++++++++----- codex/librarian/telemeter/scheduled_time.py | 18 +++++ codex/librarian/telemeter/telemeter.py | 33 ++++++--- 3 files changed, 102 insertions(+), 28 deletions(-) diff --git a/codex/librarian/cron/crond.py b/codex/librarian/cron/crond.py index b00838e56..35c466be7 100644 --- a/codex/librarian/cron/crond.py +++ b/codex/librarian/cron/crond.py @@ -1,23 +1,42 @@ """Perform maintenance tasks.""" +from collections.abc import Callable +from datetime import datetime from threading import Condition, Event -from time import sleep from types import MappingProxyType -from typing import override +from typing import NamedTuple, override from django.db import connections from django.utils import timezone as django_timezone +from loguru._logger import Logger from codex.librarian.scribe.janitor.scheduled_time import get_janitor_time from codex.librarian.scribe.janitor.tasks import JanitorNightlyTask -from codex.librarian.telemeter.scheduled_time import get_telemeter_time +from codex.librarian.telemeter.scheduled_time import ( + get_telemeter_time, + mark_telemeter_attempt, +) from codex.librarian.telemeter.tasks import TelemeterTask from codex.librarian.threads import NamedThread -_TASK_TIME_FUNCTION_MAP = MappingProxyType( + +class _CronJob(NamedTuple): + """When a recurring task runs next, and how to spend the slot it runs in.""" + + get_time: Callable[[Logger], datetime | None] + # Called just before the task is queued, for jobs whose next time is + # read back out of state the job itself writes. ``None`` for jobs + # whose schedule is pure clock arithmetic and so advances on its own. + claim: Callable[[], None] | None = None + + +_CRON_JOBS: MappingProxyType[type, _CronJob] = MappingProxyType( { - JanitorNightlyTask: get_janitor_time, - TelemeterTask: get_telemeter_time, + # Always the next midnight, so queueing it moves the schedule. + JanitorNightlyTask: _CronJob(get_janitor_time), + # Read out of the telemeter Timestamp, which the send writes on a + # thread this one never joins. See ``mark_telemeter_attempt``. + TelemeterTask: _CronJob(get_telemeter_time, mark_telemeter_attempt), } ) @@ -34,8 +53,8 @@ def __init__(self, *args, **kwargs) -> None: def _create_task_times(self) -> None: task_times = {} - for task_class, func in _TASK_TIME_FUNCTION_MAP.items(): - if dttm := func(self.log): + for task_class, job in _CRON_JOBS.items(): + if dttm := job.get_time(self.log): task_times[dttm] = task_class self._task_times = tuple(sorted(task_times.items())) @@ -51,14 +70,34 @@ def _get_timeout(self) -> int: self.log.debug(f"Next scheduled job at {next_time} in {delta}.") return max(0, int(delta.total_seconds())) - def _run_expired_jobs(self) -> None: + def _enqueue_job(self, task_class: type) -> None: + """Spend the job's slot before queueing it, never after.""" + claim = _CRON_JOBS[task_class].claim + if claim is not None: + claim() + self.librarian_queue.put(task_class()) + + def _run_expired_jobs(self, *, timed_out: bool) -> None: + """ + Queue every job whose scheduled time has arrived. + + ``timed_out`` means the wait ran its full course instead of being + cut short by ``end_timeout``, so the job at the head of the + schedule is due even if the clock reads a hair short of its time + — ``_get_timeout`` truncates the delta to whole seconds, so the + wait always ends slightly early. Missing that job cost more than + a late run: ``_create_task_times`` would immediately push the + nightly janitor out to the *following* midnight and skip a night. + That is what ``sleep(2) # fix time rounding problems`` was for. + """ now = django_timezone.now() + if timed_out and self._task_times: + now = max(now, self._task_times[0][0]) for dttm, task_class in self._task_times: - if dttm < now: - self.librarian_queue.put(task_class()) - else: + if dttm > now: # Times are always ordered so stop checking at the first future job. break + self._enqueue_job(task_class) @override def run(self) -> None: @@ -66,10 +105,8 @@ def run(self) -> None: try: self.run_start() with self._cond: + self._create_task_times() while not self._stop_event.is_set(): - self._run_expired_jobs() - self._create_task_times() - sleep(2) # try to fix double jobs timeout = self._get_timeout() # Idle gaps between scheduled tasks are typically # hours-to-days. Release the conn so the next @@ -78,10 +115,18 @@ def run(self) -> None: # whole window. Reopen on the next query is # ~5-20 ms, invisible against the wait. connections.close_all() - self._cond.wait(timeout=timeout) + # ``Condition.wait`` returns False only when the whole + # timeout elapsed; True means ``end_timeout`` notified. + timed_out = not self._cond.wait(timeout=timeout) if self._stop_event.is_set(): break - sleep(2) # fix time rounding problems + self._run_expired_jobs(timed_out=timed_out) + # Recompute *after* queueing, so the new schedule sees + # the slots those jobs just claimed. Recomputing first + # re-read the telemeter's unchanged send time and put + # the same task on the queue again, once per pass, for + # as long as the offloaded send took to finish. + self._create_task_times() except Exception: self.log.exception(f"In {self.__class__.__name__}") self.log.debug(f"Stopped {self.__class__.__name__}.") diff --git a/codex/librarian/telemeter/scheduled_time.py b/codex/librarian/telemeter/scheduled_time.py index 5721f3882..97924a959 100644 --- a/codex/librarian/telemeter/scheduled_time.py +++ b/codex/librarian/telemeter/scheduled_time.py @@ -49,6 +49,24 @@ def _get_scheduled_time(ts) -> datetime | None: return telemeter_time +def mark_telemeter_attempt() -> None: + """ + Spend this week's send slot. + + :func:`_get_scheduled_time` decides whether to schedule by comparing + the week's slot against ``updated_at``, so ``updated_at`` has to move + the moment the task is *queued*. The send itself records its own + attempt, but it runs on a throwaway thread ``BookmarkThread`` spawns + and never joins, and it only gets to that write after roughly thirty + count queries plus up to a five second network timeout. Waiting for + it left the slot unspent, so every pass of the cron loop recomputed + the same overdue time and queued the task again. + """ + ts = get_telemeter_timestamp() + # ``updated_at`` is ``auto_now``, so a plain save is the touch. + ts.save() + + def get_telemeter_time(log: Logger) -> datetime | None: """Get the time to send telemetry.""" # Should we schedule telemeter at all? diff --git a/codex/librarian/telemeter/telemeter.py b/codex/librarian/telemeter/telemeter.py index a780466b9..94094905e 100644 --- a/codex/librarian/telemeter/telemeter.py +++ b/codex/librarian/telemeter/telemeter.py @@ -2,7 +2,6 @@ import json from base64 import a85decode, b64encode -from http import HTTPStatus from lzma import compress from os import environ from typing import Final @@ -89,12 +88,12 @@ def _post_stats(data) -> None: request = Request( # noqa: S310 _POST, data=compressed_data, headers=_new_headers(), method="POST" ) - # urlopen raises HTTPError on 4xx & 5xx, so reaching here is a success. - # http.client's response has no raise_for_status(); that was requests'. - with urlopen(request, timeout=_TIMEOUT) as response: # noqa: S310 - if response.status >= HTTPStatus.BAD_REQUEST: - reason = f"Stats server returned {response.status}" - raise ValueError(reason) + # urlopen raises HTTPError on any status outside 2xx, and follows 3xx, so + # returning from here is a success. Checking response.status ourselves + # would be dead code; http.client's response has no raise_for_status(), + # that was requests'. The server answers 202: queued, not yet stored. + with urlopen(request, timeout=_TIMEOUT): # noqa: S310 + pass def _send_telemetry(uuid) -> None: @@ -118,10 +117,22 @@ def send_telemetry(log) -> None: try: _send_telemetry(ts.value) except Exception as exc: - # Not debug: a transport bug here went unnoticed for months - # because every failure was invisible at the default log level. - log.warning(f"Failed to send anonymous stats: {exc}") - # update updated_at, even on failure to prevent rapid rescheudling. + # This was briefly a warning, because a transport bug went + # unnoticed for months while the stats server answered 200 to + # everything. The server reports honest statuses now, so its + # operator sees failures in the access log and this install has + # no reason to say anything: debug is invisible at the default + # log level, and a stats server is never the user's problem. + # repr, not str: str(TimeoutError()) is empty, and an HTTPError's + # repr names the status code. Neither can carry a response body. + log.debug(f"Failed to send anonymous stats: {exc!r}") + # Record the attempt whether or not it succeeded: retrying a + # failed send would turn a stats outage into a stampede. The cron + # thread already claimed this week's slot before queueing the + # task (``mark_telemeter_attempt``) — it cannot wait for this + # write, which happens on a thread it never joins — so this is a + # backstop for any other caller, and it leaves the recorded time + # honest about when the send actually finished. ts.save() except Exception as exc: log.debug(f"Failed to get or set telemeter timestamp: {exc}") From c014acb1b8e2c5d38911def4a904357f0271f505 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 17:45:58 -0700 Subject: [PATCH 50/82] test crond double enqueu fix --- tests/test_crond.py | 204 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 tests/test_crond.py diff --git a/tests/test_crond.py b/tests/test_crond.py new file mode 100644 index 000000000..cfa7f3aed --- /dev/null +++ b/tests/test_crond.py @@ -0,0 +1,204 @@ +""" +Cron scheduling invariants. + +:class:`CronThread` queues due jobs and *then* recomputes the schedule. +For the telemeter that schedule lives in the database and is written by +the send itself, which ``BookmarkThread`` runs on a daemon thread nobody +joins — so the recompute has to see a slot the scheduler spent, not one +the send has not gotten around to writing yet. +""" + +from __future__ import annotations + +from datetime import timedelta +from queue import SimpleQueue +from threading import Lock +from typing import TYPE_CHECKING +from uuid import UUID + +from django.test import TestCase +from django.utils import timezone +from loguru import logger as _loguru_logger + +from codex.choices.admin import AdminFlagChoices +from codex.librarian.cron.crond import CronThread +from codex.librarian.scribe.janitor.tasks import JanitorNightlyTask +from codex.librarian.telemeter.scheduled_time import ( + get_telemeter_time, + mark_telemeter_attempt, +) +from codex.librarian.telemeter.tasks import TelemeterTask +from codex.librarian.telemeter.telemeter import get_telemeter_timestamp +from codex.models.admin import AdminFlag, Timestamp +from codex.startup import init_admin_flags + +if TYPE_CHECKING: + from loguru._logger import Logger + +# The send time is the install uuid scaled across the week, so int=0 puts +# it at Monday 00:00 UTC — always in the past, so the job is always due. +_START_OF_WEEK_UUID = str(UUID(int=0)) +_LONG_AGO = timedelta(days=30) +# ``loguru.logger`` *is* a ``loguru._logger.Logger`` at runtime; loguru's +# stubs declare a second, unrelated ``loguru.Logger`` for the singleton, +# and that is not the type the librarian annotates its ``log`` params with. +_LOG: Logger = _loguru_logger # pyright: ignore[reportAssignmentType] # ty: ignore[invalid-assignment] + + +def _make_telemeter_overdue() -> Timestamp: + """Make the telemeter look installed long ago and never sent.""" + init_admin_flags() + AdminFlag.objects.filter(key=AdminFlagChoices.SEND_TELEMETRY.value).update(on=True) + ts = get_telemeter_timestamp() + long_ago = timezone.now() - _LONG_AGO + # A queryset update writes the columns auto_now/auto_now_add own. + Timestamp.objects.filter(pk=ts.pk).update( + value=_START_OF_WEEK_UUID, created_at=long_ago, updated_at=long_ago + ) + ts.refresh_from_db() + return ts + + +def _new_cron_thread() -> tuple[CronThread, SimpleQueue]: + """Build a cron thread wired to a queue the test can read, never started.""" + queue = SimpleQueue() + return CronThread(_LOG, queue, Lock()), queue + + +def _drain(queue: SimpleQueue) -> list: + """Everything the loop put on the librarian queue.""" + tasks = [] + while not queue.empty(): + tasks.append(queue.get_nowait()) + return tasks + + +def _cron_pass(thread: CronThread) -> None: + """ + One trip around the cron loop, minus the wait. + + ``timed_out=True`` is the worst case for the job at the head of the + schedule: it is the value the loop uses when it has waited out the + full timeout, so nothing about a short clock reading can excuse a + second enqueue. + """ + thread._run_expired_jobs(timed_out=True) # noqa: SLF001 + thread._create_task_times() # noqa: SLF001 + + +def _scheduled_classes(thread: CronThread) -> set[type]: + """Collect the task classes on the thread's current schedule.""" + return {task_class for _, task_class in thread._task_times} # noqa: SLF001 + + +class CronScheduleTestCase(TestCase): + """The loop must queue an overdue job exactly once.""" + + def _start(self) -> tuple[CronThread, SimpleQueue]: + """Prime a cron thread on an overdue telemeter, the way ``run`` primes it.""" + _make_telemeter_overdue() + thread, queue = _new_cron_thread() + thread._create_task_times() # noqa: SLF001 + return thread, queue + + def test_telemeter_is_queued_once_while_the_send_is_in_flight(self) -> None: + """ + The reported bug: three passes, no send, one task. + + Never calling ``send_telemetry`` is the point — it stands in for + the offloaded send still collecting counts or blocked on its five + second network timeout, having written nothing back yet. + """ + thread, queue = self._start() + for _ in range(3): + _cron_pass(thread) + queued = [task for task in _drain(queue) if isinstance(task, TelemeterTask)] + assert len(queued) == 1, ( + f"Queued {len(queued)} TelemeterTasks; the send never ran, so the " + "loop re-read an unclaimed send time and enqueued it again." + ) + + def test_queueing_the_telemeter_takes_it_off_the_schedule(self) -> None: + """A claimed slot must be gone from the very next recompute.""" + thread, _ = self._start() + assert TelemeterTask in _scheduled_classes(thread) + _cron_pass(thread) + assert TelemeterTask not in _scheduled_classes(thread) + # Not merely absent from the tuple — unschedulable at the source, + # which is what keeps ``_get_timeout`` off zero and the loop from + # spinning. + assert get_telemeter_time(_LOG) is None + + def test_next_week_is_scheduled_again(self) -> None: + """Spending this week's slot must not retire the job.""" + thread, _ = self._start() + _cron_pass(thread) + assert get_telemeter_time(_LOG) is None + # Age the claim past the start of this week: the slot moves with + # the calendar, the claim does not. + Timestamp.objects.filter(key=Timestamp.Choices.TELEMETER_SENT.value).update( + updated_at=timezone.now() - timedelta(days=8) + ) + assert get_telemeter_time(_LOG) is not None + + +class CronWakeupTestCase(TestCase): + """Which jobs a wakeup is allowed to run.""" + + @staticmethod + def _janitor_a_moment_out() -> tuple[CronThread, SimpleQueue]: + """Put the nightly janitor just past the clock, as truncation does.""" + thread, queue = _new_cron_thread() + soon = timezone.now() + timedelta(milliseconds=500) + thread._task_times = ((soon, JanitorNightlyTask),) # noqa: SLF001 + return thread, queue + + def test_a_full_timeout_runs_the_job_it_waited_for(self) -> None: + """ + ``_get_timeout`` truncates to whole seconds, so the wait ends early. + + Declining the job then would let ``_create_task_times`` push the + nightly janitor out to the following midnight and skip a night. + """ + thread, queue = self._janitor_a_moment_out() + thread._run_expired_jobs(timed_out=True) # noqa: SLF001 + assert not queue.empty() + assert isinstance(queue.get_nowait(), JanitorNightlyTask) + + def test_being_woken_early_runs_nothing_early(self) -> None: + """``end_timeout`` is a nudge to reschedule, not a licence to run.""" + thread, queue = self._janitor_a_moment_out() + thread._run_expired_jobs(timed_out=False) # noqa: SLF001 + assert queue.empty() + + +class TelemeterScheduleTestCase(TestCase): + """What ``get_telemeter_time`` will and will not schedule.""" + + def test_an_overdue_send_is_scheduled_in_the_past(self) -> None: + """A missed slot stays due rather than rolling over.""" + _make_telemeter_overdue() + dttm = get_telemeter_time(_LOG) + assert dttm is not None + assert dttm <= timezone.now() + + def test_disabled_telemetry_is_not_scheduled(self) -> None: + """The admin flag is checked before anything else.""" + _make_telemeter_overdue() + AdminFlag.objects.filter(key=AdminFlagChoices.SEND_TELEMETRY.value).update( + on=False + ) + assert get_telemeter_time(_LOG) is None + + def test_a_fresh_install_waits_a_day(self) -> None: + """New installs don't report on their first day.""" + ts = _make_telemeter_overdue() + Timestamp.objects.filter(pk=ts.pk).update(created_at=timezone.now()) + assert get_telemeter_time(_LOG) is None + + def test_claiming_the_slot_unschedules_it(self) -> None: + """The unit the cron thread relies on, independent of the loop.""" + _make_telemeter_overdue() + assert get_telemeter_time(_LOG) is not None + mark_telemeter_attempt() + assert get_telemeter_time(_LOG) is None From 7c65e0d889c07ea0a811e78b51657e7f64599625 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 17:46:21 -0700 Subject: [PATCH 51/82] test telmemeter logging fixes --- tests/test_telemeter.py | 89 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/tests/test_telemeter.py b/tests/test_telemeter.py index 76a7f0beb..37a9dba3e 100644 --- a/tests/test_telemeter.py +++ b/tests/test_telemeter.py @@ -1,19 +1,22 @@ """Tests for the anonymous stats payload and how it is transmitted.""" import json +from datetime import timedelta from email.message import Message from lzma import decompress from typing import override from unittest.mock import patch -from urllib.error import HTTPError +from urllib.error import HTTPError, URLError from urllib.parse import urlsplit import pytest from django.http import QueryDict from django.test import TestCase +from django.utils import timezone from codex.librarian.telemeter import telemeter from codex.librarian.telemeter.stats import CodexStats +from codex.models.admin import Timestamp from codex.serializers.admin.stats import ( AdminStatsRequestSerializer, StatsSerializer, @@ -40,9 +43,6 @@ class _FakeResponse: """Stand in for http.client.HTTPResponse.""" - def __init__(self, status: int = 200) -> None: - self.status = status - def __enter__(self): return self @@ -50,9 +50,46 @@ def __exit__(self, *_args) -> bool: return False +class _DebugOnlyLog: + """ + A log that can only be called at debug. + + Failing to reach the stats server is never the user's problem, so it is + reported at debug and nowhere else. Exposing only that method makes a + revert to warning() an AttributeError rather than a silent regression. + """ + + def __init__(self) -> None: + self.logged: list[str] = [] + + def debug(self, msg) -> None: + self.logged.append(msg) + + def sent_stats_quietly(self, *fragments: str) -> bool: + """Whether one debug line reports a failed send, mentioning fragments.""" + return any( + "Failed to send anonymous stats" in msg + and all(fragment in msg for fragment in fragments) + for msg in self.logged + ) + + class TelemeterTransportTestCase(TestCase): """The url and headers the telemeter actually puts on the wire.""" + @override + def setUp(self) -> None: + """ + Seed the admin flags the send path reads. + + Without the SEND_TELEMETRY row, _send_telemetry raises DoesNotExist at + the flag lookup and never reaches the mocked urlopen, so a transport + test would pass without exercising any transport. + """ + from codex.startup import init_admin_flags + + init_admin_flags() + def test_posts_to_wire_version_two(self) -> None: """Codex 2.2.3 and later report the expanded payload as version 2.""" assert telemeter._VERSION == "2" # noqa: SLF001 @@ -95,19 +132,41 @@ def test_http_error_reaches_the_caller(self) -> None: ): telemeter._post_stats({"uuid": "abc"}) # noqa: SLF001 - def test_send_telemetry_survives_a_failed_post(self) -> None: - """A dead stats server must never break the install.""" - logged = [] - - class _Log: - @staticmethod - def warning(msg) -> None: - logged.append(msg) - + def test_a_rejected_post_is_survived_quietly(self) -> None: + """A stats server that refuses the post must not break or alarm.""" + log = _DebugOnlyLog() error = HTTPError(telemeter._POST, 500, "boom", Message(), None) # noqa: SLF001 with patch.object(telemeter, "urlopen", side_effect=error): - telemeter.send_telemetry(_Log()) - assert logged + telemeter.send_telemetry(log) + # The status code, not merely some line: the outer handler logs at + # debug too, so presence alone would pass even if this one regressed + # to warning and blew up on the log above. + assert log.sent_stats_quietly("500") + + def test_an_unreachable_server_is_survived_quietly(self) -> None: + """Nor is a timeout or a dns failure the user's problem.""" + log = _DebugOnlyLog() + with patch.object(telemeter, "urlopen", side_effect=URLError(TimeoutError())): + telemeter.send_telemetry(log) + assert log.sent_stats_quietly("TimeoutError") + + def test_a_failed_send_still_waits_a_week(self) -> None: + """ + The timestamp marks the attempt, not the success. + + Retrying a failed send would turn a stats outage into a stream of + requests from every install at once, so the week's slot is spent + either way. + """ + ts = telemeter.get_telemeter_timestamp() + stale = timezone.now() - timedelta(days=30) + # A queryset update writes the column that auto_now would overwrite. + Timestamp.objects.filter(pk=ts.pk).update(updated_at=stale) + error = HTTPError(telemeter._POST, 503, "down", Message(), None) # noqa: SLF001 + with patch.object(telemeter, "urlopen", side_effect=error): + telemeter.send_telemetry(_DebugOnlyLog()) + ts.refresh_from_db() + assert ts.updated_at > stale class TelemeterStatsTestCase(TestCase): From 4bec196b34cf5af2880abe92d31054db9b588c60 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sun, 26 Jul 2026 19:45:23 -0700 Subject: [PATCH 52/82] feat(api): reintroduce Swagger UI at /api/v4/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The interactive docs were lost in the v4 cutover (9b8314d3b, v2.0.0) when codex/urls/api/v3.py was deleted; only the raw schema route survived. Mount SpectacularSwaggerSplitView at the v4 root, gated on FEATURES.swagger — the flag that until now only switched a CSP overlay for routes that no longer existed. The split view keeps its init javascript in a second same-origin request, so no inline diff --git a/frontend/src/components/reader/toolbars/top/reader-toolbar-top.vue b/frontend/src/components/reader/toolbars/top/reader-toolbar-top.vue index 7f3d13b90..799fc9c94 100644 --- a/frontend/src/components/reader/toolbars/top/reader-toolbar-top.vue +++ b/frontend/src/components/reader/toolbars/top/reader-toolbar-top.vue @@ -136,7 +136,7 @@ export default { book.childCount = 0; // Browser card items carry an ``ids`` array; the reader's book only // has a ``pk``. Without ``ids`` the online-tag dialog's ``idTaggable`` - // check fails and the tag-by-id tab never appears for a single comic. + // check fails and its ID inputs never appear for a single comic. book.ids = book.pk == undefined ? [] : [book.pk]; return book; }, diff --git a/frontend/src/stores/online-tag.js b/frontend/src/stores/online-tag.js index 2d70903d8..56374e704 100644 --- a/frontend/src/stores/online-tag.js +++ b/frontend/src/stores/online-tag.js @@ -33,7 +33,11 @@ export const useOnlineTagStore = defineStore("onlineTag", { deleteOriginal, mergeAllSources, rename, + ids, }) { + // ``ids`` pins an issue id per source ({metron: "metron:123"}, single + // comic only): those sources are fetched by id and the rest are + // searched, in one lookup, so a mixed run still merges. const response = await HTTP.post("/admin/tag-sessions/start", { collection, pks: pks.map(String), @@ -43,32 +47,11 @@ export const useOnlineTagStore = defineStore("onlineTag", { deleteOriginal, mergeAllSources, rename, + ids: ids || {}, }); this.activeSessionId = response.data.sessionId; return response.data; }, - async tagById({ - collection, - pk, - identifier, - identifiers, - source, - mergeAllSources, - rename, - }) { - // Tag one comic by a known Metron / Comic Vine issue id, skipping - // search. Returns the resolved { source, id } the server queued. - const response = await HTTP.post("/admin/tag-by-id", { - collection, - pk: String(pk), - identifier, - identifiers: identifiers || [identifier], - source: source || "", - mergeAllSources, - rename, - }); - return response.data; - }, async discoverSession() { const response = await HTTP.get("/admin/tag-sessions"); const sid = response.data.sessionId; diff --git a/frontend/tests/unit/launcher-dialog.test.js b/frontend/tests/unit/launcher-dialog.test.js index 1475541bd..1e3456b48 100644 --- a/frontend/tests/unit/launcher-dialog.test.js +++ b/frontend/tests/unit/launcher-dialog.test.js @@ -1,10 +1,11 @@ /* * Tests for the unified Online Tagging dialog. * - * One dialog with two expansion panels: "Search" (filename search + match) - * and "By ID" (exact issue id). Only one panel opens at a time; the action - * button reads "Search" or "Tag" depending on the open panel; the By ID panel - * only exists when the selection is a single, id-taggable comic. + * One pane: sources, optional per-source pinned ids, and the search controls. + * A source with a pinned id is fetched by that id; the rest are searched, in + * one session — so the action button reads "Search", "Tag by ID", or + * "Tag by ID & Search" depending on how many selected sources are pinned. The + * id inputs only appear when the selection is a single, id-taggable comic. */ import { createTestingPinia } from "@pinia/testing"; import { mount } from "@vue/test-utils"; @@ -46,39 +47,71 @@ function mountDialog({ describe("OnlineTagLauncherDialog", () => { describe("action button", () => { - test("reads Search on the search panel and Tag on the By ID panel", async () => { + test("reads Search when nothing is pinned", async () => { const { wrapper } = mountDialog(); + wrapper.vm.sources = ["metron", "comicvine"]; + await wrapper.vm.$nextTick(); + expect(wrapper.vm.actionLabel).toBe("Search"); + }); + + test("reads Tag by ID when every selected source is pinned", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.sources = ["metron"]; + wrapper.vm.idInputs = ["metron:12345"]; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.actionLabel).toBe("Tag by ID"); + }); + + test("reads Tag by ID & Search when only some sources are pinned", async () => { + const { wrapper } = mountDialog(); - wrapper.vm.activeTab = "byId"; + wrapper.vm.sources = ["metron", "comicvine"]; + wrapper.vm.idInputs = ["metron:12345"]; await wrapper.vm.$nextTick(); - expect(wrapper.vm.actionLabel).toBe("Tag"); + expect(wrapper.vm.actionLabel).toBe("Tag by ID & Search"); }); test("search is disabled without a selected source", async () => { const { wrapper } = mountDialog(); - wrapper.vm.activeTab = "search"; wrapper.vm.sources = []; await wrapper.vm.$nextTick(); expect(wrapper.vm.actionDisabled).toBe(true); }); - test("By ID is disabled until an identifier is entered", async () => { + test("an unpinned source is still searchable, so submit stays enabled", async () => { const { wrapper } = mountDialog(); - wrapper.vm.activeTab = "byId"; + wrapper.vm.sources = ["metron"]; await wrapper.vm.$nextTick(); - expect(wrapper.vm.actionDisabled).toBe(true); - wrapper.vm.idInputs = ["metron:12345"]; - await wrapper.vm.$nextTick(); expect(wrapper.vm.actionDisabled).toBe(false); }); + test("an id for an unconfigured source blocks submit", async () => { + const { wrapper } = mountDialog({ + taggingDefaults: { + hasMetronCredentials: true, + hasComicvineCredentials: false, + metronKeySet: true, + }, + }); + + wrapper.vm.sources = ["metron"]; + wrapper.vm.idInputs = ["comicvine:4000-456"]; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.pinnedIds).toEqual({}); + expect(wrapper.vm.unconfiguredIdWarning).toContain("Comic Vine"); + expect(wrapper.vm.actionDisabled).toBe(true); + }); + test("disables the action when no source credentials are configured", () => { const { wrapper } = mountDialog({ taggingDefaults: { @@ -125,7 +158,7 @@ describe("OnlineTagLauncherDialog", () => { expect(wrapper.find(".credentialWarning").exists()).toBe(false); }); - test("tracks the By ID tab's id source", async () => { + test("returns once a pinned Metron id auto-selects Metron", async () => { const { wrapper } = mountDialog({ taggingDefaults: { hasMetronCredentials: true, @@ -133,7 +166,7 @@ describe("OnlineTagLauncherDialog", () => { metronKeySet: false, }, }); - wrapper.vm.activeTab = "byId"; + wrapper.vm.sources = ["comicvine"]; wrapper.vm.idInputs = ["comicvine:4000-12345"]; await wrapper.vm.$nextTick(); expect(wrapper.vm.metronLegacyCredentials).toBe(false); @@ -166,7 +199,7 @@ describe("OnlineTagLauncherDialog", () => { }); }); - describe("idTaggable (whether the By ID panel exists)", () => { + describe("idTaggable (whether the id inputs exist)", () => { test("true for a single comic", () => { const { wrapper } = mountDialog(); expect(wrapper.vm.idTaggable).toBe(true); @@ -187,11 +220,10 @@ describe("OnlineTagLauncherDialog", () => { }); }); - describe("Search panel", () => { - test("submit starts a tagging session", async () => { + describe("submit", () => { + test("an unpinned session starts a plain search", async () => { const { wrapper, onlineTagStore } = mountDialog(); - wrapper.vm.activeTab = "search"; wrapper.vm.sources = ["metron"]; await wrapper.vm.$nextTick(); await wrapper.vm.submit(); @@ -203,9 +235,9 @@ describe("OnlineTagLauncherDialog", () => { sources: ["metron"], mode: "auto", promptsMode: "ask", + ids: {}, }), ); - expect(onlineTagStore.tagById).not.toHaveBeenCalled(); }); test("submits sources in the admin-configured priority order", async () => { @@ -217,7 +249,6 @@ describe("OnlineTagLauncherDialog", () => { defaultSources: ["comicvine", "metron"], }; - wrapper.vm.activeTab = "search"; // Checkbox v-model recorded the clicks metron-first... wrapper.vm.sources = ["metron", "comicvine"]; await wrapper.vm.$nextTick(); @@ -228,30 +259,156 @@ describe("OnlineTagLauncherDialog", () => { expect.objectContaining({ sources: ["comicvine", "metron"] }), ); }); - }); - describe("By ID panel", () => { - test("submit tags by id", async () => { + test("a mixed session sends the pinned id and both sources", async () => { + const { wrapper, onlineTagStore } = mountDialog(); + + wrapper.vm.sources = ["metron", "comicvine"]; + wrapper.vm.idInputs = ["metron:12345"]; + await wrapper.vm.$nextTick(); + await wrapper.vm.submit(); + + expect(onlineTagStore.startSession).toHaveBeenCalledWith( + expect.objectContaining({ + ids: { metron: "metron:12345" }, + sources: ["metron", "comicvine"], + }), + ); + expect(wrapper.emitted("started")).toBeTruthy(); + }); + + test("a fully pinned session still goes through startSession", async () => { const { wrapper, onlineTagStore } = mountDialog(); - onlineTagStore.tagById.mockResolvedValue({ source: "metron", id: 12345 }); - wrapper.vm.activeTab = "byId"; + wrapper.vm.sources = ["metron"]; wrapper.vm.idInputs = ["metron:12345"]; await wrapper.vm.$nextTick(); await wrapper.vm.submit(); - expect(onlineTagStore.tagById).toHaveBeenCalledWith({ - collection: "comics", - pk: 7, - identifier: "metron:12345", - identifiers: ["metron:12345"], - source: "", - mergeAllSources: false, - rename: false, + expect(onlineTagStore.startSession).toHaveBeenCalledWith( + expect.objectContaining({ ids: { metron: "metron:12345" } }), + ); + expect(wrapper.emitted("started")).toBeTruthy(); + }); + + test("both sources pinned sends both ids", async () => { + const { wrapper, onlineTagStore } = mountDialog(); + + wrapper.vm.sources = ["metron", "comicvine"]; + wrapper.vm.idInputs = ["metron:12345", "comicvine:4000-456"]; + await wrapper.vm.$nextTick(); + await wrapper.vm.submit(); + + expect(onlineTagStore.startSession).toHaveBeenCalledWith( + expect.objectContaining({ + ids: { metron: "metron:12345", comicvine: "comicvine:4000-456" }, + }), + ); + }); + + test("a stale id can't leak from a selection with no id inputs", async () => { + const { wrapper, onlineTagStore } = mountDialog({ + book: { collection: "comics", ids: [7, 8], childCount: 2 }, }); - expect(onlineTagStore.startSession).not.toHaveBeenCalled(); + + wrapper.vm.sources = ["metron"]; + wrapper.vm.idInputs = ["metron:12345"]; + await wrapper.vm.$nextTick(); + await wrapper.vm.submit(); + + expect(wrapper.vm.idTaggable).toBe(false); + expect(onlineTagStore.startSession).toHaveBeenCalledWith( + expect.objectContaining({ ids: {} }), + ); }); + }); + describe("pinned ids", () => { + test("pinning a source that isn't selected selects it", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.sources = ["comicvine"]; + await wrapper.vm.$nextTick(); + wrapper.vm.idInputs = ["metron:12345"]; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.sources).toContain("metron"); + }); + + test("clicking an existing-id chip selects its source", async () => { + const { wrapper } = mountDialog({ + identifiers: [ + { + source: "metron", + type: "comic", + code: "12345", + displayName: "Metron", + }, + ], + }); + + wrapper.vm.sources = ["comicvine"]; + await wrapper.vm.$nextTick(); + wrapper.vm.addId(wrapper.vm.existingOptions[0].value); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.sources).toContain("metron"); + expect(wrapper.vm.pinnedIds).toEqual({ metron: "metron:12345" }); + }); + + test("a bare number pins once its source is picked", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.idInputs = ["12345"]; + await wrapper.vm.$nextTick(); + expect(wrapper.vm.pinnedIds).toEqual({}); + + wrapper.vm.sourceChoice = "metron"; + await wrapper.vm.$nextTick(); + expect(wrapper.vm.pinnedIds).toEqual({ metron: "12345" }); + }); + + test("only the first id per source is used", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.idInputs = ["metron:12345", "metron:99999"]; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.pinnedIds).toEqual({ metron: "metron:12345" }); + expect(wrapper.vm.duplicateIdWarning).toContain("Metron"); + }); + }); + + describe("search controls with every source pinned", () => { + test("match mode and prompts are disabled and explain why", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.sources = ["metron"]; + wrapper.vm.idInputs = ["metron:12345"]; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.searchControlsDisabled).toBe(true); + expect(wrapper.vm.matchModeHint).toBe( + "Not used when all sources are tagged by ID.", + ); + expect(wrapper.vm.promptsModeHint).toBe( + "Not used when all sources are tagged by ID.", + ); + }); + + test("they come back for a mixed session", async () => { + const { wrapper } = mountDialog(); + + wrapper.vm.sources = ["metron", "comicvine"]; + wrapper.vm.idInputs = ["metron:12345"]; + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.searchControlsDisabled).toBe(false); + expect(wrapper.vm.promptsModeHint).toContain("Pauses on ambiguous"); + }); + }); + + describe("id inputs", () => { test("offers the comic's Metron / Comic Vine ids as options", () => { const { wrapper } = mountDialog({ identifiers: [ @@ -296,7 +453,6 @@ describe("OnlineTagLauncherDialog", () => { test("a bare integer with both sources requires a source pick", async () => { const { wrapper } = mountDialog(); - wrapper.vm.activeTab = "byId"; wrapper.vm.idInputs = ["12345"]; await wrapper.vm.$nextTick(); @@ -313,48 +469,18 @@ describe("OnlineTagLauncherDialog", () => { }, }); - wrapper.vm.activeTab = "byId"; wrapper.vm.idInputs = ["12345"]; await wrapper.vm.$nextTick(); expect(wrapper.vm.needsSourceSelect).toBe(false); expect(wrapper.vm.needsSourcePick).toBe(false); + expect(wrapper.vm.pinnedIds).toEqual({ metron: "12345" }); expect(wrapper.vm.actionDisabled).toBe(false); }); - test("selecting an existing id fills the field and submits it", async () => { - const { wrapper, onlineTagStore } = mountDialog({ - identifiers: [ - { - source: "metron", - type: "comic", - code: "12345", - displayName: "Metron", - }, - ], - }); - onlineTagStore.tagById.mockResolvedValue({ source: "metron", id: 12345 }); - - wrapper.vm.activeTab = "byId"; - // The chip's @click adds the option value to the chips input. - wrapper.vm.addId(wrapper.vm.existingOptions[0].value); - await wrapper.vm.submit(); - - expect(onlineTagStore.tagById).toHaveBeenCalledWith({ - collection: "comics", - pk: 7, - identifier: "metron:12345", - identifiers: ["metron:12345"], - source: "", - mergeAllSources: false, - rename: false, - }); - }); - test("only the bare-number chip triggers the source select", async () => { const { wrapper } = mountDialog(); - wrapper.vm.activeTab = "byId"; wrapper.vm.idInputs = ["https://metron.cloud/issue/12345/"]; await wrapper.vm.$nextTick(); // A URL self-identifies, so no manual source needed. @@ -365,41 +491,17 @@ describe("OnlineTagLauncherDialog", () => { expect(wrapper.vm.needsSourceSelect).toBe(true); }); - test("merge is gated on two identifiers in the input", async () => { + test("a pasted pair splits into one chip per source", async () => { const { wrapper } = mountDialog(); - wrapper.vm.activeTab = "byId"; - wrapper.vm.idInputs = ["metron:12345"]; - await wrapper.vm.$nextTick(); - expect(wrapper.vm.canMergeById).toBe(false); - wrapper.vm.setIdInputs(["metron:12345 comicvine:4000-456"]); await wrapper.vm.$nextTick(); - // The space-joined paste splits into two chips. + expect(wrapper.vm.idInputs).toEqual([ "metron:12345", "comicvine:4000-456", ]); - expect(wrapper.vm.canMergeById).toBe(true); - }); - - test("submits both identifiers when merging by id", async () => { - const { wrapper, onlineTagStore } = mountDialog(); - onlineTagStore.tagById.mockResolvedValue({ source: "metron", id: 12345 }); - - wrapper.vm.activeTab = "byId"; - wrapper.vm.idInputs = ["metron:12345", "comicvine:4000-456"]; - wrapper.vm.mergeAllSources = true; - await wrapper.vm.$nextTick(); - await wrapper.vm.submit(); - - expect(onlineTagStore.tagById).toHaveBeenCalledWith( - expect.objectContaining({ - identifier: "metron:12345", - identifiers: ["metron:12345", "comicvine:4000-456"], - mergeAllSources: true, - }), - ); + expect(wrapper.vm.pinnedSources).toEqual(["metron", "comicvine"]); }); }); diff --git a/frontend/tests/unit/online-tag-store.test.js b/frontend/tests/unit/online-tag-store.test.js index 4f1da261f..95d47dd1a 100644 --- a/frontend/tests/unit/online-tag-store.test.js +++ b/frontend/tests/unit/online-tag-store.test.js @@ -147,4 +147,44 @@ describe("useOnlineTagStore — resolution reconciliation", () => { }); }); +describe("useOnlineTagStore — startSession", () => { + const started = { data: { sessionId: "s1", skipped: 0 } }; + + it("sends the pinned per-source ids", async () => { + const store = useOnlineTagStore(); + HTTP.post.mockResolvedValue(started); + + await store.startSession({ + collection: "comics", + pks: [7], + sources: ["metron", "comicvine"], + mode: "auto", + promptsMode: "ask", + ids: { metron: "metron:12345" }, + }); + + expect(HTTP.post).toHaveBeenCalledWith( + "/admin/tag-sessions/start", + expect.objectContaining({ + pks: ["7"], + sources: ["metron", "comicvine"], + ids: { metron: "metron:12345" }, + }), + ); + expect(store.activeSessionId).toBe("s1"); + }); + + it("defaults ids to an empty mapping for a plain search", async () => { + const store = useOnlineTagStore(); + HTTP.post.mockResolvedValue(started); + + await store.startSession({ collection: "comics", pks: [7] }); + + expect(HTTP.post).toHaveBeenCalledWith( + "/admin/tag-sessions/start", + expect.objectContaining({ ids: {} }), + ); + }); +}); + export default {}; diff --git a/frontend/tests/unit/rename-toggle.test.js b/frontend/tests/unit/rename-toggle.test.js index 78a4c3b5d..03e42822e 100644 --- a/frontend/tests/unit/rename-toggle.test.js +++ b/frontend/tests/unit/rename-toggle.test.js @@ -2,7 +2,8 @@ * Tests for the comicbox "rename files" toggle across the UI. * * Behavior locked in here: - * - The online-tag store forwards `rename` to the start and by-id endpoints. + * - The online-tag store forwards `rename` to the start endpoint, searching + * or tagging by a pinned id. * - The edit panel seeds its rename toggle from the admin default and lets * the toggle alone enable Save (rename-only, no tag edits). * - The admin Tagging tab renders a "Rename files" default and binds it. @@ -47,18 +48,18 @@ describe("useOnlineTagStore — rename forwarding", () => { ); }); - it("forwards rename to the by-id endpoint", async () => { + it("forwards rename alongside a pinned id", async () => { const store = useOnlineTagStore(); - HTTP.post.mockResolvedValue({ data: { source: "metron", id: 1 } }); - await store.tagById({ + HTTP.post.mockResolvedValue({ data: { sessionId: "s1" } }); + await store.startSession({ collection: "comics", - pk: 1, - identifier: "metron:1", + pks: [1], + ids: { metron: "metron:1" }, rename: true, }); expect(HTTP.post).toHaveBeenCalledWith( - "/admin/tag-by-id", - expect.objectContaining({ rename: true }), + "/admin/tag-sessions/start", + expect.objectContaining({ ids: { metron: "metron:1" }, rename: true }), ); }); }); diff --git a/tests/test_onlinetag_resume_endpoints.py b/tests/test_onlinetag_resume_endpoints.py index e78ce6f6d..702691e0b 100644 --- a/tests/test_onlinetag_resume_endpoints.py +++ b/tests/test_onlinetag_resume_endpoints.py @@ -28,7 +28,7 @@ "prompts_mode": "ask", "delete_original": False, "merge_all_sources": False, - "dry_run": False, + "ids": {}, } @@ -87,9 +87,12 @@ def test_resume_enqueues_task_over_remaining_pks(self) -> None: assert task.prompts_mode == "ask" def test_resume_drops_params_from_an_older_codex(self) -> None: - # A descriptor written before a knob was removed (auto_threshold) must - # still resume instead of raising TypeError on the task constructor. - set_resume_state({**_PARAMS, "auto_threshold": 0.85}, [11]) + # A descriptor written before a knob was removed (auto_threshold, + # dry_run) must still resume instead of raising TypeError on the task + # constructor. An older descriptor also predates ``ids`` entirely. + stale = {**_PARAMS, "auto_threshold": 0.85, "dry_run": False} + del stale["ids"] + set_resume_state(stale, [11]) with patch(_QUEUE_TARGET) as mocked_queue: response = self.client.post(_RESUME_URL) @@ -98,6 +101,18 @@ def test_resume_drops_params_from_an_older_codex(self) -> None: task = mocked_queue.put.call_args.args[0] assert isinstance(task, BulkOnlineTagTask) assert task.comic_pks == frozenset([11]) + assert task.ids == {} + + def test_resume_keeps_pinned_ids(self) -> None: + """A paused pinned session resumes pinned; JSON string keys coerce back.""" + set_resume_state({**_PARAMS, "ids": {"metron": 12345}}, [11]) + + with patch(_QUEUE_TARGET) as mocked_queue: + response = self.client.post(_RESUME_URL) + + assert response.status_code == HTTPStatus.ACCEPTED + task = mocked_queue.put.call_args.args[0] + assert task.ids == {"metron": 12345} def test_resume_conflict_when_scan_active(self) -> None: set_resume_state(_PARAMS, [11]) diff --git a/tests/test_onlinetag_session_manager.py b/tests/test_onlinetag_session_manager.py index 00729a352..4dab49541 100644 --- a/tests/test_onlinetag_session_manager.py +++ b/tests/test_onlinetag_session_manager.py @@ -284,18 +284,43 @@ def test_run_session_unresolved_stored_id_falls_back_to_search(self) -> None: assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] assert searched == [Path(comic.path)] - def test_run_session_dry_run_skips_prefetch(self) -> None: - """A dry run never prefetches; every comic goes to the search pass.""" + def test_run_session_passes_pinned_ids_to_session(self) -> None: + """A pinned id per source reaches OnlineSession, which fetches it.""" + comic = _make_comic() + self.manager._pass_runner = _double( # noqa: SLF001 + SimpleNamespace(collect_results=lambda *_args, **_kwargs: None) + ) + task = BulkOnlineTagTask( + comic_pks=frozenset({comic.pk}), + session_id="scan-pinned", + sources=("metron", "comicvine"), + mode="auto", + ids={"metron": 12345}, + ) + + with patch(_PATCH_TARGET, _FakeSession): + self.manager.run_session(task) + + assert _FakeSession.last_kwargs["ids"] == {"metron": 12345} + + def test_run_session_pinned_ids_bypass_the_stored_id_prepass(self) -> None: + """ + A pinned request keeps its comic in the session instead of prefetching. + + The prepass drops what it fetches out of the search set, which would + leave the unpinned sources with nothing to search — the pinned and + searched sources have to resolve in the same lookup to merge. + """ comic = _make_comic() self._add_issue_id(comic, "metron", "123495") searched: list = [] self._capture_search_paths(searched) task = BulkOnlineTagTask( comic_pks=frozenset({comic.pk}), - session_id="scan-dry", - sources=("metron",), + session_id="scan-pinned-prepass", + sources=("metron", "comicvine"), mode="auto", - dry_run=True, + ids={"comicvine": 456}, ) fetch_calls: list = [] @@ -307,6 +332,32 @@ def test_run_session_dry_run_skips_prefetch(self) -> None: assert fetch_calls == [] assert searched == [Path(comic.path)] + assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + + def test_run_session_pinned_ids_land_in_resume_params(self) -> None: + """A paused pinned session resumes still pinned.""" + comic = _make_comic() + captured: list = [] + self.manager._pass_runner = _double( # noqa: SLF001 + SimpleNamespace( + collect_results=lambda state, *_a, **_k: captured.append( + state.resume_params + ) + ) + ) + task = BulkOnlineTagTask( + comic_pks=frozenset({comic.pk}), + session_id="scan-pinned-resume", + sources=("metron",), + mode="auto", + ids={"metron": 12345}, + ) + + with patch(_PATCH_TARGET, _FakeSession): + self.manager.run_session(task) + + assert captured + assert captured[0]["ids"] == {"metron": 12345} def test_prefetch_keeps_comic_in_status_out_of_search_and_resume(self) -> None: """A prefetched comic shows as matched yet is excluded from Resume.""" diff --git a/tests/test_read_only_library.py b/tests/test_read_only_library.py index 52c72e81f..f45b2ec17 100644 --- a/tests/test_read_only_library.py +++ b/tests/test_read_only_library.py @@ -18,7 +18,15 @@ from django.core.cache import cache from django.test import Client, TestCase -from codex.models import Comic, Imprint, Library, Publisher, Series, Volume +from codex.models import ( + Comic, + ComicboxTaggingDefaults, + Imprint, + Library, + Publisher, + Series, + Volume, +) from codex.startup import init_admin_flags _TEST_PASSWORD: Final = "test-pw-hush-S106" # noqa: S105 @@ -144,6 +152,21 @@ def test_online_tag_skips_read_only(self) -> None: assert frozenset(task.comic_pks) == {self.rw_comic.pk} assert self._body(resp)["skipped"] == 1 + def test_online_tag_by_id_rejects_read_only_comic(self) -> None: + """A pinned id can't rescue a comic whose library is read-only.""" + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, defaults={"metron_key": "t"} + ) + payload = { + "collection": "comics", + "pks": [str(self.ro_comic.pk)], + "ids": {"metron": "metron:123"}, + } + with patch("codex.views.admin.onlinetag.LIBRARIAN_QUEUE") as queue: + resp = self._post(_ONLINE_TAG_URL, payload) + assert resp.status_code == HTTPStatus.BAD_REQUEST, resp.content + assert not queue.put.called + def _editable(self, collection: str, pks) -> bool: pks_str = ",".join(str(pk) for pk in pks) url = f"/api/v4/browse/{collection}/{pks_str}/metadata" diff --git a/tests/test_tag_by_id.py b/tests/test_tag_by_id.py index f7a999d6b..aea3867d2 100644 --- a/tests/test_tag_by_id.py +++ b/tests/test_tag_by_id.py @@ -1,18 +1,18 @@ """ Tests for tagging a comic by a known online issue id. -Covers the three layers the feature adds: the identifier parser (URL / -``source:id`` / bare ``4000-NNN`` / bare integer precedence), the session -manager's ``tag_by_id`` (fetch → write, or surface a not-found), and the admin -endpoint's validation + task enqueue. +Tagging by id is not a separate endpoint: an id pinned per source rides along +in the ordinary start-session request, so a session can fetch the pinned +sources by id and search the rest in one lookup. Covered here: the identifier +parser (URL / ``source:id`` / bare ``4000-NNN`` / bare integer precedence), the +explicit-id config builder the stored-id prepass and prompt resolution share, +and the start endpoint's pinned-id validation + task enqueue. """ from __future__ import annotations -import shutil from http import HTTPStatus -from pathlib import Path -from typing import Any, Final, override +from typing import Final, override from unittest.mock import patch import pytest @@ -20,37 +20,22 @@ from django.contrib.auth.models import User from django.core.cache import caches from django.test import Client, SimpleTestCase, TestCase -from loguru import logger from codex.librarian.onlinetag.explicit_id import ( _result_has_requested_id, build_explicit_id_config, ) -from codex.librarian.onlinetag.session_manager import OnlineTagSessionManager -from codex.librarian.onlinetag.tasks import OnlineTagByIdTask -from codex.librarian.scribe.tagwrite_errors import get_tag_write_errors -from codex.librarian.scribe.tasks import BulkTagWriteTask -from codex.models import ( - Comic, - ComicboxTaggingDefaults, - Imprint, - Library, - Publisher, - Series, - Volume, -) +from codex.librarian.onlinetag.tasks import BulkOnlineTagTask +from codex.models import ComicboxTaggingDefaults from codex.views.admin.identifier_parse import parse_identifier_input -from codex.views.admin.tagwrite import AdminTagByIdView +from codex.views.admin.onlinetag import AdminOnlineTagStartView -_TMP_DIR: Final = Path("/tmp/codex.tests.tagbyid") # noqa: S108 -_FETCH_TARGET: Final = ( - "codex.librarian.onlinetag.session_manager.fetch_tags_by_explicit_id" -) -_VIEW_QUEUE_TARGET: Final = "codex.views.admin.tagwrite.LIBRARIAN_QUEUE" -_TAG_BY_ID_URL: Final = "/api/v4/admin/tag-by-id" +_VIEW_QUEUE_TARGET: Final = "codex.views.admin.onlinetag.LIBRARIAN_QUEUE" +_START_URL: Final = "/api/v4/admin/tag-sessions/start" _TEST_PASSWORD: Final = "test-pw-hush-S106" # noqa: S105 _PK: Final = 7 _ISSUE_ID: Final = 12345 +_CV_ISSUE_ID: Final = 456 class ParseIdentifierInputTests(SimpleTestCase): @@ -164,114 +149,6 @@ def test_build_explicit_id_config_merge(self) -> None: assert set(settings.online.auth.sources) == {"metron", "comicvine"} -def _double(stub: object) -> Any: - """Pass a test double through the manager's strictly-typed seams.""" - return stub - - -class _FakeQueue: - """Collects everything put on it for assertions.""" - - def __init__(self) -> None: - self.items: list = [] - - def put(self, item) -> None: - self.items.append(item) - - -def _make_comic() -> Comic: - _TMP_DIR.mkdir(exist_ok=True, parents=True) - library = Library.objects.create(path=str(_TMP_DIR)) - publisher = Publisher.objects.create(name="P") - imprint = Imprint.objects.create(name="I", publisher=publisher) - series = Series.objects.create(name="S", publisher=publisher, imprint=imprint) - volume = Volume.objects.create( - name="1", publisher=publisher, imprint=imprint, series=series - ) - path = _TMP_DIR / "c.cbz" - path.touch() - return Comic.objects.create( - library=library, - path=path, - issue_number=1, - name="c", - publisher=publisher, - imprint=imprint, - series=series, - volume=volume, - size=1, - file_type="CBZ", - ) - - -class TagByIdSessionManagerTests(TestCase): - """tag_by_id fetches the issue and enqueues a write, or surfaces failure.""" - - @override - def setUp(self) -> None: - caches["default"].clear() - caches["tagging"].clear() - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={"metron_key": "t"}, - ) - self.queue = _FakeQueue() # pyright: ignore[reportUninitializedInstanceVariable] - self.manager = OnlineTagSessionManager( # pyright: ignore[reportUninitializedInstanceVariable] - _double(logger), - _double(self.queue), - thread_queue=None, - ) - - @override - def tearDown(self) -> None: - shutil.rmtree(_TMP_DIR, ignore_errors=True) - - def test_success_enqueues_write(self) -> None: - comic = _make_comic() - tags = {"series": "X", "identifiers": {"metron": {"key": "123"}}} - task = OnlineTagByIdTask(comic_pk=comic.pk, source="metron", issue_id=123) - - with patch(_FETCH_TARGET, return_value=tags): - self.manager.tag_by_id(task) - - writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - assert len(writes) == 1 - assert writes[0].comic_pks == frozenset({comic.pk}) - assert writes[0].per_comic_patches == {comic.pk: tags} - - def test_not_found_records_error_and_skips_write(self) -> None: - comic = _make_comic() - task = OnlineTagByIdTask(comic_pk=comic.pk, source="metron", issue_id=999) - - with patch(_FETCH_TARGET, return_value=None): - self.manager.tag_by_id(task) - - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - errors = get_tag_write_errors() - assert errors - assert errors[0]["path"] == str(comic.path) - - def test_missing_source_credentials_skips_fetch(self) -> None: - comic = _make_comic() - # Only Metron is configured; a Comic Vine request must not fetch. - task = OnlineTagByIdTask(comic_pk=comic.pk, source="comicvine", issue_id=1) - - with patch(_FETCH_TARGET) as fetch: - self.manager.tag_by_id(task) - - fetch.assert_not_called() - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - - def test_unknown_comic_skips_fetch(self) -> None: - task = OnlineTagByIdTask(comic_pk=999999, source="metron", issue_id=1) - - with patch(_FETCH_TARGET) as fetch: - self.manager.tag_by_id(task) - - fetch.assert_not_called() - assert not self.queue.items - - def _make_admin() -> User: return User.objects.create_user( username="tag_by_id_admin", @@ -281,20 +158,20 @@ def _make_admin() -> User: ) -class TagByIdViewAuthTests(TestCase): - """The tag-by-id endpoint requires admin auth.""" +class TagByIdAuthTests(TestCase): + """The start endpoint requires admin auth, pinned ids or not.""" def test_anonymous_blocked(self) -> None: response = Client().post( - _TAG_BY_ID_URL, - data={"collection": "comics", "pk": "1", "identifier": "metron:1"}, + _START_URL, + data={"collection": "comics", "pks": ["1"], "ids": {"metron": "1"}}, content_type="application/json", ) assert response.status_code == HTTPStatus.FORBIDDEN -class TagByIdViewTests(TestCase): - """Validation paths return 400; a valid request enqueues the task.""" +class TagByIdStartViewTests(TestCase): + """Pinned-id validation returns 400; a valid request enqueues the task.""" @override def setUp(self) -> None: @@ -307,17 +184,25 @@ def setUp(self) -> None: defaults={"metron_key": "t"}, ) - def _post(self, identifier: str, source: str | None = None): - data: dict[str, str] = { - "collection": "comics", - "pk": str(_PK), - "identifier": identifier, - } - if source is not None: - data["source"] = source - return self.client.post( - _TAG_BY_ID_URL, data=data, content_type="application/json" - ) + def _post(self, ids: dict[str, str], sources: list[str] | None = None): + data: dict = {"collection": "comics", "pks": [str(_PK)], "ids": ids} + if sources is not None: + data["sources"] = sources + return self.client.post(_START_URL, data=data, content_type="application/json") + + def _post_resolving_one_comic(self, ids: dict[str, str], **kwargs): + """Post with the comic resolution and queue stubbed; return the task.""" + with ( + patch.object( + AdminOnlineTagStartView, + "resolve_comic_pks", + return_value=frozenset({_PK}), + ), + patch(_VIEW_QUEUE_TARGET) as mocked_queue, + ): + response = self._post(ids, **kwargs) + assert response.status_code == HTTPStatus.ACCEPTED, response.content + return mocked_queue.put.call_args.args[0] def test_no_credentials_returns_400(self) -> None: ComicboxTaggingDefaults.objects.update_or_create( @@ -329,7 +214,7 @@ def test_no_credentials_returns_400(self) -> None: "comicvine_key": "", }, ) - response = self._post("metron:1") + response = self._post({"metron": "1"}) assert response.status_code == HTTPStatus.BAD_REQUEST def test_legacy_login_still_counts_as_configured(self) -> None: @@ -338,69 +223,83 @@ def test_legacy_login_still_counts_as_configured(self) -> None: pk=1, defaults={"metron_key": "", "metron_user": "u", "metron_password": "p"}, ) - with ( - patch.object( - AdminTagByIdView, "resolve_comic_pks", return_value=frozenset({_PK}) - ), - patch(_VIEW_QUEUE_TARGET), - ): - response = self._post(f"metron:{_ISSUE_ID}") - assert response.status_code == HTTPStatus.ACCEPTED + task = self._post_resolving_one_comic({"metron": f"metron:{_ISSUE_ID}"}) + assert task.ids == {"metron": _ISSUE_ID} def test_unparseable_identifier_returns_400(self) -> None: - response = self._post("not-an-id") + response = self._post({"metron": "not-an-id"}) assert response.status_code == HTTPStatus.BAD_REQUEST def test_unconfigured_source_returns_400(self) -> None: # Only Metron is configured; a Comic Vine id has no credentials. - response = self._post("comicvine:4000-1") + response = self._post({"comicvine": "comicvine:4000-1"}) assert response.status_code == HTTPStatus.BAD_REQUEST - def test_success_enqueues_task(self) -> None: - with ( - patch.object( - AdminTagByIdView, "resolve_comic_pks", return_value=frozenset({_PK}) - ), - patch(_VIEW_QUEUE_TARGET) as mocked_queue, + def test_unknown_source_key_returns_400(self) -> None: + response = self._post({"gcd": "123"}) + assert response.status_code == HTTPStatus.BAD_REQUEST + + def test_id_for_another_source_returns_400(self) -> None: + """A Comic Vine id filed under the Metron key is a mistake, not a hint.""" + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, defaults={"metron_key": "t", "comicvine_key": "k"} + ) + response = self._post({"metron": "comicvine:4000-1"}) + assert response.status_code == HTTPStatus.BAD_REQUEST + + def test_id_for_unselected_source_returns_400(self) -> None: + """A pinned source that isn't being run would silently do nothing.""" + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, defaults={"metron_key": "t", "comicvine_key": "k"} + ) + response = self._post({"comicvine": "4000-1"}, sources=["metron"]) + assert response.status_code == HTTPStatus.BAD_REQUEST + + def test_multiple_comics_with_pinned_id_returns_400(self) -> None: + """One issue id can't describe a multi-comic selection.""" + with patch.object( + AdminOnlineTagStartView, + "resolve_comic_pks", + return_value=frozenset({_PK, _PK + 1}), ): - response = self._post(f"metron:{_ISSUE_ID}") - - assert response.status_code == HTTPStatus.ACCEPTED - task = mocked_queue.put.call_args.args[0] - assert isinstance(task, OnlineTagByIdTask) - assert task.comic_pk == _PK - assert task.source == "metron" - assert task.issue_id == _ISSUE_ID - assert task.extra_ids == () - - def test_merge_two_ids_enqueues_extra_ids(self) -> None: - # Merge on + a Metron and a Comic Vine id: the second id rides along as - # an extra to fetch and merge. A single id leaves extra_ids empty. + response = self._post({"metron": f"metron:{_ISSUE_ID}"}) + assert response.status_code == HTTPStatus.BAD_REQUEST + + def test_blank_id_is_ignored(self) -> None: + """An empty token is a cleared input, not a pin: plain search.""" + task = self._post_resolving_one_comic({"metron": " "}) + assert task.ids == {} + + def test_success_enqueues_task_with_pinned_id(self) -> None: + task = self._post_resolving_one_comic({"metron": f"metron:{_ISSUE_ID}"}) + assert isinstance(task, BulkOnlineTagTask) + assert task.comic_pks == frozenset({_PK}) + assert task.ids == {"metron": _ISSUE_ID} + + def test_mixed_pin_and_search(self) -> None: + """Metron pinned, Comic Vine unpinned: one task, one pinned source.""" ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={ - "metron_key": "t", - "comicvine_key": "k", - "default_sources": ["metron", "comicvine"], - "merge_all_sources": True, + pk=1, defaults={"metron_key": "t", "comicvine_key": "k"} + ) + task = self._post_resolving_one_comic( + {"metron": f"metron:{_ISSUE_ID}"}, sources=["metron", "comicvine"] + ) + assert task.ids == {"metron": _ISSUE_ID} + assert task.sources == ("metron", "comicvine") + + def test_both_sources_pinned(self) -> None: + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, defaults={"metron_key": "t", "comicvine_key": "k"} + ) + task = self._post_resolving_one_comic( + { + "metron": f"metron:{_ISSUE_ID}", + "comicvine": f"4000-{_CV_ISSUE_ID}", }, + sources=["metron", "comicvine"], ) - data = { - "collection": "comics", - "pk": str(_PK), - "identifier": f"metron:{_ISSUE_ID}", - "identifiers": [f"metron:{_ISSUE_ID}", "comicvine:4000-456"], - } - with ( - patch.object( - AdminTagByIdView, "resolve_comic_pks", return_value=frozenset({_PK}) - ), - patch(_VIEW_QUEUE_TARGET) as mocked_queue, - ): - response = self.client.post( - _TAG_BY_ID_URL, data=data, content_type="application/json" - ) + assert task.ids == {"metron": _ISSUE_ID, "comicvine": _CV_ISSUE_ID} - assert response.status_code == HTTPStatus.ACCEPTED - task = mocked_queue.put.call_args.args[0] - assert task.extra_ids == (("comicvine", 456),) + def test_bare_number_resolves_against_the_sole_configured_source(self) -> None: + task = self._post_resolving_one_comic({"metron": str(_ISSUE_ID)}) + assert task.ids == {"metron": _ISSUE_ID} diff --git a/tests/test_tagging_rename_wiring.py b/tests/test_tagging_rename_wiring.py index 7a5f7327c..8c5e1de30 100644 --- a/tests/test_tagging_rename_wiring.py +++ b/tests/test_tagging_rename_wiring.py @@ -2,7 +2,7 @@ Wiring tests for the comicbox rename toggle. Confirms ``rename`` threads through the manual tag-write view (request value or -admin default), the preflight filename preview, the tag-by-id online path, and +admin default), the preflight filename preview, the online tag-by-id path, and deferred-prompt serialization. """ @@ -17,10 +17,8 @@ from django.contrib.auth.models import User from django.core.cache import caches from django.test import Client, SimpleTestCase, TestCase -from loguru import logger from codex.librarian.onlinetag.session_manager import OnlineTagSessionManager -from codex.librarian.onlinetag.tasks import OnlineTagByIdTask from codex.librarian.scribe.tasks import BulkTagWriteTask from codex.models import ( Comic, @@ -31,6 +29,7 @@ Series, Volume, ) +from codex.views.admin.onlinetag import AdminOnlineTagStartView from codex.views.admin.tagwrite import AdminTagWritePreflightView, AdminTagWriteView _TMP_DIR: Final = Path("/tmp/codex.tests.tagrenamewiring") # noqa: S108 @@ -38,29 +37,13 @@ _PK: Final = 7 _TAG_WRITE_URL: Final = "/api/v4/admin/tag-write" _PREFLIGHT_URL: Final = "/api/v4/admin/tag-write/preflight" +_START_URL: Final = "/api/v4/admin/tag-sessions/start" _QUEUE_TARGET: Final = "codex.views.admin.tagwrite.LIBRARIAN_QUEUE" +_START_QUEUE_TARGET: Final = "codex.views.admin.onlinetag.LIBRARIAN_QUEUE" _VIEW_COMICBOX_TARGET: Final = "codex.views.admin.tagwrite.Comicbox" -_FETCH_TARGET: Final = ( - "codex.librarian.onlinetag.session_manager.fetch_tags_by_explicit_id" -) _PREVIEW_NAME: Final = "Series v01 #001.cbz" -def _double(stub: object) -> Any: - """Pass a test double through a concretely-typed seam.""" - return stub - - -class _FakeQueue: - """Collects everything put on it for assertions.""" - - def __init__(self) -> None: - self.items: list = [] - - def put(self, item) -> None: - self.items.append(item) - - class _PreviewComicbox: """Minimal Comicbox stand-in for the preflight filename preview.""" @@ -265,7 +248,7 @@ def test_preview_lists_every_comic(self) -> None: class TagByIdRenamePropagationTests(TestCase): - """rename on the task flows into the enqueued BulkTagWriteTask.""" + """rename survives alongside a pinned id on the start request.""" @override def setUp(self) -> None: @@ -274,26 +257,32 @@ def setUp(self) -> None: ComicboxTaggingDefaults.objects.update_or_create( pk=1, defaults={"metron_key": "t"} ) - self.queue = _FakeQueue() # pyright: ignore[reportUninitializedInstanceVariable] - self.manager = OnlineTagSessionManager( # pyright: ignore[reportUninitializedInstanceVariable] - _double(logger), _double(self.queue), thread_queue=None - ) + self.client = Client() + self.client.force_login(_make_admin()) - @override - def tearDown(self) -> None: - shutil.rmtree(_TMP_DIR, ignore_errors=True) + def test_rename_propagates_with_a_pinned_id(self) -> None: + data = { + "collection": "comics", + "pks": [str(_PK)], + "ids": {"metron": "metron:123"}, + "rename": True, + } + with ( + patch.object( + AdminOnlineTagStartView, + "resolve_comic_pks", + return_value=frozenset({_PK}), + ), + patch(_START_QUEUE_TARGET) as mocked_queue, + ): + response = self.client.post( + _START_URL, data=data, content_type="application/json" + ) - def test_rename_propagates_to_write(self) -> None: - comic = _make_comic() - tags = {"series": "X", "identifiers": {"metron": {"key": "123"}}} - task = OnlineTagByIdTask( - comic_pk=comic.pk, source="metron", issue_id=123, rename=True - ) - with patch(_FETCH_TARGET, return_value=tags): - self.manager.tag_by_id(task) - writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - assert len(writes) == 1 - assert writes[0].rename is True + assert response.status_code == HTTPStatus.ACCEPTED, response.content + task = mocked_queue.put.call_args.args[0] + assert task.rename is True + assert task.ids == {"metron": 123} class _DeferredPrompt: From 97c65d6d8069b72deb7329e4913720de6094b6f0 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 1 Aug 2026 17:58:53 -0700 Subject: [PATCH 65/82] update deps --- bun.lock | 4 ++-- frontend/bun.lock | 6 +++--- pyproject.toml | 2 +- uv.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bun.lock b/bun.lock index 7dfdda8e1..d2c8ed3e0 100644 --- a/bun.lock +++ b/bun.lock @@ -35,8 +35,8 @@ "eslint-plugin-unicorn": "^72.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.1", - "eslint-plugin-yml": "^3.6.0", - "eslint_d": "^15.0.2", + "eslint-plugin-yml": "^3.7.0", + "eslint_d": "^15.0.3", "postcss-scss": "^4.0.9", "prettier": "^3.9.6", "prettier-plugin-nginx": "^1.0.3", diff --git a/frontend/bun.lock b/frontend/bun.lock index 4cef888cd..b62780c64 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -15,7 +15,7 @@ "vue": "^3.5.40", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", - "vuetify": "^4.1.6", + "vuetify": "^4.1.7", "xior": "^0.8.3", }, "devDependencies": { @@ -26,14 +26,14 @@ "@unhead/bundler": "^3.2.3", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", - "@vue/typescript-plugin": "^3.3.8", + "@vue/typescript-plugin": "^3.3.9", "happy-dom": "^20.11.1", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.102.0", "sass-loader": "^17.0.0", "toml": "^5.0.0", "typeface-roboto": "^1.1.13", - "vite": "^8.1.5", + "vite": "^8.2.0", "vite-plugin-checker": "^0.14.5", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.1", diff --git a/pyproject.toml b/pyproject.toml index bb3bb57d9..f3306cce5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "adrf~=0.1.12", "bidict~=0.23", "channels~=4.2", - "comicbox[pdf]~=4.7.1", + "comicbox[pdf]~=4.8.0", "cryptography>=48.0.0", "dateparser~=1.2", "django-allauth[socialaccount]~=65.13", diff --git a/uv.lock b/uv.lock index 0b3376522..6c0f3de57 100644 --- a/uv.lock +++ b/uv.lock @@ -653,7 +653,7 @@ requires-dist = [ { name = "adrf", specifier = "~=0.1.12" }, { name = "bidict", specifier = "~=0.23" }, { name = "channels", specifier = "~=4.2" }, - { name = "comicbox", extras = ["pdf"], specifier = "~=4.7.1" }, + { name = "comicbox", extras = ["pdf"], specifier = "~=4.8.0" }, { name = "cryptography", specifier = ">=48.0.0" }, { name = "dateparser", specifier = "~=1.2" }, { name = "django", specifier = "~=6.0" }, @@ -739,7 +739,7 @@ wheels = [ [[package]] name = "comicbox" -version = "4.7.1" +version = "4.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, @@ -776,9 +776,9 @@ dependencies = [ { name = "xmltodict" }, { name = "zipremove" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/15/b1dcfc4aa93829f13bd71fc440e8432fea7eb01fc6fc02001faa1dea5d8f/comicbox-4.7.1.tar.gz", hash = "sha256:eda90b247d1961b32ece8b878a9ce1204a39d609d56b21eac337d5e7d727b45a", size = 1018345, upload-time = "2026-07-29T05:33:53.615Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/2b/38f5122b1aa64f72b36d851335b5ac532ea74f8c44b7d1c094d796f3177a/comicbox-4.8.0.tar.gz", hash = "sha256:28acf608904f2353e7c48d422d29d1f62f493ef9b36e40bf983577348cebb75a", size = 1019177, upload-time = "2026-08-02T00:57:00.542Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/1a/579af5c6853adcd48e177717dd7ff24b273cf34e45d9e8269e88cb5fcd4e/comicbox-4.7.1-py3-none-any.whl", hash = "sha256:cac0906bcc0a8f393a09db5b1590091c120dde2ed349856965a7c87816fdb8e6", size = 343779, upload-time = "2026-07-29T05:33:52.472Z" }, + { url = "https://files.pythonhosted.org/packages/38/3c/e36e02402579645f818d12602a5d0c3336fd05cff773ca3c6c994c1885bc/comicbox-4.8.0-py3-none-any.whl", hash = "sha256:7f96ee493d9fb416846a55eb8a2bec61e6ec1460625821b6bf83cdbeac623798", size = 344052, upload-time = "2026-08-02T00:56:59.356Z" }, ] [package.optional-dependencies] From f54e70b6afc2d1f7dc5ef26e000e29836bdecdf9 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Sat, 1 Aug 2026 18:03:48 -0700 Subject: [PATCH 66/82] style(news): prettier wrap the online tagging entry Co-Authored-By: Claude Fable 5 --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index a8537c4d6..c626de807 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,8 +10,8 @@ border-radius: 128px; - Features - Online tagging combines search and tag by id. A source given an issue id - fetches it directly, the rest search, and the results merge - (comicbox 4.8.0). + fetches it directly, the rest search, and the results merge (comicbox + 4.8.0). - Fixes - Tagging a comic with renaming enabled no longer fails the import and From f0b513fe92dcd6737af4dc2757800704b07ff1b5 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 3 Aug 2026 23:40:33 -0700 Subject: [PATCH 67/82] feat(tagging): restore the custom URL for Comic Vine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2.2.4 (0dd3e5199) removed the custom URL fields for both online sources. Metron's was correctly retired — mokkari hardcodes METRON_URL and comicbox warns the url is a no-op — but Comic Vine's worked: comicbox passes it through OnlineCredentials.comicvine_url to OnlineSourceCredentials.url and on to simyan's base_url, which is what lets tagging run against a Comic Vine proxy or mirror. Restores the Comic Vine half only, no comicbox change needed: - Model field + migration 0052. A plain URLField rather than an EncryptedCharField, because unlike the API keys it is not a secret and must read back for the admin form's placeholder. - Admin serializer (read+write), validate request serializer, and the validate view's _CREDENTIAL_FIELDS. - All three librarian consumers: the scan session's OnlineCredentials, the explicit-id auth mapping, and the credential validator's simyan base_url, so Test checks the endpoint the scan will actually use. - Backup/restore sidecar column, serializer, and restore allowlist. _reconcile_columns retrofits existing sidecar files; a legacy metron_url column in an older backup stays silently ignored. - Telemetry reports only bool(comicvine_url) — never the value. - The admin Tagging tab's Comic Vine panel gets the field back, its save button reverts to "Save Comic Vine Credentials" now that a URL-only save is possible again, and Clear removes both. A URL alone is not a credential: it must not satisfy the session manager's configured-sources gate, enable the source checkbox, or pass validation without a key. Tests pin all three. URLs dropped by 0051 are unrecoverable; admins re-enter them. Co-Authored-By: Claude Fable 5 --- NEWS.md | 7 +++ .../onlinetag/credential_validator.py | 2 + codex/librarian/onlinetag/explicit_id.py | 5 +- codex/librarian/onlinetag/session_manager.py | 1 + codex/librarian/telemeter/admin_stats.py | 3 +- ...2_comicboxtaggingdefaults_comicvine_url.py | 29 ++++++++++ codex/models/admin.py | 5 ++ codex/serializers/admin/stats.py | 3 +- codex/serializers/admin/tagging.py | 4 ++ codex/user_data/restore.py | 9 ++-- codex/user_data/schema.sql | 1 + codex/user_data/serializers.py | 1 + codex/views/admin/tagging_validate.py | 1 + .../src/components/admin/tabs/stats-tab.vue | 1 + .../src/components/admin/tabs/tagging-tab.vue | 20 +++++-- frontend/tests/unit/rename-toggle.test.js | 1 + frontend/tests/unit/stats-tab.test.js | 1 + frontend/tests/unit/tagging-tab.test.js | 53 +++++++++++++++++-- tests/test_admin_singleton_settings.py | 48 +++++++++++++++++ tests/test_credential_validator.py | 27 ++++++++-- tests/test_onlinetag_session_manager.py | 26 +++++++++ tests/test_tag_by_id.py | 10 ++++ tests/test_telemeter_privacy.py | 1 + tests/test_user_data_dump.py | 2 + tests/test_user_data_restore.py | 18 +++++++ 25 files changed, 260 insertions(+), 19 deletions(-) create mode 100644 codex/migrations/0052_comicboxtaggingdefaults_comicvine_url.py diff --git a/NEWS.md b/NEWS.md index c626de807..e2750e125 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,13 @@ width: 128px; border-radius: 128px; " /> +## v2.2.6 + +- Features + - The Comic Vine custom URL is back, for tagging against a Comic Vine proxy + or mirror. URLs discarded by v2.2.4 must be entered again. Metron Cloud's + stays gone; it never did anything. + ## v2.2.5 - Features diff --git a/codex/librarian/onlinetag/credential_validator.py b/codex/librarian/onlinetag/credential_validator.py index 4b539289c..b504f7d43 100644 --- a/codex/librarian/onlinetag/credential_validator.py +++ b/codex/librarian/onlinetag/credential_validator.py @@ -137,6 +137,8 @@ def _validate_comicvine(creds: OnlineCredentials) -> ValidationResult: "cache_expiry": DO_NOT_CACHE, "ratelimit_path": tmp_path / "ratelimits.sqlite", } + if creds.comicvine_url: + kwargs["base_url"] = creds.comicvine_url cv = Comicvine(**kwargs) try: cv.list_publishers(params={"limit": "1"}, max_results=1) diff --git a/codex/librarian/onlinetag/explicit_id.py b/codex/librarian/onlinetag/explicit_id.py index 22f9f13bd..47b8413d6 100644 --- a/codex/librarian/onlinetag/explicit_id.py +++ b/codex/librarian/onlinetag/explicit_id.py @@ -52,7 +52,10 @@ def _build_auth_source( user=credentials.metron_user, password=credentials.metron_password, ) - return OnlineSourceCredentials(key=credentials.comicvine_key) + return OnlineSourceCredentials( + key=credentials.comicvine_key, + url=credentials.comicvine_url, + ) def build_explicit_id_config( diff --git a/codex/librarian/onlinetag/session_manager.py b/codex/librarian/onlinetag/session_manager.py index 6ab3c64ef..2ad566ac5 100644 --- a/codex/librarian/onlinetag/session_manager.py +++ b/codex/librarian/onlinetag/session_manager.py @@ -115,6 +115,7 @@ def _build_credentials(self) -> OnlineCredentials | None: metron_user=defaults.metron_user or "", metron_password=defaults.metron_password or "", comicvine_key=defaults.comicvine_key or "", + comicvine_url=defaults.comicvine_url or "", ) @staticmethod diff --git a/codex/librarian/telemeter/admin_stats.py b/codex/librarian/telemeter/admin_stats.py index e4a17b2b5..e734a6a72 100644 --- a/codex/librarian/telemeter/admin_stats.py +++ b/codex/librarian/telemeter/admin_stats.py @@ -143,7 +143,7 @@ def _default_sources(defaults: ComicboxTaggingDefaults) -> dict[str, int]: def get_tagging_stats() -> dict[str, Any]: - """Report the online tagging defaults. Never the credentials.""" + """Report the online tagging defaults. Never the credentials or the url.""" defaults = ComicboxTaggingDefaults.objects.first() if not defaults: return {} @@ -160,6 +160,7 @@ def get_tagging_stats() -> dict[str, Any]: defaults.metron_key or (defaults.metron_user and defaults.metron_password) ), "has_comicvine_credentials": bool(defaults.comicvine_key), + "comicvine_url_set": bool(defaults.comicvine_url), } diff --git a/codex/migrations/0052_comicboxtaggingdefaults_comicvine_url.py b/codex/migrations/0052_comicboxtaggingdefaults_comicvine_url.py new file mode 100644 index 000000000..284cc10bf --- /dev/null +++ b/codex/migrations/0052_comicboxtaggingdefaults_comicvine_url.py @@ -0,0 +1,29 @@ +"""Generated by Django 6.0.7 on 2026-08-03 12:00.""" + +from django.db import migrations, models + + +class Migration(migrations.Migration): + """ + Restore the custom Comic Vine URL override removed by 0051. + + Only the Comic Vine half comes back. comicbox passes the url through to + simyan's ``base_url``, so pointing tagging at a Comic Vine proxy or mirror + works. Metron's twin stays gone: mokkari hardcodes its API endpoint and + comicbox warns that the url is ignored. + + 0051 dropped the column outright, so any URL saved before it is + unrecoverable — admins re-enter it. + """ + + dependencies = [ + ("codex", "0051_remove_comicboxtaggingdefaults_urls"), + ] + + operations = [ + migrations.AddField( + model_name="comicboxtaggingdefaults", + name="comicvine_url", + field=models.URLField(blank=True, default="", max_length=256), + ), + ] diff --git a/codex/models/admin.py b/codex/models/admin.py index 3e8c4d0ae..2f7e7d3dc 100644 --- a/codex/models/admin.py +++ b/codex/models/admin.py @@ -124,6 +124,11 @@ class PromptsModeChoices(TextChoices): metron_user = EncryptedCharField() metron_password = EncryptedCharField() comicvine_key = EncryptedCharField() + # Points simyan at a Comic Vine proxy or mirror instead of the public API. + # Not a secret, and the admin form echoes it back as a placeholder, so it + # stays a plain URLField. Metron has no twin: mokkari hardcodes its + # endpoint, so a custom URL there was always a no-op. + comicvine_url = URLField(max_length=256, blank=True, default="") # Active session id + pending prompts used to live here. They are # transient operational state — they only matter while a tagging diff --git a/codex/serializers/admin/stats.py b/codex/serializers/admin/stats.py index a34f4075e..6f8e4ce95 100644 --- a/codex/serializers/admin/stats.py +++ b/codex/serializers/admin/stats.py @@ -171,7 +171,7 @@ class StatsTaggingSerializer(Serializer): """ Online Tagging Defaults. - Credentials report only whether they are configured. + Credentials and the Comic Vine url report only whether they are configured. """ default_match_mode = CharField(required=False, read_only=True) @@ -183,6 +183,7 @@ class StatsTaggingSerializer(Serializer): default_format_count = IntegerField(required=False, read_only=True) has_metron_credentials = BooleanField(required=False, read_only=True) has_comicvine_credentials = BooleanField(required=False, read_only=True) + comicvine_url_set = BooleanField(required=False, read_only=True) class StatsAuthSerializer(Serializer): diff --git a/codex/serializers/admin/tagging.py b/codex/serializers/admin/tagging.py index 9890d5183..997589932 100644 --- a/codex/serializers/admin/tagging.py +++ b/codex/serializers/admin/tagging.py @@ -106,6 +106,7 @@ class TaggingValidateRequestSerializer(Serializer): ) metron_key = CharField(required=False, allow_blank=True) comicvine_key = CharField(required=False, allow_blank=True) + comicvine_url = CharField(required=False, allow_blank=True) class TaggingRateLimitWindowSerializer(Serializer): @@ -207,6 +208,9 @@ class Meta(BaseModelSerializer.Meta): "merge_all_sources", "metron_key", "comicvine_key", + # Not a secret, so unlike the keys this reads back: the form shows + # the saved url as its placeholder. + "comicvine_url", "metron_key_set", "comicvine_key_set", "has_metron_credentials", diff --git a/codex/user_data/restore.py b/codex/user_data/restore.py index 99084d456..6a806ab3c 100644 --- a/codex/user_data/restore.py +++ b/codex/user_data/restore.py @@ -374,6 +374,7 @@ def _build_tagging_defaults(row) -> dict[str, Any]: "metron_user": "", "metron_password": "", "comicvine_key": "", + "comicvine_url": "", } # ``sqlite3.Row`` raises on a missing column, so skip what a sidecar # predating a column doesn't carry (e.g. metron_key) and let the model @@ -386,10 +387,10 @@ def _build_tagging_defaults(row) -> dict[str, Any]: defaults["default_sources"] = json.loads(row["default_sources"] or "[]") defaults["delete_original"] = bool(row["delete_original"]) # ``active_session_id`` / ``active_prompts`` used to be on this row before - # they moved to the Django cache as transient state, and ``metron_url`` / - # ``comicvine_url`` before the custom URL overrides were removed. Older - # sidecar backups still carry those columns — since this comprehension - # walks ``coalesced`` rather than the row, they are silently ignored. + # they moved to the Django cache as transient state, and ``metron_url`` + # before that no-op override was removed for good. Older sidecar backups + # still carry those columns — since this comprehension walks ``coalesced`` + # rather than the row, they are silently ignored. return defaults diff --git a/codex/user_data/schema.sql b/codex/user_data/schema.sql index 35c680d1d..5156c0729 100644 --- a/codex/user_data/schema.sql +++ b/codex/user_data/schema.sql @@ -176,6 +176,7 @@ CREATE TABLE IF NOT EXISTS tagging_defaults ( metron_user TEXT NOT NULL DEFAULT '', metron_password TEXT NOT NULL DEFAULT '', comicvine_key TEXT NOT NULL DEFAULT '', + comicvine_url TEXT NOT NULL DEFAULT '', updated_at TEXT ); diff --git a/codex/user_data/serializers.py b/codex/user_data/serializers.py index 2f78ae89b..714d9b68d 100644 --- a/codex/user_data/serializers.py +++ b/codex/user_data/serializers.py @@ -381,6 +381,7 @@ def serialize_tagging_defaults( "metron_user": defaults.metron_user or "", "metron_password": defaults.metron_password or "", "comicvine_key": defaults.comicvine_key or "", + "comicvine_url": defaults.comicvine_url or "", "updated_at": _datetime_str(getattr(defaults, "updated_at", None)), }, ) diff --git a/codex/views/admin/tagging_validate.py b/codex/views/admin/tagging_validate.py index 7fb4aeb56..03afdb2a9 100644 --- a/codex/views/admin/tagging_validate.py +++ b/codex/views/admin/tagging_validate.py @@ -26,6 +26,7 @@ "metron_user", "metron_password", "comicvine_key", + "comicvine_url", ) diff --git a/frontend/src/components/admin/tabs/stats-tab.vue b/frontend/src/components/admin/tabs/stats-tab.vue index a301c164d..933f3246e 100644 --- a/frontend/src/components/admin/tabs/stats-tab.vue +++ b/frontend/src/components/admin/tabs/stats-tab.vue @@ -103,6 +103,7 @@ const TAGGING_LABELS = Object.freeze({ defaultFormatCount: "Write Formats", hasMetronCredentials: "Metron Credentials", hasComicvineCredentials: "Comic Vine Credentials", + comicvineUrlSet: "Custom Comic Vine URL", }); const AUTH_LABELS = Object.freeze({ oidcEnabled: "Single Sign On", diff --git a/frontend/src/components/admin/tabs/tagging-tab.vue b/frontend/src/components/admin/tabs/tagging-tab.vue index f80c510cc..e72190795 100644 --- a/frontend/src/components/admin/tabs/tagging-tab.vue +++ b/frontend/src/components/admin/tabs/tagging-tab.vue @@ -329,14 +329,22 @@ }}

+
- Save Comic Vine API Key + Save Comic Vine Credentials { diff --git a/frontend/tests/unit/stats-tab.test.js b/frontend/tests/unit/stats-tab.test.js index b6c6b5c08..9a3207162 100644 --- a/frontend/tests/unit/stats-tab.test.js +++ b/frontend/tests/unit/stats-tab.test.js @@ -86,6 +86,7 @@ const STATS = { defaultSources: { metron: 1, comicvine: 1 }, hasMetronCredentials: true, hasComicvineCredentials: false, + comicvineUrlSet: false, }, auth: { oidcEnabled: false, diff --git a/frontend/tests/unit/tagging-tab.test.js b/frontend/tests/unit/tagging-tab.test.js index 269922984..eb40cda9e 100644 --- a/frontend/tests/unit/tagging-tab.test.js +++ b/frontend/tests/unit/tagging-tab.test.js @@ -43,6 +43,7 @@ const BASE_DEFAULTS = { hasComicvineCredentials: false, metronKeySet: false, comicvineKeySet: false, + comicvineUrl: "", }; const SOURCE_TIP = "A source can only be enabled once its API key is saved."; @@ -185,9 +186,9 @@ describe("AdminTaggingTab — Online Sources enable checkboxes", () => { expect(vm.metronEnabled).toBe(false); }); - test("no custom URL field is offered for either source", async () => { - // mokkari hardcodes the metron.cloud endpoint, so the override never - // did anything; the Comic Vine twin went with it. + test("only Comic Vine offers a custom URL field", async () => { + // simyan takes a base_url, so a Comic Vine proxy or mirror works. mokkari + // hardcodes the metron.cloud endpoint, so Metron has no such field. const wrapper = mountTab({ hasMetronCredentials: true, hasComicvineCredentials: true, @@ -201,8 +202,11 @@ describe("AdminTaggingTab — Online Sources enable checkboxes", () => { await new Promise((resolve) => setTimeout(resolve, 0)); expect(wrapper.text()).toContain("Save Metron Cloud API Key"); - expect(wrapper.text()).toContain("Save Comic Vine API Key"); - expect(wrapper.text()).not.toContain("Custom URL"); + expect(wrapper.text()).toContain("Save Comic Vine Credentials"); + // Metron's panel renders first, Comic Vine's second. + const [metronPanel, comicvinePanel] = wrapper.findAll(".sourcePanel"); + expect(metronPanel.text()).not.toContain("Custom URL"); + expect(comicvinePanel.text()).toContain("Custom URL"); }); test("Metron reads as an API key source, like Comic Vine", () => { @@ -286,6 +290,45 @@ describe("AdminTaggingTab — Online Sources enable checkboxes", () => { }); }); + test("a custom URL saves on its own, without an API key", async () => { + const wrapper = mountTab(); + const vm = wrapper.vm; + const store = useAdminStore(); + + vm.comicvineUrlLocal = "https://cv.example.com/api"; + await vm.saveComicvineCredentials(); + expect(store.updateTaggingDefaults).toHaveBeenCalledWith({ + comicvineUrl: "https://cv.example.com/api", + }); + expect(vm.comicvineUrlLocal).toBe(""); + // A url is not a credential, so it must not enable the source. + expect(vm.draft.defaultSources).not.toContain("comicvine"); + }); + + test("clearing the Comic Vine key clears the custom URL too", () => { + const wrapper = mountTab({ hasComicvineCredentials: true }); + const store = useAdminStore(); + + wrapper.vm.clearComicvineCredentials(); + expect(store.updateTaggingDefaults).toHaveBeenCalledWith({ + comicvineKey: "", + comicvineUrl: "", + }); + }); + + test("testing Comic Vine validates against the typed URL", async () => { + const wrapper = mountTab({ hasComicvineCredentials: true }); + const vm = wrapper.vm; + const store = useAdminStore(); + + vm.comicvineUrlLocal = "https://cv.example.com/api"; + await vm.testComicvineCredentials(); + expect(store.validateTaggingCredentials).toHaveBeenCalledWith({ + source: "comicvine", + comicvineUrl: "https://cv.example.com/api", + }); + }); + test("an install with only a legacy login can still be tested", () => { // No key typed and metronKeySet false, but credentials exist server side. const vm = mountTab({ hasMetronCredentials: true, metronKeySet: false }).vm; diff --git a/tests/test_admin_singleton_settings.py b/tests/test_admin_singleton_settings.py index 5627d80f7..505fceca7 100644 --- a/tests/test_admin_singleton_settings.py +++ b/tests/test_admin_singleton_settings.py @@ -157,6 +157,54 @@ def test_legacy_login_survives_a_put_that_omits_the_key(self) -> None: assert defaults.metron_user == "stored_user" assert defaults.metron_password == "stored_pw" # noqa: S105 + def test_comicvine_url_saves_alone_and_reads_back(self) -> None: + """The custom URL is not a secret: it saves without a key and echoes.""" + from codex.models import ComicboxTaggingDefaults + + resp = _put_envelope( + self.client, + "/api/v4/admin/tagging-defaults", + {"comicvineUrl": "https://cv.example.com/api"}, + ) + assert resp.status_code == HTTPStatus.OK, resp.content + body = resp.json()["data"] + # Unlike the keys, the url reads back — the form shows it as a + # placeholder. + assert body["comicvineUrl"] == "https://cv.example.com/api" + # A url is not a credential, so the source stays unconfigured. + assert body["hasComicvineCredentials"] is False + defaults = ComicboxTaggingDefaults.objects.get(pk=1) + assert defaults.comicvine_url == "https://cv.example.com/api" + assert defaults.comicvine_key == "" + + def test_clearing_comicvine_credentials_clears_the_url_too(self) -> None: + from codex.models import ComicboxTaggingDefaults + + ComicboxTaggingDefaults.objects.filter(pk=1).update( + comicvine_key="key", + comicvine_url="https://cv.example.com/api", + ) + resp = _put_envelope( + self.client, + "/api/v4/admin/tagging-defaults", + {"comicvineKey": "", "comicvineUrl": ""}, + ) + assert resp.status_code == HTTPStatus.OK, resp.content + body = resp.json()["data"] + assert body["hasComicvineCredentials"] is False + assert body["comicvineUrl"] == "" + defaults = ComicboxTaggingDefaults.objects.get(pk=1) + assert defaults.comicvine_key == "" + assert defaults.comicvine_url == "" + + def test_tagging_defaults_rejects_a_malformed_comicvine_url(self) -> None: + resp = _put_envelope( + self.client, + "/api/v4/admin/tagging-defaults", + {"comicvineUrl": "not a url"}, + ) + assert resp.status_code == HTTPStatus.BAD_REQUEST, resp.content + def test_tagging_defaults_rejects_unknown_source(self) -> None: """An unknown source name is a 400, not a silently-stored junk value.""" resp = _put_envelope( diff --git a/tests/test_credential_validator.py b/tests/test_credential_validator.py index ba0ea1cf8..2023bcac5 100644 --- a/tests/test_credential_validator.py +++ b/tests/test_credential_validator.py @@ -156,6 +156,28 @@ def test_comicvine_success(self) -> None: instance.list_publishers.assert_called_once_with( params={"limit": "1"}, max_results=1 ) + # No override means simyan's own default endpoint. + assert "base_url" not in mocked.call_args.kwargs + + def test_comicvine_custom_url_overrides_the_endpoint(self) -> None: + """A saved custom URL becomes simyan's base_url.""" + creds = OnlineCredentials( + comicvine_key="key", comicvine_url="https://cv.example.com/api" + ) + with patch("simyan.comicvine.Comicvine") as mocked: + instance = MagicMock() + instance.list_publishers.return_value = [] + mocked.return_value = instance + results = validate_credentials(creds, {"comicvine"}) + assert results == {"comicvine": ValidationResult(ok=True)} + assert mocked.call_args.kwargs["base_url"] == "https://cv.example.com/api" + + def test_comicvine_url_alone_is_not_credentials(self) -> None: + """A custom URL cannot authenticate; the key is still required.""" + creds = OnlineCredentials(comicvine_url="https://cv.example.com/api") + results = validate_credentials(creds, {"comicvine"}) + assert results["comicvine"].ok is False + assert "required" in (results["comicvine"].error or "").lower() def test_comicvine_auth_failure(self) -> None: from simyan.errors import AuthenticationError @@ -202,9 +224,8 @@ def test_unknown_source_silently_skipped(self) -> None: assert results == {} -# Sanity: catch accidental drift in the comicbox dataclass. The url fields -# are still defined there; codex no longer forwards them since the custom -# URL overrides were removed (mokkari ignores its url entirely). +# Sanity: catch accidental drift in the comicbox dataclass. Codex forwards +# every field here except ``metron_url``, which mokkari ignores entirely. _EXPECTED_CRED_FIELDS: Final = frozenset( { "metron_key", diff --git a/tests/test_onlinetag_session_manager.py b/tests/test_onlinetag_session_manager.py index 4dab49541..3323adefa 100644 --- a/tests/test_onlinetag_session_manager.py +++ b/tests/test_onlinetag_session_manager.py @@ -888,3 +888,29 @@ def test_no_metron_credentials_at_all_is_unconfigured(self) -> None: }, ) assert self.manager._build_credentials() is None # noqa: SLF001 + + def test_comicvine_custom_url_reaches_comicbox(self) -> None: + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={ + "comicvine_key": "key", + "comicvine_url": "https://cv.example.com/api", + }, + ) + credentials = self.manager._build_credentials() # noqa: SLF001 + assert credentials is not None + assert credentials.comicvine_url == "https://cv.example.com/api" + + def test_a_custom_url_alone_is_not_credentials(self) -> None: + """A url cannot authenticate, so it must not configure the source.""" + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={ + "metron_key": "", + "metron_user": "", + "metron_password": "", + "comicvine_key": "", + "comicvine_url": "https://cv.example.com/api", + }, + ) + assert self.manager._build_credentials() is None # noqa: SLF001 diff --git a/tests/test_tag_by_id.py b/tests/test_tag_by_id.py index aea3867d2..9314e5471 100644 --- a/tests/test_tag_by_id.py +++ b/tests/test_tag_by_id.py @@ -136,6 +136,16 @@ def test_build_explicit_id_config_passes_legacy_login(self) -> None: assert auth.user == "u" assert auth.password == "p" # noqa: S105 + def test_build_explicit_id_config_passes_the_comicvine_url(self) -> None: + """Tagging by explicit id honors the custom Comic Vine endpoint.""" + creds = OnlineCredentials( + comicvine_key="k", comicvine_url="https://cv.example.com/api" + ) + settings = build_explicit_id_config("comicvine", 456, creds) + auth = settings.online.auth.sources["comicvine"] + assert auth.key == "k" + assert auth.url == "https://cv.example.com/api" + def test_build_explicit_id_config_merge(self) -> None: # Merge: both sources pinned by explicit id (primary first), first_wins # off, auth for both. comicbox runs every id-pinned source and merges. diff --git a/tests/test_telemeter_privacy.py b/tests/test_telemeter_privacy.py index 5f4562073..e4d6a2c4b 100644 --- a/tests/test_telemeter_privacy.py +++ b/tests/test_telemeter_privacy.py @@ -131,6 +131,7 @@ def _seed_secrets(cls) -> None: metron_user=f"{SENTINEL}-metron-user", metron_password=f"{SENTINEL}-metron-password", comicvine_key=f"{SENTINEL}-comicvine-key", + comicvine_url=f"https://{SENTINEL}-comicvine.example.com", default_sources=["metron", f"{SENTINEL}-source"], ) cls._set_singleton( diff --git a/tests/test_user_data_dump.py b/tests/test_user_data_dump.py index 7691cdc5c..695ed376b 100644 --- a/tests/test_user_data_dump.py +++ b/tests/test_user_data_dump.py @@ -94,6 +94,7 @@ def test_dump_round_trip_tagging_defaults(self) -> None: ComicboxTaggingDefaults.objects.filter(pk=1).update( metron_key="token", comicvine_key="key", + comicvine_url="https://cv.example.com/api", ) counts = dump_user_data() @@ -103,6 +104,7 @@ def test_dump_round_trip_tagging_defaults(self) -> None: assert len(rows) == 1 assert rows[0]["metron_key"] == "token" assert rows[0]["comicvine_key"] == "key" + assert rows[0]["comicvine_url"] == "https://cv.example.com/api" def test_dump_settings_browser_show_flags(self) -> None: """show.{publishers,imprints,series,volumes} → show_{p,i,s,v} columns.""" diff --git a/tests/test_user_data_restore.py b/tests/test_user_data_restore.py index 7a9068c68..c46633133 100644 --- a/tests/test_user_data_restore.py +++ b/tests/test_user_data_restore.py @@ -372,6 +372,7 @@ def _row(**overrides) -> dict: "metron_user": "", "metron_password": "", "comicvine_key": "", + "comicvine_url": "", } row.update(overrides) return row @@ -400,3 +401,20 @@ def test_legacy_login_round_trips(self) -> None: ) assert defaults["metron_user"] == "u" assert defaults["metron_password"] == "p" # noqa: S105 + + def test_comicvine_url_round_trips(self) -> None: + from codex.user_data.restore import _build_tagging_defaults + + defaults = _build_tagging_defaults( + self._row(comicvine_url="https://cv.example.com/api") + ) + assert defaults["comicvine_url"] == "https://cv.example.com/api" + + def test_a_retired_metron_url_column_is_ignored(self) -> None: + """Sidecars predating the removal carry a column the model dropped.""" + from codex.user_data.restore import _build_tagging_defaults + + defaults = _build_tagging_defaults( + self._row(metron_url="https://metron.example.com") + ) + assert "metron_url" not in defaults From 25e4c7e924791becf0073ca6872bf8a633200a80 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 3 Aug 2026 23:52:42 -0700 Subject: [PATCH 68/82] bump version and update deps --- bun.lock | 24 +- frontend/bun.lock | 196 ++++---------- frontend/package.json | 8 +- package.json | 8 +- pyproject.toml | 2 +- uv.lock | 610 +++++++++++++++++++++--------------------- 6 files changed, 376 insertions(+), 472 deletions(-) diff --git a/bun.lock b/bun.lock index d2c8ed3e0..d4bfd08c3 100644 --- a/bun.lock +++ b/bun.lock @@ -182,7 +182,7 @@ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.65.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.65.0", "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.63.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/typescript-estree": "8.63.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.66.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/typescript-estree": "8.66.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A=="], @@ -230,7 +230,7 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.12.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA=="], - "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.24", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-U5isuChJ58H1M6oRGaIotYbH2gM1rhzKUjtiarqpLNG/3+fzXIXs5RyTLrYQJmQPnXbvIMPFvI9z8ufpFZc8lg=="], + "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.26", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-2eawZk4MZvkEtMZFdScmCE0R1Rti85uRZpmbe9eAv5Q3blDZ5OxlrC9IHlSnxhDCqOO2xKNaD3oufl9BUlI0yA=="], "@xml-tools/parser": ["@xml-tools/parser@1.0.11", "", { "dependencies": { "chevrotain": "7.1.1" } }, "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA=="], @@ -406,7 +406,7 @@ "eslint-plugin-package-json": ["eslint-plugin-package-json@1.6.2", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.5.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-Zp0CdqXKQqB9luUYa/TOwnGq3VlLotx1UDflZOzEt3D5A5kxu3Lj1VFJ087jKudEhc+K3kF20XV/aKZAlDuslw=="], - "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.10.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.62.1", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-HiqpDrUDbGrMC6iHQbemgDyHJ0366Vyz/qRWmxQcSAkmG25cXr8BdRgx8yAhOKhEfBXn8Rnf/mTCsV4EqUJSxg=="], + "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.10.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.65.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Kprsp9Us0GqAesYaAIzUViw57xYp5WBqzXrcE0Mtww++E5fexWXYBipMuuD7yvyH4vvpBH0+oJ+OMAmZ0oYXkw=="], "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.6", "", { "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.13" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ=="], @@ -424,7 +424,7 @@ "eslint-plugin-vue": ["eslint-plugin-vue@10.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.4", "semver": "^7.8.5", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.3.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-dL9x9rBHqqNcByWiLOHK6L0SB97V82/NC0cZRn9cXPjM7pCuWlpQQP9bFH4vjBv80ej1ZpzAkuD8zWH1o9bZbA=="], - "eslint-plugin-vue-scoped-css": ["eslint-plugin-vue-scoped-css@3.1.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "es-toolkit": "^1.46.1", "postcss": "^8.4.31", "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "eslint": ">=9.38.0", "postcss-scss": "^4.0.3", "postcss-styl": "^0.12.0", "vue-eslint-parser": ">=7.1.0" }, "optionalPeers": ["postcss-scss", "postcss-styl"] }, "sha512-GIskMvLPnDtiu88rWXQHy2b2QZ4j959N5UgghML64jH0sg3Km+HRa9m7nkpcEBGLD4iA4vtMDbBIoLdFcbT8lQ=="], + "eslint-plugin-vue-scoped-css": ["eslint-plugin-vue-scoped-css@3.1.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "es-toolkit": "^1.46.1", "postcss": "^8.4.31", "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "eslint": ">=9.38.0", "postcss-scss": "^4.0.3", "postcss-styl": "^0.12.0", "vue-eslint-parser": ">=7.1.0" }, "optionalPeers": ["postcss-scss", "postcss-styl"] }, "sha512-Kt3VC6C14HnbAzkumVA+p420BBdoym/gJjQtyYBX6V2ktOTDOVRfPDj9qJwHfVevJ0nOY5WeLXa0SV1wvSwGMQ=="], "eslint-plugin-yml": ["eslint-plugin-yml@3.7.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-cLkVHdBHyRifThJA3ufuEW+imPRO7rHBQXdWgthouY6qEapUw8/0zW6V8NcMEGWzuMykc/ITYl6AuhZLPzI+fw=="], @@ -1138,7 +1138,11 @@ "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], - "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.63.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.63.0", "@typescript-eslint/tsconfig-utils": "8.63.0", "@typescript-eslint/types": "8.63.0", "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0" } }, "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g=="], + + "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.66.0", "", {}, "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.66.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.66.0", "@typescript-eslint/tsconfig-utils": "8.66.0", "@typescript-eslint/types": "8.66.0", "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg=="], "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], @@ -1232,11 +1236,13 @@ "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.63.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.63.0", "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="], - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.63.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.66.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.66.0", "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g=="], - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.63.0", "", { "dependencies": { "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.66.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g=="], + + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.66.0", "", { "dependencies": { "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], @@ -1262,6 +1268,8 @@ "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], diff --git a/frontend/bun.lock b/frontend/bun.lock index b62780c64..bf64bd648 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -58,13 +58,9 @@ "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], - "@devframes/hub": ["@devframes/hub@0.7.11", "", { "dependencies": { "birpc": "^4.0.0", "destr": "^2.0.5", "nostics": "^1.2.0", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "tinyexec": "^1.2.4", "zigpty": "^0.2.1" }, "peerDependencies": { "devframe": "0.7.11" } }, "sha512-r+qyGg0Hu1w1q7mNDCmuZUryeZAi2sUC8FTwEbwjqPT5SGjWnIoj1rMmRbJIOIBaC+2ofOejTGel4jklmzHc3w=="], + "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - "@devframes/json-render": ["@devframes/json-render@0.7.11", "", { "dependencies": { "@json-render/core": "^0.19.0", "nostics": "^1.2.0", "zod": "^4.3.6" }, "peerDependencies": { "@devframes/hub": "0.7.11", "devframe": "0.7.11" }, "optionalPeers": ["@devframes/hub"] }, "sha512-Iza65fq6U9OSxKUEhGokfpfMz21ISLkWF9jeS0bQHugTLKRGcPRO/tu3YE+nTe6EiKP3kQFBvSNZ1FcZ1FPfSg=="], - - "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], @@ -104,8 +100,6 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@json-render/core": ["@json-render/core@0.19.0", "", { "dependencies": { "zod": "^4.3.6" } }, "sha512-vvcyZ+10EDZKbEyB1J2kXOGfDaiZR2LurZGSqi2r5STHyKr+Te85DWaBxTwRGgM7U1LtIvNx85BzzjElRKoAIg=="], - "@mdi/font": ["@mdi/font@7.4.47", "", {}, "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw=="], "@mdi/js": ["@mdi/js@7.4.47", "", {}, "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ=="], @@ -138,47 +132,7 @@ "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], - "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.140.0", "", { "os": "android", "cpu": "arm" }, "sha512-ZfjDZ422mo7eo3b3VltqNsV9kmv1qt/sPEAMSl64iOSwhVfd0eIZ9LB79Mbs1xYXJnk7WSROwzBCKDIiVxPTvQ=="], - - "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.140.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Ia8jSvikUX6Sf+Ht+KOCUF/k1HpR0VlmqIYymubmWDebOEGtsyliHDR6JxsZ4IX3/c/GbrB1uh09aVGQv/LQmQ=="], - - "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.140.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-G6VK0nK61pH0d0mBjUqSZbVxGqqO5uzeginLDQj+gOO6ObfJjXRwgkD/ol0w1INcnFeAb6YGGO7qc3ueGHaycQ=="], - - "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.140.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-HazBOuZzd2pO1C2uMmp8Gv7mhzMHqKSKDS1OZfcLEvpIcgA+48J92HEtNanVHDIzRD9PRPCV6aS6fkZIWOVl8Q=="], - - "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.140.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9hSUU+HmTUyOe4JzMHxNGgLWNY7rrO+6ShicZwImNJacEAACDMIkuEQQkvXSL+WJN50jaNtLYJv8s4OcBdpyUQ=="], - - "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.140.0", "", { "os": "linux", "cpu": "arm" }, "sha512-RAEuQsYtS0KcDFqN0ABTjyyNlokS91JeuDuoW9tEbG0JTbRNXnpQUdbYc/16JoA6Z/2ALbNrE3KmxtqDiuIjCQ=="], - - "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.140.0", "", { "os": "linux", "cpu": "arm" }, "sha512-c4CkHvPvqfojouredJ0w3e6+jiBq0SbFyhH61kr/zPb/7XsaYTNKQ54vmlSsopfdQbNDX40ZeK9Abs2Qet6wcw=="], - - "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.140.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yrjmLj8ixPB25yqvPGr28meGjb+keed7m1GqqY/0uqkhZIoT4t9zmfwUgFEtC33C7dtE+UQ7TU0IaVxf97SWJg=="], - - "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.140.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ggGMQTN8Agwxp2WiLMpdY671dt0qTDJWiWlJeig3HnUwTnerRl0J2JdGVghWBeDcss2D9S2V2Js6dZHEiVabVA=="], - - "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.140.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-IgTs8xYAFgAUGNmR65tIqjlJ8vKgrfXzC515e9goSdfMyKQV4aJpd2pUUudU4u51G64H0/DSEJEXKOraxm9ZCA=="], - - "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.140.0", "", { "os": "linux", "cpu": "none" }, "sha512-A1x+PMWZmSGaFVOx2YeNTFau8uD+QO14/vLP4GrcuvUPs3+nBkUOjy9Lus86ftHsDojjYMbvBelmKc3F7Rv08g=="], - - "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.140.0", "", { "os": "linux", "cpu": "none" }, "sha512-zBqpfRo2myWPrPo5xUjeZqlnPXPXsX8BcWtWff66/eGRQdbPjhzPgXa/F+AtxT2afUViPxbuDlwscMKzQ5tg+g=="], - - "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.140.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2M1DPm/8w9I//YzFlFC9qXw+r2tJFh5CYwRlYTq2vUJQS7qoQftEDeCZ8EnN7KHtvSiXvYj8mZI5pR7DpXmcEw=="], - - "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.140.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8aRDbZ/U/jO8N7go1MO72jtbpb4uswV8d7vOkMvt/BPgZiyEYvl1VIWK4ESxZZhnJ4tqwVldgX7dNiP/eB1Jdg=="], - - "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.140.0", "", { "os": "linux", "cpu": "x64" }, "sha512-xRqpeI8U2sQQS1W5BMWRyMTxtagkuLG2dEWruet5lFsWHTvBth11/TpSaJatHdqVVwHN0q3uuoS9zRsGinq8hg=="], - - "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.140.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbGRe26MqAKciFRvXeHNQJ6VAHYs9R4miP89sEAncysM3n+f4lnyLWgsa9kklJNpfnxdq2yRoNYHFqwBckVimw=="], - - "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.140.0", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vFiC1hqys+hkX1GnQkIoiTQJNiUm43Z0lO35ETKXTw0YtpW7+cN58YRRXFAQQ+TgpkIi3lrhcxdlnqz+Oi3ptQ=="], - - "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.140.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-fGSQldwEYKhM+H8uLt76Op8hh5+FYaR6lvvQ1Txw3Mhn86DyQXLcI0fi1EkFlTK7F+46OCk/j0AJMzZQm6g5Xg=="], - - "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.140.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-sDS2Bai+g3ZWYwfZqmosiSuFDBcVnZ3Ta6pszzsiJoLMqsJEWKcxXXbGa7b7yXr++W2lQNPb3ZRJ8czseqL7RA=="], - - "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.140.0", "", { "os": "win32", "cpu": "x64" }, "sha512-kHbE1zWyb5OQgJA6/5P4WjiuB01sYdQwtZnSSyE58FQEXDAMnyeeq4vj7KgN75i5SlBzOs8A5MrtlD3gOlDKqQ=="], - - "@oxc-project/types": ["@oxc-project/types@0.140.0", "", {}, "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ=="], + "@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.23.0", "", { "os": "android", "cpu": "arm" }, "sha512-8IJyWRLVAyhTfe9/TIEbQqSQnl5rUqYJrUOS6Dkr+Mq9FGHMxDGeiEmwkBqCvDP5KckpPh/GYSgbag66O6JsCw=="], @@ -336,13 +290,9 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@unhead/bundler": ["@unhead/bundler@3.2.3", "", { "dependencies": { "@vitejs/devtools-kit": "^0.4.1", "magic-string": "^1.0.0", "oxc-parser": "^0.140.0", "oxc-walker": "^1.0.0", "ufo": "^1.6.4", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.2.3", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "rolldown": ">=1.0.0-beta.0", "unhead": "^3.2.3", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "esbuild", "lightningcss", "rolldown", "vite", "webpack"] }, "sha512-fL1LRdTyPYe9dzenElL96yaFW0LcXTUl1RSa5a2ni0n/GxMtvioPwz9y/fD4JnbVG//Kwn3YCv1jfUmlhcoW4g=="], - - "@unhead/vue": ["@unhead/vue@3.2.3", "", { "dependencies": { "@unhead/bundler": "3.2.3", "hookable": "^6.1.1", "unhead": "3.2.3", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-u1qi/0tDyytDSAex8+JMgeDMDlBh3RuzFmTaPOEmbAIx4BG9ej/18ChfM962DC0G7kKhOiuqiSpCfWR5S3XKGg=="], + "@unhead/bundler": ["@unhead/bundler@3.3.0", "", { "dependencies": { "magic-string": "^1.1.0", "oxc-walker": "^1.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.3.0", "@vitejs/devtools-kit": "^0.4.1", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0", "unhead": "^3.3.0", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "@vitejs/devtools-kit", "esbuild", "lightningcss", "oxc-parser", "rolldown", "vite", "webpack"] }, "sha512-H5Zvq5PjT6/ojRROs2PqE4MOrvwuTcMfYUQTx9HUdmS3uqfqK9pKZEkZTLC/8WUVWyiyPv6NW3+CK+3w0IUaww=="], - "@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.1", "", { "peerDependencies": { "valibot": "^1.4.0" } }, "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A=="], - - "@vitejs/devtools-kit": ["@vitejs/devtools-kit@0.4.5", "", { "dependencies": { "@devframes/hub": "^0.7.11", "@devframes/json-render": "^0.7.11", "devframe": "^0.7.11", "local-pkg": "^1.2.1", "mlly": "^1.8.2", "nostics": "^1.2.0", "nypm": "^0.6.8" }, "peerDependencies": { "vite": "*" } }, "sha512-fBjJ8Dyv0XvtyxxWyraah3+WC+7QDePfRQZ60Hh+gS0KrbuvgxoESiBVikE5IG9hwy03WliOM4yPWO/sVRP3+A=="], + "@unhead/vue": ["@unhead/vue@3.3.0", "", { "dependencies": { "@unhead/bundler": "3.3.0", "hookable": "^6.1.1", "unhead": "3.3.0", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-VUCYDCAIt7+izwxVqkHdZ4vP/gsrNYCUZ3Jp5/k8oDPAqbv/gu2p40NrQbM7OXNrUlHCCvJ0qZE+0nXrVtHTqg=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], @@ -428,7 +378,7 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], @@ -442,8 +392,6 @@ "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], - "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -460,8 +408,6 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "crossws": ["crossws@0.4.10", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA=="], - "css-select": ["css-select@4.3.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", "domhandler": "^4.3.1", "domutils": "^2.8.0", "nth-check": "^2.0.1" } }, "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], @@ -480,12 +426,8 @@ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "devframe": ["devframe@0.7.11", "", { "dependencies": { "@valibot/to-json-schema": "^1.7.1", "birpc": "^4.0.0", "crossws": "^0.4.10", "destr": "^2.0.5", "h3": "2.0.1-rc.22", "mrmime": "^2.0.1", "nostics": "^1.2.0", "pathe": "^2.0.3", "ufo": "^1.6.4", "valibot": "^1.4.2" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "cac": "^7.0.0" }, "optionalPeers": ["@modelcontextprotocol/sdk", "cac"] }, "sha512-EC65NA7B1Q1H0daTMiPn2BLCjekIVC8V1WvoPyXAM/pBhNGGlRRaSVSfmAsaYmqSDT3osD8nWZ6KYhExU2KOfA=="], - "dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], @@ -564,8 +506,6 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "h3": ["h3@2.0.1-rc.22", "", { "dependencies": { "rou3": "^0.8.1", "srvx": "^0.11.15" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA=="], - "happy-dom": ["happy-dom@20.11.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg=="], "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], @@ -618,29 +558,29 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], @@ -648,9 +588,7 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "magic-regexp": ["magic-regexp@0.11.0", "", { "dependencies": { "magic-string": "^0.30.21", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "unplugin": "^3.0.0" } }, "sha512-LG77Z/gVnwz7oaDpD4heX6ryl+lcr4l1B2gnP4MMvt2pGhGC1Dfj7dl1pXpP4ih+VQFLuAadeKVa+lARAzfW+Q=="], - - "magic-string": ["magic-string@1.0.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA=="], + "magic-string": ["magic-string@1.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g=="], "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], @@ -660,8 +598,6 @@ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], - "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], @@ -682,19 +618,15 @@ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - "nypm": ["nypm@0.6.8", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.2.4" }, "bin": { "nypm": "./dist/cli.mjs" } }, "sha512-Q9K4Diu6l5u6xJQogeFSs/zKtyMSgFKFtRQV+tHP4kL7KPm2grpBU0dFIwFaXwNxN0MtfKWc43VpCugAa+LPsw=="], - "obug": ["obug@2.1.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="], "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "oxc-parser": ["oxc-parser@0.140.0", "", { "dependencies": { "@oxc-project/types": "^0.140.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.140.0", "@oxc-parser/binding-android-arm64": "0.140.0", "@oxc-parser/binding-darwin-arm64": "0.140.0", "@oxc-parser/binding-darwin-x64": "0.140.0", "@oxc-parser/binding-freebsd-x64": "0.140.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.140.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.140.0", "@oxc-parser/binding-linux-arm64-gnu": "0.140.0", "@oxc-parser/binding-linux-arm64-musl": "0.140.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.140.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.140.0", "@oxc-parser/binding-linux-riscv64-musl": "0.140.0", "@oxc-parser/binding-linux-s390x-gnu": "0.140.0", "@oxc-parser/binding-linux-x64-gnu": "0.140.0", "@oxc-parser/binding-linux-x64-musl": "0.140.0", "@oxc-parser/binding-openharmony-arm64": "0.140.0", "@oxc-parser/binding-wasm32-wasi": "0.140.0", "@oxc-parser/binding-win32-arm64-msvc": "0.140.0", "@oxc-parser/binding-win32-ia32-msvc": "0.140.0", "@oxc-parser/binding-win32-x64-msvc": "0.140.0" } }, "sha512-h6QFWd6lBMfjESqgQ27GjzrSDb0qbznp7VDQqp2zvgsrWut4vcchyMIzOVXvGQ2GMZgKw9RWrFNWv9WqGL0p7Q=="], - "oxc-resolver": ["oxc-resolver@11.23.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.23.0", "@oxc-resolver/binding-android-arm64": "11.23.0", "@oxc-resolver/binding-darwin-arm64": "11.23.0", "@oxc-resolver/binding-darwin-x64": "11.23.0", "@oxc-resolver/binding-freebsd-x64": "11.23.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.23.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.23.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.23.0", "@oxc-resolver/binding-linux-arm64-musl": "11.23.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.23.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.23.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.23.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.23.0", "@oxc-resolver/binding-linux-x64-gnu": "11.23.0", "@oxc-resolver/binding-linux-x64-musl": "11.23.0", "@oxc-resolver/binding-openharmony-arm64": "11.23.0", "@oxc-resolver/binding-wasm32-wasi": "11.23.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.23.0", "@oxc-resolver/binding-win32-x64-msvc": "11.23.0" } }, "sha512-f0+l598CJMOLnYPXsXxttJALH0ljtivdRMKtvHhxRuWa5FYmw5+qODARl8oYjMC/brpzKcrpdORsOBrTqhBZ9A=="], - "oxc-walker": ["oxc-walker@1.0.0", "", { "dependencies": { "magic-regexp": "^0.11.0" }, "peerDependencies": { "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0" }, "optionalPeers": ["oxc-parser", "rolldown"] }, "sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ=="], + "oxc-walker": ["oxc-walker@1.1.1", "", { "peerDependencies": { "@oxc-project/types": ">=0.98.0", "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0" }, "optionalPeers": ["@oxc-project/types", "oxc-parser", "rolldown"] }, "sha512-Hwd7dq28zu/Er99+bpWp16CGwFrhyalJh0W3JOB7XpPvgWo3MqMi2ksWGKuzzA9zt50mJ2pIUwR5lpAdZ9ACNQ=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -742,16 +674,12 @@ "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], - "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], "rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.0.1", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^18.0.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg=="], - "rou3": ["rou3@0.8.1", "", {}, "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA=="], - "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "sass": ["sass@1.102.0", "", { "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw=="], @@ -774,8 +702,6 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "srvx": ["srvx@0.11.21", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-GWTHjKMeekX8CwJf4VU9Oo6mJpSGaflGMddbCvR+Cmmh9sslRMiGbAoqqZacE0r1ncARh6buCEETr2W52F8b1w=="], - "stable-hash-x": ["stable-hash-x@0.1.1", "", {}, "sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], @@ -850,15 +776,13 @@ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-level-regexp": ["type-level-regexp@0.1.17", "", {}, "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg=="], - "typeface-roboto": ["typeface-roboto@1.1.13", "", {}, "sha512-YXvbd3a1QTREoD+FJoEkl0VQNJoEjewR2H11IjVv4bp6ahuIcw0yyw/3udC4vJkHw3T3cUh85FTg8eWef3pSaw=="], "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "unhead": ["unhead@3.2.3", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-BBkKvthUOufEJzG4C4u5NCdxnGMNaQdb//oPDI8lkv/6qj0faEqnPIvIPejt3FnSE501LX25Gbe2MVgKDuFnyQ=="], + "unhead": ["unhead@3.3.0", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-cMqydgLO/qSJBshSM8nVFpihwjefJ5X+G3xhw5QwyPtkcS4+G1c3WFJ+UP9QEYor/2NfZ5hzGXl8iUMpsRxXkA=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], @@ -870,8 +794,6 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "valibot": ["valibot@1.4.2", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg=="], - "vite": ["vite@8.2.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.23", "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ=="], "vite-plugin-checker": ["vite-plugin-checker@0.14.5", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "chokidar": "^5.0.0", "npm-run-path": "^6.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.4", "proper-lockfile": "^4.1.2", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@biomejs/biome": ">=2.4.12", "eslint": ">=9.39.4", "meow": "^13.2.0 || ^14.0.0", "optionator": "^0.9.4", "oxlint": ">=1", "stylelint": ">=16.26.1", "typescript": "*", "vite": ">=5.4.21", "vue-tsc": "~2.2.10 || ^3.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "meow", "optionator", "oxlint", "stylelint", "typescript", "vue-tsc"] }, "sha512-c9lQ92eisUO+F7Fd93aelojmiOS+NQpPgQ1XR2LTQHox1/laZf4yAoQj+L3RA9Vgh10e2nFd9b8r2LLyYZsbpA=="], @@ -918,7 +840,7 @@ "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], - "xior": ["xior@0.8.3", "", { "dependencies": { "tiny-lru": "^11.2.11" } }, "sha512-yHzIBnpGS3qhSjGnvi5IvLQVPlWXDYmvtaO6TfOaIUMes/6lFnoHL02cdHo5xPj3JUSfEts9Q3hTiVGfnHAh5w=="], + "xior": ["xior@0.8.4", "", { "dependencies": { "tiny-lru": "^11.2.11" } }, "sha512-aY2tUpDuLSKFfcgHs3HpS1cLy9Di31zBfw7CQvmsLONh4TOIfBg/B+5n3HE+xinJbhDvaCgtG5vmEgSXgz/axQ=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -930,38 +852,20 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zigpty": ["zigpty@0.2.1", "", {}, "sha512-MR9JqJx2wf5f4wz8zpx050AlqrmWeIW+1h0SO5iEyhG3HFRjY5luC3szS2ux2EGuPjE5OU9ZAuiCBeMWBTrqZw=="], - - "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], "@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], - "@devframes/hub/nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], - - "@devframes/json-render/nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - - "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - - "@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - - "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - "@types/ws/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], - "@vitejs/devtools-kit/nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], - "@vitest/mocker/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "@vitest/snapshot/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -986,8 +890,6 @@ "@vue/compiler-ssr/@vue/shared": ["@vue/shared@3.5.39", "", {}, "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA=="], - "@vue/devtools-kit/birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], - "@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.40", "", { "dependencies": { "@vue/compiler-core": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA=="], @@ -1000,8 +902,6 @@ "cross-spawn/path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "devframe/nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], - "dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], "editorconfig/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -1012,22 +912,16 @@ "happy-dom/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], - "magic-regexp/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "magic-string-ast/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "vite/rolldown": ["rolldown@1.2.1", "", { "dependencies": { "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.1", "@rolldown/binding-darwin-arm64": "1.2.1", "@rolldown/binding-darwin-x64": "1.2.1", "@rolldown/binding-freebsd-x64": "1.2.1", "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", "@rolldown/binding-linux-arm64-gnu": "1.2.1", "@rolldown/binding-linux-arm64-musl": "1.2.1", "@rolldown/binding-linux-ppc64-gnu": "1.2.1", "@rolldown/binding-linux-s390x-gnu": "1.2.1", "@rolldown/binding-linux-x64-gnu": "1.2.1", "@rolldown/binding-linux-x64-musl": "1.2.1", "@rolldown/binding-openharmony-arm64": "1.2.1", "@rolldown/binding-wasm32-wasi": "1.2.1", "@rolldown/binding-win32-arm64-msvc": "1.2.1", "@rolldown/binding-win32-x64-msvc": "1.2.1" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw=="], "vitest/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -1064,28 +958,6 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - - "vite/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - - "vite/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - - "vite/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - - "vite/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - - "vite/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - - "vite/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - - "vite/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - - "vite/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - - "vite/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - - "vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], - "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.142.0", "", {}, "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ=="], "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA=="], @@ -1118,6 +990,8 @@ "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ=="], + "vitest/vite/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "vitest/vite/postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="], "vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.40", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ=="], @@ -1140,6 +1014,28 @@ "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw=="], + "vitest/vite/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "vitest/vite/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "vitest/vite/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "vitest/vite/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "vitest/vite/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "vitest/vite/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "vitest/vite/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "vitest/vite/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "vitest/vite/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "vitest/vite/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "vitest/vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "vitest/vite/postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], "vue/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], diff --git a/frontend/package.json b/frontend/package.json index 34ca1cc04..5fe2f4104 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "codex", - "version": "2.2.5", + "version": "2.2.6", "private": true, "description": "ui for codex api", "type": "module", @@ -16,7 +16,7 @@ "test:ci": "vitest run --reporter verbose --update" }, "dependencies": { - "@unhead/vue": "^3.2.3", + "@unhead/vue": "^3.3.0", "@vueuse/core": "^14.4.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", @@ -27,14 +27,14 @@ "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", "vuetify": "^4.1.7", - "xior": "^0.8.3" + "xior": "^0.8.4" }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", "@types/node": "^26.1.2", - "@unhead/bundler": "^3.2.3", + "@unhead/bundler": "^3.3.0", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", diff --git a/package.json b/package.json index 51d2f9c11..05aa8fe20 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.24", + "@vitest/eslint-plugin": "^1.6.26", "eslint": "^10.8.0", "eslint_d": "^15.0.3", "eslint-config-prettier": "^10.1.8", @@ -83,16 +83,16 @@ "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", "eslint-plugin-package-json": "^1.6.2", - "eslint-plugin-perfectionist": "^5.10.0", + "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-regexp": "^3.1.1", "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", - "eslint-plugin-unicorn": "^72.0.0", + "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-vue": "^10.10.0", - "eslint-plugin-vue-scoped-css": "^3.1.1", + "eslint-plugin-vue-scoped-css": "^3.1.3", "eslint-plugin-yml": "^3.7.0", "postcss-scss": "^4.0.9", "prettier": "^3.9.6", diff --git a/pyproject.toml b/pyproject.toml index f3306cce5..28467066f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ readme = "README.md" requires-python = ">=3.12" license = "GPL-3.0-only" name = "codex" -version = "2.2.5" +version = "2.2.6" [[project.authors]] name = "AJ Slater" email = "aj@slater.net" diff --git a/uv.lock b/uv.lock index 6c0f3de57..6aa5e83ef 100644 --- a/uv.lock +++ b/uv.lock @@ -387,87 +387,87 @@ wheels = [ [[package]] name = "cffi" -version = "2.1.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, - { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, - { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, - { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, - { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, - { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, - { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, - { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, - { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, - { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, - { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, - { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, - { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, - { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, - { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, - { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, - { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, - { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, - { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, - { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, - { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, - { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, - { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, - { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, - { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, - { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, - { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, - { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, - { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, - { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, - { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, - { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, - { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, - { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, - { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, - { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, - { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, - { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, - { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, - { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, - { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, - { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, - { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, ] [[package]] @@ -567,7 +567,7 @@ wheels = [ [[package]] name = "codex" -version = "2.2.5" +version = "2.2.6" source = { editable = "." } dependencies = [ { name = "adrf" }, @@ -888,71 +888,71 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, - { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, - { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, - { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, - { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, - { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, - { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, - { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, - { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, - { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, - { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, - { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, - { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, - { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, - { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, - { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, - { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, - { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, - { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, - { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, - { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, - { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, - { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, - { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, - { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, - { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, - { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, - { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, - { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, - { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, - { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, - { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, - { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, - { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, - { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, +version = "7.15.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/45/78dbf9604ee5b3db24efbf26bed1cb58862fb40480cba821963c69348751/coverage-7.15.3.tar.gz", hash = "sha256:ae7ea5a4614acf399ef0483c4cb34f8f8f01df848d8fcbe7d3ce0865733f1c4d", size = 935592, upload-time = "2026-08-02T18:50:17.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/6c/bac99d9d4c6abe856e93bf3f5212982ac0bfac126dd4a042753bd53bc5af/coverage-7.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:79a3e32e83227d83d9684459ed579769b56c369ac2d7313099b2d9e031d2e10f", size = 222499, upload-time = "2026-08-02T18:48:15.018Z" }, + { url = "https://files.pythonhosted.org/packages/aa/bc/cb9a39b083bc1aa70586482dab25c9be20bab0ec6c155340e50d9066bb1e/coverage-7.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:767feb87c5886d781d0a69fafd450a20826ddab7b79bce1665deb64d21441b60", size = 222866, upload-time = "2026-08-02T18:48:16.884Z" }, + { url = "https://files.pythonhosted.org/packages/58/fb/beaa453d62000a0a5b39838bee2a137afe609a50a71f55e83c73461e513b/coverage-7.15.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50951e37033c40548d777b8a8454a2cd622dba1136780065678dccaec307c47f", size = 254367, upload-time = "2026-08-02T18:48:18.507Z" }, + { url = "https://files.pythonhosted.org/packages/66/64/43e72500ed6815cef189f9193f29d7af4b078830337c95ea976cd0c0d427/coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:63a4ff67364afb2cac826b8bbd78a5c50ce656a7b7137436b44d7b96a9271088", size = 257103, upload-time = "2026-08-02T18:48:20.172Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/2893e2937adfe02f45fd38e4a8a0a0d8b7a02ff9e012ac3d009bee3c4f16/coverage-7.15.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95e42856509675fe26560310313a6117640e96f9a1e19bb3d220116a27c94c", size = 258220, upload-time = "2026-08-02T18:48:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/30/b4/d5e6e2eb1a62961083734291304b1f85df72e2abe95c76eb88a7f472afd0/coverage-7.15.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abad631cba27094b4631993f4c72e89ac0ca1b3a0236c7abaf8ca79aea619851", size = 260481, upload-time = "2026-08-02T18:48:23.682Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/9b72c5c6a9798a9a12cf65f66e077cc1fdd396e61915c862688f9afe1cae/coverage-7.15.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b0807f1f051dd82a234ad6acdb6f1425baede60be1e84e862496c8cc9262ab9", size = 254749, upload-time = "2026-08-02T18:48:25.32Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/e1c2f759e2dbce559ba85c40c0e4acfecc6cff4b740c294c88e41ccc6111/coverage-7.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8d6df7aeb5bc464040bbc9ae173d875785d3677ebc4307817997d622d74225e", size = 256138, upload-time = "2026-08-02T18:48:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ab/48cc7e760f769e86ae290a125ea6e7209dfbdbbbb7ff4f5d9d1ee7a45d57/coverage-7.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:974471c506c9f5758808b47c1ebf7949ecd0848f5c1020e78675fefe5ff46866", size = 254283, upload-time = "2026-08-02T18:48:29.082Z" }, + { url = "https://files.pythonhosted.org/packages/15/26/39529a68154f99b3a1829debd8b25eac384effeec890a293b5bbdcb49186/coverage-7.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5cba0c9c13e35c86df7998f1afaf6b1da224a3a39e4da59bdabf60c148046dcb", size = 258352, upload-time = "2026-08-02T18:48:30.892Z" }, + { url = "https://files.pythonhosted.org/packages/91/2f/55b82aa3d8d7dd8023a56e7c5c2a70e39a3c44b3353c6cf3faec9ad51566/coverage-7.15.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4d608dc36a364dce33acbf4fc3a50f9d2054c945f233bb0a2cdb4b90bfa17646", size = 253852, upload-time = "2026-08-02T18:48:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/839f4045124cd3518ecf2c58967e58a911202834e7c5a03cfdf2ab0b29f6/coverage-7.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2395869280554a1941da904423c12660c39f721315e1c02d076a7fe0971382f0", size = 255725, upload-time = "2026-08-02T18:48:34.848Z" }, + { url = "https://files.pythonhosted.org/packages/75/21/d25e3e2a9e327798078c877f469dfb6def860bf6e25036529046227d3e15/coverage-7.15.3-cp312-cp312-win32.whl", hash = "sha256:24f3b21840c3eb76cef3cc70b2bf6649010c64471a84a446538a39306e1ba04d", size = 224566, upload-time = "2026-08-02T18:48:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/df90cc1e8d095ce263968a93e04829821b2afb31ac2752c06a2e0a8e3c13/coverage-7.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:fa7b17902c3c1dd8a7adb52679b7f6340bba08443d710c8838e04db8cf62be2a", size = 225098, upload-time = "2026-08-02T18:48:38.941Z" }, + { url = "https://files.pythonhosted.org/packages/65/c7/ec49e43c58967a07163e2d1c6bbd58112b825b2772ab66784afd6a5400ba/coverage-7.15.3-cp312-cp312-win_arm64.whl", hash = "sha256:fcbe83fb7258eacd293bf5322d88807acb35ed12a5cfa99dd8215c083e3b0235", size = 224485, upload-time = "2026-08-02T18:48:40.682Z" }, + { url = "https://files.pythonhosted.org/packages/68/6e/62ae61e1fc434956bec38ed1d5b1c494f58cf579dbd998e77abffe7b3e6b/coverage-7.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1182eed05674c63d40951fae27c43e822749f04d25f75df64c2e4fa3168678de", size = 222522, upload-time = "2026-08-02T18:48:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/13/ff/c74c673d81e0e77b6608c3d21331e3db42e30daeb3c8a0a8860d4c9e2e14/coverage-7.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c0c4b0d7c4cd56e470d0c9d8441f42e8a96cdfd95050fec027f1d4dd9f11006c", size = 222894, upload-time = "2026-08-02T18:48:44.274Z" }, + { url = "https://files.pythonhosted.org/packages/a1/91/ccb30f5ffafd7d69d0b18e5162f9b711a5654e807b7b0c13497f0826b33f/coverage-7.15.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5c9fce9f4998b0d50a753da765b9215a14decc7863822c89d72da7a89ca625b3", size = 253890, upload-time = "2026-08-02T18:48:46.097Z" }, + { url = "https://files.pythonhosted.org/packages/29/c6/e92a66cda49a2751b09826d51258f199b92aa0cb005bc5f34e9729a52a9c/coverage-7.15.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a47e2a0a0ace9241e70ee00e44520f88b843094603dd54303f1bafecd929c30", size = 256484, upload-time = "2026-08-02T18:48:47.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/730929164b457cf25cf76c23898b90f9039a104a647890801b6586797b14/coverage-7.15.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95bad94f83807ae60ed76f3ac012f69b2605ac9ea81bee959a5a483f7fa09c10", size = 257723, upload-time = "2026-08-02T18:48:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/9e/be/04cb5672cb19f5c389eda81ba22d89807699a949653d3625b0e0fda169da/coverage-7.15.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:228e172a76c428bb17d1ab78a2ff188990b0597e5dbd291f52a4edf7412de049", size = 259854, upload-time = "2026-08-02T18:48:51.413Z" }, + { url = "https://files.pythonhosted.org/packages/96/25/5e7fd6af39f6507071455944b8906dd1fe5b7b6bffb6a163ceb20afa0d13/coverage-7.15.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cea9fb33887c99349996266f1fd60abe5af3577a90633392001d27ef46b4b66e", size = 254085, upload-time = "2026-08-02T18:48:53.158Z" }, + { url = "https://files.pythonhosted.org/packages/23/c8/55e58a853f1e61163a6e755897bd14a059d78411e86560f39d9951c019b5/coverage-7.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81760de3155d7f52c21860c4046628dc6bed182f72e3c028e2b4fd46f65aa040", size = 255850, upload-time = "2026-08-02T18:48:55.031Z" }, + { url = "https://files.pythonhosted.org/packages/be/74/8bcec66dbcf3d22bea2a0b2b77ee2fa6f766a647d0023d4eabbc4f2b2756/coverage-7.15.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b47ea0a1d3a3d089826c6cbfad8429d7d8872e28e86baa95ddef330f6875da21", size = 253818, upload-time = "2026-08-02T18:48:57.163Z" }, + { url = "https://files.pythonhosted.org/packages/ce/06/450b673fdfece0997b4e16a31d6bde6b18889c578f1013ddd34c962ac6f9/coverage-7.15.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5459ba486b2a5d58a6c05254779ecdf525e7f20174d0210ceda75ba40fdb8f2c", size = 257973, upload-time = "2026-08-02T18:48:59.098Z" }, + { url = "https://files.pythonhosted.org/packages/56/fd/3ec7409aec0ddc943132452b65672f065f043b844f1830e1fe173c98b3ab/coverage-7.15.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c59209f80a08dbfcdd5109a80dc623cd3b9d22895c85757d34f57a6e6e95570f", size = 253638, upload-time = "2026-08-02T18:49:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/75/20/30a8dabb194123631c93f860fdd86401ad405d56cfb1841873afbfe4e92b/coverage-7.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f863856c1779d4a5bb6a94698a2f9073e09c6706501f76f3e7780e72df97d21c", size = 255407, upload-time = "2026-08-02T18:49:03.143Z" }, + { url = "https://files.pythonhosted.org/packages/13/4d/e14365b1953b43653341412f9088b0d752614c626a73a705ff9af400f3a3/coverage-7.15.3-cp313-cp313-win32.whl", hash = "sha256:00cbdc5e322927dc30c5e42b863819b1bb867cc66f26ab5372c585850876ab93", size = 224575, upload-time = "2026-08-02T18:49:05.011Z" }, + { url = "https://files.pythonhosted.org/packages/1c/64/88f762ea80de2070207246faef514513be874486b2773528f2cc2b4b515c/coverage-7.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:835528518a1d823cf336740324b2f335f7c01e609e74abcb5d5163b3e66661e3", size = 225116, upload-time = "2026-08-02T18:49:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/ab/66/03c34c53a319f522554cd29d4f2e16c5eab61aa4cdcf55753129fd7d926c/coverage-7.15.3-cp313-cp313-win_arm64.whl", hash = "sha256:0d2e1f2cbbf36b842f3e2aff8d118c60d677adb498bc6c7fa9c6838738f82767", size = 224509, upload-time = "2026-08-02T18:49:09.129Z" }, + { url = "https://files.pythonhosted.org/packages/35/6f/8c2dc014357618b3226c90f731b8282766c3685786f422558991dc49fbf2/coverage-7.15.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1e3bb08ad574bd9fb6a991f645728f70d333c1c1958dd5fcde65e24cb862813d", size = 222571, upload-time = "2026-08-02T18:49:11.242Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/d867c7ceae9d56b7e74ee61ea834f1aa4f9a1e1c7f0ce39393ba573b1c12/coverage-7.15.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e5860eaff02a0b7f1b73304bdf846596ee62ab3a78d25c68044ebf684cb1fef", size = 222902, upload-time = "2026-08-02T18:49:13.448Z" }, + { url = "https://files.pythonhosted.org/packages/62/77/4f6dfc490c5f2bcacb2d296d9aa4d1e128c43b48e94ad313fec7f49f09ad/coverage-7.15.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:60874e5bd67f0b1bdbe42ab42c7bafa66a6fb8de88721af6df3f7a02713960cd", size = 253947, upload-time = "2026-08-02T18:49:15.304Z" }, + { url = "https://files.pythonhosted.org/packages/16/8a/6777f192af264165103e2a3d3768dbadb9894a0a2359a16877141d9ae8f5/coverage-7.15.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9147be876e9d83765e0b82176674dc248a6b9283e25e01e7462611b97e9b731", size = 256452, upload-time = "2026-08-02T18:49:17.801Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7b/3d7ac46a0234bc684f41ee42be95e29b2b6525695adb04083609d5ac2149/coverage-7.15.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61a01f8c3804760fcc5a3d31c4f3cab792d660d44e17bf7adeaf0ea51e07821e", size = 257798, upload-time = "2026-08-02T18:49:19.878Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/c6ee59c29afcb5fdb35f936381340d1a06429a07c48f20e809646647acbe/coverage-7.15.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95bf3e7f26f792e25eb185f85a5a659d48479265176dcfe22b6f334fd0081b5c", size = 260112, upload-time = "2026-08-02T18:49:21.858Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e1/e8ea39a46e89e3a143312ee5f80336e992e3ae8fe44bf9c76b83fefeed42/coverage-7.15.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44c41eff9e413fed8740eca75d5438ebeb9d3e45e7cd37c67329213e7a72c764", size = 253944, upload-time = "2026-08-02T18:49:23.926Z" }, + { url = "https://files.pythonhosted.org/packages/95/67/31ab5f6a37fd887d1386f81f0da9306851ad2264e9baaa9c7f606e0b3e17/coverage-7.15.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54146bafb61f3ba9895b43af0dd17eba01561d586d44ce84ea221b0cbbee5a9e", size = 255805, upload-time = "2026-08-02T18:49:25.973Z" }, + { url = "https://files.pythonhosted.org/packages/fb/6a/ee505a80c8fd89620fb337c0596daecff87f33171fbb4ee3015fc3d7331f/coverage-7.15.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:af000dd1bb859ff8066fda4c79512ff938c798116540307226b373099c7b151f", size = 253769, upload-time = "2026-08-02T18:49:27.883Z" }, + { url = "https://files.pythonhosted.org/packages/b0/41/6ab0f81c9e89660230d8f3f581d4732e5ddb75a885b0a5dfc73d315dc94f/coverage-7.15.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a1b82490577f3889950b5a04f18712aef0207243e0749d60fe28c3c73ebfd5fd", size = 258045, upload-time = "2026-08-02T18:49:30.201Z" }, + { url = "https://files.pythonhosted.org/packages/bc/62/c995e91cae28cf31d6defab3bfb553dda5ac83ac7381b0f2b121264c307a/coverage-7.15.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c4fc90a60154c3e4b8a2dc206d6dbe852f1c235c249e0dc0cef909d032c9591a", size = 253587, upload-time = "2026-08-02T18:49:32.349Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/f2049980f82d6890321f2065f9e66216eabbf4b2001815db958bc543f40a/coverage-7.15.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f25bb884814a892948b4c20394db3f2364dd452d9492736479e7a493e63b0eb6", size = 255243, upload-time = "2026-08-02T18:49:34.324Z" }, + { url = "https://files.pythonhosted.org/packages/1d/82/2c841b67a978c0eb9c3707630b68f93f9e7585d78bb906bc8823ec6b07a5/coverage-7.15.3-cp314-cp314-win32.whl", hash = "sha256:722dbf8e7828fbcfe0dc8586167dc0a5ce85ad6ea171dbb21ed3f8d6581d3cb8", size = 224759, upload-time = "2026-08-02T18:49:36.326Z" }, + { url = "https://files.pythonhosted.org/packages/b3/78/5c93ec43784fd3e404ca23cd0584ae24bc1732de4a3fc194b68c3be88db0/coverage-7.15.3-cp314-cp314-win_amd64.whl", hash = "sha256:64d0845f9c3ed47302bed265c15ab4dbb64aa4ec1490839b8e328f4e7fa914d2", size = 225246, upload-time = "2026-08-02T18:49:38.366Z" }, + { url = "https://files.pythonhosted.org/packages/9d/77/813a054371f3b018cc63c6bdb46a3c35d5e95d4e3ed4f1449d4196106db5/coverage-7.15.3-cp314-cp314-win_arm64.whl", hash = "sha256:69bc14684f8fbbee9f9dbaa4fe79719b0da9725fc37956785c06ec365acf6926", size = 224673, upload-time = "2026-08-02T18:49:40.552Z" }, + { url = "https://files.pythonhosted.org/packages/8f/63/8c9f36cc71178d26db930baa03a4494abcc516d8d41bf820d0d85ef1d80b/coverage-7.15.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f92df943c24b96cb215ca26b4f6a2283e63c5db80f1635aceea7fff11311917b", size = 223298, upload-time = "2026-08-02T18:49:42.634Z" }, + { url = "https://files.pythonhosted.org/packages/54/66/211f24d058ce9f56ebf1420d55b7574fdae924f6da3836f83c8bd4793e38/coverage-7.15.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:66591c46bdd2971d3ae2bc503a5f0459c2edcaf6b7e045b292000cc95bc6cb95", size = 223568, upload-time = "2026-08-02T18:49:44.706Z" }, + { url = "https://files.pythonhosted.org/packages/dd/bb/9c2ad5574a0d6420a96c6cade4f8a683931b9e79fe609f8924d7b6964616/coverage-7.15.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa64458b81b18bfc67cdf1f6dc02b23e3edc672f2f8e11771fad75865415a43", size = 264932, upload-time = "2026-08-02T18:49:47.153Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/938c39e77bdd5a0a440412f975609ce3702dabbda6ac715719d93ca45a7b/coverage-7.15.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:447f5421ccf5475956cf516d4ca1d575f487947b6f4e11f9d80c6aefe24b3dc8", size = 267052, upload-time = "2026-08-02T18:49:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a3/7b431a98af35d9cc6394e54cde9435b33b8591672fbece6a4931267d7a8e/coverage-7.15.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a0c77ef8cd483a4987a5d12d1d9d5f7ee598dfdc6c0844417d847e5768dc779", size = 269473, upload-time = "2026-08-02T18:49:51.599Z" }, + { url = "https://files.pythonhosted.org/packages/32/58/dbc9951dce46be47a732823a1c571f62bcabdd54a68d8c281489a1a55cfb/coverage-7.15.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0b273f4ff657446a06c2d85bf80e134fa869a92852ba5f87854a70e1fb44da77", size = 270591, upload-time = "2026-08-02T18:49:53.865Z" }, + { url = "https://files.pythonhosted.org/packages/71/bd/1d610772c7c0889bfe477a59c46ee66ea53e271f3f06951e9d55b317f7c6/coverage-7.15.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daea8c4fafa22488600405be2c2be525a9406fba3fc0a83acc726db3e14e2005", size = 264007, upload-time = "2026-08-02T18:49:55.875Z" }, + { url = "https://files.pythonhosted.org/packages/69/97/852eb3dcdba156b1a9078503f098499916bf889f964b61ad4a08223ac169/coverage-7.15.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93ff57c530f3fa7aa69f92fb9b8892b8aa82712aa970842f4abf28657f42fb57", size = 266926, upload-time = "2026-08-02T18:49:57.944Z" }, + { url = "https://files.pythonhosted.org/packages/52/f8/b72cd238757fba2b587fc7dee047efe6e10b0c18343509faaaf502dd4680/coverage-7.15.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4df21bef8b800eebda9018f53d49c9ace3aeb0090c850139b27923aafcb83e91", size = 264529, upload-time = "2026-08-02T18:50:00.035Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0a/6c52ec4b7fb007cb6433d1fcfda4080cb15d75ad37ef9c31025f3427293e/coverage-7.15.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:db567b02685f26034adcbd85055f80d12cdf02111b8ed00886093d98b2874ce2", size = 268263, upload-time = "2026-08-02T18:50:02.161Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4e/f1f9aa3efd109a04353563a43fb5155340c1fdcdeaa6296ebed3b6f510ea/coverage-7.15.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5318dd51b8600b947e058cf5a4fe54d183d9d13c49b97b64ca7be05a34df9bef", size = 263377, upload-time = "2026-08-02T18:50:04.243Z" }, + { url = "https://files.pythonhosted.org/packages/dd/fb/6b268a0b2728ef1c379ad656b899274477a5f6bed1bf6765b4b387fb0601/coverage-7.15.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c995bfa383c54704839b6c4c2627a1c00895597ada0e5e8190c81d8bd620555c", size = 265688, upload-time = "2026-08-02T18:50:06.428Z" }, + { url = "https://files.pythonhosted.org/packages/29/54/1a3ea96e5d5e7cd41dc432597bfc60692910e635d05e1cc25a8ccc243581/coverage-7.15.3-cp314-cp314t-win32.whl", hash = "sha256:6433fafb8da0e1d02eb53411e0ecdadb6b88f0224fdc23317e703c0e88937d42", size = 225066, upload-time = "2026-08-02T18:50:08.533Z" }, + { url = "https://files.pythonhosted.org/packages/31/9d/a7b0d9afd18ed5274dd00651a78e7810a931c70d94b79996f150bec1a30f/coverage-7.15.3-cp314-cp314t-win_amd64.whl", hash = "sha256:fe578952b1b29fe8c777f43f241d49efac4b56724a3434f5d22ebe3c208df429", size = 225897, upload-time = "2026-08-02T18:50:10.572Z" }, + { url = "https://files.pythonhosted.org/packages/ca/11/34c5ae40b945e69aa72b87dc268135b7049905f3824af573b7073acbb946/coverage-7.15.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d2e1acb7aee29dfa8f3e48c23f36670898baca1209d9bdd3985a50c7f982165e", size = 225212, upload-time = "2026-08-02T18:50:12.63Z" }, + { url = "https://files.pythonhosted.org/packages/37/e7/7069b3d6c018917f49ba2e1c5fb910e498c7fefa3a1b78cb1b79e61ff45d/coverage-7.15.3-py3-none-any.whl", hash = "sha256:da78fa6fc7dafe4212839173133ee85afcf42c5cd5f3e47fa7c1c210453b445e", size = 214297, upload-time = "2026-08-02T18:50:14.709Z" }, ] [[package]] @@ -1390,63 +1390,73 @@ wheels = [ [[package]] name = "granian" -version = "2.7.9" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/cc/9c752e6173df02c5e37c0df7bffd50c1341109e4b4f8e5073bfd3a72dc82/granian-2.7.9.tar.gz", hash = "sha256:096d9a3396b13826bc63d2cf424ed04daf1ea077beed361d48dca2d55cb4b527", size = 129789, upload-time = "2026-07-03T12:42:03.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/7d/ea14f692056fc08d625aa62b404c10781393f98eadef0208d2fade1f5028/granian-2.7.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e04d86c24948dbf3de0b6b1ac9a9d6c82514f5a6c4f27fe49991e5178a1d0d9f", size = 6534691, upload-time = "2026-07-03T12:40:33.299Z" }, - { url = "https://files.pythonhosted.org/packages/68/6e/6e172bad9a56ee79b9eb530dc45f993159872eb948868c6eb529828d46d8/granian-2.7.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3d741def2ad09c8e30880c909023c6d6b78ebe528c5c2e95de8523d967f625f1", size = 6209984, upload-time = "2026-07-03T12:40:34.666Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/f580baa79fc38c6d0ac83e6047150e66d842e5b2e393acc5188f356c73e4/granian-2.7.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:497c99fc3d4fe0342add24e3c53da78f0d9505ec332dae9075527e00918617d4", size = 7161937, upload-time = "2026-07-03T12:40:36.203Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ed/600a820132ce60a987091b8688e60fad3276fd63c1b726c329958d5a4148/granian-2.7.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e49f859672fe1e8e062f3be791bec2b3169505719ccf83459ff48aac52ee1d0f", size = 6440102, upload-time = "2026-07-03T12:40:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/e9/c2/f38c504bdd150a5f8dc4907309d5467df827401614edabe0d7453e384770/granian-2.7.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a720940970ce47b4b348d4e3c9fefe6ce0a40c46ac851c2e4cc0dcf5bd6e2a7", size = 6951056, upload-time = "2026-07-03T12:40:38.953Z" }, - { url = "https://files.pythonhosted.org/packages/78/01/f6ece64a623e604eac9024751aaf1fab11bf38483a8563a7e009ffa55e00/granian-2.7.9-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:809ec8a9f44c69c49b158811ad78a3195d5e361662ce93dcb7afb848f5f2fd10", size = 7106920, upload-time = "2026-07-03T12:40:40.311Z" }, - { url = "https://files.pythonhosted.org/packages/62/18/e6f612945ab888cd7a4ff0d42a51f0d3d274bb1860ef90dfa7386d25ffbb/granian-2.7.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea963e616edba406969579e296f853bee164db23c1a63b8d2268459797c64810", size = 7053385, upload-time = "2026-07-03T12:40:41.6Z" }, - { url = "https://files.pythonhosted.org/packages/db/01/25eda803061400b4176d7bb3f144948a065dc76299112ac9ec1d91871cc4/granian-2.7.9-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:61270c22d6172fbb6f8e363bad19e8dcaefde27a9b0685bbffaa09835fdf44e3", size = 7346261, upload-time = "2026-07-03T12:40:43.286Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e3/b3b049d22fc1dedf771410036da06c9bf05f9d24b63474147970ebbb99a0/granian-2.7.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b3000658f76ef2069aee455d3a0835e36fb073e8fd6e95d777cc0179eb97f5b2", size = 6991885, upload-time = "2026-07-03T12:40:44.742Z" }, - { url = "https://files.pythonhosted.org/packages/3e/44/d71bf6b7e40f9d44f67b9b0ed861fb89d0912daf88cdcb2b7c69feb7f6c2/granian-2.7.9-cp312-cp312-win_amd64.whl", hash = "sha256:eecd2aa017aa92bc165a9ad33c494e1cfcbfc68a5f055b43e729749899590867", size = 4066976, upload-time = "2026-07-03T12:40:46.135Z" }, - { url = "https://files.pythonhosted.org/packages/34/c4/a66d5c6daf849d012e871ed2684a2e7f81c518ce9f9e827f5900d99d5e7e/granian-2.7.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:23aa08e4d7563f0acb45424676a7d892a146493b5db0895efb5bc8e5121e3051", size = 6534637, upload-time = "2026-07-03T12:40:47.541Z" }, - { url = "https://files.pythonhosted.org/packages/c0/5e/27fe98fabcae553e3f5087b46f3185a257ed9ee5d7f145d2fd03309bcb60/granian-2.7.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:233baf237d4d3768b4ca6cb6d26546de242e437e1af6050405dcc4673032977f", size = 6210219, upload-time = "2026-07-03T12:40:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b3/9f72bd1f36bd3825d35eff3bd8d00c7a01affdec91387fec6e33e314dc6d/granian-2.7.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64eb08bcf4aca0375fae95a27ca2e78e9dfef7ccae8e12daaac8b2f0bd1cc718", size = 7162144, upload-time = "2026-07-03T12:40:50.497Z" }, - { url = "https://files.pythonhosted.org/packages/67/e3/21e699362e4ecd56d254519d042316444b34c2048e32869101db9bfb6db7/granian-2.7.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff89b203cebba0780efa0360b6e23520323d05f799a610d4a07a5681067b7249", size = 6440457, upload-time = "2026-07-03T12:40:51.826Z" }, - { url = "https://files.pythonhosted.org/packages/25/d4/4afb3e49ca32b7b6528c2cde626ff60d43663c3567b500bcfbe40af3ca73/granian-2.7.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4ddd82390b6fb9c025007f7ccd92e99d244211898a7851103f28688889dd905", size = 6950842, upload-time = "2026-07-03T12:40:53.317Z" }, - { url = "https://files.pythonhosted.org/packages/9c/1d/2a19bce46d187296752c9a54cf833f0472232eb6ad4152e99439163fbd1a/granian-2.7.9-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18e3f870a8c42500fe6c8d73d24a6888920e2a8740810ef1ddb83b0b67ce643e", size = 7106973, upload-time = "2026-07-03T12:40:54.916Z" }, - { url = "https://files.pythonhosted.org/packages/e2/36/091e26516a7d9093068665dfeffc61dd87c0cb9b9869ffdf452296ab237b/granian-2.7.9-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5dda81fbf58d8f173b9990e6300525359dc2641b88f03bbbe31cc693db5f530b", size = 7052872, upload-time = "2026-07-03T12:40:56.475Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/a4dca67e6b68014d548d957735521ce68b2e99a5474722663d048c740063/granian-2.7.9-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:cae7ccfd34519241e92311e966763650f839b07b1195541cb0f5c180ef22df99", size = 7346092, upload-time = "2026-07-03T12:40:58.276Z" }, - { url = "https://files.pythonhosted.org/packages/c8/88/842e71b39a3a02a16d49b4495c20150055e0888aec6ceb1b965df3a872a5/granian-2.7.9-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed501eafd6cb3c63924466860bf478ad23691d7ad16678b2069e75b5328a074c", size = 6991558, upload-time = "2026-07-03T12:41:00.072Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2f/51a4db315adbc5e28bf5ac97cb93e04c5e7e6705dc0ef6c8220851453074/granian-2.7.9-cp313-cp313-win_amd64.whl", hash = "sha256:e79316015dd68624e021281b3d0e2d9446f04160db4f14140561fe4c0ffeaaa0", size = 4066887, upload-time = "2026-07-03T12:41:01.634Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7f/62673800ed73a85d5b629f493d06a387834196e9463b115ef4fee35d1928/granian-2.7.9-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:9aa2087ae2103e99ba169f53d718258205bf2ace7df87c9b6dd52e3f71a90335", size = 6331598, upload-time = "2026-07-03T12:41:03.07Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f3/08878d6a5b010e39f168b3cffe5011183eab4464a58144ea5711d22d9888/granian-2.7.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d52c262b5e0d345c0c0684b7536ea42f370a7bbb4720b1b625d60d84d87c81be", size = 6099199, upload-time = "2026-07-03T12:41:04.617Z" }, - { url = "https://files.pythonhosted.org/packages/34/b3/fc344b4bc668d5f4eae9bddbd0eb8f2517214470dd40d178c6c51d14be22/granian-2.7.9-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6d08149dfa9777ba281ddf7d7df7ae5473cf6c2fa8815ac33301a24acf33e875", size = 6299537, upload-time = "2026-07-03T12:41:05.99Z" }, - { url = "https://files.pythonhosted.org/packages/4f/32/65a9b41f107bf76fd23c6c6102eee6ac1112cb84250408d034517cb415aa/granian-2.7.9-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d019f88e7944c3e3967da833e758c85cb6c35c7a82a8185eba23243b5cf24b7c", size = 7210150, upload-time = "2026-07-03T12:41:07.633Z" }, - { url = "https://files.pythonhosted.org/packages/29/f3/60413bf1a6f6da32b10a3dcfb45c2d09de2dc2f6829555323239635b6398/granian-2.7.9-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e155eb6d3c8827ef3821c80ae6a5f7a61f37562d121240128ce099e3db06ad2f", size = 6673489, upload-time = "2026-07-03T12:41:09.429Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/6cd1b05e0a868b224232fb203838272cdd3165d16338a847f726b21926e2/granian-2.7.9-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:ac516f3cbdce733578ee91a8139d624a1dccf9ab782fb9f3ab58254ebe6c29ad", size = 6829812, upload-time = "2026-07-03T12:41:10.945Z" }, - { url = "https://files.pythonhosted.org/packages/9e/72/cbb85914df3e09e9ad6a59406157622a50e64df960e378d1091195a5a0a2/granian-2.7.9-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:73e8fe3d162c66d1eddaee825e59379bbf7a384ff776ae984baf796cf1b81fe3", size = 6976912, upload-time = "2026-07-03T12:41:12.412Z" }, - { url = "https://files.pythonhosted.org/packages/57/7c/ce3d1ef6f8999106a41d828c88f154b6bc6f700950894613e62a3bb46e2e/granian-2.7.9-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:12da3d84a9dce7706a19f6a98e8f7c3624721ff66bc34a60cfc4d58e2d6593b9", size = 7418223, upload-time = "2026-07-03T12:41:13.956Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2f/38daac752e7384c5da56803a65428ef29a196df8e436082813b391877ef5/granian-2.7.9-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:887f0f1e045314044712a2e3799991c7be0ba4129f6806b82e40f502417694d9", size = 6951268, upload-time = "2026-07-03T12:41:15.646Z" }, - { url = "https://files.pythonhosted.org/packages/e4/95/4cbe8728c9e609f33ec676e194a6296cd628bfb9d6c1310f4ab06634f09a/granian-2.7.9-cp313-cp313t-win_amd64.whl", hash = "sha256:2836fd595a144be7e70faebddf1ce7b4e0c136a7017d75660b88c8da63c0f0ff", size = 4003202, upload-time = "2026-07-03T12:41:17.072Z" }, - { url = "https://files.pythonhosted.org/packages/43/fa/b388d36bef00f28f2c99df3ab7a08878116d4d1d654b6f93f7b0ef9cde5b/granian-2.7.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:17e6975266757b05fd59163a3b5ccd7a9d50c227b13e58b9e5b47eff0609c17f", size = 6451170, upload-time = "2026-07-03T12:41:18.484Z" }, - { url = "https://files.pythonhosted.org/packages/8f/ac/2fdc222d98960c5d0a1d1970ee52f9f4e3a953b2862d7be8fb043e74e0c1/granian-2.7.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:554543e1085ff6bf60eb0f0e995a8a412e92165e92113f9bae9e1fdfeec2ca72", size = 6169769, upload-time = "2026-07-03T12:41:20.31Z" }, - { url = "https://files.pythonhosted.org/packages/b2/79/beaa9a25285aea37b7bf9c2fa7357871b51ae1a7ee79501d570386e188ab/granian-2.7.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1f06eb3a1c5ce9bf050e94f37310a6add0410ddee75fc65563e86d1619eea384", size = 7269037, upload-time = "2026-07-03T12:41:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/be/e4/ef1ca00cc17664548427908da36d9a16e29e7adb34318d5173d1c00ecd1c/granian-2.7.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d17720e112e40563bfefa7b8031d37372234b568784c07250322887631f168f8", size = 6539295, upload-time = "2026-07-03T12:41:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/9d/c5/80826359e26079665562b362f0a5f05261183a8b423e31c954f110313bb4/granian-2.7.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607a02fbda1905cc0b0764f09fab1d601299e482ebb1d9722e61ca08742fb0df", size = 6981221, upload-time = "2026-07-03T12:41:24.861Z" }, - { url = "https://files.pythonhosted.org/packages/ab/77/d595c9698b7799e28c4dc9a3091d30dbea9e20e9e92926e47f9c974b7a77/granian-2.7.9-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2eb74974682bf8635dff79f356d3fd86193c8c1ebed1baffa75ad1793fddbc7", size = 7131507, upload-time = "2026-07-03T12:41:26.347Z" }, - { url = "https://files.pythonhosted.org/packages/99/46/5200e2b09feae19b7a96371eb2f1523faf2e99aae60584f286db0263cea6/granian-2.7.9-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:33ba7e10bfd30b196b866a9b4b828c9a108a22325936f7d07def89a6b9f21dce", size = 7067731, upload-time = "2026-07-03T12:41:27.999Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/432aaa769de91176944d3210734e27c223c77b76da359699067cd90d4c65/granian-2.7.9-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:e2cf4c926e99718576e4ba201d884a56b0e62d8ede39c7468b26cbe2d98c30e5", size = 7449207, upload-time = "2026-07-03T12:41:29.481Z" }, - { url = "https://files.pythonhosted.org/packages/d8/83/8e5e429ebb1465c998403a60e0078784d28b819954c387a9558d99b6c3c5/granian-2.7.9-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:665a82ce3f48a4b5e1f4cc83115d3b9a2647b3f222d072db6716223e6dfc86d2", size = 7031706, upload-time = "2026-07-03T12:41:31.209Z" }, - { url = "https://files.pythonhosted.org/packages/51/5c/b82a31436c740dd0a87e091998f9c350739cbd260aa3880db6f5418112a7/granian-2.7.9-cp314-cp314-win_amd64.whl", hash = "sha256:74d6e50277621e2faa41931d4ddaeae0735e39a20ebaccc49a2282549a4d5297", size = 4080721, upload-time = "2026-07-03T12:41:32.64Z" }, - { url = "https://files.pythonhosted.org/packages/11/fb/aa241630b4e987c0b343d08c71e20a89098f9c863a12842afa19930f82a0/granian-2.7.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:1a9b34e5d46a48fa6f017d6afa546f4d35aa6a2a10737e9c49c5c9108e6a4280", size = 6254314, upload-time = "2026-07-03T12:41:34.139Z" }, - { url = "https://files.pythonhosted.org/packages/76/1c/787f63df68e28b9e5793efd80c7ba9e1a0e77663c921525552ac3b0d9305/granian-2.7.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea97a1928b414a35903667440875e0f0f4d2f7a341e8d198ab57f0b2cb70af28", size = 6078452, upload-time = "2026-07-03T12:41:36.228Z" }, - { url = "https://files.pythonhosted.org/packages/b5/60/fcc41cd8f394c12785fc2f9d9843ad4329e90a2468d0aaf3474be3255e90/granian-2.7.9-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:57edbef65585ac69d6ada7b9d7cb6fcd6ebeb2e663833c246f72d3634851f5ac", size = 6298703, upload-time = "2026-07-03T12:41:37.906Z" }, - { url = "https://files.pythonhosted.org/packages/05/0f/555c2f436cf386614e8197ffc3a27c6e3a812149fd84367d2f988361b4fe/granian-2.7.9-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e39cdfcd5a0ed8e9e8a5e21cd65085ac3a4c73ac5f258f804aedc729cdfa2bb", size = 7241919, upload-time = "2026-07-03T12:41:39.679Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4c/13bd13b0dcc2697b521e75efbeede7328c0809f3e93e964362fd334c0dd4/granian-2.7.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c254a1b2027612f0df6e47e8059fd304cda287be25421e1dced4d6b56fb054c", size = 6673295, upload-time = "2026-07-03T12:41:41.483Z" }, - { url = "https://files.pythonhosted.org/packages/02/0a/c0b977247bf3dd4efa92903dd316b8089f7e67cb21922e83cdcdd98201f7/granian-2.7.9-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f9669323b5cd90be7565711bc75363eccf75b6aecdbb0b286f1a860199ef800c", size = 6830753, upload-time = "2026-07-03T12:41:42.979Z" }, - { url = "https://files.pythonhosted.org/packages/08/b2/8f50b2d83452ee3100fcd1b26a5de5206b8befeb9d36b79e6c20d5f6dacd/granian-2.7.9-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:c6475981a0cbf766575a0146bdbe52439185a5040cef2a3969214ca1ef6a5e18", size = 6976795, upload-time = "2026-07-03T12:41:44.6Z" }, - { url = "https://files.pythonhosted.org/packages/92/3a/bec3533aaaff69a9a984f3fd578a2ca29548c9d8503770b1ba5c2260aecd/granian-2.7.9-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3022cde5c662716db8fe16838ba351e0897768435b9c732afc9a4a7322ed05d5", size = 7420697, upload-time = "2026-07-03T12:41:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d9/4526cc50a1fe3addcc29fe3c7c676d64046f724a4d8e4e29d76ba7dcbe04/granian-2.7.9-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c7e3193da47554561142be7a58520b36a01f6a2da57a7bcbe61f970fb53cc0c", size = 6950255, upload-time = "2026-07-03T12:41:48.023Z" }, - { url = "https://files.pythonhosted.org/packages/0a/62/aa89482ffbc3e56d533cf8c87a91cfb58c486e6a77c522fa34c9f8874113/granian-2.7.9-cp314-cp314t-win_amd64.whl", hash = "sha256:d2cef5a15afee90683944a3b23694e97d75adeafd59ef85ff3896bc9462695d2", size = 3992366, upload-time = "2026-07-03T12:41:49.523Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/58/22/f289f681277c8a1cd2c29177fc21bad080b270d6b60ac6e91a19cafe795a/granian-2.8.0.tar.gz", hash = "sha256:be842c6737b41beb6fe6521f9c46695a1843ec9e086a18bbbc945ed170ff28e2", size = 129256, upload-time = "2026-08-03T18:58:55.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ab/432706e82c0bb95ecaf9aeee26d36c13b2917a358377ecf07675b363d61a/granian-2.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:de8fc64f423f000a0097886e0c4e8ae1bfb4b4d0699edc6244da768c05c0b40b", size = 4442542, upload-time = "2026-08-03T18:56:43.266Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a5/b7835933f999a73d8fd5b15f6734f6abb40edac4d759b2490a069762c1ad/granian-2.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76fdd00bb5842a7993ce57e7d254e75d5e9d7425b49d02a341b93d618047f1fa", size = 4264376, upload-time = "2026-08-03T18:56:45.041Z" }, + { url = "https://files.pythonhosted.org/packages/18/63/5245e1d11a3920b1d7705d6748e736b07a48f1415817a2b7b0eab921b26c/granian-2.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:edca648754927c913dfcf52068b96418b66810017fd628d19cb1b65f78dfd9a6", size = 4812379, upload-time = "2026-08-03T18:56:46.69Z" }, + { url = "https://files.pythonhosted.org/packages/93/00/a2dd16309d39be4888fe90da677692f0148feff589438b536425ffd3d3de/granian-2.8.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:899b1af43754ee8ef6beaece26947cee3fa03487f0193d5fc17fb55435903a68", size = 4446390, upload-time = "2026-08-03T18:56:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/95/87/8f9e929ac017233ce14123601765d095305b407df0a512ae8c14ea741380/granian-2.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278db6788bacb37e9e57abb429229f74c5b5f6f5dca06f66fad027a4ff6dc51c", size = 4860867, upload-time = "2026-08-03T18:56:50.346Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2e/5124c9c4d66d3e8405f3cc5fa4074d06a703546456c002587af9d3c2822a/granian-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5827ee36b7e5af4a104430d802cc0454913dd2f984b0b594c42ba049a1deeb61", size = 5023347, upload-time = "2026-08-03T18:56:52.17Z" }, + { url = "https://files.pythonhosted.org/packages/5a/14/d4624e7c22c7b26d1ba0e296a6c25621a6daeedd09a1399fdf9c34e2e8ec/granian-2.8.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67e1f73705aac805ec1e7e10c38946a4dd49f26e018d250014d1fd9a924dca64", size = 4993318, upload-time = "2026-08-03T18:56:53.693Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7b/55fd1dca67c273460fe13f622cfbcf17812b27077656bb9c7982b88e9d31/granian-2.8.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:89d4904bb657503ab41b304340c42371c9f9b01f83b6c7c136f5a0d8bff50efd", size = 4923508, upload-time = "2026-08-03T18:56:55.918Z" }, + { url = "https://files.pythonhosted.org/packages/18/b7/1ee1f572b86c5681387ca10e10750c3d35583db43aa85213bbc33f2cf6f3/granian-2.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49bbcf8c20370cba6d0d5ba1aac95f5e9d22a3e95b10b420d49337629d2f4fcc", size = 4924703, upload-time = "2026-08-03T18:56:57.783Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2f/0b4e8a671e52b868140a80b67ddf665aa85622114a2dc28dc550119cbf49/granian-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:471e4d122518ca23776677bcd104c37bdf31377a4a8b8c668e3d6551e90213bf", size = 3112321, upload-time = "2026-08-03T18:56:59.879Z" }, + { url = "https://files.pythonhosted.org/packages/95/45/1c1f3b80b19813a8c3f025397aea4b56b62d2d422100b10498a7f13bcf35/granian-2.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9e28aa83940629169986615ecbcc39c026e193df41ba8264202fefba5edaa8b6", size = 4442295, upload-time = "2026-08-03T18:57:01.543Z" }, + { url = "https://files.pythonhosted.org/packages/28/1b/e686b7504e125a2dd5757f2b668c0ef4707467d04add2e42cfa9b19f9139/granian-2.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf3ed0a22327aeb1b15e36c3d56c2174a68a0dd6a57554ac5b7456fd231e258a", size = 4263150, upload-time = "2026-08-03T18:57:03.18Z" }, + { url = "https://files.pythonhosted.org/packages/bb/03/740fa22bdbeec4fa55cf017956955532ba760584a9479690442e1c831179/granian-2.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6848acafe50547b2f6c3f5d92b5a3d873dff97267287195a45f18a9270ca2952", size = 4812578, upload-time = "2026-08-03T18:57:04.911Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/4971ab3775153452940c1f38dff2a141168dc2bcffb4348382ed47fe828d/granian-2.8.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f03da08e21e6d0a6b0ac6be3dca4485e2ec1a609e9349e07c390c745f0c8b29c", size = 4446673, upload-time = "2026-08-03T18:57:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/82/f3/bd62b2c9830fa3bfbfbac7e07ab086a0f0b3e5ab6613fcf017a389f975ef/granian-2.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a63cb450f71760a56c80794c0c49bc638d58f8f4e9f91202a21c08014994c", size = 4859343, upload-time = "2026-08-03T18:57:08.593Z" }, + { url = "https://files.pythonhosted.org/packages/23/fd/10345aabf40982f9483671c93be9a1892ece07ca3bfe68babc4171caa946/granian-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6ebfd8af38136054e6ea2e86b18649c5676d924fc3a40383ebbb3af6185e929e", size = 5023131, upload-time = "2026-08-03T18:57:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/42d540dae26873938dbc19cf0d692b9644c8d8a6aee668fa78c79f751897/granian-2.8.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:39b620297be07d71ff92aaf2c52f5a376a7888d251a3e953cad81620751ee0a6", size = 4993372, upload-time = "2026-08-03T18:57:13.361Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/025227fb79b3b7bcfc810922863fdbc05bf3932030d207773bf2bba5bf60/granian-2.8.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:67c1db11193797fe1de61e74ac858fbac4e4d27f1b8c4b437be40b15700fccf8", size = 4923411, upload-time = "2026-08-03T18:57:15.079Z" }, + { url = "https://files.pythonhosted.org/packages/11/a3/aae60898ffb725ebc7f2caa5055d1aa4c6aa49c8ba29dea9f559e2eeea3a/granian-2.8.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4f4fa63ed4fe67ec12f9a26d29779f52c35cbc8c57fe337f7bea25f8c47981f6", size = 4924152, upload-time = "2026-08-03T18:57:16.83Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/5d5b9b3b02f0abe30496ad3cd7123221666fd54b0a3a3d9fa3a18c66de51/granian-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a1d8dce913504188d4fddb12e33515ee5bef6c4658e9eabb0bd5dd43d9c1e4de", size = 3112465, upload-time = "2026-08-03T18:57:18.622Z" }, + { url = "https://files.pythonhosted.org/packages/dc/10/1c8231d6592bce2855d002c0b82939c62bca317c82cf37ac98ed219b94d7/granian-2.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:55aad6e52f7b5d22c468304f5862b154e08ff231c622e956d63f4b5a7f308e36", size = 4423359, upload-time = "2026-08-03T18:57:20.484Z" }, + { url = "https://files.pythonhosted.org/packages/69/37/39b99ffff95b9cce64c9964ff21f15e75b3b5c96f0529b56ecd87a8df037/granian-2.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:95517d0f5c38cbed7b94a6d28dfb6710330b879024a9ee73dacf6b4ade638925", size = 4259820, upload-time = "2026-08-03T18:57:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/e1/32/846784ed1e3b7aa84e473268a9c43cfd2c5b97cb9dd0161e4719e04ecf53/granian-2.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36a53d61e6d5f04af257700d2b83700d64be5f392d0d07a2cb568daa2f778c8e", size = 4805453, upload-time = "2026-08-03T18:57:24.103Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/7e24caa2211fc669076aa223867e2bdb3f0a5bb9d74bbdbe9cfb4c8b5d9a/granian-2.8.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc2f421b899d3a8615ae705016f41a6e0a4940c7005dbf58fc8962a412bd760", size = 4444078, upload-time = "2026-08-03T18:57:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/ea/be/9eedfbc98b0fb6f0213adc0338d530467cd1acaa98aa026fab881c672a8f/granian-2.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036eac55cb9d67457b50b86a1c364d39c3f0ce4e06d86a87d3af8de7d173e721", size = 4870806, upload-time = "2026-08-03T18:57:28.289Z" }, + { url = "https://files.pythonhosted.org/packages/d4/eb/3b140540414e540a2708a4008eabfe3958c2b010ca998738fe6173300f9a/granian-2.8.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:35624cf9fe8bbb416890871e63de7f3460555ed9bf0ac69b533f796a4382a746", size = 5026508, upload-time = "2026-08-03T18:57:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d4/adcec457beec4f2775ee3cc5d4ac43c795edb0adf104cb48d9d1181b0924/granian-2.8.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:09c73e7835a7bba35f14320b8e57dac1da4981c8c6d3a08cd6e9c557860bbb1b", size = 5043848, upload-time = "2026-08-03T18:57:31.992Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7b/0ef38e58546d757c12d751bf8bc9ab3b16287d6cf09656ec6d53874b0ed0/granian-2.8.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:1bb0c4c2ac258f5e96cb5ea1d1c674c3d1b7c7bdf110ae97bae173bb003405e4", size = 4918203, upload-time = "2026-08-03T18:57:34.282Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/e832f520be4c3439148ea13b8c5b635297138bc572115284b55c4425343f/granian-2.8.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:de30f61a321571cfaa7ab1650f324b26959fce6826d341bae520cf2786368a09", size = 4933696, upload-time = "2026-08-03T18:57:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/eb/40/965f20eb6cedc318d08c361fcc8b3c85efac3aadf5adf298b32f6075ebbc/granian-2.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:dca589ddf61239e08a52ec6fa7f62290cd9ab707588dc928d9b65df971ef202c", size = 3096154, upload-time = "2026-08-03T18:57:37.979Z" }, + { url = "https://files.pythonhosted.org/packages/b6/93/64cf513a32fa2f64a421b58b0b00486d03e48cdd69c367ce185140e00f62/granian-2.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a3e0905fa79ab2b5752e9222fb87003b1e69f7162eebe6eeec0ab924e5e67312", size = 4320699, upload-time = "2026-08-03T18:57:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d4/4bbb6b1d9c56c9cb5ce636fc3c2d31ec0baacf6975d379b5ae4ab2c3627c/granian-2.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c228897c11931c623ee27e07ba512a86667d7c7af0172c4334ae8a0dc66c6f6", size = 4144380, upload-time = "2026-08-03T18:57:41.668Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1c/da817b9cf934fb041ae423744a1c89823af3299f91c2569fa04b4cd0f3b7/granian-2.8.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640003ab77b6dc29b0c247cdb8d970cb2f4986de2e7c72849e9f5fe565eca18e", size = 4282845, upload-time = "2026-08-03T18:57:43.494Z" }, + { url = "https://files.pythonhosted.org/packages/ba/09/35c8b5d5001ec3024a1bef4c6305582cb5e8f8a2f2e85f90eead59910593/granian-2.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81e12dcd0b504c8704b54c6a88733a57de21726ee9567a018f8cf7972e190753", size = 4812269, upload-time = "2026-08-03T18:57:45.884Z" }, + { url = "https://files.pythonhosted.org/packages/36/6e/99b59975f249faaa066320c4f2927102c6740de87be8421cae99c47688d1/granian-2.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bfd4726c4c61f39dbdb4e4e2bfe8fd0ace2cea58eb34ce720036e8e09b1c27a", size = 4748058, upload-time = "2026-08-03T18:57:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/c0/83/42aeb107969349a07b7385dda0222fad830d318f1d5bdca084823650f859/granian-2.8.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6c1aeca05c84434ecafb2297aabaaf1e39a267e66eee4420b7669cf0c47df369", size = 4842775, upload-time = "2026-08-03T18:57:50.651Z" }, + { url = "https://files.pythonhosted.org/packages/17/35/d5f6e690eecdf4a63460d65f363527fe3db69477bfd0df631f395c554a4f/granian-2.8.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ec5281054da6e342e7be3d3d5bf764288faa2e6407d26ef5b9a909fa5b5ca44d", size = 5038742, upload-time = "2026-08-03T18:57:52.407Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e1/98b97433013a1219ed1c1238befa7aa0eb83d7bbc872e63193e847e152fe/granian-2.8.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7b76586d95b798453ee89304a1bb25b1ea9301f564cd3a6a20268b8139a9cad5", size = 4944066, upload-time = "2026-08-03T18:57:54.488Z" }, + { url = "https://files.pythonhosted.org/packages/41/70/47897e417dd22c83e731d85e8f388ed29a1b88a97404063c05a2510fc11e/granian-2.8.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:7d516e7ae705e0d5f8ece085ac18b8817dc4c8adfb7f0f68e9ee529e991664b2", size = 4987791, upload-time = "2026-08-03T18:57:56.46Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8e/76f58b9953a1aa1ba317ce0d96073a47dbb515d562fa524ea8a6b9564bc7/granian-2.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9383c97c12829b0b0accb3aad5011ad765e74a90d54d8381cf722ca4f7bdfc0a", size = 3082991, upload-time = "2026-08-03T18:57:58.204Z" }, + { url = "https://files.pythonhosted.org/packages/47/8e/264f9215206a177d6a0958ec6a496923c6fa0fca785a350884e9d134fce8/granian-2.8.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:c13e89315cfd91821399c78f1dfe5c63b95cd9b44c9da1052eee66c00e60c719", size = 4424059, upload-time = "2026-08-03T18:57:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/42/bd/6c62d461da7ccb76ecc57e2e4c29a63fcb55d21a4b04fa030e849e6d0225/granian-2.8.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f956f26d638268312b7cd583fc079cee1bc3474a29508dcb2bf7f797822912bb", size = 4259907, upload-time = "2026-08-03T18:58:01.491Z" }, + { url = "https://files.pythonhosted.org/packages/95/ed/cd6b65766c1f35718fe1d83de985ae79ebd2068ea27ab39457ab95439f54/granian-2.8.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fcd0888e6ab17a14dabd7b8b3a1cda12795a754660279d2b17b6abcbaa65644", size = 4807122, upload-time = "2026-08-03T18:58:03.404Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c7/36c80a53a43e20e8ecc0587c9b82e0c23ef82b26e258ff05750462fe689f/granian-2.8.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b771aec7246a9e220f841c24e522f493b67ad2df4a7474e4fa8f960844826c9", size = 4444740, upload-time = "2026-08-03T18:58:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/0f/34/760cbeca09daae682e20a6f4a894248edae35dd5e40f27ef6fe8aab315db/granian-2.8.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2fdbbe31574800b2b14dcd2868be06f0e0b333817a37fc0fdae2024de2d4612", size = 4870669, upload-time = "2026-08-03T18:58:06.874Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/daad58c44a0f934ffa5991440054b738758c645ecd35a8d81266f6bdd2df/granian-2.8.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:826bb163325f1bff16f884c98b7f109998a3a9042406c31f7f407114e8196020", size = 5026641, upload-time = "2026-08-03T18:58:08.82Z" }, + { url = "https://files.pythonhosted.org/packages/2f/8e/2ed4b28cfb482ccb3e9d6b0812f64ef40598829c53dcca9260aa93994fc5/granian-2.8.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:fe48c902ace46e8dfaa24585c2941b51a22b5d77d3676a502ae48caf4ba624ca", size = 5043662, upload-time = "2026-08-03T18:58:10.706Z" }, + { url = "https://files.pythonhosted.org/packages/c8/df/9afd95c900d0c4fb5d57d1de1d9340cbcad9813b0374c4ac37b0a00c710d/granian-2.8.0-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:6f9876220f56a0ffc2bc41a977f6fdbe0458a8559c3f950a0497fe71e52a16e3", size = 4918816, upload-time = "2026-08-03T18:58:12.489Z" }, + { url = "https://files.pythonhosted.org/packages/16/ff/ec09ab42ba46676440715b4810aca60ef92d39dffa68669a4daa0bf01252/granian-2.8.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:0d797398949f8de4259926df69d0020b6897ca325672043529d9a6b702bddafe", size = 4934107, upload-time = "2026-08-03T18:58:14.417Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a0/1f2de683c90137ae5a66e89ca7aa72752edb2e0c55490442169007a9bbae/granian-2.8.0-cp315-cp315-win_amd64.whl", hash = "sha256:808986933f844f54dd9c6ea47a9e0e39cbbd12b91b315a8de1ea98abb9314e80", size = 3096056, upload-time = "2026-08-03T18:58:16.3Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d2/21d2fbf8517b2c0d82bda45dfa7ed26d933a5698ac7107c43da366635223/granian-2.8.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:8bf00fd66844846dac9278a15db864ec68c60878098ad638451fc8a3e61f845b", size = 4320343, upload-time = "2026-08-03T18:58:18.126Z" }, + { url = "https://files.pythonhosted.org/packages/94/1e/81284fa128f77dd6132ed62f29f0a099aec8622f2acd3c3fa7a733dc8375/granian-2.8.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ca1a764c00b89509eb7a45f1479118481628380e1bd1d94c2248e097abd63017", size = 4144285, upload-time = "2026-08-03T18:58:20.122Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8b/2f6a7012264011ebe6ea29afdeb333b6c50be3956b70cd59eecf248d599c/granian-2.8.0-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:042c177e75a5e67144a39476882ed735ac9558e48097a5658111adbf939c0d1e", size = 4281969, upload-time = "2026-08-03T18:58:22.433Z" }, + { url = "https://files.pythonhosted.org/packages/76/72/ff6d27931f004e231dcead6c9d5d0c252a73358462885f19e1223784c72a/granian-2.8.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:faa739c7674c53fb9e6b72500232e6360c13e531d8186cf5ef569ba8ccbd5f2c", size = 4813597, upload-time = "2026-08-03T18:58:24.563Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8d/7501ff01a8ca751608783252729ec067a7dc498c4323ae2e31ac17c1dc30/granian-2.8.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04c83c5d7a387635d8c7cde11a95f0babee1cabce3c469de4494a9f93a015394", size = 4746980, upload-time = "2026-08-03T18:58:26.392Z" }, + { url = "https://files.pythonhosted.org/packages/2f/14/3f635ba0065fc36f7820704059bbc46065dd9e4fae55b28de46a91dff9f9/granian-2.8.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:7cd88ca58924a63eed769ce64cea3c29c876791db14988f2d6f3f6fd5cb18061", size = 4842380, upload-time = "2026-08-03T18:58:28.178Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/008efb0f414ff7c8c8d0d9112abb5fa9a93574043cbe4e5f69f9ab80caf2/granian-2.8.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:2052d1cf74369430b960f7731db823a1f604e375179182bba3511e88d976eb43", size = 5038154, upload-time = "2026-08-03T18:58:30.131Z" }, + { url = "https://files.pythonhosted.org/packages/da/4b/01e0f0ad66c886a55c44912385ec72e7da68ece6becf3b67bb402022b605/granian-2.8.0-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:0e96f5bdbbac30e6abff4e7c2268582734ba8a908608c95fbc9a1db5b849bc3f", size = 4946307, upload-time = "2026-08-03T18:58:32.26Z" }, + { url = "https://files.pythonhosted.org/packages/5d/50/2a961f66228426d9c95b4d799f1fea5460f34ea01749108118d81ea33eb8/granian-2.8.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:0d84b516c3d720d22448d935e136d8ddaabb5a8715bf839d8f28de578d35443f", size = 4987962, upload-time = "2026-08-03T18:58:34.178Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0a/711050f09a1b027b13db918e7f272ff5e7f7b4699d9994fbe39f285e2e74/granian-2.8.0-cp315-cp315t-win_amd64.whl", hash = "sha256:5c4d67d96ab296164c36ef672a55251ef12c5e4060c1796fcba6c3fd82394d27", size = 3082796, upload-time = "2026-08-03T18:58:36.052Z" }, ] [package.optional-dependencies] @@ -1614,15 +1624,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/2c/5f0903a53a62029875aaa3884c38070cc388248a2c1b9aa935632669e5a7/ImageHash-4.3.2-py2.py3-none-any.whl", hash = "sha256:02b0f965f8c77cd813f61d7d39031ea27d4780e7ebcad56c6cd6a709acc06e5f", size = 296657, upload-time = "2025-02-01T08:45:36.102Z" }, ] -[[package]] -name = "importlib-resources" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/06/b56dfa750b44e86157093bc8fca0ab81dccbf5260510de4eaf1cb69b5b99/importlib_resources-7.1.0.tar.gz", hash = "sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708", size = 44985, upload-time = "2026-04-12T16:36:09.232Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/db/55a262f3606bebcae07cc14095338471ad7c0bbcaa37707e6f0ee49725b7/importlib_resources-7.1.0-py3-none-any.whl", hash = "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1", size = 37232, upload-time = "2026-04-12T16:36:08.219Z" }, -] - [[package]] name = "infer-types" version = "1.0.0" @@ -2486,7 +2487,7 @@ wheels = [ [[package]] name = "picopt" -version = "6.7.0" +version = "6.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "confuse" }, @@ -2509,9 +2510,9 @@ dependencies = [ { name = "treestamps" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/86/e685ddecd9d10fa248a240bbb7dc9cfd2a47ee0ec2d253200e6c8185a747/picopt-6.7.0.tar.gz", hash = "sha256:7c0bd915d9ea47c6e5422d2156995dbccdd2dd122f6bc9a2671088b5772ddb8e", size = 10570287, upload-time = "2026-07-29T03:26:45.781Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/63/ee5eca124d2e90b0cb5e51edcce55c386bde805a5926d659e9c288672651/picopt-6.8.1.tar.gz", hash = "sha256:c8c5237dec29f29e8ae596305de118a0d209eedbd21e95d75abf9a35e2e6b8d3", size = 10575634, upload-time = "2026-08-03T04:01:20.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/54/cd50faa120ee7b65684c60be70b9653a1a2b40893a395da51cff4f14688c/picopt-6.7.0-py3-none-any.whl", hash = "sha256:e163693db14ab4a69f26d0b139175ed4583d9c835a512a14cc6b5bd7ad6a1516", size = 145246, upload-time = "2026-07-29T03:26:43.723Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/1c2ae915b0848dcf445771b2dd889cc9f472365c1e61423760573d3a207a/picopt-6.8.1-py3-none-any.whl", hash = "sha256:725c23993515e879f8ceefd1cf0da067b0aed6564183221d5a2c660471bfd9e6", size = 145780, upload-time = "2026-08-03T04:01:19.043Z" }, ] [[package]] @@ -3350,11 +3351,11 @@ wheels = [ [[package]] name = "rarfile" -version = "4.4" +version = "4.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/84/a00a5be12adbe5abb0094513ad6d3aefc80fe5dfa4a5604e22561d4b7d3b/rarfile-4.4.tar.gz", hash = "sha256:b7d29d671223cdc80e8b992df212d152ad88e6c961d4426c3fb2fefe8dd86fdf", size = 155437, upload-time = "2026-07-22T16:19:15.533Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/eb/33ea5625de4e84144069ea021a282ea3c44ff7bd341b740bcadff028bea7/rarfile-4.5.tar.gz", hash = "sha256:7425d0afa180f0092db903abb1526a130b36858980aad90b3694f48e41420155", size = 155541, upload-time = "2026-08-02T20:45:53.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/06/7135da7d7d662767aee9aeebaa360f4ad29f08d9198cc1b1632428114acd/rarfile-4.4-py3-none-any.whl", hash = "sha256:befb813c283fbcdd46dda2bad090f8272c8bb1de246d04cd570e9d890294d119", size = 29932, upload-time = "2026-07-22T16:19:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/f5/36/ffd87452bf72310dfd1185ade33e5b8be374ab370d19573a8197ca25c95b/rarfile-4.5-py3-none-any.whl", hash = "sha256:c74341f4b9a3a3ebb35ef396d59daf059eb028f34995a7162950a41d97b84de9", size = 30035, upload-time = "2026-08-02T20:45:52.212Z" }, ] [[package]] @@ -3506,69 +3507,69 @@ wheels = [ [[package]] name = "resvg-py" -version = "0.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/9f/b2cf731a445e454c0d96b76e39843d8c26c2a9f0d78594dfbe2d4a3ad63c/resvg_py-0.3.3.tar.gz", hash = "sha256:7884f3e2d85e943fc9939ba6a639e373f45043ffec3b735df1fe9f02cb8eac91", size = 1748355, upload-time = "2026-06-19T13:35:18.182Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/77/3bd376c96b7d42ff4a316e060fa0c3fad05171b3c3637e844899789057a9/resvg_py-0.3.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9268ceb1629d34cfc6f244f0ad7a8fcd6376f9f4ce822c0c3c30df79de712fd7", size = 1043528, upload-time = "2026-06-19T13:33:35.697Z" }, - { url = "https://files.pythonhosted.org/packages/96/d3/c0a08794554c6348f27b79b30567d8f3f8ad16f8f5bfcb523a764101b7f4/resvg_py-0.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de8191176a578492a6922caf33599a297322c78173e1b939b9464459fed635c7", size = 996042, upload-time = "2026-06-19T13:33:37.052Z" }, - { url = "https://files.pythonhosted.org/packages/18/37/027520514ddb13a496e9a7637c65985d96bb4ffb1ec77c9847264309fd5d/resvg_py-0.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57194e058aacee5e5d7694c5dbb00cde56d9a760ef1433a7e7f2a8b22e2c7576", size = 1171015, upload-time = "2026-06-19T13:33:38.551Z" }, - { url = "https://files.pythonhosted.org/packages/54/f8/63c45f7959a019b5155e0db14acc572f80fab5173453102ae45375f42008/resvg_py-0.3.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f3a199a51221fee9bd904c3a0b5c60c842e0197b21f0e3198d3de063a7a284a", size = 1116297, upload-time = "2026-06-19T13:33:39.93Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f1/f90bccdd1682808bc5394d4aa6f8db653ed259f92ccd3b4185a579e62ce4/resvg_py-0.3.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c7bec5f079bf2debe16af59940fd01749f5d17700d15dd66cb4f4442cb80a44", size = 1269731, upload-time = "2026-06-19T13:33:41.579Z" }, - { url = "https://files.pythonhosted.org/packages/ba/70/94cb6c67b575c20de0dafc5fff670210706aa68ba535cd64147ad5c3a7aa/resvg_py-0.3.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a858234af1c42ff9f75d449b0b2f20ac819c1075bb9221c2c3170840cdb5685", size = 1252336, upload-time = "2026-06-19T13:33:43.102Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fe/2392102c39e1b8512b79500ecc201bde4f05dc205aa7166d2c216e226619/resvg_py-0.3.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c78673fa0fd242dd7d9d802f475bdefb3cb57e5f504c2e2f3adca4c305d3ef", size = 1173517, upload-time = "2026-06-19T13:33:44.549Z" }, - { url = "https://files.pythonhosted.org/packages/76/d2/34abb5816f8373855d6fe27883681c577baf20fbf845fa1c84a56047b4ef/resvg_py-0.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2524fc51eb2d4cb39d5850864416d3fca152979be2490a388083e71f45ad3370", size = 1242458, upload-time = "2026-06-19T13:33:46.003Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ef/b637eec467feb953197e2ddead4b29e40b29b56df785d3823b360274f441/resvg_py-0.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1ed821036d7e9009fb11b63ae7f41d158fe0a66ebd64ab6825fd3830ff1c2c2", size = 1348813, upload-time = "2026-06-19T13:33:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/4c/14/29401706c5c774fd7cb87f3df66ae6a7bf038696780d95b4d72efc2f5043/resvg_py-0.3.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d95c89e55bef7093c9894a08a0f16482c4cc35ff023b5b053075675841e5c178", size = 1393013, upload-time = "2026-06-19T13:33:49.165Z" }, - { url = "https://files.pythonhosted.org/packages/d9/51/5da062383f8a87c681b37838184ebc5992e65ce943d615259fe3e84c030b/resvg_py-0.3.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3d48bdfd3a75ef7de6d27fd0b50a43bd8dd7a66fa93e07f690a98a9767478bb2", size = 1415303, upload-time = "2026-06-19T13:33:50.607Z" }, - { url = "https://files.pythonhosted.org/packages/a2/86/6d7428e3ba6c5d98683168b90f9ffa8ed6a448853d6218cb64a5eaff869c/resvg_py-0.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:45427ef75e949f01a419d6f2a71ff37f14110d52a7364fe43f7248b88fd282da", size = 1392534, upload-time = "2026-06-19T13:33:51.91Z" }, - { url = "https://files.pythonhosted.org/packages/28/ca/beb6a3b5e9e0431fce396501a562077835a5fa36501903fc861518e1dad5/resvg_py-0.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a4f185e73898995adceabc59a81c0da4765176d751b90be3aa73203990b36d28", size = 986515, upload-time = "2026-06-19T13:33:53.541Z" }, - { url = "https://files.pythonhosted.org/packages/ea/91/51540a9ed1319d49ca8a91b66903eae0e6f75672070c63bff88c36a8d805/resvg_py-0.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:6e6f3180cae3505706ce32eec1233a27fc7b9d5d304e20c04f79d07897c6a5f7", size = 924872, upload-time = "2026-06-19T13:33:54.779Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2b/8c01060900a196c83bd81b2cf5620aa8d18caa9595c06869fd4a87465945/resvg_py-0.3.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:fa413cfd0854f7449cd37748ce642ce491d2e6e23f369d180b2861ed5d0e2ddf", size = 1043644, upload-time = "2026-06-19T13:33:56.522Z" }, - { url = "https://files.pythonhosted.org/packages/ac/55/e46cad72ba85ad5d9ea52e0bc7d6848c00abaffcd077f242f4c439e2e583/resvg_py-0.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79c1922c3db974f71cc8ee23bd6b18fdb24e13c74493cb1c9428c9f126f89ff8", size = 995909, upload-time = "2026-06-19T13:33:57.713Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f2/f44a408de073227f008788336abd6911f4ce78a237e170cc7edfa3d79eeb/resvg_py-0.3.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34485a62e3dc1e502d91855154d57cb54e6dcaf28a558e33205d77892585360d", size = 1171096, upload-time = "2026-06-19T13:33:59.178Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ae/ae1556ffd2233c64d615e58f9331ac19dbac9c4906185a655f6157215721/resvg_py-0.3.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb2dfec2e604477a2e831cb3d505b541590c1261df8a1e6d02745f80f405047c", size = 1116351, upload-time = "2026-06-19T13:34:00.785Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1b/e923b072db6c9e324bca9c4b2a17041805d63b2a4f7298b64908ff1c5afb/resvg_py-0.3.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c88a143eaf64316bdff1fafb93e622b276d3588d1d59ea4f5f03108e4d399dca", size = 1269710, upload-time = "2026-06-19T13:34:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/73/3c/522395b5faccb25ed8bea3af7d8aac371c5e201e771cf885e6b61fb9e92f/resvg_py-0.3.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:864de609f4866580e033541273c7b61cb05ab815db7e52c089cea3c5aea60366", size = 1252423, upload-time = "2026-06-19T13:34:03.474Z" }, - { url = "https://files.pythonhosted.org/packages/4a/10/e900789bb2e0db46adebfffcdbc816c0efe3af2e861f382ce8804d6d3912/resvg_py-0.3.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e148630bea35d7897bc66116fb440b6ac826da10fad088092104e56343c12088", size = 1173465, upload-time = "2026-06-19T13:34:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/7e33d8280f36deefe62310c565b242cdc656154952ef33275944b39fd23e/resvg_py-0.3.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6515cb342b3cdac57c3c70ec4bdb05b8f26f10ab9a90d208ccdfd731a38749cd", size = 1242546, upload-time = "2026-06-19T13:34:06.826Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e6/37b65267ba4044e9658916cfe48d47f32dd26a64754dbbc027c0367838e8/resvg_py-0.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bf5884dfc349510b874a107ee2eaec2ae6e7f6627bb6f754f9c3bb1b09a1bb11", size = 1348939, upload-time = "2026-06-19T13:34:08.45Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e1/c1a8d322044e69b76be44658b7e1c5179a187938cbedb850eb502569e742/resvg_py-0.3.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:81ad1f27feacab23317a4d639345e9c58dd2bbe8665b5010d52d774217dcf944", size = 1393048, upload-time = "2026-06-19T13:34:10.11Z" }, - { url = "https://files.pythonhosted.org/packages/83/fa/74fb4600f8f6a8159cad2382e33354cbfa808e61f0d34c0035ef832bc63e/resvg_py-0.3.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5aaed9b203336f9f887b6724351f1c41898e91fdf5a02b1d427667da019b533a", size = 1415362, upload-time = "2026-06-19T13:34:11.471Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c4/e7667bde2e827fcf3d6f740e3a25e7cdf8cd6c45fc7bfaedaa639fad9653/resvg_py-0.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dd5b113ce8c8daf2ce37b547c6e1c359f8e9485ca1affa4cb4d6d482c0becede", size = 1392521, upload-time = "2026-06-19T13:34:12.727Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5d/2696a6a517df8a78a3df5578e3e7c71be020f0500c370c903d30924d8650/resvg_py-0.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:fe4ab3bd1e5e3ca3935d77f3fd1f681f5f7c555226cbc79a5e0153fea701f30e", size = 986438, upload-time = "2026-06-19T13:34:14.12Z" }, - { url = "https://files.pythonhosted.org/packages/12/54/b75a66dd6fd24df7864d11f96a83bdf7dcd957682a01037d5ba5bb80cf50/resvg_py-0.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:b86beb3738eb7426169bb6f23b3aad060eb7939fbce8e27540a138c82dce281d", size = 924885, upload-time = "2026-06-19T13:34:15.443Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ee/72f08009318b86ba639d407869ed366a2a24d7e5f87bd85c06b5874b5ee4/resvg_py-0.3.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:fc0fd718a2db0b46a53d87537a7e3d9160e3074ad3a8b752e6d1b9a0af6edcb3", size = 1132285, upload-time = "2026-06-19T13:34:17.209Z" }, - { url = "https://files.pythonhosted.org/packages/99/c0/64c7bbc0395fb9c93489f8979259fe06457c72bf088688c9e6794c584f76/resvg_py-0.3.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:47b2de290d80891742b800eec09076f4c299f468181cb6c81656ffd10c4d8267", size = 1143197, upload-time = "2026-06-19T13:34:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/a4/fd/e00ea05ccb4e6f07595c9e04a9d10a6f5c989284abb102ca11727a2dbdaa/resvg_py-0.3.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:dbf698e09f2671a6d0f5757dbc15e5cb675b003ab5f11147e097553e8f2a1616", size = 1044054, upload-time = "2026-06-19T13:34:20.222Z" }, - { url = "https://files.pythonhosted.org/packages/9d/a2/82846e4748ff75d84dc06e1c6baf7e229e27ec8891a20bf4f37890ec71b2/resvg_py-0.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d2b747d6f77fc985f7b7c5798b69ddbf0df8303d01b87899e34a2eba76e75e86", size = 996575, upload-time = "2026-06-19T13:34:21.513Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b7/49cd8a32c097ca14820cdf59064a7a6b988462caeb573b4ca60039d48fae/resvg_py-0.3.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaac963aa895ee7a23e41209e6625605771f3807bee9a3556ea346517754cd0e", size = 1171446, upload-time = "2026-06-19T13:34:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/dfde9d64de840c0bd4ec240c4a701b7a3a1fe8fdea82f6192f6f81875f66/resvg_py-0.3.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a14768012cf41232dd78ef8e571cf942f69ac8d3dd06923c408f9856f431c5a", size = 1116803, upload-time = "2026-06-19T13:34:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/b8/53/89a8083cd18adbd179058ef3daff7a84fcbf29d308876163716e45a146f2/resvg_py-0.3.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fef023a7806b1749fb5ecbe27c4250ba7399f14721644b33cf0d3f5971c870b0", size = 1270465, upload-time = "2026-06-19T13:34:26.46Z" }, - { url = "https://files.pythonhosted.org/packages/06/72/1825c9dec2019ae21229d2ad7afbabaadbcaa3141f0af46068a221d06a13/resvg_py-0.3.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6850ea01a74ba8ef922db312bc06393d46fff1411f60d1d6c7d02586914744e", size = 1253351, upload-time = "2026-06-19T13:34:27.845Z" }, - { url = "https://files.pythonhosted.org/packages/25/64/093558bd67e04a286da3dc3ce77015d8773ccce75fc862d4547d699bcd53/resvg_py-0.3.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0dc7bb865abb7a3cb6092d63b9d0e06216c4b2d1615f5b9b4b488c120ed2326", size = 1174217, upload-time = "2026-06-19T13:34:29.143Z" }, - { url = "https://files.pythonhosted.org/packages/bf/12/c9fad7f5a770b661f910b0bc06ff4983f86f426cdee73c206258ceefeaa1/resvg_py-0.3.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5bc650ce1fed0db08a719036d5b3e966c454921eb5ffdca19d48594e74ceabc", size = 1243458, upload-time = "2026-06-19T13:34:30.739Z" }, - { url = "https://files.pythonhosted.org/packages/ef/23/59d765066c896a5740d092a89c5568f16666969dabc0f5ab774df2b99323/resvg_py-0.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ad4ca89b858b8a4955f675d8c053308a92a35071e45881d0dec925073cb064a5", size = 1349724, upload-time = "2026-06-19T13:34:32.38Z" }, - { url = "https://files.pythonhosted.org/packages/bd/03/982f178d0c1788716409829ae67466aca58fd6b325e890b4446c71f7e060/resvg_py-0.3.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:bd3d3c86ca7ab2ec809bd9e617ca59c6c37778464cebd77a542851c30d9dd018", size = 1393310, upload-time = "2026-06-19T13:34:33.997Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/9add96dd1f8286720e979345bcefcc62878969fe46643c5466d8fab7242c/resvg_py-0.3.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:293e532a31a99231024290949249592bcf3823c75b23985684265f98281537ba", size = 1416001, upload-time = "2026-06-19T13:34:35.396Z" }, - { url = "https://files.pythonhosted.org/packages/72/aa/19828db4167bb9c99bec64cb57f5ffba73ff991ef97c165a5c2720e468f2/resvg_py-0.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5dc3f2f4550bd0ca3e03dd16d3a3af44a08b116a51012980eb4becd132e286d", size = 1393281, upload-time = "2026-06-19T13:34:36.683Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/db10d8f61ac62089605311411d81557b2b85cb19c5eac338a60d72a59d89/resvg_py-0.3.3-cp314-cp314-win32.whl", hash = "sha256:5e6f8f78f40b7393960e4dddd937304878aa84d6f90effaf609e6308fde34731", size = 972925, upload-time = "2026-06-19T13:34:38.107Z" }, - { url = "https://files.pythonhosted.org/packages/aa/79/b74d530abb493e3089fae71a09a64eacfc878fa2030a0d2cbc6879b91752/resvg_py-0.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:abe83cbb03fb990f9ad0af3e6a7981dd82d4175c5e64a41ebe6d520e0b5bd033", size = 986775, upload-time = "2026-06-19T13:34:39.299Z" }, - { url = "https://files.pythonhosted.org/packages/68/2b/cfd32d44b7abca3d59ba36d37bea83c80a6aac650c5aac0341a3598e8fcd/resvg_py-0.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:4fffccdb51359a867ca4aa602c15cda05ea9df63bf6d7860beb4b527850e615e", size = 925197, upload-time = "2026-06-19T13:34:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/52/95/47bf074556cb9f388a42de5250c8edfc60e14e83227b2ba43e0f955dc22e/resvg_py-0.3.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17856adeefcf221c632c694f80d2648d207b0f73654caa00218d48955c3b0622", size = 1171425, upload-time = "2026-06-19T13:34:42.071Z" }, - { url = "https://files.pythonhosted.org/packages/1c/82/371429aad2269cdc2beee7c35374d825009f5f2e188ec2d8dfdd3ceca9de/resvg_py-0.3.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2ab7fc40a6c6a2f050c79ac2e3568a98e30f1650ea5513893ed6615f73355fda", size = 1116662, upload-time = "2026-06-19T13:34:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/74/f3/efb2686e6b09cef373351f9bc202faf0e6263114361b3ac154c8c6c43eab/resvg_py-0.3.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b30970735648dcc06327a8d614dce8f77a4c307d023d2c026e19bca3996325c", size = 1270467, upload-time = "2026-06-19T13:34:44.635Z" }, - { url = "https://files.pythonhosted.org/packages/41/3e/21d3ff3035f321bd0950edba2eede7d132d5615683a3c125e05559f21ec9/resvg_py-0.3.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca8a487f375d585d4dca7afedbfc17667731248b53d924d6471e73d42fc4f373", size = 1253273, upload-time = "2026-06-19T13:34:46.358Z" }, - { url = "https://files.pythonhosted.org/packages/be/b1/c5238633b87e9da2a69a4189ca4dd110089ff4b58eebffa55c4aa2c12e19/resvg_py-0.3.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8bed4f81f9d9e09a1c7f15d1f51c2797dea80722147c6c907ab56532f1593aa", size = 1174298, upload-time = "2026-06-19T13:34:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5d/a16ded58a4d784c124ae1b0ef91e1bee4b6b83c0b0ae3aaedc0a387b745f/resvg_py-0.3.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b92a38614550852b522ce20523efad16b9e7b2dad31ec61eb7f04897c0ee05c", size = 1243652, upload-time = "2026-06-19T13:34:49.791Z" }, - { url = "https://files.pythonhosted.org/packages/81/fb/ab5a1e289a898c87c4d613d6ac7384dac17a3d5025ead2255f1dd1bf01d9/resvg_py-0.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:28c401eba4e7b43b51ae5c16aa0b684f92dade7b8ca7be0e5141b81e0bfed356", size = 1349607, upload-time = "2026-06-19T13:34:51.219Z" }, - { url = "https://files.pythonhosted.org/packages/06/e8/d8b1816e02820f0c7c225da305bdff4795cf39609f67fda5a5f375ff07fd/resvg_py-0.3.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:afd3008c6ec8af1cea3130826d6a58cbb1c2c97039e501e287bcd7243de5a28a", size = 1393322, upload-time = "2026-06-19T13:34:52.665Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9a/021cc8f091b230583e0beba7a15050092e3d9df1f565e8c1a982d42c23bd/resvg_py-0.3.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:397ed997a2f7a737f5d78fe49ac41f45b7f2481e7001b3691a7a12f71267ed88", size = 1416223, upload-time = "2026-06-19T13:34:54.186Z" }, - { url = "https://files.pythonhosted.org/packages/06/09/b2812133ab10db96a82f8530cb7ffc50c71cfeac5f6f2060c2df71d8da88/resvg_py-0.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83e9d1a01152f3a2a2266486e3ff04b37b6d535902a8923d2fbe95b8b201ce76", size = 1393394, upload-time = "2026-06-19T13:34:55.501Z" }, - { url = "https://files.pythonhosted.org/packages/fd/93/d50cfe83c24684c1a60e4a7d92fc896aaa3e83abf4e859faa5011c88f838/resvg_py-0.3.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36b12285554208096e31ea55d4135cd0f4a6141761e52b8e65252bb3eda8e555", size = 1174363, upload-time = "2026-06-19T13:34:56.907Z" }, - { url = "https://files.pythonhosted.org/packages/93/1c/cb77dd3d4970de67db98746cc9fbe5e1f33a5ad1c417617f0ae103748362/resvg_py-0.3.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d6d38e48aedf6738097f8e59f4de83fcc48a0a783ff1457a3f614525944dd4e7", size = 1243472, upload-time = "2026-06-19T13:34:58.326Z" }, - { url = "https://files.pythonhosted.org/packages/cc/84/0a8fd77deca4007966431d44f874f5fb38a107971be958cd5f7ec13467a9/resvg_py-0.3.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba8e52c504b220db092926c5c312a8233d95864bf0a5a3674be936f92a70c913", size = 1174436, upload-time = "2026-06-19T13:35:00.094Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ab/1a088f887ae32f5e3fc7ff906c94d8ddc4a4c2665672f3ba39190dd4bd5c/resvg_py-0.3.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18524420b5a96336a147703875d2d6a74b974776f7a3081ca416c7413b90c45b", size = 1243714, upload-time = "2026-06-19T13:35:01.556Z" }, +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/31/38e2aa968d1d294469c5beba43c1256dab435f328c172d7b91ec310bb87d/resvg_py-0.3.4.tar.gz", hash = "sha256:a398b46979a23ff0699e7009f9ba12bbcab93769aea58a3bb3ad94534afa4131", size = 1746109, upload-time = "2026-08-02T23:11:30.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/29/766f6eed8fcb4d6298950de250135c635b6cdf801d041e0081cacdf3f9b2/resvg_py-0.3.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b256b132d06ba05ba634fcf5d360eee2f6197b410d578b90f008ce53387fd50b", size = 1201042, upload-time = "2026-08-02T23:10:04.615Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0b/61907558f3b09ed6678a54f11474bd7b1639b5d189001a90a6c2e26e6f6b/resvg_py-0.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e6c691c541360180e174f0169e1e65651cbe4a5acd0f3887251921198104005", size = 1147038, upload-time = "2026-08-02T23:10:05.894Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d6/b3b2563459e06efb25d3ff556d55f5c34af7d043077f9418d920091cd0ad/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07bc0340f60095802883b5344205e8b2f4e14502e827fd3792f7767dd4ddaa19", size = 1343304, upload-time = "2026-08-02T23:10:07.083Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9c/9af1121fdf01d567808c16d62a4499312b2427b500959d73ff13f7e6906a/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d95b6de22a7852d05c05c03149d104e8662605bf39c4043a9622cf2f930fb6df", size = 1267162, upload-time = "2026-08-02T23:10:08.136Z" }, + { url = "https://files.pythonhosted.org/packages/34/53/91a20a3aa0d26ed4ae7ff5b66524cd5d9c74d4aed3756d793efb7ac1ab22/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc1abb404a5c4390a1ebe57e0f3c100ac9e88bcb3e246e3c79786e44aedc725", size = 1440905, upload-time = "2026-08-02T23:10:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/15/7d/0bdd4e8ccc69a7d9da9f828d16f0659b7a3fc065a7556ef85304bc4b55be/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b1d253c5becce29c2b5574b74ad30fc7bbca0b27febadd317b326b9fd09053", size = 1398000, upload-time = "2026-08-02T23:10:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/d3/75/1d35b767ca110a47476fe2813792a53788e57548734e838b043d44c4c421/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c646bbd2a01683527e7df3fcac47b8f9c4fe8a84b9cb26f6c41fe21f4dfbe134", size = 1343934, upload-time = "2026-08-02T23:10:12.255Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4b/e76ae56f47e7507a6f24a34e5e3e87ad8d06f1049d0cbe33e34bc7a159b8/resvg_py-0.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff104ea3de66bc5e9b9548b67b3f35bb74d92a84a5671e0143338c139757a2e", size = 1425791, upload-time = "2026-08-02T23:10:13.418Z" }, + { url = "https://files.pythonhosted.org/packages/85/3c/3b5231f21fdd66d907419c41bf297d52f35d190eeda2f94dd4bc0d037a92/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:45d460432ffcbdf38c3ef6623d2ea5e3c711d737c29dc5aff42c8ec8624f7d04", size = 1520883, upload-time = "2026-08-02T23:10:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b3552e4d99d23f828ae084841b839cd6d51fa15993da1f9e2f7dd281d12e/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b36f62616004a6825e045b8211ac3c2c0a20f0f5fde13f4f9f572511747af053", size = 1544173, upload-time = "2026-08-02T23:10:15.659Z" }, + { url = "https://files.pythonhosted.org/packages/89/ee/d7432ed0a1c2192126d1ffbf2820e11f924a4ee365eb625ed0f95af53d68/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a5d801e1c49537f713fac90e22345bc00120ab81264e821b717a9129d5cfca07", size = 1587768, upload-time = "2026-08-02T23:10:16.792Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d4/db8cd9268ce2762a1469c83c20b10a865eb0510d2822748b5dfc80268709/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7e8ddb925c854a600ab3e1f676d472252cd3971a86304faf67f6cc46220a79", size = 1560670, upload-time = "2026-08-02T23:10:18.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f4/36e2cdd2e68e0857673adc530eb7d35d7a8580267db1b9f25fc23427b5bf/resvg_py-0.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:5250a9296e3e96bfcccce3920ed1a90d76e8dfa153ca4273143917b2f1edbbcb", size = 1142998, upload-time = "2026-08-02T23:10:19.235Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/5699c0f9db7b15d5be43d1acf63b1b24728bc90e05b38467e12c42565cb2/resvg_py-0.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:c58e12e14c890e5754177e6dc68b921a73bfc047627371fd9362bcdae3d275b6", size = 1061655, upload-time = "2026-08-02T23:10:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/aa/41/eb1a9fbe03624dda18e22c9b14fa1c3d6efd0663734e352e2efedc019246/resvg_py-0.3.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2482bf877984b6da7a6502c7c154486a43401d391087b8b521a3aed373d7fa60", size = 1201226, upload-time = "2026-08-02T23:10:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/ae/7f/3ed80c16df81aae5672dbb9299a73e0efe54e93bd9c5b6e9db0606918c1c/resvg_py-0.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a4e76a1709d0c34d96402f8e53ee7b8fbfecbdcb5d4fc5289d326c629a5ecfb8", size = 1147174, upload-time = "2026-08-02T23:10:22.701Z" }, + { url = "https://files.pythonhosted.org/packages/40/f8/d705c618839261cc8952324755bab871f91525f91d495c10d25fc643bacd/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c84d954db4314181a6f3ce55f3231ff4d658111ac5cc5d1ebde140db84da5921", size = 1343258, upload-time = "2026-08-02T23:10:23.806Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/331807cf83ccdc16a9bedb89a82753bdc6e436c802937a4a7c3bab3c6e3c/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2145320fb78966d241e2d8ea9a8ca15ea1b1481219e6fbdc9b9e38dae5f2f82", size = 1267206, upload-time = "2026-08-02T23:10:24.887Z" }, + { url = "https://files.pythonhosted.org/packages/65/99/466af312185b71fb58bad7b4784c1dcd6d3afb1d8ca6e1e164dac504bffb/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:626a00d21b7dc87b3ad46f5ecbdda5f6ef12c9425516294413307ecca01552b2", size = 1441009, upload-time = "2026-08-02T23:10:25.959Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9d/725b30a5960372d837cc0decf2710789caffd27735beaa23c44e51591778/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9aeaf72f10067c714ca1945f6d77991c06265ac12d546857f98326d0c8fc4594", size = 1398078, upload-time = "2026-08-02T23:10:27.613Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e4/fbba3141ea69b3ab5b9dc7a11c5a0e64b29f8d2fd1caf18825432e67a94c/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c496c5ecbe7ce2e02905a89f40a0f4d577e3c35429c2714828f45dd3ffe3598b", size = 1343997, upload-time = "2026-08-02T23:10:28.732Z" }, + { url = "https://files.pythonhosted.org/packages/03/06/4c0090073d53fa43bf3aee089ba99b02a3ec55e0a44007f39cdfa2157867/resvg_py-0.3.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a25b7c9a6ebe2c35196941bbebc10d3902aefe915ec9ab2682701cacfddcf9a3", size = 1425766, upload-time = "2026-08-02T23:10:29.823Z" }, + { url = "https://files.pythonhosted.org/packages/54/5c/b2721a2be2ef0db935a6a595fc0902dea7acc08ac5a6095056ada77b2a94/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a6076d7275faf0c5e4ccb0472d09911402c99ca1d1237a4f1fba5cb97a8dc8e", size = 1521063, upload-time = "2026-08-02T23:10:30.916Z" }, + { url = "https://files.pythonhosted.org/packages/2f/72/b531fa672c566404c6dd498766b834f25b267a99988d293c19e9548b2b1d/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d94f5e800e869944af328ecf40fb2667013a205afd6f8da7b78608325bd5b0b4", size = 1544232, upload-time = "2026-08-02T23:10:31.992Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f2/89a4469672fee1219cfb477971eb1f4d719253d99a403b4ca764a1f7452f/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cd9f7a586c51b58ce3dd27767ab204f4e42c1bef787ec71cde68416959fac8da", size = 1587702, upload-time = "2026-08-02T23:10:33.323Z" }, + { url = "https://files.pythonhosted.org/packages/6e/07/58079a9f1402e53162d7fcd9ed98cef39f8db2af5b354a2d49902d3a317e/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2f517d86d159e9265a35c1d854adff8d2d60e5a5433f9b9a5e1c79ae56853e6", size = 1560847, upload-time = "2026-08-02T23:10:34.533Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e1/0e89adb5cbaeb82b7137521233dfadae15a10a29d5001a5dfdd1d37a00a3/resvg_py-0.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:fa8c36ff008cfe6281d2865a0dbdd1f5dd3b779a17fd948f3db6183ddbfc51e4", size = 1142940, upload-time = "2026-08-02T23:10:35.665Z" }, + { url = "https://files.pythonhosted.org/packages/3c/88/417fca98314a02eb3172b359b6d4afb0412e0cff353b6c4b054ad567ccea/resvg_py-0.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:753d3630bb3ffcbf0a66635cb045f35f588c42e7f9ed7a0dfd33dd5d56d316f3", size = 1061619, upload-time = "2026-08-02T23:10:36.94Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2a/6e60f8d433bd54acb1d609492075853967bc49c0e89dbfc7dfe65eee16b4/resvg_py-0.3.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:51a1ff58e44161e6a9f038f75d1732aaad4c578ec0c9308fa41c041861830a04", size = 1307381, upload-time = "2026-08-02T23:10:38.272Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/0ec352ee090dfdd63b3399aa51a2231e00eb9b2b121816594628bc68aa78/resvg_py-0.3.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d5905c0dc20e61e379d91b36773c938fb045d7c7cba930523f7a641589f74b81", size = 1312914, upload-time = "2026-08-02T23:10:39.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/dc/7ea87665848296ae20f1295d061afbd475eaed35190886a993960f9f8af3/resvg_py-0.3.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:856ad94a8bc96f68e50f0045896b680abb0521ca9900c10078068123cf895d2e", size = 1201764, upload-time = "2026-08-02T23:10:40.546Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f7/cde835d58a971c51af664924ac7df1e3b03045f12fce930bc2a9a2b83654/resvg_py-0.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b99941a95889eacc63c34f5259b0db48c856e1106390073dd2c8f10d991c5c84", size = 1147810, upload-time = "2026-08-02T23:10:41.681Z" }, + { url = "https://files.pythonhosted.org/packages/97/0e/f4ea7cf44a81eb6e0186f7b3da489611cb467502a5425eb0a15100d85d13/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7c6bdc37a5e0ce8903ce13eeae5f8538071bfb42203ed20af446a95f4acdf4", size = 1343936, upload-time = "2026-08-02T23:10:42.785Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f5/6b18873f054df55f5168e42af74906afd1773d1d985778c4ab9f888eebb2/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e482f490dbc96687c024770858b6b8854f0d18d94c439e27b653b78374ec6a2b", size = 1267539, upload-time = "2026-08-02T23:10:43.91Z" }, + { url = "https://files.pythonhosted.org/packages/79/5d/b34e513c96be0a75c56013bb593a4f35b5b02d3952356cff8a9eeb359844/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:313d056d64ba9bdbb014539ca68b8b5d31cfaba3deb94b7dd8ed89d0db5547c0", size = 1441991, upload-time = "2026-08-02T23:10:45.318Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4c/2cfd1f01dfbc0eeca38f831d848e216346eda7bfe40ce1156fb4016140c1/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c360aff07c307b7a8648e7e2b4e5d17f96632afcb730c85e5e6b97ee718519ee", size = 1398593, upload-time = "2026-08-02T23:10:46.608Z" }, + { url = "https://files.pythonhosted.org/packages/aa/14/af4525f0911fcaee39fe9876894e62ff4ddba24c5ecf0492ef264fe22875/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:740b2a1bf1026b63c200bc02a8e9eb1363ad5f9ad679f4700db5fa5342cf4fd7", size = 1344806, upload-time = "2026-08-02T23:10:47.699Z" }, + { url = "https://files.pythonhosted.org/packages/1e/06/f403db4b157ae38d3e96d27ef06780a9309ceacd6114390713f376d69537/resvg_py-0.3.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4e9ddc3053d6c1684e9991ed67c6399d1824f2db9884470ab28b789b361365de", size = 1426797, upload-time = "2026-08-02T23:10:49.076Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3c/26543d7816c68caf83727dea887bbaa7a4e42c1dcbc63ae97f4001cd9f8e/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5a7d7058eea552ec2a5a467871e983996240aafd7318b14ae45441b099d2bc7d", size = 1521642, upload-time = "2026-08-02T23:10:50.325Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6a/891d38412414dc40a7f02b7a195913e37b52a06c84b6f3ed729fe9ba6d4f/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:e9176711eb6e35f2e1bff3d29f2d88751668263717b072070261bad1666dc2e1", size = 1544524, upload-time = "2026-08-02T23:10:51.452Z" }, + { url = "https://files.pythonhosted.org/packages/db/df/e91f2f8342ee730425cd009d636191517bbce9d10d3d3c3d9d628ed2135d/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:eaf735fad2a766bfb85cd48cce29a87136b679f423bb97d48b5025a5a5076bbf", size = 1588438, upload-time = "2026-08-02T23:10:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/38/03/cfc0c8b9e9ae997304b13f073b60640da89ff8b68071cac9abb9b3c23cf5/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b6679a1e90cb805c95e0a5983a75f8a6b83a491d847e6da11a40d40b30b6f147", size = 1561531, upload-time = "2026-08-02T23:10:53.868Z" }, + { url = "https://files.pythonhosted.org/packages/7a/00/f14bcebd3b649f610d3bcee63fe2c2f5cf7614131eeca3decf70100d6ff8/resvg_py-0.3.4-cp314-cp314-win32.whl", hash = "sha256:399d2fe4675c41624a785206b4929dc3b3949b3d471b593a516e62e88473e316", size = 1125195, upload-time = "2026-08-02T23:10:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/6f/04/33983346c599e4ed0ee33d6b87b985dcf03ad29e1fffa1635fd976116617/resvg_py-0.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:bc29c6c70acb56d4c4a6d624eb306b044e974db124dec0dec673b7d4ca572e7b", size = 1143434, upload-time = "2026-08-02T23:10:56.315Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/9fb4c51f56775b453ac46b1e3002e92fe12939694e4ec755b1719d01fa83/resvg_py-0.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:feda5d1eb3e5268c1062b35d953907b2e35b92262c851abce270f3b42696a1ef", size = 1062193, upload-time = "2026-08-02T23:10:57.87Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c6/065cbf979bae9b78b3b27d7a46b9d969c40ed67fb74b474fbdaf7e4eb148/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61ec5cbb44a3a250368e474153c58309e162f10ee636628e740d5062724b4a65", size = 1343946, upload-time = "2026-08-02T23:10:58.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/76/33d2dded2d70f38eedb3164ef0795c2ea1f85997399c0ddf754a39b17715/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5b734365c6e70eb3b28de15a3fc7b82facab384d7c11590674cea186f9c6103", size = 1267388, upload-time = "2026-08-02T23:11:00.054Z" }, + { url = "https://files.pythonhosted.org/packages/a5/64/59362bd36a3533a44a101858c19a4249e0109e9d763aabc209037d6ddb32/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c3135ecd0a56c61e797b05fd977928f4e3dd0c9e0f688579e6e4e7d2118c0fb", size = 1442131, upload-time = "2026-08-02T23:11:01.205Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/e3300cbfa2c2aa787fb04e0d18ef16a9c3fa26b9b0b05081bc1f63b917f7/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26bc305d56f6268afa9304a41168e3f275fd63bd4a2d1a98b99d17ff68066208", size = 1398530, upload-time = "2026-08-02T23:11:02.525Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/ba22fae05074d841ccd91f18afe4c1f87564b5de2449bb5884d272ea99bc/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c9e12339f1fccb2cbd0b7ef96a7cd0dba6c8e134aa8f8ba77297251e7292614", size = 1344910, upload-time = "2026-08-02T23:11:04.236Z" }, + { url = "https://files.pythonhosted.org/packages/02/1d/22e7933ef9eeffd7264a444b4070fb2a6961c1249fa7cd31058ec01e61e3/resvg_py-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42bb20e4e4c7cf7dc2d2d9ca5e73ff536df1faa2ecefc7d5822d213eddc65949", size = 1426964, upload-time = "2026-08-02T23:11:05.599Z" }, + { url = "https://files.pythonhosted.org/packages/00/d3/fc04f814ae93f2bff14abe3886acf1b7e57a6538f0f3dcbc8f180c1c4fb2/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c30955d4fb8fcabc39f64a704a4370c23aa1f248a3e6bc089e1d45dcd24b4ff", size = 1521457, upload-time = "2026-08-02T23:11:06.918Z" }, + { url = "https://files.pythonhosted.org/packages/79/c1/95288f004e3c21bc4bf122c12b7926e5f41c3bb6695c3e9bf918bc7452b7/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:459c809a5b5fa0c715baaf986c5b8f7ca9486788931dcf32f30595ffd16e5555", size = 1544417, upload-time = "2026-08-02T23:11:07.998Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/f93d7c95a5b9a3e34534be3bb4237e00540f2fbdd4c179aee825af33fa93/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3493ea58465d1bf4eb24143d147d3de5f011aaaaebd9b68669949d1d06bff76a", size = 1588351, upload-time = "2026-08-02T23:11:09.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/ff/21a3b80d6b7c5b1733dc10f7b40bd0ea1f2d6585360aa2a64026addf8749/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00117ddb9829ae479540a1885b4d88bbc0e3b5d77197ac0e289b7dd0c00c6b74", size = 1561626, upload-time = "2026-08-02T23:11:10.626Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/a0b36d5263048c15840b549610bfef5873f538274c5fa6d53d0c266600fd/resvg_py-0.3.4-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:168c669311f83836dbd643a03f0ae564d4489baaa3be86a77ae40261e7952039", size = 1344910, upload-time = "2026-08-02T23:11:11.685Z" }, + { url = "https://files.pythonhosted.org/packages/70/a4/88b5574250dcf5d8e06b812868aa4c8ee03f79113ae5ae86bba3cd8640ca/resvg_py-0.3.4-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:434e442871756358e4243037485be890615a3c802985efac4fc4d1be862a41c3", size = 1426954, upload-time = "2026-08-02T23:11:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/27/1c/cc59160651004f163c1d1925de94eb1df64868ff1a0303db10e3b1ef86fe/resvg_py-0.3.4-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b55849172c67417acc142f6eb6cc11278968a5756cba4b3c89dae51299048efa", size = 1344996, upload-time = "2026-08-02T23:11:14.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/046456598595dad61c76923cea0110cd57b413334686cbd32524b079cb2d/resvg_py-0.3.4-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1a89721ffecd83c673f048b8d7abb298e5010e764b0ac59938bab71afebed3f", size = 1427098, upload-time = "2026-08-02T23:11:15.477Z" }, ] [[package]] @@ -4094,45 +4095,45 @@ wheels = [ [[package]] name = "treestamps" -version = "4.1.1" +version = "5.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/33/db241ab2748986e86406919bbfb273333dc226ca2656121546aa655807ed/treestamps-4.1.1.tar.gz", hash = "sha256:7c287f49c118f7b6bba35d8154846ad68acb9d15be0bc2edf1d277e1f057e124", size = 211456, upload-time = "2026-07-09T23:20:58.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/dc/9415b1376bca84fb89162202a2a7cbaaf9dbff5025c93a8ddfb5ecadea92/treestamps-5.0.1.tar.gz", hash = "sha256:89f06f285163abe40fa318367a6f98ffa147198719fa7e8b1dd776a58f89ba20", size = 225674, upload-time = "2026-08-03T03:24:44.571Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/ea/518f3ec0a673e6606608cd0a1edbca39c3909e3ba58c5e9ccf8791822628/treestamps-4.1.1-py3-none-any.whl", hash = "sha256:af747ad8e61a655308f32b8fd1d3c022ebca5d1af76d54e6682872935ef5874a", size = 32665, upload-time = "2026-07-09T23:20:57.536Z" }, + { url = "https://files.pythonhosted.org/packages/30/ef/466124caf10c07606ce54dba8bdddea20f402747c9c9e613a70e1c5a1b5d/treestamps-5.0.1-py3-none-any.whl", hash = "sha256:596732cc314036d7cf9d25eca42b315b28e8fa65176ba5df1aacfa66de3fd074", size = 37807, upload-time = "2026-08-03T03:24:43.581Z" }, ] [[package]] name = "ty" -version = "0.0.65" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cf561927e8e9ab5c1892a833b664aa9cd6f051a75f6280c66d8047246bda/ty-0.0.65.tar.gz", hash = "sha256:b7134bffcc00b715fa8291e84d845782ced810a998dc1f7f11d71c85c4046325", size = 6460098, upload-time = "2026-07-29T18:31:03.27Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/4e/71e2d325d2b53a1afad81624ad076b2ede413213fc4a18cb05b78c568571/ty-0.0.65-py3-none-linux_armv6l.whl", hash = "sha256:dc556c9f05408bef4c4ef02b2cc382e4e5f797b4b20d64410289848f0d76705f", size = 12298466, upload-time = "2026-07-29T18:30:12.744Z" }, - { url = "https://files.pythonhosted.org/packages/57/77/fec8f29647c55794efa430a7f365e44f5ce7ffb6459d9445a87fac569bec/ty-0.0.65-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d2e0d34cc0a28a17ef0cf81135c5ebabc3562131f9079138ba5e7bae0f56bd", size = 11942421, upload-time = "2026-07-29T18:30:16.076Z" }, - { url = "https://files.pythonhosted.org/packages/13/09/7f3766aef9dc627e2698cf4e3e59cf53389dcae3812040d33c1aa931230f/ty-0.0.65-py3-none-macosx_11_0_arm64.whl", hash = "sha256:685f49a9312bbf69d5b65bbb66384fed1f927403ea030c217b9289092d7e46c4", size = 11451922, upload-time = "2026-07-29T18:30:19.155Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7b/1a77cd50e0befb50f55b8bf9bd3ed3eddf184bf28c61b56727039e0774fc/ty-0.0.65-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f564b5ebe78e2f3a8e7b8eacb1292eb88b7c0f3c8630671cfca31abc0709cd9", size = 11994999, upload-time = "2026-07-29T18:30:22.315Z" }, - { url = "https://files.pythonhosted.org/packages/63/7b/feda16f3a4a0a99be27431e0c9598eeeec0db1eb2fec9a15976698209418/ty-0.0.65-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c983e156fe9e113fb56389e13d327b6b8549fe866de9b269684723a88e9b732d", size = 12090662, upload-time = "2026-07-29T18:30:24.93Z" }, - { url = "https://files.pythonhosted.org/packages/ed/3e/3f69bf9c9307dbdc0771719f65ce5b556e7bdeeaccbdd599d4f57866d801/ty-0.0.65-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3663b7396e8b1a9954e20e732de7ccb0192bf4118473069b4945920d6923921", size = 12822094, upload-time = "2026-07-29T18:30:28.012Z" }, - { url = "https://files.pythonhosted.org/packages/90/38/8fa791b3bb503ee2b46ad81690cd1bdd54519582df6d805cee57fe143e85/ty-0.0.65-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306ed01f29d6e108e98feb233dbbf5878a027603b71bd3743b343977933a9f16", size = 13357833, upload-time = "2026-07-29T18:30:31.122Z" }, - { url = "https://files.pythonhosted.org/packages/c1/73/4dda396a201e1dd0ed3594a9b48e559cb41c4bc048c6cd4c4d1b39eb4313/ty-0.0.65-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28bcfc8898c94f079a9100e684bcf312b6a64ad3a7d4ebb35a4591546030a2cd", size = 12977303, upload-time = "2026-07-29T18:30:33.944Z" }, - { url = "https://files.pythonhosted.org/packages/a5/26/c250c2c569adc53a8591716641388397bcb2a442e4a30b952ae81b50c0e0/ty-0.0.65-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a75bd0c245c38802a8f488378e74f92feb7dd33db7d63fbdd6fdf82791ba730", size = 12579338, upload-time = "2026-07-29T18:30:37.199Z" }, - { url = "https://files.pythonhosted.org/packages/d3/94/4a5647d44753ca218fc930d7e4d9bf468d0ed4a0ad4b3d57588bc1bbacf7/ty-0.0.65-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9e5e1bdea9662d2b5312b4e99f319f4e6e2ea427511b5fbc546141b79ec53f76", size = 12957731, upload-time = "2026-07-29T18:30:39.937Z" }, - { url = "https://files.pythonhosted.org/packages/36/b6/1e22fa11a1e0dfb20b1c7f3cbfd8170273aada2a82f9ecd3055275370c44/ty-0.0.65-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:03a88493d4842889f65280ae241e06b399d57eb3c63571054cad21a4c33b3b69", size = 11938625, upload-time = "2026-07-29T18:30:42.603Z" }, - { url = "https://files.pythonhosted.org/packages/5c/0a/fe5f22ef62b193201bc5566762e22049762cd485bfafb5095a7050760054/ty-0.0.65-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:600b8bf6f4940cf7ffb2f43d3716faaf38dcb97cd8617c55771451bc0276408f", size = 12105592, upload-time = "2026-07-29T18:30:45.419Z" }, - { url = "https://files.pythonhosted.org/packages/76/fd/922b3a6e9d697452cdbb4b7e3f636868add5ec652154518a736e4364f3b7/ty-0.0.65-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c28007bc79d648c1ddaf1e65885d07baec48eb87240da442f608e4107c1b7d8", size = 12387335, upload-time = "2026-07-29T18:30:48.405Z" }, - { url = "https://files.pythonhosted.org/packages/77/22/a1a08ebc84c083db2fb55e3b5cd186db0c067692f4921146f601360231e2/ty-0.0.65-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c852da96091ad22361e6586b7c7ba98e1334dcd4d8ffb67e47f4fb673de33f77", size = 12682710, upload-time = "2026-07-29T18:30:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/eaaa410a25bbdea19722109b5422380a0e211b3afcf3071d15953ddbd5db/ty-0.0.65-py3-none-win32.whl", hash = "sha256:cf529d538f1403b14b0511e6ec3cdb95d3d974adabf24cc76cedc533368c3edc", size = 11692341, upload-time = "2026-07-29T18:30:54.35Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0f/6d48f206dce9d7e53fe3b5ea0f0ab5800dd9d2365b2b48f736783436c43f/ty-0.0.65-py3-none-win_amd64.whl", hash = "sha256:234a321e33c7cbbfbd67bfa0b01b685dd9c21f1841781a21e5ca1fa0b25f1d5d", size = 12729355, upload-time = "2026-07-29T18:30:57.275Z" }, - { url = "https://files.pythonhosted.org/packages/96/aa/7446f7725e303cf78e058c893af1f0552b9451895454908706f4c6c3494b/ty-0.0.65-py3-none-win_arm64.whl", hash = "sha256:b9424be1ec56d93ff18609fb1c0a0a2283fe1282cd6d1c7604f97d73b94d61f2", size = 12051375, upload-time = "2026-07-29T18:31:00.579Z" }, +version = "0.0.66" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/58/4f6ab2a86589e422a3cf840bcf6114c565e4c39ddf4d0b7cd328af5b52b4/ty-0.0.66.tar.gz", hash = "sha256:24bddd4479ce445b51ac015410dd2d34af1cadd62a77f5b3cb269149ed83f9b5", size = 6520402, upload-time = "2026-08-04T01:09:47.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/4d/bbc28310d6d887ef73e5800f062c4bf54caa35a1b47d70c7b03d0515ecf1/ty-0.0.66-py3-none-linux_armv6l.whl", hash = "sha256:8b46450438b54b732338e4d7a78a7d2f5e1a012a13d77d121aacaca20fb814e2", size = 12409743, upload-time = "2026-08-04T01:09:01.229Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/c3d2eb2242fa0a2ef445ca2c7009dc9118e5b3dcb8b8a8bec70d58c8e4bc/ty-0.0.66-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8e4adbe662bc3c62b52b83d46b07f703fdc3c123bb72601606c58be5ea017ed3", size = 12078362, upload-time = "2026-08-04T01:09:04.233Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/7a4b5e45d701a8de6b714dacf6ffca91b0411baaceaa781d494037623a18/ty-0.0.66-py3-none-macosx_11_0_arm64.whl", hash = "sha256:776814351735847eb934f9a3cbea21d2278ba14aa0fc099f683da11bc2d5c90a", size = 11583289, upload-time = "2026-08-04T01:09:06.973Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/fbb1f71ee2999f981f8c4b3b139231e4bcff4ede0c3b37e858fd1334ed26/ty-0.0.66-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cca1da877f613965b954bfd22d495386e260ec767c5536c8022cca98a260ea5d", size = 12137274, upload-time = "2026-08-04T01:09:09.592Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/9e0662f6603a5ac171ae6b314396e677ead4b45695fc948efb0a4837051f/ty-0.0.66-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa523e777bc36c0fbf8ded5844096f46cbfc3712fe5a003351a94259b7e86cf4", size = 12207047, upload-time = "2026-08-04T01:09:12.533Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/12e01bc2ea47fa7bf20fc6cdd3249d6a340be9be4edc52b1d77256245dd0/ty-0.0.66-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f7993c1f95f80a4e44056e6aaf8e46e608d778db131ae5ce59262ab59358f9a", size = 12919304, upload-time = "2026-08-04T01:09:15.175Z" }, + { url = "https://files.pythonhosted.org/packages/75/88/c6c0d3a8e71c9cdb560cc5c627a4bba6c47b5eec460b2f8c449b57c6d03a/ty-0.0.66-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b29354bcbac9f53b6952d8f46789bd81eeb9bdd7a68df7d26e654ca7498c3c", size = 13470963, upload-time = "2026-08-04T01:09:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/27/c2/2f8c18063412ad80e1b3f87afce7bd50982b4a048166607b67f80660a9fb/ty-0.0.66-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbf4e5325f7f584d9c346946e7c415b2e3cd3b8f1119d468082a90a6afd020ce", size = 13244773, upload-time = "2026-08-04T01:09:20.59Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f6/fdae2b95831116dffc055ad53a99a8f21437263651047b3ad46def4950a3/ty-0.0.66-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bd304363764bd723c22fb20b17035c345420fdf66fee856934b158ebde08a91", size = 12751343, upload-time = "2026-08-04T01:09:23.225Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/7e75f0371d11463a256dc2bee98b0df401e0fa06021e200b8b601d21949d/ty-0.0.66-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:205d8589bd957ea9718d488b731b2fbdd0d1b1cefd37c79f52c0deb7cafddfef", size = 13068057, upload-time = "2026-08-04T01:09:26.052Z" }, + { url = "https://files.pythonhosted.org/packages/b4/84/f20f24518f6f0bea936e2e668ede250b9ce0774624559931599bd1f42772/ty-0.0.66-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7304a1df54741a2343801354f41d7ad87974acb541ee3e93c26cb6a1a0b863af", size = 12082318, upload-time = "2026-08-04T01:09:28.877Z" }, + { url = "https://files.pythonhosted.org/packages/08/20/70ca0eac2427d4a58a81a3a9426b20e46fb4a5a13aefc9edc2e5172e1243/ty-0.0.66-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cf2c062863e5da0f588b0b120fec0da2e81c0913ee4cd07b50d77aa60ffd8deb", size = 12228978, upload-time = "2026-08-04T01:09:31.905Z" }, + { url = "https://files.pythonhosted.org/packages/7c/01/5a461ab34456d788248780830aed673c9e0e796f6e9dd92d3dbf02e04d7f/ty-0.0.66-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a58d32c879d86428978332adf21e85009ec269314a23d330c3483c65b57aedc7", size = 12471917, upload-time = "2026-08-04T01:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/46/ed/f8eb5eff7c9ee644490c6d2626425935c097acc54871adf659ea70624a2c/ty-0.0.66-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2f810fa3516c977630d78dbe9161592b3c27029fa9bb81074366624d9b5e4b6", size = 12858086, upload-time = "2026-08-04T01:09:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/7f/8a78361f752274a550a7f6f07f127b246ece7d7fdde31121d22074e46eab/ty-0.0.66-py3-none-win32.whl", hash = "sha256:d28c3df565a387c1c5ea359aa452a46d19163616ef71be819058a424a62f1ae1", size = 11782494, upload-time = "2026-08-04T01:09:40.136Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e2/deed22b823ce309b8410d50414fd15afe9e90aee8b8ca04789ba55c21231/ty-0.0.66-py3-none-win_amd64.whl", hash = "sha256:e3a457f3312c078f24c47d0da6e4f73de34d0a77ed2de22571c066c80b2fd5e7", size = 12893338, upload-time = "2026-08-04T01:09:42.825Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ce/6c828f42ef1ed39f53f57f9c0cdcdf03e23666fa7a29d799042a2c34bcb4/ty-0.0.66-py3-none-win_arm64.whl", hash = "sha256:2f62ae247b9c75674fcc060635f9f00210357da7681de59234d97b50fb9e9e94", size = 12227381, upload-time = "2026-08-04T01:09:45.365Z" }, ] [[package]] name = "typer" -version = "0.27.0" +version = "0.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -4140,9 +4141,9 @@ dependencies = [ { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430, upload-time = "2026-07-15T19:21:07.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/40/4a3db7990d1f62a53182aa96eaef57aeb2886a27f90a195bc66713565d31/typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df", size = 203994, upload-time = "2026-08-03T14:41:03.438Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716, upload-time = "2026-07-15T19:21:05.553Z" }, + { url = "https://files.pythonhosted.org/packages/43/89/9518bc0c3929bee36b3a4a8e3daddd6e03f92f9961c66d4983b837160543/typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56", size = 122874, upload-time = "2026-08-03T14:41:04.391Z" }, ] [[package]] @@ -4165,15 +4166,14 @@ wheels = [ [[package]] name = "typeshed-client" -version = "2.12.0" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-resources" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/5d/97d6fa8c204b0d42be931e7a568b306cda43f93ff45f8b47537ee61390de/typeshed_client-2.12.0.tar.gz", hash = "sha256:54dcfa25fcff82cedcb1b51c03c1b3c51a614097aab8fe49e4316aff6f79d9c7", size = 529457, upload-time = "2026-06-02T04:00:51.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/eb/ef31fadb73c5a9da6e88adf146b9db88282cf17136beb65a1c88905afe2f/typeshed_client-2.13.0.tar.gz", hash = "sha256:85ce73be98a5e3a51cafb2e2921653e3759ce7e9d1530adcca935dec201152f2", size = 532578, upload-time = "2026-08-03T04:11:13.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/98/3aef1587ec9b3a5cd062b8d8910545a0ee3715c31fd65b1f2386b27b94af/typeshed_client-2.12.0-py3-none-any.whl", hash = "sha256:da3ef54a0e60c0f45f59006b73a3cb20b1acbf7a784e982476d8193e8828ddf5", size = 796929, upload-time = "2026-06-02T04:00:49.643Z" }, + { url = "https://files.pythonhosted.org/packages/76/2b/83e50380aa9ca001ee74460f027b518eefd330edb7ab7787442424f1e584/typeshed_client-2.13.0-py3-none-any.whl", hash = "sha256:71bc9ed297ddb32d3c4b2d29b5e688c6b0b9a21ca8ce6082d0aa7cee9cad5077", size = 800208, upload-time = "2026-08-03T04:11:12.368Z" }, ] [[package]] From 97a08eea985ad4bb7d3edef339d1f4a434178c2d Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 4 Aug 2026 00:38:30 -0700 Subject: [PATCH 69/82] bump dockerfile source version --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2ef4027bf..97d64f06a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -117,7 +117,7 @@ RUN set -eux \ && rm -f /tmp/${CODEX_WHEEL} # ---- Stage 4: final (production image) ------------------------------------ -FROM ghcr.io/ajslater/python-debian:3.14.4-slim-trixie_0 AS final +FROM ghcr.io/ajslater/python-debian:3.14.6-slim-trixie_0 AS final ARG CODEX_VERSION=dev LABEL org.opencontainers.image.title="Codex" \ org.opencontainers.image.description="Codex Comic Server" \ @@ -157,4 +157,4 @@ COPY --from=wheel-installer /usr/local /usr/local VOLUME /comics VOLUME /config EXPOSE 9810 -CMD ["/usr/local/bin/codex"] \ No newline at end of file +CMD ["/usr/local/bin/codex"] From e5dec1002ff2f551a52ec5608a9f62fca4266631 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 4 Aug 2026 00:41:05 -0700 Subject: [PATCH 70/82] granian 2.8.0 --- CLAUDE.md | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b75f75632..9fd614167 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,7 +103,7 @@ sibling `cfg` boilerplate system. Key fragments: `codex.mk`, `django.mk`, ### Key Libraries -- **Backend:** Django 6, Channels 4.2, DRF 3.16, Granian 2.7, comicbox (comic +- **Backend:** Django 6, Channels 4.2, DRF 3.16, Granian 2.8, comicbox (comic parsing), Pillow, django-cachalot, loguru - **Frontend:** Vue 3.5, Vite 8, Vuetify 4, Pinia 3, xior - **Database:** SQLite + WAL + FTS5 full-text search diff --git a/pyproject.toml b/pyproject.toml index 28467066f..eee6a7826 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "djangorestframework~=3.16", "django~=6.0", "drf-spectacular~=0.28", - "granian~=2.7", + "granian~=2.8", "humanize~=4.12", "lark~=1.2", "loguru~=0.7.3", diff --git a/uv.lock b/uv.lock index 6aa5e83ef..ac457a129 100644 --- a/uv.lock +++ b/uv.lock @@ -666,7 +666,7 @@ requires-dist = [ { name = "djangorestframework-camel-case", specifier = "~=1.4" }, { name = "djangorestframework-jsonapi", specifier = "~=8.1" }, { name = "drf-spectacular", specifier = "~=0.28" }, - { name = "granian", specifier = "~=2.7" }, + { name = "granian", specifier = "~=2.8" }, { name = "humanize", specifier = "~=4.12" }, { name = "lark", specifier = "~=1.2" }, { name = "loguru", specifier = "~=0.7.3" }, From 7a6ee380580b9f2e5d3c0815f819e85db3478967 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 4 Aug 2026 14:36:52 -0700 Subject: [PATCH 71/82] update deps --- Dockerfile | 2 +- bun.lock | 12 ++++++------ frontend/bun.lock | 12 ++++++------ frontend/package.json | 4 ++-- package.json | 2 +- uv.lock | 18 +++++++++--------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index 97d64f06a..5808324e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -157,4 +157,4 @@ COPY --from=wheel-installer /usr/local /usr/local VOLUME /comics VOLUME /config EXPOSE 9810 -CMD ["/usr/local/bin/codex"] +CMD ["/usr/local/bin/codex"] \ No newline at end of file diff --git a/bun.lock b/bun.lock index d4bfd08c3..cfa0311ae 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,7 @@ "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.24", + "@vitest/eslint-plugin": "^1.6.26", "eslint": "^10.8.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-array-func": "^5.1.1", @@ -25,16 +25,16 @@ "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", "eslint-plugin-package-json": "^1.6.2", - "eslint-plugin-perfectionist": "^5.10.0", + "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-regexp": "^3.1.1", "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", - "eslint-plugin-unicorn": "^72.0.0", + "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-vue": "^10.10.0", - "eslint-plugin-vue-scoped-css": "^3.1.1", + "eslint-plugin-vue-scoped-css": "^3.1.3", "eslint-plugin-yml": "^3.7.0", "eslint_d": "^15.0.3", "postcss-scss": "^4.0.9", @@ -420,13 +420,13 @@ "eslint-plugin-toml": ["eslint-plugin-toml@1.5.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "debug": "^4.1.1", "toml-eslint-parser": "^1.0.1" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-qBjRywEkKxO2uYOjus//6GVF1r+Hg5QDkRO8RTY6XcaXgWfBU0DhwpFmJa2Ljf0Sz49r7DdZlpKwwHmJ4nmH1Q=="], - "eslint-plugin-unicorn": ["eslint-plugin-unicorn@72.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@eslint/css-tree": "^4.0.4", "browserslist": "^4.28.4", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.49.0", "detect-indent": "^7.0.2", "entities": "^4.5.0", "find-up-simple": "^1.0.1", "globals": "^17.7.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "is-identifier": "^1.1.0", "pluralize": "^8.0.0", "quote-js-string": "^0.1.0", "regjsparser": "^0.13.2", "reserved-identifiers": "^1.2.0", "semver": "^7.8.5", "strip-indent": "^4.1.1", "yaml": "^2.9.0" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-hqO6ksoOHO+ZhdseTuKRVQbx9U7PRO/cv8qAR1mctwzdVO2hYud8uS9luAhp43RJgziYgHAph8eHyipT8GL0ng=="], + "eslint-plugin-unicorn": ["eslint-plugin-unicorn@73.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@eslint/css-tree": "^4.0.4", "browserslist": "^4.28.4", "change-case": "^5.4.4", "ci-info": "^4.4.0", "core-js-compat": "^3.49.0", "detect-indent": "^7.0.2", "entities": "^4.5.0", "find-up-simple": "^1.0.1", "globals": "^17.7.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "is-identifier": "^1.1.0", "pluralize": "^8.0.0", "quote-js-string": "^0.1.0", "regjsparser": "^0.13.2", "reserved-identifiers": "^1.2.0", "semver": "^7.8.5", "strip-indent": "^4.1.1", "yaml": "^2.9.0" }, "peerDependencies": { "eslint": ">=10.4" } }, "sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw=="], "eslint-plugin-vue": ["eslint-plugin-vue@10.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.4", "semver": "^7.8.5", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.3.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-dL9x9rBHqqNcByWiLOHK6L0SB97V82/NC0cZRn9cXPjM7pCuWlpQQP9bFH4vjBv80ej1ZpzAkuD8zWH1o9bZbA=="], "eslint-plugin-vue-scoped-css": ["eslint-plugin-vue-scoped-css@3.1.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "es-toolkit": "^1.46.1", "postcss": "^8.4.31", "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "eslint": ">=9.38.0", "postcss-scss": "^4.0.3", "postcss-styl": "^0.12.0", "vue-eslint-parser": ">=7.1.0" }, "optionalPeers": ["postcss-scss", "postcss-styl"] }, "sha512-Kt3VC6C14HnbAzkumVA+p420BBdoym/gJjQtyYBX6V2ktOTDOVRfPDj9qJwHfVevJ0nOY5WeLXa0SV1wvSwGMQ=="], - "eslint-plugin-yml": ["eslint-plugin-yml@3.7.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-cLkVHdBHyRifThJA3ufuEW+imPRO7rHBQXdWgthouY6qEapUw8/0zW6V8NcMEGWzuMykc/ITYl6AuhZLPzI+fw=="], + "eslint-plugin-yml": ["eslint-plugin-yml@3.8.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-LC1mTQnjqhmmevGHsK+2daDe9FI87pZ9P2ey8vue8YSIqKxOYNWUsCMo/pW9o0ipT5KTkI4gktq/YSXk9NeFUQ=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], diff --git a/frontend/bun.lock b/frontend/bun.lock index bf64bd648..9c0825732 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,7 +5,7 @@ "": { "name": "codex", "dependencies": { - "@unhead/vue": "^3.2.3", + "@unhead/vue": "^3.3.0", "@vueuse/core": "^14.4.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", @@ -16,14 +16,14 @@ "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", "vuetify": "^4.1.7", - "xior": "^0.8.3", + "xior": "^0.8.4", }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", "@types/node": "^26.1.2", - "@unhead/bundler": "^3.2.3", + "@unhead/bundler": "^3.3.0", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", @@ -290,9 +290,9 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@unhead/bundler": ["@unhead/bundler@3.3.0", "", { "dependencies": { "magic-string": "^1.1.0", "oxc-walker": "^1.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.3.0", "@vitejs/devtools-kit": "^0.4.1", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0", "unhead": "^3.3.0", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "@vitejs/devtools-kit", "esbuild", "lightningcss", "oxc-parser", "rolldown", "vite", "webpack"] }, "sha512-H5Zvq5PjT6/ojRROs2PqE4MOrvwuTcMfYUQTx9HUdmS3uqfqK9pKZEkZTLC/8WUVWyiyPv6NW3+CK+3w0IUaww=="], + "@unhead/bundler": ["@unhead/bundler@3.3.1", "", { "dependencies": { "magic-string": "^1.1.0", "oxc-walker": "^1.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.3.1", "@vitejs/devtools-kit": "^0.4.1", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0", "unhead": "^3.3.1", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "@vitejs/devtools-kit", "esbuild", "lightningcss", "oxc-parser", "rolldown", "vite", "webpack"] }, "sha512-F9gEgqpUKqHFzOv+7Pgm3TbmXhUdLX+WjZiPAK/huLDzblzZmvAUE9vRDymWaOST7jqGT/tPKkwl2kqbgb3nPw=="], - "@unhead/vue": ["@unhead/vue@3.3.0", "", { "dependencies": { "@unhead/bundler": "3.3.0", "hookable": "^6.1.1", "unhead": "3.3.0", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-VUCYDCAIt7+izwxVqkHdZ4vP/gsrNYCUZ3Jp5/k8oDPAqbv/gu2p40NrQbM7OXNrUlHCCvJ0qZE+0nXrVtHTqg=="], + "@unhead/vue": ["@unhead/vue@3.3.1", "", { "dependencies": { "@unhead/bundler": "3.3.1", "hookable": "^6.1.1", "unhead": "3.3.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-iS+eiE1NehbV/eF7wzR7UUuUVTv8fIphFOCekQvEMuPqfKPCB8f3wf7sgPgUngYX6mdgM6PmkGmaOQgTBxqwbw=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], @@ -782,7 +782,7 @@ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "unhead": ["unhead@3.3.0", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-cMqydgLO/qSJBshSM8nVFpihwjefJ5X+G3xhw5QwyPtkcS4+G1c3WFJ+UP9QEYor/2NfZ5hzGXl8iUMpsRxXkA=="], + "unhead": ["unhead@3.3.1", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-eqHlbLyuvIXw898WopQmosTml4PdYW5ZhXDom/sf7ysAqQB9uvYrw2/dNvbrmkJTufSkmNmgGCjoQcvoT2mS/A=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], diff --git a/frontend/package.json b/frontend/package.json index 5fe2f4104..8471769db 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "test:ci": "vitest run --reporter verbose --update" }, "dependencies": { - "@unhead/vue": "^3.3.0", + "@unhead/vue": "^3.3.1", "@vueuse/core": "^14.4.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", @@ -34,7 +34,7 @@ "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", "@types/node": "^26.1.2", - "@unhead/bundler": "^3.3.0", + "@unhead/bundler": "^3.3.1", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", diff --git a/package.json b/package.json index 05aa8fe20..e35fb17f2 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.3", - "eslint-plugin-yml": "^3.7.0", + "eslint-plugin-yml": "^3.8.0", "postcss-scss": "^4.0.9", "prettier": "^3.9.6", "prettier-plugin-nginx": "^1.0.3", diff --git a/uv.lock b/uv.lock index ac457a129..3ae1014df 100644 --- a/uv.lock +++ b/uv.lock @@ -1026,7 +1026,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/8c3ac3d8bc94e6de8 [[package]] name = "dateparser" -version = "1.4.1" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, @@ -1034,9 +1034,9 @@ dependencies = [ { name = "regex" }, { name = "tzlocal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/f4/561c49bca97af561d34eed27e3e831135eb5cb88e754c1150be41820f5c6/dateparser-1.4.1.tar.gz", hash = "sha256:f265df13c0380e2e07543ba74b67c0681aaa1096981ffcd35227e1aa0cb81c7c", size = 314734, upload-time = "2026-06-15T08:45:47.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/6a/9f06999c4f27e9192c5eb38bfffadc2e6752df8178e97e88b10b9eb4c682/dateparser-1.4.2.tar.gz", hash = "sha256:bed2a3fd9bad8f2fb2d72b57748bada260b3a9349a264c22ffc23c3249d7049a", size = 338363, upload-time = "2026-08-04T12:11:03.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/7c/2e5dcf53909deddd0bf38cbe277ad9806be038276b1c6c436561b4d9b2e2/dateparser-1.4.1-py3-none-any.whl", hash = "sha256:f25d4e051a84be27a35bd297e3e1dc59ff78373701b89be352ba80372d22d0d0", size = 300503, upload-time = "2026-06-15T08:45:45.951Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1b/349e07ad184d64e81109e85a3557d7e05631fa3d05344169114ba743c4d3/dateparser-1.4.2-py3-none-any.whl", hash = "sha256:752f3d49d477cf7f60a7a9c8bcb19c882496ede0e377d5a3d80014cdfeca7050", size = 316546, upload-time = "2026-08-04T12:11:01.396Z" }, ] [[package]] @@ -1063,16 +1063,16 @@ wheels = [ [[package]] name = "django" -version = "6.0.7" +version = "6.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/55/664f24ff81c9ea19cb7dfc851afeae1f3c2390c7aee01d4ded68b5c1580d/django-6.0.7.tar.gz", hash = "sha256:2998503fc083124fb58037084bfa00de323c7c743f05f1b4284e77bff0ab8890", size = 10921299, upload-time = "2026-07-07T13:51:26.485Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/28/5fee292e588dbdb782582436d1eb62ac8809ec55b98ef27f5b9676a00e9a/django-6.0.8.tar.gz", hash = "sha256:cb0bd962d27fc866f3c514b20aae6a7df56ec80b488f9899da46d675cd051526", size = 10924565, upload-time = "2026-08-04T15:03:39.469Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/ec/1ce5334b6a2c52ce619c23a0be8d366a57a0e080ebb2d88266e5c849157c/django-6.0.7-py3-none-any.whl", hash = "sha256:a037427c2288443a8c02a1b02295a31c239663aa682bc50b1976afb7cf6a769e", size = 8373344, upload-time = "2026-07-07T13:51:20.007Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b4/23e74d261eebfa9c8baed5e8810f09858b9afd613d1037f42ff7aa95c87f/django-6.0.8-py3-none-any.whl", hash = "sha256:9b98b7e1902e0e575ea4f42c175fc9512784f7f2580898286aee3388b322219d", size = 8376956, upload-time = "2026-08-04T15:03:35.138Z" }, ] [[package]] @@ -2469,11 +2469,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.2" +version = "26.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] From b82febda67880c9a9fecda6d5fb97c5d8d6ba91f Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 4 Aug 2026 14:54:10 -0700 Subject: [PATCH 72/82] update devenv --- bin/release-tag.sh | 11 +++++++++++ bun.lock | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100755 bin/release-tag.sh diff --git a/bin/release-tag.sh b/bin/release-tag.sh new file mode 100755 index 000000000..db4f783c9 --- /dev/null +++ b/bin/release-tag.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Merge main into develop and tag a release +set -euo pipefail +TAG=$1 +git checkout main +git pull +git tag v"$TAG" +git push --tags +git checkout develop +git merge main --no-edit +git push diff --git a/bun.lock b/bun.lock index cfa0311ae..1a920e050 100644 --- a/bun.lock +++ b/bun.lock @@ -35,7 +35,7 @@ "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.3", - "eslint-plugin-yml": "^3.7.0", + "eslint-plugin-yml": "^3.8.0", "eslint_d": "^15.0.3", "postcss-scss": "^4.0.9", "prettier": "^3.9.6", From ba64c919ea6db121fd7f277c151f1665361c6220 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Wed, 5 Aug 2026 20:10:56 -0700 Subject: [PATCH 73/82] update devenv and deps --- bun.lock | 10 +++++----- package.json | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 66037f614..8dd8dca8d 100644 --- a/bun.lock +++ b/bun.lock @@ -24,7 +24,7 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", - "eslint-plugin-package-json": "^1.6.2", + "eslint-plugin-package-json": "^1.6.3", "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", @@ -32,10 +32,10 @@ "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": ">=73.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.3", - "eslint-plugin-yml": "^3.8.0", + "eslint-plugin-yml": "^3.8.1", "eslint_d": "^15.0.3", "postcss-scss": "^4.0.9", "prettier": "^3.9.6", @@ -404,7 +404,7 @@ "eslint-plugin-no-use-extend-native": ["eslint-plugin-no-use-extend-native@0.7.3", "", { "dependencies": { "is-get-set-prop": "^2.0.0", "is-js-type": "^3.0.0", "is-obj-prop": "^2.0.0", "is-proto-prop": "^3.0.1" }, "peerDependencies": { "eslint": "^9.3.0 || ^10.0.0" } }, "sha512-kYJhgZkiZIavu/wIwrO+n4GemQcMX53kWCNZNr7nGMkRD1aBFLkDpBivEYP7nIJINCo9fzPbFjrpeX5kr2Qbww=="], - "eslint-plugin-package-json": ["eslint-plugin-package-json@1.6.2", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.5.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-Zp0CdqXKQqB9luUYa/TOwnGq3VlLotx1UDflZOzEt3D5A5kxu3Lj1VFJ087jKudEhc+K3kF20XV/aKZAlDuslw=="], + "eslint-plugin-package-json": ["eslint-plugin-package-json@1.6.3", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-32ZHJFzEtsWeZfFr/YMQzq7amG/9jewUuNIKmnkl7QoT7gwclxUwurqRMrjWK2S9lh4prZkNbymROQ6aDpb06Q=="], "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.10.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.65.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Kprsp9Us0GqAesYaAIzUViw57xYp5WBqzXrcE0Mtww++E5fexWXYBipMuuD7yvyH4vvpBH0+oJ+OMAmZ0oYXkw=="], @@ -426,7 +426,7 @@ "eslint-plugin-vue-scoped-css": ["eslint-plugin-vue-scoped-css@3.1.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "es-toolkit": "^1.46.1", "postcss": "^8.4.31", "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "eslint": ">=9.38.0", "postcss-scss": "^4.0.3", "postcss-styl": "^0.12.0", "vue-eslint-parser": ">=7.1.0" }, "optionalPeers": ["postcss-scss", "postcss-styl"] }, "sha512-Kt3VC6C14HnbAzkumVA+p420BBdoym/gJjQtyYBX6V2ktOTDOVRfPDj9qJwHfVevJ0nOY5WeLXa0SV1wvSwGMQ=="], - "eslint-plugin-yml": ["eslint-plugin-yml@3.8.0", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-LC1mTQnjqhmmevGHsK+2daDe9FI87pZ9P2ey8vue8YSIqKxOYNWUsCMo/pW9o0ipT5KTkI4gktq/YSXk9NeFUQ=="], + "eslint-plugin-yml": ["eslint-plugin-yml@3.8.1", "", { "dependencies": { "@eslint/core": "^1.0.1", "@eslint/plugin-kit": "^0.7.0", "@ota-meshi/ast-token-store": "^0.3.0", "diff-sequences": "^29.0.0", "escape-string-regexp": "5.0.0", "natural-compare": "^1.4.0", "yaml-eslint-parser": "^2.0.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-E/70psRwxz5EJ8dBtzrFfqSiiInuSytbdtFCjueb/GcKjsWzPBfxfhtQePImMAPjHwMA/VDurO7DJwStDJ4wWg=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], diff --git a/package.json b/package.json index e35fb17f2..50c159be6 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", - "eslint-plugin-package-json": "^1.6.2", + "eslint-plugin-package-json": "^1.6.3", "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", @@ -90,10 +90,10 @@ "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": ">=73.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.3", - "eslint-plugin-yml": "^3.8.0", + "eslint-plugin-yml": "^3.8.1", "postcss-scss": "^4.0.9", "prettier": "^3.9.6", "prettier-plugin-nginx": "^1.0.3", From 8717fcc65d2551fe170f2618ccca11091f0fbba8 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Wed, 5 Aug 2026 23:46:44 -0700 Subject: [PATCH 74/82] use nodejs26 in the builder --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5808324e9..014da9dc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ############################################################################### # ---- Stage 1: builder (build tools + Node for compilation) ----------------- -FROM nikolaik/python-nodejs:python3.14-nodejs24 AS builder-base +FROM nikolaik/python-nodejs:python3.14-nodejs26 AS builder-base # nodejs25 blocked on bug https://github.com/nodejs/node/issues/60303 COPY debian.sources /etc/apt/sources.list.d/ @@ -157,4 +157,4 @@ COPY --from=wheel-installer /usr/local /usr/local VOLUME /comics VOLUME /config EXPOSE 9810 -CMD ["/usr/local/bin/codex"] \ No newline at end of file +CMD ["/usr/local/bin/codex"] From 516b9cd0a06db96c1d4aa7855338eda5469ee2ad Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Wed, 5 Aug 2026 23:53:01 -0700 Subject: [PATCH 75/82] update deps --- Dockerfile | 2 +- frontend/bun.lock | 82 ++++++++++--- frontend/package.json | 2 +- package.json | 2 +- uv.lock | 260 +++++++++++++++++++++--------------------- 5 files changed, 198 insertions(+), 150 deletions(-) diff --git a/Dockerfile b/Dockerfile index 014da9dc8..51f7e757c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -157,4 +157,4 @@ COPY --from=wheel-installer /usr/local /usr/local VOLUME /comics VOLUME /config EXPOSE 9810 -CMD ["/usr/local/bin/codex"] +CMD ["/usr/local/bin/codex"] \ No newline at end of file diff --git a/frontend/bun.lock b/frontend/bun.lock index b55a83cca..28a89672a 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -54,7 +54,7 @@ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], @@ -322,7 +322,7 @@ "@vue/compiler-dom": ["@vue/compiler-dom@3.5.39", "", { "dependencies": { "@vue/compiler-core": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg=="], - "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/compiler-core": "3.5.40", "@vue/compiler-dom": "3.5.40", "@vue/compiler-ssr": "3.5.40", "@vue/shared": "3.5.40", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw=="], + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.41", "@vue/compiler-dom": "3.5.41", "@vue/compiler-ssr": "3.5.41", "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ=="], "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="], @@ -334,11 +334,11 @@ "@vue/language-core": ["@vue/language-core@3.3.9", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-in/68oAa4BCtVY6n/nkuhLIkV8DHYd2UivedJ6cMZ6UYtlq9jaoaSNUBHYCVO44z3nKg7MdE5OBoHKt5SxeBKQ=="], - "@vue/reactivity": ["@vue/reactivity@3.5.40", "", { "dependencies": { "@vue/shared": "3.5.40" } }, "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA=="], + "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="], - "@vue/runtime-core": ["@vue/runtime-core@3.5.40", "", { "dependencies": { "@vue/reactivity": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw=="], + "@vue/runtime-core": ["@vue/runtime-core@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg=="], - "@vue/runtime-dom": ["@vue/runtime-dom@3.5.40", "", { "dependencies": { "@vue/reactivity": "3.5.40", "@vue/runtime-core": "3.5.40", "@vue/shared": "3.5.40", "csstype": "^3.2.3" } }, "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ=="], + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/runtime-core": "3.5.41", "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw=="], "@vue/server-renderer": ["@vue/server-renderer@3.5.39", "", { "dependencies": { "@vue/compiler-ssr": "3.5.39", "@vue/shared": "3.5.39" }, "peerDependencies": { "vue": "3.5.39" } }, "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw=="], @@ -808,7 +808,7 @@ "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], - "vue": ["vue@3.5.40", "", { "dependencies": { "@vue/compiler-dom": "3.5.40", "@vue/compiler-sfc": "3.5.40", "@vue/runtime-dom": "3.5.40", "@vue/server-renderer": "3.5.40", "@vue/shared": "3.5.40" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig=="], + "vue": ["vue@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/server-renderer": "3.5.41", "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg=="], "vue-component-meta": ["vue-component-meta@3.3.9", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.9", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-4u1VmMVjqs9BbKaISBZD7b+dNdqFteh5BYN8xVaXkjjJ66no1sRtDnS6aSs5/P5AzQQGPw10snHTf/z3HpBn2w=="], @@ -854,7 +854,7 @@ "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], - "@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "@babel/parser/@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], @@ -870,32 +870,46 @@ "@vitest/snapshot/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "@vue-macros/common/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/compiler-core": "3.5.40", "@vue/compiler-dom": "3.5.40", "@vue/compiler-ssr": "3.5.40", "@vue/shared": "3.5.40", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw=="], + + "@vue/compiler-core/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "@vue/compiler-core/@vue/shared": ["@vue/shared@3.5.39", "", {}, "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA=="], "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vue/compiler-dom/@vue/shared": ["@vue/shared@3.5.39", "", {}, "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA=="], - "@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.40", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ=="], + "@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], - "@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.40", "", { "dependencies": { "@vue/compiler-core": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA=="], + "@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], - "@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.40", "", { "dependencies": { "@vue/compiler-dom": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg=="], + "@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], + + "@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "@vue/compiler-sfc/postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="], - "@vue/compiler-ssr/@vue/shared": ["@vue/shared@3.5.39", "", {}, "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA=="], "@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.40", "", { "dependencies": { "@vue/compiler-core": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA=="], + "@vue/reactivity/@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], + + "@vue/runtime-core/@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], + + "@vue/runtime-dom/@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], + "@vue/server-renderer/@vue/shared": ["@vue/shared@3.5.39", "", {}, "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA=="], + "ast-kit/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "ast-walker-scope/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "ast-walker-scope/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], "buffer-image-size/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], @@ -928,9 +942,11 @@ "vitest/vite": ["vite@8.1.4", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.16", "rolldown": "~1.1.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ=="], - "vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.40", "", { "dependencies": { "@vue/compiler-core": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA=="], + "vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], + + "vue/@vue/server-renderer": ["@vue/server-renderer@3.5.41", "", { "dependencies": { "@vue/compiler-ssr": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ=="], - "vue/@vue/server-renderer": ["@vue/server-renderer@3.5.40", "", { "dependencies": { "@vue/compiler-ssr": "3.5.40", "@vue/runtime-dom": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw=="], + "vue/@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], "vue-router/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -944,10 +960,26 @@ "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "@vue/compiler-sfc/postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + "@vue-macros/common/@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.40", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.40", "", { "dependencies": { "@vue/compiler-core": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.40", "", { "dependencies": { "@vue/compiler-dom": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg=="], + + "@vue-macros/common/@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue-macros/common/@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "@vue-macros/common/@vue/compiler-sfc/postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="], + + "@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.40", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ=="], + "ast-kit/@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "ast-walker-scope/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], "editorconfig/minimatch/brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], @@ -994,16 +1026,26 @@ "vitest/vite/postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="], - "vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.40", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ=="], + "vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], - "vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.40", "", { "dependencies": { "@vue/compiler-dom": "3.5.40", "@vue/shared": "3.5.40" } }, "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg=="], + "vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@vue-macros/common/@vue/compiler-sfc/@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@vue-macros/common/@vue/compiler-sfc/postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + + "@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "ast-kit/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + "editorconfig/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -1040,6 +1082,12 @@ "vue/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@vue-macros/common/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@2.0.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], } } diff --git a/frontend/package.json b/frontend/package.json index 8471769db..e554f93b0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,7 @@ "pinia": "^4.0.2", "pretty-bytes": "^7.1.1", "text-case": "^1.2.11", - "vue": "^3.5.40", + "vue": "^3.5.41", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", "vuetify": "^4.1.7", diff --git a/package.json b/package.json index 50c159be6..16e4b075e 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", - "eslint-plugin-unicorn": ">=73.0.0", + "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.3", "eslint-plugin-yml": "^3.8.1", diff --git a/uv.lock b/uv.lock index 3ae1014df..f661c0212 100644 --- a/uv.lock +++ b/uv.lock @@ -1186,14 +1186,14 @@ wheels = [ [[package]] name = "djangorestframework" -version = "3.17.1" +version = "3.17.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/35/c96055e700fdff25da3a7b7756cfd1d4dc54f38b9bc6d6c5e19e3a0fdc20/djangorestframework-3.17.2.tar.gz", hash = "sha256:89ed713b6dc83e1539f214b7d10808ae19bb8511004beba886225da6d5c9dafa", size = 906683, upload-time = "2026-08-05T07:47:22.5Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/e1/2c516bdc83652b1a60c6119366ac2c0607b479ed05cd6093f916ca8928f8/djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457", size = 898844, upload-time = "2026-03-24T16:58:31.845Z" }, + { url = "https://files.pythonhosted.org/packages/a2/46/c14108e400b208c394325eb63fbae06c81341b6447fa1a6f9da718b17fe7/djangorestframework-3.17.2-py3-none-any.whl", hash = "sha256:cb0546a7415d5b46c04e0f4fe0a54b2109f4fdd5e83ca773c8c6183a6493d042", size = 899109, upload-time = "2026-08-05T07:47:20.853Z" }, ] [[package]] @@ -1227,7 +1227,7 @@ wheels = [ [[package]] name = "djlint" -version = "1.43.2" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1238,45 +1238,45 @@ dependencies = [ { name = "pyyaml" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/33/7efcde4581d1295c1e64a75b6d1ac444b4c2557b668e73032070ff5fc081/djlint-1.43.2.tar.gz", hash = "sha256:5a783d9b419b0833be1a13f213c0ea5e4a86aa505bd5d159884a2a349d06f7e8", size = 79429, upload-time = "2026-08-01T18:11:35.424Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/d3/2d91ba6789a31d36c5a60b50b7ee0e438e49f0d5292906f1490548877f89/djlint-1.43.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b408ab06be47b5698246bacd5c52a5bb521004bf020101de3c97f05272c7187c", size = 798549, upload-time = "2026-08-01T18:10:48.972Z" }, - { url = "https://files.pythonhosted.org/packages/48/8a/f0a709a49160373f8c1830c74f3430cecf4e73256b5d45c9cb651439ac7e/djlint-1.43.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f72b2fa7576c2d1dc354526200d24ab31497e58e873b7fd7f65e7b434b51c084", size = 746050, upload-time = "2026-08-01T18:10:50.269Z" }, - { url = "https://files.pythonhosted.org/packages/89/f7/cff8daea9afc14c6fbe69eaf4bb45241b6fd8a1a55332a1700e5521d9447/djlint-1.43.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0f786367a863981d474ddef25f4567f32ffe4615a36da2e8cdf5023e0454f0d", size = 785811, upload-time = "2026-08-01T18:10:51.449Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c5/af91d53a9a4c2d51816c6930fd42bf457af27c3cfdf24072863e65354230/djlint-1.43.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495be4c8c88edeb155c854ef1bc49085789ab963f833c527383fea4cacd98fcc", size = 826151, upload-time = "2026-08-01T18:10:52.835Z" }, - { url = "https://files.pythonhosted.org/packages/00/72/dbde12bd9f34abe414302ffa3415f6f3446a89ad38a08c34ff408fb82989/djlint-1.43.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:873d45307377c37dce8380e427988aebb35cf049e81d6eb93e2c0867a19a6936", size = 797484, upload-time = "2026-08-01T18:10:54.107Z" }, - { url = "https://files.pythonhosted.org/packages/77/c7/eceb231602c07a85082053ac1fad06d93282b2e48889a835d1c4d560010e/djlint-1.43.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd70b3d735b0df87fe8f03dd0336b423eb2f62409edbc82aa418463647711a58", size = 839468, upload-time = "2026-08-01T18:10:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/7e/f0/99a655e012f117c2756bde96bf2ce2ef5b8ca44a583c89d6f7ed2c98757c/djlint-1.43.2-cp312-cp312-win32.whl", hash = "sha256:c5efeafcb82a75d6cb181e3cb138eeba4683794d37187fdf7eb06df0d3d971c6", size = 604579, upload-time = "2026-08-01T18:10:56.702Z" }, - { url = "https://files.pythonhosted.org/packages/03/e4/b3026c993dc562485306ed7bd3ba2f16a6594ab1e3bbe3877d3113700eec/djlint-1.43.2-cp312-cp312-win_amd64.whl", hash = "sha256:f690e4f57e5a98dc2e21858ed5fdb9fd0b4a03194d27ec984b5ccfad8ad49e3c", size = 682885, upload-time = "2026-08-01T18:10:57.988Z" }, - { url = "https://files.pythonhosted.org/packages/1b/0f/bef91d7a15390efb655ec6ae6a2d4339d40ef09cea45ff8f83f97b4709a7/djlint-1.43.2-cp312-cp312-win_arm64.whl", hash = "sha256:7d417d05a91571fe89ef8771586b97b65fb706409d67e511dd3401b55b3a31ba", size = 601172, upload-time = "2026-08-01T18:10:59.27Z" }, - { url = "https://files.pythonhosted.org/packages/19/c9/8e8ccc6fe34e778365d643b1068f9de44ec70b9c5ba3f8bf4e7fe364b8a8/djlint-1.43.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ba0528252afefdff3158bae5e5ab388de991975cb46cca5ba9aade90b61cc76", size = 788894, upload-time = "2026-08-01T18:11:00.584Z" }, - { url = "https://files.pythonhosted.org/packages/70/b1/27d3fe64a9577c11d79b8a6f3e55948c82f71fbae43d70cc68bc39196d46/djlint-1.43.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:059244446da78b8fd73bfe2d176fc9d90f4588f418361128222fa334a06d7d0d", size = 738192, upload-time = "2026-08-01T18:11:01.829Z" }, - { url = "https://files.pythonhosted.org/packages/d1/bb/fee48fa37c7ebdfa34b5bec62bd59e1424c615f78beff8788114666cf080/djlint-1.43.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3b253574e91c0add5715d9250eab20366a9dd77266d4ce562c64ba7ed560fc", size = 782208, upload-time = "2026-08-01T18:11:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/e5/cf/cd6d7513155862dfe82077b36f5b289a9072ce3842201cc1603fe43969bd/djlint-1.43.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:172f820ef5383f9fbb850303d31576ee9472d15326206d2e53d80afc1499ea86", size = 824374, upload-time = "2026-08-01T18:11:04.404Z" }, - { url = "https://files.pythonhosted.org/packages/36/65/a70bea9c9b18a874b4eff35bcd4606ee151608f29db3a170789b3c8e1252/djlint-1.43.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bceea92d58455664235c2cc95381c3775cdb7100d4803c35f2d3d0ce4b2ce66a", size = 794660, upload-time = "2026-08-01T18:11:05.597Z" }, - { url = "https://files.pythonhosted.org/packages/24/27/356e11b22ac6703b7c8d13f4505cb21add69ee6db0509e274f53a1cb8e09/djlint-1.43.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a9461bb2c4694b748fc89ec112503aa2296131e9ffe62b068338195f8de0feae", size = 837100, upload-time = "2026-08-01T18:11:06.962Z" }, - { url = "https://files.pythonhosted.org/packages/f9/d5/407a18447980b42e48a54c53deb4b0d13e32238309377773bbfc951d9ef7/djlint-1.43.2-cp313-cp313-win32.whl", hash = "sha256:87bd05fa5e6e4bc9d2a0dc7820eedc3ae2a197e50ed8acec8c0f8460fdc28a79", size = 605109, upload-time = "2026-08-01T18:11:08.084Z" }, - { url = "https://files.pythonhosted.org/packages/07/c4/ed2a5a81584f6d6017c44d72be18bc4a70bc75e2652ce72b3c7abb6712f1/djlint-1.43.2-cp313-cp313-win_amd64.whl", hash = "sha256:33fc19aee2145c55d971cbf4b21f6d298b673102716f7ea4818c3b89859dd4dc", size = 683941, upload-time = "2026-08-01T18:11:09.205Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d8/1c7f3dc0df0b59ee2cab442f9936add181f50396413bab5ca366d6601d56/djlint-1.43.2-cp313-cp313-win_arm64.whl", hash = "sha256:25317887d2f00ffc9a0dec1122c2305555d2642a2b31d4341e9472960aa941ea", size = 602018, upload-time = "2026-08-01T18:11:10.268Z" }, - { url = "https://files.pythonhosted.org/packages/04/e9/4baeab9fcb940d192e40ea9ea34395e105bc86df8a1737f15d46105c2d92/djlint-1.43.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ed20fa2ff45cc3e6a7ee10762d1c55adb44726651b909d0fed9ef229cf6b5b88", size = 783339, upload-time = "2026-08-01T18:11:11.674Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b9/46de92b11323bf77983e6a53da2dca45f8ee0f32c2b479be260d6f39fe03/djlint-1.43.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c0838196bf64cb91c9af2b3b767f881e9f202dbfb0b78d2c99b96a77f483abe", size = 738422, upload-time = "2026-08-01T18:11:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/34/ea/51383c99d94dff790a5587e9011dd856e8a765d45f07d55b08cda61f942a/djlint-1.43.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bab0add0c5f8e173ca88156ad47942b96b26cfe2c9eefeca0e2885358b9b7d0e", size = 791625, upload-time = "2026-08-01T18:11:14.321Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/ad185ad8631fbf398c92a922661367dedb8cfbd9b95f688c86ee733a5387/djlint-1.43.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07d21ea1f7255746d6560acf7b2f8c6075bc0fd671d65bc8cfc04c212d7e4278", size = 823617, upload-time = "2026-08-01T18:11:15.524Z" }, - { url = "https://files.pythonhosted.org/packages/e9/47/f9b16f3959288b44497f5770a8d44e6ac7ac94c09c2d7304610bd594c73e/djlint-1.43.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7b867dd145267bac965412e39a0ac29bd08b4ed7c12d47d70a522e76dd8ce9", size = 800945, upload-time = "2026-08-01T18:11:17.068Z" }, - { url = "https://files.pythonhosted.org/packages/13/be/a638305e2d187247e615652037053ccfa5ffc1b44b78468dfc9e92e69996/djlint-1.43.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4c1257246d9cfa203883b00b7a954d1d553e11d3e8aaeecf9eb7ec8526448a91", size = 836916, upload-time = "2026-08-01T18:11:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/eb/3fd34919552f12711d2edcfdb1e8fe599632ef018e4f44157f3918ba3d8e/djlint-1.43.2-cp314-cp314-win32.whl", hash = "sha256:a41bec381a167ac07de9924debc8a6a863749a789ba5af2c5ce43b1a7d9cca8e", size = 611954, upload-time = "2026-08-01T18:11:19.931Z" }, - { url = "https://files.pythonhosted.org/packages/da/4f/9327d4e9f0553e5ecac0e918fd260e1ada0569c601d5bec9f49675b4f72f/djlint-1.43.2-cp314-cp314-win_amd64.whl", hash = "sha256:0552ec07a7f857bcbf10cf924e6d53b75595994bf7c7f7c159f71193e6378bc3", size = 695877, upload-time = "2026-08-01T18:11:21.124Z" }, - { url = "https://files.pythonhosted.org/packages/64/f0/7f2c9f7ab49f768da213cd1ee82008dad7220617b6dc6e0b6f58667e953d/djlint-1.43.2-cp314-cp314-win_arm64.whl", hash = "sha256:f3850e29bc99741f34e7a013443f6272756a1f4ee43f24a5192d6d51d40b88d7", size = 614738, upload-time = "2026-08-01T18:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/07/69/6f8f0d31f2e67f6b34af485f270f6ef3b1c9bcbb1412811566d55e99037d/djlint-1.43.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3b5414a8677757669a8ab2c90a288dfc19091ffb48eb2b7f14430e2321392e3a", size = 847765, upload-time = "2026-08-01T18:11:23.654Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f8/ea398c5226d211ba05cb4c9c2b1cc12ce76850adc5663b22222ea8859ced/djlint-1.43.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7cfed64eace027bfc8c8e4f40fb7236681a6d22f0d341b6db0c3281227286f45", size = 795641, upload-time = "2026-08-01T18:11:24.954Z" }, - { url = "https://files.pythonhosted.org/packages/24/bd/dda3a5659aa4145d63d2463795c8c2c145d98a5445612c6a8c94b4762c14/djlint-1.43.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc01c9a8a6747ca531ec0d68cb4932f1432781428dbebb88648316627dbbe3c", size = 824096, upload-time = "2026-08-01T18:11:26.152Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7f/04abfb3ba5d538dd7954812bc17e00cf0cc9a935bdb53b50f6dd427c8535/djlint-1.43.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:404bf179a80bc79dda9ed0c23378de86cdc16b52fcc0bd5c3c4de08645fe289e", size = 857819, upload-time = "2026-08-01T18:11:27.394Z" }, - { url = "https://files.pythonhosted.org/packages/b0/db/0545f4b9abc72d6a583c07804856794aa12c053b0e76f483250a4be220a4/djlint-1.43.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9334fc05fee895e4df19a28470ae43eee04f079200c5130b183b1df2cfe657b9", size = 833564, upload-time = "2026-08-01T18:11:28.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/34/c455f7265ce325e6bba197b13019b832ddd22b1fbda5f6aac51afd737989/djlint-1.43.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1877288f95d3cf6952148cba1775f16203f18b4b4a50903697a31971f9b03e5", size = 870083, upload-time = "2026-08-01T18:11:29.837Z" }, - { url = "https://files.pythonhosted.org/packages/03/2f/78dfdfa4a23301a6a194983fa9dfdadd3d024e5fab917e567259c380a1c2/djlint-1.43.2-cp314-cp314t-win32.whl", hash = "sha256:e2f7a085e7791224a4b4e6810b8f49c73488bceaf3c7cd41bfd6d6c69d775fb3", size = 646299, upload-time = "2026-08-01T18:11:30.953Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e5/7fe1b6e62207af3f27bc023a6a7306c2207639266a9f85c00cba98853477/djlint-1.43.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9c66502e108d4641e70d37b1e1d5b74048e55f4d6e4d28d3fdb95b2c36b5476b", size = 724390, upload-time = "2026-08-01T18:11:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/04/2c/6e22bf4d4429069ba3a4b1e93e709b5d25b95387b4f29faef060c439959a/djlint-1.43.2-cp314-cp314t-win_arm64.whl", hash = "sha256:9df0871c77722707439fe341a4a48e230ca00e1b8287cb4f69a354ebca89b8d1", size = 642444, upload-time = "2026-08-01T18:11:33.305Z" }, - { url = "https://files.pythonhosted.org/packages/6e/09/094e5357615e1297c2fa4e3efecafabb36fbe462b4f7c0da216219bc8d82/djlint-1.43.2-py3-none-any.whl", hash = "sha256:e62ae32100f897992f19ac58ff0b67e8d8a13427ffadff327744c56f95ded5eb", size = 92962, upload-time = "2026-08-01T18:11:34.53Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5d/24/8589327d057b67ebef2c775f9ddd2d44f8c7599f0a335bcdb0fc4b7bf3e1/djlint-1.44.0.tar.gz", hash = "sha256:9a52a3bafeff0eb6fb9a68798c9ea25c6522b40fba5c139ffef0dbc961ce0b59", size = 81320, upload-time = "2026-08-04T23:37:23.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/2c/03989fccdceff7f04bc3cd768428c5a399260dcb3a8ee35bdec6ea8ce998/djlint-1.44.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36c84fd7c909874f2ad52c23741d4efa2a0f39036e92ef37e2c46eee3b04a8e3", size = 806977, upload-time = "2026-08-04T23:36:39.454Z" }, + { url = "https://files.pythonhosted.org/packages/4a/de/ff8620da5b2e26d1918c47b645a3b6f8da88bb8081796ef2e38c60fb7fb6/djlint-1.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3e72c3d900b5bd5efd4eb6301ebb97ba271fef2e33872dfe0171a67d1dc5ae6e", size = 756029, upload-time = "2026-08-04T23:36:40.43Z" }, + { url = "https://files.pythonhosted.org/packages/d1/74/c941cc4165baead55994b864c725949c651babed108e4c060e6ae118c7a8/djlint-1.44.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95e5b031c925504d6c8bfe989d80fb9e686cedbc174be32e4b0e91a741766df0", size = 796458, upload-time = "2026-08-04T23:36:41.841Z" }, + { url = "https://files.pythonhosted.org/packages/07/d2/1ef8810563ab3e74546e05571b0ae6d2794bc6af13153fadef2283a2c0e0/djlint-1.44.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f26549bea48f31e2b40fc429ee868a53883854fbbdc3046570de758271a9d3a", size = 835735, upload-time = "2026-08-04T23:36:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/aeeb9b5c22455294c84246dcdfafbd9f8224cb398a0f3624d74769ae82d6/djlint-1.44.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eab32aac420c370f1d980b6053dd35195cb0919b52d293a90215cedf922605c9", size = 805751, upload-time = "2026-08-04T23:36:44.026Z" }, + { url = "https://files.pythonhosted.org/packages/7c/db/7ce8a783146a8378150e876b7990cbeea0bf7cb6b58826688d6cdfd583ee/djlint-1.44.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb43cb30c5fef2eeadd0f75a65b2db30c0c47ba735791e208cd98ea4472602db", size = 850548, upload-time = "2026-08-04T23:36:45.334Z" }, + { url = "https://files.pythonhosted.org/packages/80/54/850353f3c7501e0a49f1eb55ae38f1c8bfa271ca92e38c873b9b6d0d1a5d/djlint-1.44.0-cp312-cp312-win32.whl", hash = "sha256:d5f17cbd6b1f9f0ff1560f2ce0beec59b84fce5ea13068919ec0a1ce560231b7", size = 611159, upload-time = "2026-08-04T23:36:46.552Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e0/13ddf7e392eb21e68ca6adfc2596fb5e91ab7d2f96027fb6c0845f80be4b/djlint-1.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba7ae53f53f8005367ac45bef90428d926f897b59e715da3a45ab6604acc53de", size = 690568, upload-time = "2026-08-04T23:36:47.595Z" }, + { url = "https://files.pythonhosted.org/packages/71/f8/cad12adf1baf03aa8030c427acef55bc7026fdfc1da7c9d09928457051d3/djlint-1.44.0-cp312-cp312-win_arm64.whl", hash = "sha256:9a10c631af7231aa43841d2acd0602de99e1ba7e292a7c91cd7fd4878e09d9aa", size = 607658, upload-time = "2026-08-04T23:36:48.696Z" }, + { url = "https://files.pythonhosted.org/packages/7c/09/af7607c6d6cc503c61c8fbbf305fe4548e110439c5a7102cbcbbbf7e899b/djlint-1.44.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd816cd84d7652c847d41e1393ee0618b09d6330b2322a64504b8a863239504f", size = 799694, upload-time = "2026-08-04T23:36:49.918Z" }, + { url = "https://files.pythonhosted.org/packages/68/c8/1662853ef863060c2cb4915bec38e434d292db639ef9850ae5cd9cd39bad/djlint-1.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:595602fa18a1fd8c236ac353ca3e013bc09e438888dd9bb2af650f783d61aa24", size = 748158, upload-time = "2026-08-04T23:36:51.064Z" }, + { url = "https://files.pythonhosted.org/packages/47/09/837358b537cd64c675f5a2b3c08253865f4c30fadb9444292359334e2691/djlint-1.44.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b43475383dab66c212cf42daf206a5eb39cfc43654898af099fc14ed4ef6fc2a", size = 792562, upload-time = "2026-08-04T23:36:52.275Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9b/9b64c5a62ddf4b7d398bd49bd61795870687f39f41c3c67bf32ec105641d/djlint-1.44.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:198407a4e561bb9135013516a05e1385cd943f4199167a3f6df8fd4e74d3d75b", size = 832562, upload-time = "2026-08-04T23:36:53.662Z" }, + { url = "https://files.pythonhosted.org/packages/21/0b/7f9df254773ba97388990b1b0907931ccb4700c1d548b55323980dc1fbda/djlint-1.44.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ef1ee5cac7bd4fbf56c175652ae111cb8dda7e0a791008d9bc29e0cc16ca", size = 803170, upload-time = "2026-08-04T23:36:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/95/15/870c19a8cc6e88436bede24c0f4384ce7475ec55e2ba09e51ed51b8ce2a9/djlint-1.44.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cef398778562128c08b91355023a0158d4e8f3042b95f8dbfe0437054203be47", size = 846928, upload-time = "2026-08-04T23:36:56.235Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/22c1c2f7423926afa8d632a71e15c839c5ea3593bf7fff13fb2adb6b27bb/djlint-1.44.0-cp313-cp313-win32.whl", hash = "sha256:8eeaacadf5365c388f2aef4706f521f57e062dacbf95b691ce0d6d69f9a466dc", size = 611690, upload-time = "2026-08-04T23:36:57.346Z" }, + { url = "https://files.pythonhosted.org/packages/36/81/551ff68f8ced6f5816a19702588c956d3467531f9c720a01c38f5447107b/djlint-1.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:a811ff1feb6497874bc3af537582c3648dd7b90b450773433d6be093fc34878e", size = 692250, upload-time = "2026-08-04T23:36:58.535Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/73a0b2a6685b74a761d4a126d7273a9a5449dbfe635e15602d00dde0d34d/djlint-1.44.0-cp313-cp313-win_arm64.whl", hash = "sha256:0046c523f27023bbdb9ec2ff601195d80bb122eb50235453df37a2d034835e4e", size = 608274, upload-time = "2026-08-04T23:36:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/12/6b/938ce125079b118ba5e93c3f1d8fb96c90779cbb0c2ec10dcb656ac37aeb/djlint-1.44.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c0c76743399acdfd649d0dac194a2dac0bed78656231134a7d618192cf8761c", size = 794735, upload-time = "2026-08-04T23:37:00.841Z" }, + { url = "https://files.pythonhosted.org/packages/7f/e7/83b9e166d309fdb86c38a4159b159502dfb541965f0ffd440c8f9de5127e/djlint-1.44.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d17b2691ed2f833d84855b8d8e7b5104fbb5086056d18126e7b18dab605ff0d4", size = 748728, upload-time = "2026-08-04T23:37:01.939Z" }, + { url = "https://files.pythonhosted.org/packages/36/70/4d2b43779191f2fa3020fc3fe0452f5ebfa2cea2de9084a84ed7511cfa09/djlint-1.44.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f320fcc02eb074535f75adf17212ed8ab926341ca30c8e727eeec0401eadc6fc", size = 800822, upload-time = "2026-08-04T23:37:03.026Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f7/a3b43c8df9ac8bca30487572750c7069bce4b560c63a465e1aaafaab13b2/djlint-1.44.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10e3eafdd5f06e1db3c713a167c1cb9541f10287d259fae164cbe76c6a2b3212", size = 831214, upload-time = "2026-08-04T23:37:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/2a/28/e6a9931302ef6a27bcaf9acd9d12458754e3cc9f298c4d67cb18842d2f37/djlint-1.44.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6ff96db13b8a0495f9cd89aba55c598592a16aaf9bad11fcf629f800145be10a", size = 809360, upload-time = "2026-08-04T23:37:05.783Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d8/3ef6dc7ff7baa3bdc44bfc3243b653e1f2e6810ada7e0e5332f8d8c065d9/djlint-1.44.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:617cb06e307b0ee724c5738d02869adfd0da63906c07a9cea44b2f1d2ff46bb1", size = 846109, upload-time = "2026-08-04T23:37:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/95/69/03caf40bdc508d43a94530da7fac4ab4d2e2ae8e8b0a75bfc4d37c2b854a/djlint-1.44.0-cp314-cp314-win32.whl", hash = "sha256:824d91c636d644baccde7f30c421df5be89b686f5b06df85ee33e7d83ccdc3ea", size = 619403, upload-time = "2026-08-04T23:37:08.075Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b6/f868d840b5faa5f24a2bae50ea86d575008f79e1114555b1cc23e456a99b/djlint-1.44.0-cp314-cp314-win_amd64.whl", hash = "sha256:b327a42ce7a6202f06216e26caadc88caa982ab8dccc20b74394373213faf563", size = 703482, upload-time = "2026-08-04T23:37:09.215Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/09428687d4955f0de8da7f67672f8f6e5615595ba7c1859d3382a87af75d/djlint-1.44.0-cp314-cp314-win_arm64.whl", hash = "sha256:e3da3aefbdecb1c821f9d9e8b7cb0167bf1089ec37ed7dee2c6fef301920e7e3", size = 621782, upload-time = "2026-08-04T23:37:10.339Z" }, + { url = "https://files.pythonhosted.org/packages/95/f7/6f03c3878d06df554f72ee2b1cabe131e2b744c8b8494d0f839b29ffacb2/djlint-1.44.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb5b33854effafc23b6c5bacaa7bc311158bfc4c636437416b8e7f87e3a8393", size = 859322, upload-time = "2026-08-04T23:37:11.441Z" }, + { url = "https://files.pythonhosted.org/packages/95/63/681f08cd4c7b80ca847a5c951c84a4c5f1caa19fe7d71db5103a9871ada6/djlint-1.44.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:831a299a0538bdc2d59444f29a6f3c3577c782a8acf8a1680de4f4caca5da14d", size = 807326, upload-time = "2026-08-04T23:37:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/12/b5/f465ad93a139c337a36e180f156b0ff9d63ca3ac69ef315b0d9caaf7aa99/djlint-1.44.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3398964848b797c7c67e684fde7a2e88febe33135bc068e51bd7b2ac5903de4", size = 833802, upload-time = "2026-08-04T23:37:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/899306ddadab53156aa6e55ee828591bacea7205f46da377debc6a16d985/djlint-1.44.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b01666e18fbdc09b9fe203988603ca541b2e641997d0f5b820ae616502331b66", size = 866227, upload-time = "2026-08-04T23:37:14.966Z" }, + { url = "https://files.pythonhosted.org/packages/8e/46/d7a766df06bf4e0bf3f51b7c68e314d86b6abcbe845657e1e2ca636c6a1d/djlint-1.44.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2e464b2afb873b8de37ecf115ae8101476126dbacc466d1eb6f81068a032d393", size = 841068, upload-time = "2026-08-04T23:37:16.221Z" }, + { url = "https://files.pythonhosted.org/packages/49/9e/65d10e87b8c9a08c4d8658248397a7d8168259a7a87923d4c23673c8f36f/djlint-1.44.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b52031af5f5fa9714943bb65c9b2f393180317d42be83a993a2e247e57bc6d44", size = 879814, upload-time = "2026-08-04T23:37:17.433Z" }, + { url = "https://files.pythonhosted.org/packages/95/5e/c317c2ab010391953e72b2e530cf354300f93864ade983979bca5d8f1982/djlint-1.44.0-cp314-cp314t-win32.whl", hash = "sha256:0e89a7881d0f2b808dc7b94927883b7f748950f614d78dd4770e745c19b085b7", size = 653753, upload-time = "2026-08-04T23:37:18.608Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/a248ea60f09c6d9e14ea49702ac79d83233722f2e2a6568636ce96f53ff6/djlint-1.44.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e1e944cb721a04880b7bb120ad186abed1be0a5e009312b5156bbcc23f962acd", size = 731414, upload-time = "2026-08-04T23:37:19.957Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/111106a997beb32a85eac520b71c7b95b3b38f3680cb66a66b223a49cb97/djlint-1.44.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d2902a48be3861530b98078c2614b6cfec04b270530d108d3d5511009eb595d2", size = 649139, upload-time = "2026-08-04T23:37:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/3b/21/b2d83bb61808d3cc62c603431a7a20d8326d3246e919b60ca9200dfbd785/djlint-1.44.0-py3-none-any.whl", hash = "sha256:84a36fb6af2f3b55853af4eaf6659634338b948b567b8e521385c8813276f869", size = 94844, upload-time = "2026-08-04T23:37:22.645Z" }, ] [[package]] @@ -1390,73 +1390,73 @@ wheels = [ [[package]] name = "granian" -version = "2.8.0" +version = "2.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/22/f289f681277c8a1cd2c29177fc21bad080b270d6b60ac6e91a19cafe795a/granian-2.8.0.tar.gz", hash = "sha256:be842c6737b41beb6fe6521f9c46695a1843ec9e086a18bbbc945ed170ff28e2", size = 129256, upload-time = "2026-08-03T18:58:55.371Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/ab/432706e82c0bb95ecaf9aeee26d36c13b2917a358377ecf07675b363d61a/granian-2.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:de8fc64f423f000a0097886e0c4e8ae1bfb4b4d0699edc6244da768c05c0b40b", size = 4442542, upload-time = "2026-08-03T18:56:43.266Z" }, - { url = "https://files.pythonhosted.org/packages/2d/a5/b7835933f999a73d8fd5b15f6734f6abb40edac4d759b2490a069762c1ad/granian-2.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76fdd00bb5842a7993ce57e7d254e75d5e9d7425b49d02a341b93d618047f1fa", size = 4264376, upload-time = "2026-08-03T18:56:45.041Z" }, - { url = "https://files.pythonhosted.org/packages/18/63/5245e1d11a3920b1d7705d6748e736b07a48f1415817a2b7b0eab921b26c/granian-2.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:edca648754927c913dfcf52068b96418b66810017fd628d19cb1b65f78dfd9a6", size = 4812379, upload-time = "2026-08-03T18:56:46.69Z" }, - { url = "https://files.pythonhosted.org/packages/93/00/a2dd16309d39be4888fe90da677692f0148feff589438b536425ffd3d3de/granian-2.8.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:899b1af43754ee8ef6beaece26947cee3fa03487f0193d5fc17fb55435903a68", size = 4446390, upload-time = "2026-08-03T18:56:48.573Z" }, - { url = "https://files.pythonhosted.org/packages/95/87/8f9e929ac017233ce14123601765d095305b407df0a512ae8c14ea741380/granian-2.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278db6788bacb37e9e57abb429229f74c5b5f6f5dca06f66fad027a4ff6dc51c", size = 4860867, upload-time = "2026-08-03T18:56:50.346Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2e/5124c9c4d66d3e8405f3cc5fa4074d06a703546456c002587af9d3c2822a/granian-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5827ee36b7e5af4a104430d802cc0454913dd2f984b0b594c42ba049a1deeb61", size = 5023347, upload-time = "2026-08-03T18:56:52.17Z" }, - { url = "https://files.pythonhosted.org/packages/5a/14/d4624e7c22c7b26d1ba0e296a6c25621a6daeedd09a1399fdf9c34e2e8ec/granian-2.8.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67e1f73705aac805ec1e7e10c38946a4dd49f26e018d250014d1fd9a924dca64", size = 4993318, upload-time = "2026-08-03T18:56:53.693Z" }, - { url = "https://files.pythonhosted.org/packages/bc/7b/55fd1dca67c273460fe13f622cfbcf17812b27077656bb9c7982b88e9d31/granian-2.8.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:89d4904bb657503ab41b304340c42371c9f9b01f83b6c7c136f5a0d8bff50efd", size = 4923508, upload-time = "2026-08-03T18:56:55.918Z" }, - { url = "https://files.pythonhosted.org/packages/18/b7/1ee1f572b86c5681387ca10e10750c3d35583db43aa85213bbc33f2cf6f3/granian-2.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49bbcf8c20370cba6d0d5ba1aac95f5e9d22a3e95b10b420d49337629d2f4fcc", size = 4924703, upload-time = "2026-08-03T18:56:57.783Z" }, - { url = "https://files.pythonhosted.org/packages/bc/2f/0b4e8a671e52b868140a80b67ddf665aa85622114a2dc28dc550119cbf49/granian-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:471e4d122518ca23776677bcd104c37bdf31377a4a8b8c668e3d6551e90213bf", size = 3112321, upload-time = "2026-08-03T18:56:59.879Z" }, - { url = "https://files.pythonhosted.org/packages/95/45/1c1f3b80b19813a8c3f025397aea4b56b62d2d422100b10498a7f13bcf35/granian-2.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9e28aa83940629169986615ecbcc39c026e193df41ba8264202fefba5edaa8b6", size = 4442295, upload-time = "2026-08-03T18:57:01.543Z" }, - { url = "https://files.pythonhosted.org/packages/28/1b/e686b7504e125a2dd5757f2b668c0ef4707467d04add2e42cfa9b19f9139/granian-2.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf3ed0a22327aeb1b15e36c3d56c2174a68a0dd6a57554ac5b7456fd231e258a", size = 4263150, upload-time = "2026-08-03T18:57:03.18Z" }, - { url = "https://files.pythonhosted.org/packages/bb/03/740fa22bdbeec4fa55cf017956955532ba760584a9479690442e1c831179/granian-2.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6848acafe50547b2f6c3f5d92b5a3d873dff97267287195a45f18a9270ca2952", size = 4812578, upload-time = "2026-08-03T18:57:04.911Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f3/4971ab3775153452940c1f38dff2a141168dc2bcffb4348382ed47fe828d/granian-2.8.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f03da08e21e6d0a6b0ac6be3dca4485e2ec1a609e9349e07c390c745f0c8b29c", size = 4446673, upload-time = "2026-08-03T18:57:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/82/f3/bd62b2c9830fa3bfbfbac7e07ab086a0f0b3e5ab6613fcf017a389f975ef/granian-2.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a63cb450f71760a56c80794c0c49bc638d58f8f4e9f91202a21c08014994c", size = 4859343, upload-time = "2026-08-03T18:57:08.593Z" }, - { url = "https://files.pythonhosted.org/packages/23/fd/10345aabf40982f9483671c93be9a1892ece07ca3bfe68babc4171caa946/granian-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6ebfd8af38136054e6ea2e86b18649c5676d924fc3a40383ebbb3af6185e929e", size = 5023131, upload-time = "2026-08-03T18:57:10.981Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/42d540dae26873938dbc19cf0d692b9644c8d8a6aee668fa78c79f751897/granian-2.8.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:39b620297be07d71ff92aaf2c52f5a376a7888d251a3e953cad81620751ee0a6", size = 4993372, upload-time = "2026-08-03T18:57:13.361Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/025227fb79b3b7bcfc810922863fdbc05bf3932030d207773bf2bba5bf60/granian-2.8.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:67c1db11193797fe1de61e74ac858fbac4e4d27f1b8c4b437be40b15700fccf8", size = 4923411, upload-time = "2026-08-03T18:57:15.079Z" }, - { url = "https://files.pythonhosted.org/packages/11/a3/aae60898ffb725ebc7f2caa5055d1aa4c6aa49c8ba29dea9f559e2eeea3a/granian-2.8.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4f4fa63ed4fe67ec12f9a26d29779f52c35cbc8c57fe337f7bea25f8c47981f6", size = 4924152, upload-time = "2026-08-03T18:57:16.83Z" }, - { url = "https://files.pythonhosted.org/packages/68/7b/5d5b9b3b02f0abe30496ad3cd7123221666fd54b0a3a3d9fa3a18c66de51/granian-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a1d8dce913504188d4fddb12e33515ee5bef6c4658e9eabb0bd5dd43d9c1e4de", size = 3112465, upload-time = "2026-08-03T18:57:18.622Z" }, - { url = "https://files.pythonhosted.org/packages/dc/10/1c8231d6592bce2855d002c0b82939c62bca317c82cf37ac98ed219b94d7/granian-2.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:55aad6e52f7b5d22c468304f5862b154e08ff231c622e956d63f4b5a7f308e36", size = 4423359, upload-time = "2026-08-03T18:57:20.484Z" }, - { url = "https://files.pythonhosted.org/packages/69/37/39b99ffff95b9cce64c9964ff21f15e75b3b5c96f0529b56ecd87a8df037/granian-2.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:95517d0f5c38cbed7b94a6d28dfb6710330b879024a9ee73dacf6b4ade638925", size = 4259820, upload-time = "2026-08-03T18:57:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/e1/32/846784ed1e3b7aa84e473268a9c43cfd2c5b97cb9dd0161e4719e04ecf53/granian-2.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36a53d61e6d5f04af257700d2b83700d64be5f392d0d07a2cb568daa2f778c8e", size = 4805453, upload-time = "2026-08-03T18:57:24.103Z" }, - { url = "https://files.pythonhosted.org/packages/89/c6/7e24caa2211fc669076aa223867e2bdb3f0a5bb9d74bbdbe9cfb4c8b5d9a/granian-2.8.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc2f421b899d3a8615ae705016f41a6e0a4940c7005dbf58fc8962a412bd760", size = 4444078, upload-time = "2026-08-03T18:57:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/ea/be/9eedfbc98b0fb6f0213adc0338d530467cd1acaa98aa026fab881c672a8f/granian-2.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036eac55cb9d67457b50b86a1c364d39c3f0ce4e06d86a87d3af8de7d173e721", size = 4870806, upload-time = "2026-08-03T18:57:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/d4/eb/3b140540414e540a2708a4008eabfe3958c2b010ca998738fe6173300f9a/granian-2.8.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:35624cf9fe8bbb416890871e63de7f3460555ed9bf0ac69b533f796a4382a746", size = 5026508, upload-time = "2026-08-03T18:57:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/8e/d4/adcec457beec4f2775ee3cc5d4ac43c795edb0adf104cb48d9d1181b0924/granian-2.8.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:09c73e7835a7bba35f14320b8e57dac1da4981c8c6d3a08cd6e9c557860bbb1b", size = 5043848, upload-time = "2026-08-03T18:57:31.992Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7b/0ef38e58546d757c12d751bf8bc9ab3b16287d6cf09656ec6d53874b0ed0/granian-2.8.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:1bb0c4c2ac258f5e96cb5ea1d1c674c3d1b7c7bdf110ae97bae173bb003405e4", size = 4918203, upload-time = "2026-08-03T18:57:34.282Z" }, - { url = "https://files.pythonhosted.org/packages/a6/df/e832f520be4c3439148ea13b8c5b635297138bc572115284b55c4425343f/granian-2.8.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:de30f61a321571cfaa7ab1650f324b26959fce6826d341bae520cf2786368a09", size = 4933696, upload-time = "2026-08-03T18:57:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/eb/40/965f20eb6cedc318d08c361fcc8b3c85efac3aadf5adf298b32f6075ebbc/granian-2.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:dca589ddf61239e08a52ec6fa7f62290cd9ab707588dc928d9b65df971ef202c", size = 3096154, upload-time = "2026-08-03T18:57:37.979Z" }, - { url = "https://files.pythonhosted.org/packages/b6/93/64cf513a32fa2f64a421b58b0b00486d03e48cdd69c367ce185140e00f62/granian-2.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a3e0905fa79ab2b5752e9222fb87003b1e69f7162eebe6eeec0ab924e5e67312", size = 4320699, upload-time = "2026-08-03T18:57:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d4/4bbb6b1d9c56c9cb5ce636fc3c2d31ec0baacf6975d379b5ae4ab2c3627c/granian-2.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c228897c11931c623ee27e07ba512a86667d7c7af0172c4334ae8a0dc66c6f6", size = 4144380, upload-time = "2026-08-03T18:57:41.668Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1c/da817b9cf934fb041ae423744a1c89823af3299f91c2569fa04b4cd0f3b7/granian-2.8.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640003ab77b6dc29b0c247cdb8d970cb2f4986de2e7c72849e9f5fe565eca18e", size = 4282845, upload-time = "2026-08-03T18:57:43.494Z" }, - { url = "https://files.pythonhosted.org/packages/ba/09/35c8b5d5001ec3024a1bef4c6305582cb5e8f8a2f2e85f90eead59910593/granian-2.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81e12dcd0b504c8704b54c6a88733a57de21726ee9567a018f8cf7972e190753", size = 4812269, upload-time = "2026-08-03T18:57:45.884Z" }, - { url = "https://files.pythonhosted.org/packages/36/6e/99b59975f249faaa066320c4f2927102c6740de87be8421cae99c47688d1/granian-2.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bfd4726c4c61f39dbdb4e4e2bfe8fd0ace2cea58eb34ce720036e8e09b1c27a", size = 4748058, upload-time = "2026-08-03T18:57:47.899Z" }, - { url = "https://files.pythonhosted.org/packages/c0/83/42aeb107969349a07b7385dda0222fad830d318f1d5bdca084823650f859/granian-2.8.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6c1aeca05c84434ecafb2297aabaaf1e39a267e66eee4420b7669cf0c47df369", size = 4842775, upload-time = "2026-08-03T18:57:50.651Z" }, - { url = "https://files.pythonhosted.org/packages/17/35/d5f6e690eecdf4a63460d65f363527fe3db69477bfd0df631f395c554a4f/granian-2.8.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ec5281054da6e342e7be3d3d5bf764288faa2e6407d26ef5b9a909fa5b5ca44d", size = 5038742, upload-time = "2026-08-03T18:57:52.407Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e1/98b97433013a1219ed1c1238befa7aa0eb83d7bbc872e63193e847e152fe/granian-2.8.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7b76586d95b798453ee89304a1bb25b1ea9301f564cd3a6a20268b8139a9cad5", size = 4944066, upload-time = "2026-08-03T18:57:54.488Z" }, - { url = "https://files.pythonhosted.org/packages/41/70/47897e417dd22c83e731d85e8f388ed29a1b88a97404063c05a2510fc11e/granian-2.8.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:7d516e7ae705e0d5f8ece085ac18b8817dc4c8adfb7f0f68e9ee529e991664b2", size = 4987791, upload-time = "2026-08-03T18:57:56.46Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8e/76f58b9953a1aa1ba317ce0d96073a47dbb515d562fa524ea8a6b9564bc7/granian-2.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9383c97c12829b0b0accb3aad5011ad765e74a90d54d8381cf722ca4f7bdfc0a", size = 3082991, upload-time = "2026-08-03T18:57:58.204Z" }, - { url = "https://files.pythonhosted.org/packages/47/8e/264f9215206a177d6a0958ec6a496923c6fa0fca785a350884e9d134fce8/granian-2.8.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:c13e89315cfd91821399c78f1dfe5c63b95cd9b44c9da1052eee66c00e60c719", size = 4424059, upload-time = "2026-08-03T18:57:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/42/bd/6c62d461da7ccb76ecc57e2e4c29a63fcb55d21a4b04fa030e849e6d0225/granian-2.8.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f956f26d638268312b7cd583fc079cee1bc3474a29508dcb2bf7f797822912bb", size = 4259907, upload-time = "2026-08-03T18:58:01.491Z" }, - { url = "https://files.pythonhosted.org/packages/95/ed/cd6b65766c1f35718fe1d83de985ae79ebd2068ea27ab39457ab95439f54/granian-2.8.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fcd0888e6ab17a14dabd7b8b3a1cda12795a754660279d2b17b6abcbaa65644", size = 4807122, upload-time = "2026-08-03T18:58:03.404Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c7/36c80a53a43e20e8ecc0587c9b82e0c23ef82b26e258ff05750462fe689f/granian-2.8.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b771aec7246a9e220f841c24e522f493b67ad2df4a7474e4fa8f960844826c9", size = 4444740, upload-time = "2026-08-03T18:58:05.083Z" }, - { url = "https://files.pythonhosted.org/packages/0f/34/760cbeca09daae682e20a6f4a894248edae35dd5e40f27ef6fe8aab315db/granian-2.8.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2fdbbe31574800b2b14dcd2868be06f0e0b333817a37fc0fdae2024de2d4612", size = 4870669, upload-time = "2026-08-03T18:58:06.874Z" }, - { url = "https://files.pythonhosted.org/packages/e5/17/daad58c44a0f934ffa5991440054b738758c645ecd35a8d81266f6bdd2df/granian-2.8.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:826bb163325f1bff16f884c98b7f109998a3a9042406c31f7f407114e8196020", size = 5026641, upload-time = "2026-08-03T18:58:08.82Z" }, - { url = "https://files.pythonhosted.org/packages/2f/8e/2ed4b28cfb482ccb3e9d6b0812f64ef40598829c53dcca9260aa93994fc5/granian-2.8.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:fe48c902ace46e8dfaa24585c2941b51a22b5d77d3676a502ae48caf4ba624ca", size = 5043662, upload-time = "2026-08-03T18:58:10.706Z" }, - { url = "https://files.pythonhosted.org/packages/c8/df/9afd95c900d0c4fb5d57d1de1d9340cbcad9813b0374c4ac37b0a00c710d/granian-2.8.0-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:6f9876220f56a0ffc2bc41a977f6fdbe0458a8559c3f950a0497fe71e52a16e3", size = 4918816, upload-time = "2026-08-03T18:58:12.489Z" }, - { url = "https://files.pythonhosted.org/packages/16/ff/ec09ab42ba46676440715b4810aca60ef92d39dffa68669a4daa0bf01252/granian-2.8.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:0d797398949f8de4259926df69d0020b6897ca325672043529d9a6b702bddafe", size = 4934107, upload-time = "2026-08-03T18:58:14.417Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a0/1f2de683c90137ae5a66e89ca7aa72752edb2e0c55490442169007a9bbae/granian-2.8.0-cp315-cp315-win_amd64.whl", hash = "sha256:808986933f844f54dd9c6ea47a9e0e39cbbd12b91b315a8de1ea98abb9314e80", size = 3096056, upload-time = "2026-08-03T18:58:16.3Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d2/21d2fbf8517b2c0d82bda45dfa7ed26d933a5698ac7107c43da366635223/granian-2.8.0-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:8bf00fd66844846dac9278a15db864ec68c60878098ad638451fc8a3e61f845b", size = 4320343, upload-time = "2026-08-03T18:58:18.126Z" }, - { url = "https://files.pythonhosted.org/packages/94/1e/81284fa128f77dd6132ed62f29f0a099aec8622f2acd3c3fa7a733dc8375/granian-2.8.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ca1a764c00b89509eb7a45f1479118481628380e1bd1d94c2248e097abd63017", size = 4144285, upload-time = "2026-08-03T18:58:20.122Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8b/2f6a7012264011ebe6ea29afdeb333b6c50be3956b70cd59eecf248d599c/granian-2.8.0-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:042c177e75a5e67144a39476882ed735ac9558e48097a5658111adbf939c0d1e", size = 4281969, upload-time = "2026-08-03T18:58:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/76/72/ff6d27931f004e231dcead6c9d5d0c252a73358462885f19e1223784c72a/granian-2.8.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:faa739c7674c53fb9e6b72500232e6360c13e531d8186cf5ef569ba8ccbd5f2c", size = 4813597, upload-time = "2026-08-03T18:58:24.563Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8d/7501ff01a8ca751608783252729ec067a7dc498c4323ae2e31ac17c1dc30/granian-2.8.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04c83c5d7a387635d8c7cde11a95f0babee1cabce3c469de4494a9f93a015394", size = 4746980, upload-time = "2026-08-03T18:58:26.392Z" }, - { url = "https://files.pythonhosted.org/packages/2f/14/3f635ba0065fc36f7820704059bbc46065dd9e4fae55b28de46a91dff9f9/granian-2.8.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:7cd88ca58924a63eed769ce64cea3c29c876791db14988f2d6f3f6fd5cb18061", size = 4842380, upload-time = "2026-08-03T18:58:28.178Z" }, - { url = "https://files.pythonhosted.org/packages/da/6e/008efb0f414ff7c8c8d0d9112abb5fa9a93574043cbe4e5f69f9ab80caf2/granian-2.8.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:2052d1cf74369430b960f7731db823a1f604e375179182bba3511e88d976eb43", size = 5038154, upload-time = "2026-08-03T18:58:30.131Z" }, - { url = "https://files.pythonhosted.org/packages/da/4b/01e0f0ad66c886a55c44912385ec72e7da68ece6becf3b67bb402022b605/granian-2.8.0-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:0e96f5bdbbac30e6abff4e7c2268582734ba8a908608c95fbc9a1db5b849bc3f", size = 4946307, upload-time = "2026-08-03T18:58:32.26Z" }, - { url = "https://files.pythonhosted.org/packages/5d/50/2a961f66228426d9c95b4d799f1fea5460f34ea01749108118d81ea33eb8/granian-2.8.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:0d84b516c3d720d22448d935e136d8ddaabb5a8715bf839d8f28de578d35443f", size = 4987962, upload-time = "2026-08-03T18:58:34.178Z" }, - { url = "https://files.pythonhosted.org/packages/1f/0a/711050f09a1b027b13db918e7f272ff5e7f7b4699d9994fbe39f285e2e74/granian-2.8.0-cp315-cp315t-win_amd64.whl", hash = "sha256:5c4d67d96ab296164c36ef672a55251ef12c5e4060c1796fcba6c3fd82394d27", size = 3082796, upload-time = "2026-08-03T18:58:36.052Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/d3/a2/8429f7cd27c99ae954be8663ca46067e0d31ee84fb887a25406a9e5bc8f8/granian-2.8.1.tar.gz", hash = "sha256:41a7954e10621ca46f9bfe829a42272e4796358cc4bf27a9253f3171f871a397", size = 129348, upload-time = "2026-08-05T19:12:17.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/96/a198179414f84245d9c558d930657e9cdfed632bac1019e8a3d34b9ed1d3/granian-2.8.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c9762aca1e2801fafb4982716450a275a22f5a737c759dc64c565c0537bf688c", size = 4438898, upload-time = "2026-08-05T19:10:10.92Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/45247603569814f3a1ef15cb7edd6f564763ac166a3418996981401da067/granian-2.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b457cc252cb8a3024491d714bfe29cadf68a9c5d9a2925a8c8dee341303326c8", size = 4262223, upload-time = "2026-08-05T19:10:12.733Z" }, + { url = "https://files.pythonhosted.org/packages/31/15/076dec71a7951f4b7c5398a2a43ee9703902e408116b4d7238c432296225/granian-2.8.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5edf0f35ff158800bbb0f09371e07cfb0036d67c0780a2c7fbbc3b38e131cedd", size = 4808769, upload-time = "2026-08-05T19:10:14.433Z" }, + { url = "https://files.pythonhosted.org/packages/66/b5/20c34f862c2cad81e4f9e6c53edb8e2d30267b782382d99c7fa15b49d8bf/granian-2.8.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f634e6870b80893bfcccf076edd139f508604bc783f71e6efeec8d329034767", size = 4444821, upload-time = "2026-08-05T19:10:16.201Z" }, + { url = "https://files.pythonhosted.org/packages/4e/10/05ce265cf55d4137e02ff2c51696d656de195394a8c5e2e128e470463b85/granian-2.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0807df2a34f8ee1375cdca6c2a6b8bd3129f45adc90891ee40e84ca4ff23510f", size = 4981479, upload-time = "2026-08-05T19:10:18.046Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/61eaa3e2a4d53905eb9a9fd76ba310975401ec4fa6d560207c17464f9c24/granian-2.8.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1a69dab6357ce0dae90c9c93f6f67721c5edb440907646d7c12e6dbcbc5b1aa5", size = 5170208, upload-time = "2026-08-05T19:10:20.013Z" }, + { url = "https://files.pythonhosted.org/packages/49/4b/0daed2ea8b476fdd2dc9b672a172af7ad58e35f74ef6c14e57cf8053d3e4/granian-2.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c10f438f602c951645dcdcd16f252d6937d1e7f7d79cb4fbfc39ab8c9a2ccbb1", size = 5130268, upload-time = "2026-08-05T19:10:21.939Z" }, + { url = "https://files.pythonhosted.org/packages/14/9b/0530a3f3b6fe1725aa3c4bc3eafe39f2e32d4ffdb53b4d44d1d37a397d71/granian-2.8.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:c9b69d9d38be1111fee8e90099d720f11d8990776d01c4e33a1cc719fff8971a", size = 4920751, upload-time = "2026-08-05T19:10:23.791Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ec/a6725637bc519e8a60cdfd109131f6095bea848f614b0aca01c1dff82edc/granian-2.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80ef39104c26f632ece530a3190e38d510c6a2c583f39dbbd68306d2cc3a2b62", size = 5039184, upload-time = "2026-08-05T19:10:25.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f3/b426b4ced60ce56459d882a30d6c4cbc747011f83b041ea39ac822bd53b8/granian-2.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:41cc68959ede5094862789ce38fd2f278c8bc6397800cebe1b53d55533b522d0", size = 3117878, upload-time = "2026-08-05T19:10:27.317Z" }, + { url = "https://files.pythonhosted.org/packages/6b/05/a346264b0a00bc1837c995887b801a2792a852aea41cab09e5eba4809da2/granian-2.8.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:904ccbee39dcfdfd6117b868b9e689e20f89ed03952f6bc142fb0111285b9104", size = 4438925, upload-time = "2026-08-05T19:10:28.832Z" }, + { url = "https://files.pythonhosted.org/packages/0a/54/5ad6567577ee3b51266fd49f61639a6ae3e1c9cc92d517d9b6ea839be0bd/granian-2.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdaf976f1d24b4553bee2ffce735b10c1e5e4b12d63da259f75b1dcdea9f2168", size = 4261864, upload-time = "2026-08-05T19:10:30.497Z" }, + { url = "https://files.pythonhosted.org/packages/57/71/d7325b977304bf2f9ca12c475c2d54caae5afe7e7ca88bf011dc8c5e9886/granian-2.8.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:037a718b8138808e19d4827f18c3c28d3ae38f0bb070959a93bea587b4eb3dc0", size = 4807466, upload-time = "2026-08-05T19:10:32.329Z" }, + { url = "https://files.pythonhosted.org/packages/54/e5/f67d9e8983879ed9876f61c78475dcb19b77f68977000f60babb6c632125/granian-2.8.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d549e719bf45cdffe5899852b0b4fc8196e4f93f88245e649d47a81dbe26f63", size = 4445007, upload-time = "2026-08-05T19:10:34.118Z" }, + { url = "https://files.pythonhosted.org/packages/c3/58/68c3a0331e017148ec4a8a84697b9db2105c4a19b838404b89c1ffd6c7e5/granian-2.8.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e938408f77b8218b7b9b928e347ef3dc3afaa96ea94611c087337560acae212f", size = 4981346, upload-time = "2026-08-05T19:10:35.66Z" }, + { url = "https://files.pythonhosted.org/packages/9f/15/8717de6aec2a5927c68ebdd877008264681600d6e7495511a6d18e066fdc/granian-2.8.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4364230f54436397bab9bc51de6b4c24680f74361b6ead26dac11eeef44c7add", size = 5169529, upload-time = "2026-08-05T19:10:37.604Z" }, + { url = "https://files.pythonhosted.org/packages/0a/15/bca25dbf574ba08a3982d84121a18d8adc95a8547ee72db5188ef93541f3/granian-2.8.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:d9fc625ba6f9d820fd1c23deb295644272723dc452b7a249edd2c536e66b745b", size = 5130597, upload-time = "2026-08-05T19:10:39.198Z" }, + { url = "https://files.pythonhosted.org/packages/08/75/0991540f93cc844a9b2d4528f8c17d6b2013881ac69d23e28ec32e383509/granian-2.8.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c11f6846cdcd7fa1ac16dc4ee1b8449295d98d425275ddee8f745b9c638d0f71", size = 4920408, upload-time = "2026-08-05T19:10:41.011Z" }, + { url = "https://files.pythonhosted.org/packages/c6/14/eaf1925f9114511fcc78582f8f0bd2b1a04ba57f8004c347db7c2d84b1bd/granian-2.8.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f8f7f1f5a73571a38b6f028278a87d9ce8c56620b7b70bba5b768354cd9786ee", size = 5037981, upload-time = "2026-08-05T19:10:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/1a/49/ea06d3143a452d4654aaf7808cfc1fe79b0c11abbac279beff5839f40a33/granian-2.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:83f5ffc52d924185f271ea5e56e36b844ecc918620ba61930e89ebf1d33a650f", size = 3117032, upload-time = "2026-08-05T19:10:44.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/6d914325db4620cf2c8ae1d088367141bc9e801aecacbf3b70db74c7ae60/granian-2.8.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:036c6cb6b2100150b58ee6d48851fad7e01852bc7c402190daeeed9163574b77", size = 4422668, upload-time = "2026-08-05T19:10:46.498Z" }, + { url = "https://files.pythonhosted.org/packages/f9/38/0978fb217e7f06131e26db5b18446e5287a53a0f505149cdafb3a425f4b3/granian-2.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85488be9ff2ba5adc993e00af795003cd7f0aaa57e6133d55f0c7811cf898772", size = 4253938, upload-time = "2026-08-05T19:10:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/c2/93/cd330a86c3dc0aa431b0b7ac180ab1ced44747d47e70518e93515cc5380f/granian-2.8.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac630d0ce94c9910f8b57ea26d2dc3aca57010e2107933dd4fc02809da6050f3", size = 4803826, upload-time = "2026-08-05T19:10:50.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/15/bee4f9d8f8643f51ddd984e1b9dfc828d8c848dceb9fff9056e4a9365869/granian-2.8.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:979843bf10b6c15b161c2058955d3f19c3aee76878027937a67a7111842016d2", size = 4442823, upload-time = "2026-08-05T19:10:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/57/819b28bece903e7f44fc9905054d897efeb96c6390a0a9c0a6f5db611152/granian-2.8.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ed1863fe4b596b3ad06ffb2fec008512148aa764ceee59359bda9b53f7e8c84", size = 4897688, upload-time = "2026-08-05T19:10:53.848Z" }, + { url = "https://files.pythonhosted.org/packages/f4/09/b5788fb51b4c0fc6b5a6d2a2996f5007a2f3fc14fb3379a71097bfd34920/granian-2.8.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:88ec7b24cce99c73fc1e1b9ad53eb2c13b6ece5712f49a5ea739d883c9ce27a7", size = 5094530, upload-time = "2026-08-05T19:10:55.547Z" }, + { url = "https://files.pythonhosted.org/packages/41/c4/66bb1783d19f2cbd523795043acc4699027145061babbef78ab1e4645f01/granian-2.8.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:872870943196e84b1c7ee80d5d857b76061091eed02fb953f9603c1fdf72426e", size = 5070438, upload-time = "2026-08-05T19:10:57.278Z" }, + { url = "https://files.pythonhosted.org/packages/67/16/0441aeee2659f5d0510f664b444785e20809f1060e4fb245c74b995782ee/granian-2.8.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:f5940fe525bf732ea148749d1e24f39ed1edf3fae4aed0b3f698d01e04d63c12", size = 4917913, upload-time = "2026-08-05T19:10:59.025Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a6/6eea0ec270c526e1ca2e25adce30c4255f3d7a7f31bee68cd18c41fe9ddc/granian-2.8.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8f05c79a9eb4d2263e599727f588ec286fe4a9759d5164b43eaf30f9b94c6fdd", size = 4954250, upload-time = "2026-08-05T19:11:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/78/07/b6b9eb1ebec0a2d524eb3614911b276dc3236a57c2884a27409d6be68f19/granian-2.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:996934329308719db41659468c48f9c0650f7cba7a63e0b4ec003b55b08be818", size = 3097083, upload-time = "2026-08-05T19:11:02.412Z" }, + { url = "https://files.pythonhosted.org/packages/01/b4/06090ece7b13712084a466688fe9b215480593e9e7c534fc4f327b813edb/granian-2.8.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:579e79a0f045cf5372418cb8964c156961bbc31317802e93bd3656d505f634c4", size = 4386500, upload-time = "2026-08-05T19:11:04.148Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b8/5ba4e30917ae247cebc05b52fd6db04bd40beea108be8524e5c168c35251/granian-2.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be23e3b099ba3ab169a0de5894fe6116112067a48a79a601b824dfc0d63840fa", size = 4170540, upload-time = "2026-08-05T19:11:05.719Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8e/c89b398ad5fa86aa88caa407c3793e071715c6aea5d0cabc5b2b4039aed4/granian-2.8.1-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7be6d745e29d29a8232a29a3801382f52a4315b3eb3214e13f4c4eefeffb70b9", size = 4283755, upload-time = "2026-08-05T19:11:07.311Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3b/2af12d8d0b3013a32d64f7902010dfea416865af6f02ca61e9bae24a25c6/granian-2.8.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a28b881994f45b4cd75d3a2bf188a7b1c8a5940e1461d82da2d831835267bdf", size = 4809317, upload-time = "2026-08-05T19:11:08.935Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/fb1d51bc2a7f717c4643970ff16e232d93cb89f71880b2d57923c860d991/granian-2.8.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4da85029925d44bd30a7b4015c8c44139c0119ae428098719948455cfd6ccb", size = 4744474, upload-time = "2026-08-05T19:11:10.619Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/2f0680b3aa52be30eb55cbb57be6baa7d3f9fa8f8ca6dc7eb93bfcdcb7fe/granian-2.8.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c98361198fb8ffed51120dee2fc306bbba157d74c0072619115b23ab28cb4518", size = 4887627, upload-time = "2026-08-05T19:11:12.288Z" }, + { url = "https://files.pythonhosted.org/packages/a4/6f/63b945143d8718e7e8444e621de1574566ddc3cc53d3d05abf127f1fa3e1/granian-2.8.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ef74713cc3b50ffcfc95534a8a0161881e9a8936073da5a092d9190673811e6c", size = 5080187, upload-time = "2026-08-05T19:11:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2b/713ced986615bff43c0ef16959741200ffea18d86246b82dc43da3f80823/granian-2.8.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:f444950c84b92fa270997bcb188035f826ce34711d97940d11c044b3a624c169", size = 4939116, upload-time = "2026-08-05T19:11:16.331Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/414733c5c974fa17fcb6e8774a925413613d7a70eeebd5f6132ca8d4bdab/granian-2.8.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:82cb3e201c18110981d13cf2f1db3c48d315d5f7930460fd7dc76eda3effa06c", size = 4984040, upload-time = "2026-08-05T19:11:18.227Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/b4649b173578fac6030957d387f5fda4d8f74dd5bb859c48ee462438426d/granian-2.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:92962ee552fd69ca288bd560da62632f7f3eb4481a72831ac6de942d5ade7b2c", size = 3086397, upload-time = "2026-08-05T19:11:20.019Z" }, + { url = "https://files.pythonhosted.org/packages/7f/43/d41504a8796b28bac830dbc40626e480d6250424b692ff018c955bdd81ac/granian-2.8.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:c5189bce61b601b5d98695b793adf24f596c48d44321d725a33ddfbcf0ec8905", size = 4423581, upload-time = "2026-08-05T19:11:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/4c/10/57b8bfec161d9ca22b71f76e81f85177548f6b368506d90fafe132dfd953/granian-2.8.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cddcbd2c3a2dc0bca1f7ea0df1e768bba66657b47a212c36e588b10b56b03da6", size = 4254646, upload-time = "2026-08-05T19:11:24.016Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/fb87565f728f7f3959d8f0b0aa6b06551a92e819187a843758f9d7ddfcd7/granian-2.8.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cd970974fefda13eeb8efe6073e80e985673fe014d9fc57375e54685c91043b", size = 4804518, upload-time = "2026-08-05T19:11:26.2Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/9f1d09daad55fb88fecb01711740e5d1cbc272c8d88436bef9d3d935125e/granian-2.8.1-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d17d9ee0b5a167ef6b92888da2987a422e88e18679c66e963f09113fcffa48f7", size = 4441117, upload-time = "2026-08-05T19:11:28.107Z" }, + { url = "https://files.pythonhosted.org/packages/1b/67/4992015a32458000ab6a9833f9e1eec964428c78b60f8bdaf2ce16c49e74/granian-2.8.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a18bb543dc4bbdf605ce3f4044b46f642e7094e68f10e15ccc671db5eda4c1c8", size = 4897132, upload-time = "2026-08-05T19:11:30.32Z" }, + { url = "https://files.pythonhosted.org/packages/af/cc/18e63284494862b6de9bd19e4ee5b4fb692cffd9b51da221083cf7a8c4e7/granian-2.8.1-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:bd4bac411e108d63965d296d0f2a90282e7535f3e71965b337b670fc7121badc", size = 5095908, upload-time = "2026-08-05T19:11:32.106Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bb/e51dfee0755f42e52accbe35e4ec30086a08f5794344cb7668718c9ab637/granian-2.8.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:5bab997125ee1b6261543ab0a9c26c58799124911654ad48fee55552241b6406", size = 5072083, upload-time = "2026-08-05T19:11:34.018Z" }, + { url = "https://files.pythonhosted.org/packages/0d/8d/aa6e2ca4caf4bee6dce64240945cc96c282707e7284df1dcef0ead4563c0/granian-2.8.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:2ea4b7d14ba7aadbf8362aa22fd89f1c510f810e4f46e2f5be52523472e5a20f", size = 4918230, upload-time = "2026-08-05T19:11:36.074Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/281b96e0f734d7b159edfb903b311437d603680eec59bd3093a357ee217b/granian-2.8.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:3679e540f16c49d57a14c08ce485482e2da66e4655f99d5a64d28e32057fabdb", size = 4952321, upload-time = "2026-08-05T19:11:37.946Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/7b5cf729a9b57436fa59c2d68b03306feca92701cd02c2f39be128e8293c/granian-2.8.1-cp315-cp315-win_amd64.whl", hash = "sha256:a087f6bfb1c745082d3864b60e8add5810e16ee29c59006378f2fe9a4fa09f88", size = 3099822, upload-time = "2026-08-05T19:11:40.221Z" }, + { url = "https://files.pythonhosted.org/packages/db/ce/6eea88dfaa12f938d94fe6c95f5dd79e071f8c0cc298843be824cea3bc9d/granian-2.8.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:0f49447482471edbd9578e8ce8433c761fac3aaf685263ab1ed5456714509956", size = 4386374, upload-time = "2026-08-05T19:11:41.982Z" }, + { url = "https://files.pythonhosted.org/packages/17/d1/2de43e557d0c28a8f6f96165c00a9723ea5c990e0a8bb8854acf8d620a3e/granian-2.8.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bb0f33272483e512c9545edefc5feaa70588cc84853a4512ec6817c53abeda54", size = 4170151, upload-time = "2026-08-05T19:11:44.023Z" }, + { url = "https://files.pythonhosted.org/packages/5f/46/7561a85657df37fbc0fb3bc42fd529374d8457f3e5a64092a9daa135302e/granian-2.8.1-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f64fca05bbf79f079b8ae91111127011dab0c3240fe4806058500d161aa15b95", size = 4283850, upload-time = "2026-08-05T19:11:45.849Z" }, + { url = "https://files.pythonhosted.org/packages/06/23/61c3da2c5365ff9b5536b0ee0a6436cdc99a0bb70ef982a20f4f32a276a2/granian-2.8.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5cf9c1fbb5a9ed1c8365e56605bf90ccc3fdf8bf40b7cb486743038c1dab2ed2", size = 4809560, upload-time = "2026-08-05T19:11:47.879Z" }, + { url = "https://files.pythonhosted.org/packages/4d/3c/b63bfa7b10a321d7b501c5080f1712cda4d17be6ce5d7fd6100e2345deae/granian-2.8.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85036b45494ac0d980c6d645a84895cbde21d1ed239155a496927c6f1af540ac", size = 4743771, upload-time = "2026-08-05T19:11:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/00/3a/6aee2be22014062685cc03be75d0fa840be3b82b2af007eca9f67e97cae7/granian-2.8.1-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:e401edbcef575b6f8121349d78df061f555d08058a9233920fcfdef15fd91ded", size = 4887901, upload-time = "2026-08-05T19:11:51.475Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/0331d3351608c8e42959dff43986692627a35439cc6948cda606cf2de7ca/granian-2.8.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:277f00fe9aa6d415936d6748fab3f2a3361a63691654e2c80bf8e5dc0fcde080", size = 5080037, upload-time = "2026-08-05T19:11:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/827b48d1c27ec6ab0d97f3fd66a097b390e7359589d086dcf997ec9ee351/granian-2.8.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:f624074c54e4c5944b30c875294633527dbe145d7f1751ff2d0c7d61246d1717", size = 4938542, upload-time = "2026-08-05T19:11:55.16Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2e/9d2d8dc6b9bc12099595a55f5320f63baf74852932cb47495283cb3b10ed/granian-2.8.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:5836a2b2de833e252be2b07e6e21f2440c6c1e10a5ed528b860261d3f48d6551", size = 4985664, upload-time = "2026-08-05T19:11:57.022Z" }, + { url = "https://files.pythonhosted.org/packages/22/e1/a74b3a62c92e314a7153ad017531b838ecc5a869d9265b80179a172abdd1/granian-2.8.1-cp315-cp315t-win_amd64.whl", hash = "sha256:f166496c30c49361381d19f8a632fa13ecccad3854ae39b0001d499d3efe3bd7", size = 3086568, upload-time = "2026-08-05T19:11:58.781Z" }, ] [package.optional-dependencies] @@ -3112,14 +3112,14 @@ wheels = [ [[package]] name = "pytest-django" -version = "4.12.0" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/2b/db9a193df89e5660137f5428063bcc2ced7ad790003b26974adf5c5ceb3b/pytest_django-4.12.0.tar.gz", hash = "sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758", size = 91156, upload-time = "2026-02-14T18:40:49.235Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ae/5ed1ab2d12090bd794392d798c14d092f0470f6446eaa6d2af4183f4aa2b/pytest_django-4.13.0.tar.gz", hash = "sha256:2316e3f63e3bc799deb4212830e39b2ef1dcc3b2d8688060f1d0217db6a56ebe", size = 94200, upload-time = "2026-08-05T21:36:23.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/a5/41d091f697c09609e7ef1d5d61925494e0454ebf51de7de05f0f0a728f1d/pytest_django-4.12.0-py3-none-any.whl", hash = "sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85", size = 26123, upload-time = "2026-02-14T18:40:47.381Z" }, + { url = "https://files.pythonhosted.org/packages/27/88/52db7a840fdbd74934f8df11b7450c9919ac00f61c02e88827f3d58180f7/pytest_django-4.13.0-py3-none-any.whl", hash = "sha256:09374c12d947ca0f99e33726bcb720a6d0124f67058e1130586372bd109493e3", size = 27066, upload-time = "2026-08-05T21:36:21.763Z" }, ] [[package]] @@ -4108,27 +4108,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.66" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/58/4f6ab2a86589e422a3cf840bcf6114c565e4c39ddf4d0b7cd328af5b52b4/ty-0.0.66.tar.gz", hash = "sha256:24bddd4479ce445b51ac015410dd2d34af1cadd62a77f5b3cb269149ed83f9b5", size = 6520402, upload-time = "2026-08-04T01:09:47.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/4d/bbc28310d6d887ef73e5800f062c4bf54caa35a1b47d70c7b03d0515ecf1/ty-0.0.66-py3-none-linux_armv6l.whl", hash = "sha256:8b46450438b54b732338e4d7a78a7d2f5e1a012a13d77d121aacaca20fb814e2", size = 12409743, upload-time = "2026-08-04T01:09:01.229Z" }, - { url = "https://files.pythonhosted.org/packages/73/4e/c3d2eb2242fa0a2ef445ca2c7009dc9118e5b3dcb8b8a8bec70d58c8e4bc/ty-0.0.66-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8e4adbe662bc3c62b52b83d46b07f703fdc3c123bb72601606c58be5ea017ed3", size = 12078362, upload-time = "2026-08-04T01:09:04.233Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/7a4b5e45d701a8de6b714dacf6ffca91b0411baaceaa781d494037623a18/ty-0.0.66-py3-none-macosx_11_0_arm64.whl", hash = "sha256:776814351735847eb934f9a3cbea21d2278ba14aa0fc099f683da11bc2d5c90a", size = 11583289, upload-time = "2026-08-04T01:09:06.973Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4a/fbb1f71ee2999f981f8c4b3b139231e4bcff4ede0c3b37e858fd1334ed26/ty-0.0.66-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cca1da877f613965b954bfd22d495386e260ec767c5536c8022cca98a260ea5d", size = 12137274, upload-time = "2026-08-04T01:09:09.592Z" }, - { url = "https://files.pythonhosted.org/packages/d5/07/9e0662f6603a5ac171ae6b314396e677ead4b45695fc948efb0a4837051f/ty-0.0.66-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa523e777bc36c0fbf8ded5844096f46cbfc3712fe5a003351a94259b7e86cf4", size = 12207047, upload-time = "2026-08-04T01:09:12.533Z" }, - { url = "https://files.pythonhosted.org/packages/bb/cc/12e01bc2ea47fa7bf20fc6cdd3249d6a340be9be4edc52b1d77256245dd0/ty-0.0.66-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f7993c1f95f80a4e44056e6aaf8e46e608d778db131ae5ce59262ab59358f9a", size = 12919304, upload-time = "2026-08-04T01:09:15.175Z" }, - { url = "https://files.pythonhosted.org/packages/75/88/c6c0d3a8e71c9cdb560cc5c627a4bba6c47b5eec460b2f8c449b57c6d03a/ty-0.0.66-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b29354bcbac9f53b6952d8f46789bd81eeb9bdd7a68df7d26e654ca7498c3c", size = 13470963, upload-time = "2026-08-04T01:09:17.849Z" }, - { url = "https://files.pythonhosted.org/packages/27/c2/2f8c18063412ad80e1b3f87afce7bd50982b4a048166607b67f80660a9fb/ty-0.0.66-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbf4e5325f7f584d9c346946e7c415b2e3cd3b8f1119d468082a90a6afd020ce", size = 13244773, upload-time = "2026-08-04T01:09:20.59Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f6/fdae2b95831116dffc055ad53a99a8f21437263651047b3ad46def4950a3/ty-0.0.66-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bd304363764bd723c22fb20b17035c345420fdf66fee856934b158ebde08a91", size = 12751343, upload-time = "2026-08-04T01:09:23.225Z" }, - { url = "https://files.pythonhosted.org/packages/14/1e/7e75f0371d11463a256dc2bee98b0df401e0fa06021e200b8b601d21949d/ty-0.0.66-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:205d8589bd957ea9718d488b731b2fbdd0d1b1cefd37c79f52c0deb7cafddfef", size = 13068057, upload-time = "2026-08-04T01:09:26.052Z" }, - { url = "https://files.pythonhosted.org/packages/b4/84/f20f24518f6f0bea936e2e668ede250b9ce0774624559931599bd1f42772/ty-0.0.66-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7304a1df54741a2343801354f41d7ad87974acb541ee3e93c26cb6a1a0b863af", size = 12082318, upload-time = "2026-08-04T01:09:28.877Z" }, - { url = "https://files.pythonhosted.org/packages/08/20/70ca0eac2427d4a58a81a3a9426b20e46fb4a5a13aefc9edc2e5172e1243/ty-0.0.66-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cf2c062863e5da0f588b0b120fec0da2e81c0913ee4cd07b50d77aa60ffd8deb", size = 12228978, upload-time = "2026-08-04T01:09:31.905Z" }, - { url = "https://files.pythonhosted.org/packages/7c/01/5a461ab34456d788248780830aed673c9e0e796f6e9dd92d3dbf02e04d7f/ty-0.0.66-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a58d32c879d86428978332adf21e85009ec269314a23d330c3483c65b57aedc7", size = 12471917, upload-time = "2026-08-04T01:09:34.418Z" }, - { url = "https://files.pythonhosted.org/packages/46/ed/f8eb5eff7c9ee644490c6d2626425935c097acc54871adf659ea70624a2c/ty-0.0.66-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2f810fa3516c977630d78dbe9161592b3c27029fa9bb81074366624d9b5e4b6", size = 12858086, upload-time = "2026-08-04T01:09:37.602Z" }, - { url = "https://files.pythonhosted.org/packages/02/7f/8a78361f752274a550a7f6f07f127b246ece7d7fdde31121d22074e46eab/ty-0.0.66-py3-none-win32.whl", hash = "sha256:d28c3df565a387c1c5ea359aa452a46d19163616ef71be819058a424a62f1ae1", size = 11782494, upload-time = "2026-08-04T01:09:40.136Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e2/deed22b823ce309b8410d50414fd15afe9e90aee8b8ca04789ba55c21231/ty-0.0.66-py3-none-win_amd64.whl", hash = "sha256:e3a457f3312c078f24c47d0da6e4f73de34d0a77ed2de22571c066c80b2fd5e7", size = 12893338, upload-time = "2026-08-04T01:09:42.825Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ce/6c828f42ef1ed39f53f57f9c0cdcdf03e23666fa7a29d799042a2c34bcb4/ty-0.0.66-py3-none-win_arm64.whl", hash = "sha256:2f62ae247b9c75674fcc060635f9f00210357da7681de59234d97b50fb9e9e94", size = 12227381, upload-time = "2026-08-04T01:09:45.365Z" }, +version = "0.0.68" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/8d/d5658a08ae59f0aac8cb542a0f23c1db7072b177915e5f91f645623e2c8c/ty-0.0.68.tar.gz", hash = "sha256:789b5c7d31ac188c3c0fb5c90ed10f969f9cfac67f6acbbd7e5c80f7b37ad661", size = 6557062, upload-time = "2026-08-06T01:04:27.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/57/8f27d6866e3ba50c84c3e3d208c707647c0d68de4aa7aea18bda57d0c4f6/ty-0.0.68-py3-none-linux_armv6l.whl", hash = "sha256:12399eba3b0c6d81a2f6ace177bcdfbb7163a78673fe6048b044175d2f49222f", size = 12389838, upload-time = "2026-08-06T01:03:50.012Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/ba58ba672d176cc10266492064f2561f9afc1dd614058fa21350a1cd121a/ty-0.0.68-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4b98ac03e74265b90feaf8cd01bfb5be71b2a5ca9cfbd178a0c5c925604d49f8", size = 12022356, upload-time = "2026-08-06T01:03:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/31/a4/c6d84d017d8714e33d8531297b5d169763bcbc02d2c0e8f02ce4b77589bf/ty-0.0.68-py3-none-macosx_11_0_arm64.whl", hash = "sha256:12fb0ada6e8a1a30ae5d86fb28a24596e6b72bfb710dad76e9d1703fdd0f63a3", size = 11522087, upload-time = "2026-08-06T01:03:54.793Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e5/6b58df41be1720171025841ce45c0f15d07612ff2ed6f80c9f16869d9e2f/ty-0.0.68-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac5e7dadab73c20b354fd20dda71ec2e8a97fbcbd9c14f1bc101dbcf2a1534c7", size = 12097819, upload-time = "2026-08-06T01:03:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/69/df/6345493a8394a9d5afe177c5f6cb27358a41c278409f13721a223c180be9/ty-0.0.68-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d19bbcbb5e366ade22f230c97b80f84bb9738e8e516c05d5997f13c7e5a6434", size = 12172745, upload-time = "2026-08-06T01:03:59.579Z" }, + { url = "https://files.pythonhosted.org/packages/9a/56/bb3955d926e397cfe5aece433e270663878a4580b291be8e53bbb8cb2103/ty-0.0.68-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d50d5d35a856d9c15c56b5fc15a2f9a3ea34c450ff674a4f2da1240de7212d65", size = 12909444, upload-time = "2026-08-06T01:04:01.679Z" }, + { url = "https://files.pythonhosted.org/packages/0f/28/fe9e1a57907f85a60dc695953748d6090a5defb11c65c2cbe03861cc7d88/ty-0.0.68-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30110eabd84dcea99966695ed23a0cea053c35a0ad7182e620586ded4011cac6", size = 13388865, upload-time = "2026-08-06T01:04:03.804Z" }, + { url = "https://files.pythonhosted.org/packages/2b/16/8559df373ea54772b11253c8b33573120afaae7553b8b30b7b6300f9fc59/ty-0.0.68-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2621261f916958bc3326caf6b568b4a5ad12a8ee537e147811dfb26cda425813", size = 13127664, upload-time = "2026-08-06T01:04:06.037Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8d/ecaf8c14e89bf7eaf36a2f3baa3521abdd08cc3d8a1e84a0b805b7f29ca1/ty-0.0.68-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21339febe1306b48e2929743a9f89bc04cd0caf017d1d79538b6aa590cd7a81", size = 12689349, upload-time = "2026-08-06T01:04:08.281Z" }, + { url = "https://files.pythonhosted.org/packages/00/67/7eee09bfbbdd7e660474fd6a4166bc6740d61e044a96b519b0dbf04e0616/ty-0.0.68-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f719654f9050ae38561d671a2ddefee3d85e972fb9f9e9c27452f33bae88e41", size = 12940744, upload-time = "2026-08-06T01:04:10.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d3/4ec799ae7a3152321c45fdaee8d73f71160e10a0f510ad135fa381555487/ty-0.0.68-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0f455279f00ad0cda819311e67bc7ba3102b3b0a66949de5ccf937b60827d7a0", size = 12045945, upload-time = "2026-08-06T01:04:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/51/db/26e51c8170c3750c540ff811cb54dd53f498237e28ad0e679320fd5c2444/ty-0.0.68-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d2a4878c633d1be047551dcb9f735ee245bace3f2f4cba30e462d49a1cd6bd5", size = 12185731, upload-time = "2026-08-06T01:04:14.902Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/1403120dcee3f3cc90e5a02e7263e82d82081ee0f36834042a30bd5e1a51/ty-0.0.68-py3-none-musllinux_1_2_i686.whl", hash = "sha256:72c79892f0b9d1ea05f72c7100cdbe57851b2bf9cc5a6541a506e99fead525c3", size = 12448897, upload-time = "2026-08-06T01:04:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/76/c1/507a176fc2c1d90df7b80494ef8567ccedcc7a9256d6839cd0549fb752a2/ty-0.0.68-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e7a7e2b80f4cedb72b996d2658e8e6ff24d286273dadd28048de6723c5fc26cd", size = 12787807, upload-time = "2026-08-06T01:04:19.338Z" }, + { url = "https://files.pythonhosted.org/packages/42/bc/aa5c1d3efcc5b02c8b1ffcabb136c59f1c77780c98867ea50ee2f72d4ed8/ty-0.0.68-py3-none-win32.whl", hash = "sha256:3c541c54f6ea9a571dc2eb83c01645582df2f36c4de5e978e2f8ae75c21cca04", size = 11762964, upload-time = "2026-08-06T01:04:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b4/cbdb5d54341bde1fc39f694e2517402cd8e4451cd3fe8615092646c51e52/ty-0.0.68-py3-none-win_amd64.whl", hash = "sha256:674d89e294a2de8bdf1e4133d41866df2cc3b591d971fa87ae18041707b44f16", size = 12844190, upload-time = "2026-08-06T01:04:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/e5/6b6a7d7bc9fdcf6bd00bf2ad91e1eb2d1344e05a396d028ef67e49e3e204/ty-0.0.68-py3-none-win_arm64.whl", hash = "sha256:ba8a886d27dff2da86521a073373e6f68782a69f6ccabf41e83f84a51f210563", size = 12178880, upload-time = "2026-08-06T01:04:25.776Z" }, ] [[package]] From 96f9aaa4d570c06fa945948fc7ac6ccdd6c9225c Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 10 Aug 2026 18:38:30 -0700 Subject: [PATCH 76/82] remove old ty ignore --- codex/views/browser/filters/search/field/filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex/views/browser/filters/search/field/filter.py b/codex/views/browser/filters/search/field/filter.py index 06b0ce9a5..e19064a0e 100644 --- a/codex/views/browser/filters/search/field/filter.py +++ b/codex/views/browser/filters/search/field/filter.py @@ -18,7 +18,7 @@ class BrowserFieldQueryFilter(ComicFieldFilterView): def _combine_q(q: Q, other_q: tuple[str, Any] | Q, op: str) -> Q: if isinstance(other_q, tuple): rel: str - rel, val = other_q # ty: ignore[invalid-assignment] + rel, val = other_q if rel.endswith("__like") and val == "%": # Remove likes that would match everything return q From 09d407238f0685b7e586690ae9dfc544241dde4a Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 10 Aug 2026 18:38:40 -0700 Subject: [PATCH 77/82] update deps --- bun.lock | 20 +- frontend/bun.lock | 20 +- frontend/package.json | 8 +- package.json | 6 +- pyproject.toml | 2 +- uv.lock | 802 +++++++++++++++++++++++------------------- 6 files changed, 468 insertions(+), 390 deletions(-) diff --git a/bun.lock b/bun.lock index 8dd8dca8d..083a79ac0 100644 --- a/bun.lock +++ b/bun.lock @@ -10,8 +10,8 @@ "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.26", - "eslint": "^10.8.0", + "@vitest/eslint-plugin": "^1.6.27", + "eslint": "^10.8.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-array-func": "^5.1.1", "eslint-plugin-compat": "^7.0.2", @@ -24,7 +24,7 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", - "eslint-plugin-package-json": "^1.6.3", + "eslint-plugin-package-json": "^1.7.0", "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", @@ -230,7 +230,7 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.12.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA=="], - "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.26", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-2eawZk4MZvkEtMZFdScmCE0R1Rti85uRZpmbe9eAv5Q3blDZ5OxlrC9IHlSnxhDCqOO2xKNaD3oufl9BUlI0yA=="], + "@vitest/eslint-plugin": ["@vitest/eslint-plugin@1.6.27", "", { "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "*", "eslint": ">=8.57.0", "typescript": ">=5.0.0", "vitest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "typescript", "vitest"] }, "sha512-X1RCAfwbatG4GFbJ/1PIHP9MSZMt0JJThqbYID+vS4L783k6QGlLPe421wQE8dHXuGCcenVLsydiM4qzsYWxhg=="], "@xml-tools/parser": ["@xml-tools/parser@1.0.11", "", { "dependencies": { "chevrotain": "7.1.1" } }, "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA=="], @@ -370,7 +370,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ=="], + "eslint": ["eslint@10.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ=="], "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], @@ -404,7 +404,7 @@ "eslint-plugin-no-use-extend-native": ["eslint-plugin-no-use-extend-native@0.7.3", "", { "dependencies": { "is-get-set-prop": "^2.0.0", "is-js-type": "^3.0.0", "is-obj-prop": "^2.0.0", "is-proto-prop": "^3.0.1" }, "peerDependencies": { "eslint": "^9.3.0 || ^10.0.0" } }, "sha512-kYJhgZkiZIavu/wIwrO+n4GemQcMX53kWCNZNr7nGMkRD1aBFLkDpBivEYP7nIJINCo9fzPbFjrpeX5kr2Qbww=="], - "eslint-plugin-package-json": ["eslint-plugin-package-json@1.6.3", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-32ZHJFzEtsWeZfFr/YMQzq7amG/9jewUuNIKmnkl7QoT7gwclxUwurqRMrjWK2S9lh4prZkNbymROQ6aDpb06Q=="], + "eslint-plugin-package-json": ["eslint-plugin-package-json@1.7.0", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-6kc/2obmBs1JZ2/m65iZjDWFxC4n0OVDKTEhsnWCE6syL5qAJb0rzQtYFNaSmFe9hUWqhaTpr2+OnHQlU1X5kQ=="], "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.10.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.65.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Kprsp9Us0GqAesYaAIzUViw57xYp5WBqzXrcE0Mtww++E5fexWXYBipMuuD7yvyH4vvpBH0+oJ+OMAmZ0oYXkw=="], @@ -1158,6 +1158,8 @@ "eslint-plugin-yml/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "eslint_d/eslint": ["eslint@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ=="], + "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -1220,6 +1222,12 @@ "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + "eslint_d/eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "eslint_d/eslint/espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + + "eslint_d/eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], diff --git a/frontend/bun.lock b/frontend/bun.lock index 28a89672a..ea83fbe65 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -12,28 +12,28 @@ "pinia": "^4.0.2", "pretty-bytes": "^7.1.1", "text-case": "^1.2.11", - "vue": "^3.5.40", + "vue": "^3.5.41", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", - "vuetify": "^4.1.7", + "vuetify": "^4.1.8", "xior": "^0.8.4", }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", - "@types/node": "^26.1.2", + "@types/node": "^26.2.0", "@unhead/bundler": "^3.3.1", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", - "happy-dom": "^20.11.1", + "happy-dom": "^20.11.2", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.102.0", "sass-loader": "^17.0.0", "toml": "^5.0.0", "typeface-roboto": "^1.1.13", - "vite": "^8.2.0", + "vite": "^8.2.1", "vite-plugin-checker": "^0.14.5", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.1", @@ -282,7 +282,7 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], @@ -506,7 +506,7 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "happy-dom": ["happy-dom@20.11.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg=="], + "happy-dom": ["happy-dom@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw=="], "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], @@ -794,7 +794,7 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "vite": ["vite@8.2.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.23", "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ=="], + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], "vite-plugin-checker": ["vite-plugin-checker@0.14.5", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "chokidar": "^5.0.0", "npm-run-path": "^6.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.4", "proper-lockfile": "^4.1.2", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@biomejs/biome": ">=2.4.12", "eslint": ">=9.39.4", "meow": "^13.2.0 || ^14.0.0", "optionator": "^0.9.4", "oxlint": ">=1", "stylelint": ">=16.26.1", "typescript": "*", "vite": ">=5.4.21", "vue-tsc": "~2.2.10 || ^3.0.0" }, "optionalPeers": ["@biomejs/biome", "eslint", "meow", "optionator", "oxlint", "stylelint", "typescript", "vue-tsc"] }, "sha512-c9lQ92eisUO+F7Fd93aelojmiOS+NQpPgQ1XR2LTQHox1/laZf4yAoQj+L3RA9Vgh10e2nFd9b8r2LLyYZsbpA=="], @@ -820,7 +820,7 @@ "vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="], - "vuetify": ["vuetify@4.1.7", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-07qxu0S1oxPjmknP/Yn5276PtIhlCvHnpcp7HuOKEHni89/6BGiM2aVdKlEGeyM5RfytDkiv0SNieabGT2D8qQ=="], + "vuetify": ["vuetify@4.1.8", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0 || ^3.6.0-0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-003D/8b5462uZCD5CMRTZF3smADmOn47mzthNY0aP/xkslovR5yIc1qXjPdFN0LHcu+p3GEAVmMQabldaIQ5pg=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -924,8 +924,6 @@ "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "happy-dom/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], - "magic-string-ast/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], diff --git a/frontend/package.json b/frontend/package.json index e554f93b0..6d4331749 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,25 +26,25 @@ "vue": "^3.5.41", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", - "vuetify": "^4.1.7", + "vuetify": "^4.1.8", "xior": "^0.8.4" }, "devDependencies": { "@mdi/font": "^7.4.47", "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", - "@types/node": "^26.1.2", + "@types/node": "^26.2.0", "@unhead/bundler": "^3.3.1", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", - "happy-dom": "^20.11.1", + "happy-dom": "^20.11.2", "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.102.0", "sass-loader": "^17.0.0", "toml": "^5.0.0", "typeface-roboto": "^1.1.13", - "vite": "^8.2.0", + "vite": "^8.2.1", "vite-plugin-checker": "^0.14.5", "vite-plugin-dynamic-base": "^1.4.1", "vite-plugin-run": "^0.9.1", diff --git a/package.json b/package.json index 16e4b075e..cf82639a4 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", - "@vitest/eslint-plugin": "^1.6.26", - "eslint": "^10.8.0", + "@vitest/eslint-plugin": "^1.6.27", + "eslint": "^10.8.1", "eslint_d": "^15.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-array-func": "^5.1.1", @@ -82,7 +82,7 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", - "eslint-plugin-package-json": "^1.6.3", + "eslint-plugin-package-json": "^1.7.0", "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", diff --git a/pyproject.toml b/pyproject.toml index eee6a7826..6df8f1437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ build = ["resvg-py~=0.3.1"] lint = [ "basedpyright~=1.38", "codespell~=2.4", - "complexipy~=6.0", + "complexipy~=7.0", "django-schema-graph~=3.1", "django-types~=0.22", "djangorestframework-types~=0.9", diff --git a/uv.lock b/uv.lock index f661c0212..b2539ae87 100644 --- a/uv.lock +++ b/uv.lock @@ -84,11 +84,11 @@ wheels = [ [[package]] name = "astroid" -version = "4.1.2" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/fd/24475b7cfb70298e8921bc077adb46a3fe77887422545d8a061573e130ee/astroid-4.1.2.tar.gz", hash = "sha256:d6c4a52bfcda4bbeb7359dead642b0248b90f7d9a07e690230bd86fefd6d37f1", size = 414896, upload-time = "2026-03-22T19:16:42.075Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/a0/fc1c9f396c354456d859eb09fa13186bda34cb5b000f47a7103fcf84fe66/astroid-4.3.0.tar.gz", hash = "sha256:2b5b5048d6edc40e748e2728790e444b81c3d358e19cc89db6e973236192362f", size = 438804, upload-time = "2026-08-07T20:28:28.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/97/4ee9b0438e85bf0a808a89ef0be357319252ab27e1b313ae0aef7aeaa5a6/astroid-4.1.2-py3-none-any.whl", hash = "sha256:21312e682c0866dc5a309ee57e4b88ea92751b9955a58b1c31371cbbeb088707", size = 279956, upload-time = "2026-03-22T19:16:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/00/0a/f277a39699ac334e332c991b6ce9e47a30bb2bdac6d2141fdbd2d39835a9/astroid-4.3.0-py3-none-any.whl", hash = "sha256:47f329c1f4709c479f84f824c4f3a132816603a749cd4aa6ee9030e69951491a", size = 286532, upload-time = "2026-08-07T20:28:26.602Z" }, ] [[package]] @@ -704,7 +704,7 @@ docs = [ lint = [ { name = "basedpyright", specifier = "~=1.38" }, { name = "codespell", specifier = "~=2.4" }, - { name = "complexipy", specifier = "~=6.0" }, + { name = "complexipy", specifier = "~=7.0" }, { name = "django-schema-graph", specifier = "~=3.1" }, { name = "django-types", specifier = "~=0.22" }, { name = "djangorestframework-types", specifier = "~=0.9" }, @@ -815,62 +815,62 @@ wheels = [ [[package]] name = "complexipy" -version = "6.2.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tomli" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/82/6a5c35b7c31ef56ed3d5fc94cd84d066ac1affb2484fc94afa0dbb15c127/complexipy-6.2.0.tar.gz", hash = "sha256:c90db418f1a3e885ebe75537e5930e2ad98543e3302b55f59684db343d30de2b", size = 355624, upload-time = "2026-07-23T03:44:13.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/81/59d3cfe82ae5e604e141d9818aefec38d8aaf2135b487fb8d617273eae08/complexipy-6.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fba9cd689ac9e5b64ea293ff0c116c5b9604135e9254beff6a94e38cd4143d1a", size = 2318663, upload-time = "2026-07-23T03:42:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/a9/34/02d286db0ed86a62cac0d922b7711b82d2f829e443eee63db91e4e2f02a1/complexipy-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6ec8a85df7247c9b64c9a9a3b01fd0912b5f8ab882aebd2864e98a127b82442", size = 2260436, upload-time = "2026-07-23T03:42:22.754Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9a/dbb27707e468c8328fe081232d1e5faa79d6ff423b71ec5cbfee65c191c4/complexipy-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfc712f41774a4f753a1860c5538d1235aa29b1a166c429722cf2ea367b5bad", size = 2463028, upload-time = "2026-07-23T03:42:23.905Z" }, - { url = "https://files.pythonhosted.org/packages/70/0c/ea72103f64823d3320cca0ae19c8332cf4f3a7717fdd4210da5440c96e75/complexipy-6.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fcdbe5d947da0f8fb6d6b20c5d314671ecca6be5801cfdb4319f784a940bbe6", size = 2394585, upload-time = "2026-07-23T03:42:25.333Z" }, - { url = "https://files.pythonhosted.org/packages/38/7c/0ae87725ae80e0abd17aaeb180017e59d68d83b4917c1cbb7ddfeff4f5d4/complexipy-6.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dec6d86f4781e02339364828af9e0fc9063ba4e48f618470992947b385b89677", size = 2611760, upload-time = "2026-07-23T03:42:26.611Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e3/7973f54b785991f072fb097bfcd711da1d93858ba3e2cf864737bebdda58/complexipy-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8487d25288f2e4c13e0fde79d7e896adea7cd23cbe2a8084c796c3b877e23650", size = 2848458, upload-time = "2026-07-23T03:42:27.906Z" }, - { url = "https://files.pythonhosted.org/packages/56/9f/202f6fc146a942064d797744dd24f62a7655b902548e457f1ccc0d28a3c3/complexipy-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b57cd3804071d3faa00a2ea7e70f5c9c4c7d051666b9a2b5a2199e630ae006d", size = 2524478, upload-time = "2026-07-23T03:42:29.201Z" }, - { url = "https://files.pythonhosted.org/packages/04/62/d349bca4df2e1d0b80b77141cc1639a8f89c39775b782658952b053d2007/complexipy-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001651eff2f8c223d5763714a1ff79949de743d98c14c69894a90ebed1453b70", size = 2486988, upload-time = "2026-07-23T03:42:30.448Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d3/0e49f8945e8b6c0b313a8b0be610ee8af196bf17e9b5c86b1faec2dc87c1/complexipy-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d7180fffb12f644c8672751764698cc09561643911b77d4940e7d8ca54778575", size = 2642157, upload-time = "2026-07-23T03:42:31.769Z" }, - { url = "https://files.pythonhosted.org/packages/49/5d/159d49784c7a495aff708f03fb07792b56f9c3172354ef71ae5c77d2cbc3/complexipy-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:12d169d90d3b6341c363bd92a6c53b575b8de56b8adcf68070326f9a0bdeb3fe", size = 2738241, upload-time = "2026-07-23T03:42:33.11Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0b/2efbca058d0daeb7634debfb6176a9fd81f959c52cc07c7d1e851188fbde/complexipy-6.2.0-cp312-cp312-win32.whl", hash = "sha256:d95852e9ec89519d911cabd692966ce46d43ba367a981d20ed23e5affbb9fc4d", size = 2036394, upload-time = "2026-07-23T03:42:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/0a/47/1da2632de465d9f347e0fd22b1224e8941acc6a681ff8fbfaea34e90c5d3/complexipy-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:cc10f24ee67ff51c46868cc7672179b23114e3c8a98c321c7b7711ed9bb45433", size = 2188314, upload-time = "2026-07-23T03:42:36.023Z" }, - { url = "https://files.pythonhosted.org/packages/af/75/27c11d3921bbd575b9e398fcec895444b7dcacc28500c54e642a56198a28/complexipy-6.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f926157cdeada0bb3e8729429bea928ca210b2cf6249ac70a2fb09548a606f50", size = 2318369, upload-time = "2026-07-23T03:42:37.268Z" }, - { url = "https://files.pythonhosted.org/packages/77/80/ad38a2afa6a7f1878935493b725b7f33db94b748458224948b21a807f9d6/complexipy-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:18ec1d43b40fa5c32288b137c7516814afb73843dce7019ba6280d5f6666240f", size = 2260208, upload-time = "2026-07-23T03:42:38.563Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a4/b97e3cd5f338bcc77f0fcd8914bd2fa1deb14aeb62027e2f14d92109f9d1/complexipy-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa51743c27f6a7ff00f6cf19be871903bd101318b0eb1aa9ba958e6c1922898", size = 2462929, upload-time = "2026-07-23T03:42:39.757Z" }, - { url = "https://files.pythonhosted.org/packages/f5/0d/178f6a0e2dca3c2acdb0a4108c5942fd9ee6c5e76379a5aa9ddfc4564cb4/complexipy-6.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9d6a561815bff1cad0c43649ec1b6d501b8c0a0ebc4c84ab34fabcd5b35b78ad", size = 2394705, upload-time = "2026-07-23T03:42:41.112Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e4/00921335f5f43fe5160b4b31763df55b53daa7dcd00fe11787975f9c941e/complexipy-6.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cec2b4dc54e6b8ecc993a6e66652816ba93c3bc99729b28ed5d3410e39e713b8", size = 2611667, upload-time = "2026-07-23T03:42:42.314Z" }, - { url = "https://files.pythonhosted.org/packages/1a/3c/e0d65a1e20da7c7fdb6da365119bc7de32e3cad946d97aea96e87291ac69/complexipy-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bd3ccc2fda92ebfd82ffa53498df1783d809a0c6ad56941eed42fcf312d0acc", size = 2848850, upload-time = "2026-07-23T03:42:43.748Z" }, - { url = "https://files.pythonhosted.org/packages/87/cf/1d939868ae311c004ebeaf19403e03375f38edcf1037822216592bcc17e6/complexipy-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1dfc7145be69fad1a828b4d92391a4e70299d8cdc0d74541a9b0d02daf05c67b", size = 2524160, upload-time = "2026-07-23T03:42:45.008Z" }, - { url = "https://files.pythonhosted.org/packages/72/69/964a50dd8de47eedea4a05405fe2a2d7037fcc59702a5488c36234d39fe7/complexipy-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b3077f387b7c225d1a5416e42dd005a9bd61031494608b768fe9fe62f67e662", size = 2487218, upload-time = "2026-07-23T03:42:46.216Z" }, - { url = "https://files.pythonhosted.org/packages/3c/ed/a6304b3519bbdefc3fe3b68926be11e14708f4724be6668e9c81f5e56058/complexipy-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2d426720668664fb79f04c271da5fa2c0293ffb1e19ed4db0a8de134ad16d550", size = 2641994, upload-time = "2026-07-23T03:42:47.54Z" }, - { url = "https://files.pythonhosted.org/packages/a0/4f/9805118d69a7ab3dc5bce00ab6d1c1207f4619777b50981a0179979ecf39/complexipy-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54c00af2b42d14ba0e94c812b33c018c386013128de584f8cbc3124567d28c7e", size = 2737925, upload-time = "2026-07-23T03:42:49.301Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/749ec64ef80b3863efb6b8cef03071a0054c1041e1082059c7d0d4f23d54/complexipy-6.2.0-cp313-cp313-win32.whl", hash = "sha256:338359fb31b928f1a49063106b2cecd8e6f827897cf348efadc6fbd12719b961", size = 2036290, upload-time = "2026-07-23T03:42:50.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7d/31122b321a5647512af1d748e689556112d3b381da78ed7f1d982a5884ea/complexipy-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:624762987e37db3da602996f9190a33cf995f1768a6652d6d21f6170767750ad", size = 2187968, upload-time = "2026-07-23T03:42:51.973Z" }, - { url = "https://files.pythonhosted.org/packages/f7/fb/9d0f90e32d1a2462ed4f4012ea922a759e708cd8816506486e4347249957/complexipy-6.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:498018df8448124247880fb7d824130401a8ac18a8b17aca7ebd54b91c5502bf", size = 2319799, upload-time = "2026-07-23T03:42:53.242Z" }, - { url = "https://files.pythonhosted.org/packages/af/60/4f0b620afb20ed0a78356325d7dc5835b8c3610847d7adda3237e80cffb3/complexipy-6.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8077354d21cd7256347af67e33d0d0c2965a2512a9e174dfa01d6935a6a2c8ef", size = 2261117, upload-time = "2026-07-23T03:42:54.618Z" }, - { url = "https://files.pythonhosted.org/packages/96/ca/1843e382dc4b4f612ada7b8a3ebf2bd4e4584f3acf99fce243ed735dc883/complexipy-6.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f954e8d62891b85c0f279c335dda7df24638fa83efe3c1d5f4752100c61b4494", size = 2463571, upload-time = "2026-07-23T03:42:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/b2/53/24cf69643ab14d7070ce243f504c63624dec13c916bb8bae99ccaf2a3fde/complexipy-6.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0341b7d7ebc67a5e7b500007df2195f4f92d4e24e21a85fd954ff77f4dc6af18", size = 2394587, upload-time = "2026-07-23T03:42:57.158Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f1/c70bc68ad4bd57af77991dd3d85f27bfaf27e5263e3afea7862d0d63e561/complexipy-6.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e9c615f1910c7436e9dfc7680e79399a89bded1c554b9c0a37b94db1e6def55", size = 2612999, upload-time = "2026-07-23T03:42:58.531Z" }, - { url = "https://files.pythonhosted.org/packages/2c/eb/db02f3f6cd1c3d047aac8b10b1ff95c98af4f538e07c1d32c9ff9cf757dc/complexipy-6.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7285614c6be583ea702d22a19c09ec235b81bd11d9e754c9a940493e008a7e10", size = 2850798, upload-time = "2026-07-23T03:42:59.959Z" }, - { url = "https://files.pythonhosted.org/packages/6d/41/d3f1eef665f9833adaee9aff1232bcba43451244fbf1ab4a180365236aff/complexipy-6.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:708c80d46ac70dbfa17bf82742d89852aeb679b0d7c05379314930327841ce3f", size = 2525119, upload-time = "2026-07-23T03:43:01.331Z" }, - { url = "https://files.pythonhosted.org/packages/98/6a/bed199004647f134deafeff21afefde4e180694a58d11dd14e7287b28985/complexipy-6.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12064f3e210243c532a3734fc87c149dbb4806ac171ce42843ee816d25387def", size = 2487604, upload-time = "2026-07-23T03:43:02.689Z" }, - { url = "https://files.pythonhosted.org/packages/e7/b8/0a2b482ba94f744a54a09e54a77563c1bc22847664d7af5349d4f4940199/complexipy-6.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d78cbea32ad23dcc6ab2d5589712e9a9493f1295d13ae3bb96268f8427cea6ed", size = 2642190, upload-time = "2026-07-23T03:43:04.321Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7b/1ec6e25c7b0a5706c20c6a06d92fdbe768d103c80ccd37a8755b85c41525/complexipy-6.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:579450cf61ff04e59149e1901cb27de3d80cd4815a227ad24c2e92ebdf4b449c", size = 2738474, upload-time = "2026-07-23T03:43:05.749Z" }, - { url = "https://files.pythonhosted.org/packages/9a/d1/80f4cc44d489db0c256f05fa1bb468c80d5db0e7309bba6260218f59e167/complexipy-6.2.0-cp314-cp314-win32.whl", hash = "sha256:3a22f5f2a6843a8d9652076368b457102a0c9e95b63769b5a50ef3686427fd96", size = 2036901, upload-time = "2026-07-23T03:43:07.289Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f0/16db165c81e6b2246e9a765bbb4ce4a1929a9ca151aee0637cf78092f8b9/complexipy-6.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:89fabd582ed95cb0bae44f065b40a6afbc045d8717acbaf693ba3eafa50baaa8", size = 2190135, upload-time = "2026-07-23T03:43:08.691Z" }, - { url = "https://files.pythonhosted.org/packages/20/22/1d27d7d75d32c49d0bcabe2e118ef7f1491b9bdfe02c7043b62a574f9409/complexipy-6.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51a318d2bb83a766c5b7a1adc9d123df9fd822cee8df648c925b698506469e58", size = 2462244, upload-time = "2026-07-23T03:43:10.155Z" }, - { url = "https://files.pythonhosted.org/packages/5f/6c/680f1dc9a671f4c3aa50c4922f0cfa5605a34bbbb5861725101580d8b45a/complexipy-6.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50555b34bc069c8e06832b42755a93939215e21734891bad910ef0b6eb1e18fa", size = 2399189, upload-time = "2026-07-23T03:43:11.824Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c0/5ed9f7815c5abb4c91b2d0d78df7996c182ab61ac233f40fc92a907e23de/complexipy-6.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1348392bf58532653bf3583f18fb655b058e680432c23264a6ffc8f27451cc01", size = 2615954, upload-time = "2026-07-23T03:43:13.417Z" }, - { url = "https://files.pythonhosted.org/packages/58/56/fb835163c4af2119539875b3d9c7ce09fd1c02cb952968fd9b71b54e2916/complexipy-6.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de886742128d50e8100a8cee3b4596be2ec4b55cdedfce4ac8f9f45fbe8fbd85", size = 2850104, upload-time = "2026-07-23T03:43:15.148Z" }, - { url = "https://files.pythonhosted.org/packages/dc/5a/3b50eb8c2d6b6016379825cf330fd7ba45a5859500000bb50586e1e008e0/complexipy-6.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f50e3496d9dcc547d9c5d515bf297c87c9c57afe33cbb6df341e614fa57b1738", size = 2524210, upload-time = "2026-07-23T03:43:16.562Z" }, - { url = "https://files.pythonhosted.org/packages/51/0e/c7f061240d82abed2d34a6e5fd4165fd97f6c7d0c90dadeff1fc4a93366b/complexipy-6.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28914d763c03d3a662ac2d666bc4151852788e6e767f52dd07c94e8332a4b265", size = 2487720, upload-time = "2026-07-23T03:43:17.882Z" }, - { url = "https://files.pythonhosted.org/packages/98/0a/ed66cc8d14d8cd0d76154f30e36b05a9bb058b462541056666f5f1bd7880/complexipy-6.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:63eed569ba56d2e90a167a38f241fa06de99ee9f96ec950c55a2aa22694e4ce0", size = 2641145, upload-time = "2026-07-23T03:43:19.273Z" }, - { url = "https://files.pythonhosted.org/packages/81/2e/eb2a778b9ce6bfae57f41eb8f042df3167174d2627bf1eec37b6bf64eb44/complexipy-6.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cbbefd1fc3e72da8016647176de792a579a276b1308f0822b5b1585f16eaf5a2", size = 2737833, upload-time = "2026-07-23T03:43:20.692Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/1dc7456c01d46ffb5a18e4cdbcba9c3e81ba209f5587ea2280e1e5b49d26/complexipy-6.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa80249ef9b552da259f2cc2b16482f547f05f76a4f377dcff3f568ea7fcaed0", size = 2614198, upload-time = "2026-07-23T03:43:22.271Z" }, - { url = "https://files.pythonhosted.org/packages/5e/39/2d4be85a221c167a36960175ea006ad310680c35cbe005cd5ba4c5322a92/complexipy-6.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42442a233ecaec535e6543869867184ac0cd6dead64f57ce581e650d4d5dfbfb", size = 2487356, upload-time = "2026-07-23T03:43:23.613Z" }, - { url = "https://files.pythonhosted.org/packages/4a/5f/fd5a17602585ff8625072ab356b36ee6de42cf82a865cd919dbf68a87241/complexipy-6.2.0-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04f4c4e08d2d4650828b33b88ebe7521e71fcc2d1a7883808e92404c414676e1", size = 2616158, upload-time = "2026-07-23T03:43:25.003Z" }, - { url = "https://files.pythonhosted.org/packages/a5/df/af4eebce70ed8f91a94f84cf65c7aac2720c808eacc9772b8f53df0cb23a/complexipy-6.2.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:037fe51f18b3aad5b09ac74880735d3dc21dd54b62fcb02a8d5f1acbd76da8f5", size = 2488018, upload-time = "2026-07-23T03:43:26.33Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/69/17/42a5d52d74e9acb1ffd1496006f4b5c49318f516fbc12aa287fe31ba3791/complexipy-7.0.0.tar.gz", hash = "sha256:2cd8251532f8d9f560266179ff80353427f79604c3bdf9e3640ed32dca9b4e5c", size = 441274, upload-time = "2026-08-10T20:21:15.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/ed/53d0ea342fda7768eb98d1584403ce80258cf920cea5758dc0ea57e03990/complexipy-7.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ec9d47a4a662b234a0c365fb92e0685ad54eeb1ed6d18b1c1e5922de0bcf0773", size = 2384248, upload-time = "2026-08-10T20:19:12.306Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/0755f74366397fa8a6156f045ba94c6251930830654bc74c88529ac7b704/complexipy-7.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d722f7fe6f7ce06f74353085521edd67db8a1fafa5905c0f91c744a7508b63a2", size = 2321164, upload-time = "2026-08-10T20:19:13.667Z" }, + { url = "https://files.pythonhosted.org/packages/e1/67/05b3cc5928f50c6622e8350283c621b54e263fe0df722e945232b57a174e/complexipy-7.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e7754d6e5ba3866b0775f75e7b3ce69216af9faa2debe75c794ce2581e821e", size = 2524667, upload-time = "2026-08-10T20:19:15.058Z" }, + { url = "https://files.pythonhosted.org/packages/39/93/6d94fc163fa0a36ad3456e5eec866c1047d2011286125f5365f4f9270856/complexipy-7.0.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81190504987362d37ae8c24322bb5bfa026ae04734b5fedade7f1f2689d456e2", size = 2458564, upload-time = "2026-08-10T20:19:16.454Z" }, + { url = "https://files.pythonhosted.org/packages/91/49/aee95aaa534446147108d391de1a0e6837ed9dc56f36be736862ed9fdcf7/complexipy-7.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0f448a1f0e8fe060eaa1cd6b92a89f01026c97701f86b7de3f6a11e7c131ff1", size = 2688406, upload-time = "2026-08-10T20:19:17.82Z" }, + { url = "https://files.pythonhosted.org/packages/ac/51/66a1dec58f844f9a208b8d43304d2a5cc0813e246c77432d41b24903db76/complexipy-7.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fbc1ab3e21b0291ae3a929bc85d688c02094781727ee3a42c8020cd9b7ec7b2", size = 2918803, upload-time = "2026-08-10T20:19:19.172Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c5/db2d5f31635b5fdfd5ac4d05d84a64606ab6945b08145bf406b69b7ccae2/complexipy-7.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73eb093258a3842b33f9230fb43ceb05373ca4cb94a5ebefd71d70f34e6a54d2", size = 2586235, upload-time = "2026-08-10T20:19:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/85/34/c51f8c5b7be4cf7fde506315817be3bc8b426c65d37991bfaf13d3bb148d/complexipy-7.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c96a70e8f75abfbf4f3fd86de33644068c1378fdb2d17919844d4e134611549", size = 2549725, upload-time = "2026-08-10T20:19:22.148Z" }, + { url = "https://files.pythonhosted.org/packages/31/c8/78e65675fee73cd5eb52ed2090869e6436c13334d3724615ef528cf46a4a/complexipy-7.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78e5b57e1c23b8262bc659b6902ca0daba224c4f23c8e5d21a2a9ebb1aaeb05d", size = 2705292, upload-time = "2026-08-10T20:19:23.594Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d2/47f707af4bdd443e2d2d3275b5b0e3e882065d56e9f514f04de97f60da96/complexipy-7.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e0ce95a7f9afed083dcba81f50b13ab024bc16c1c4595908df251e6d32be49", size = 2807342, upload-time = "2026-08-10T20:19:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e0/e32df61e990224286fb34b487312973baf76ccd0e9feb96de16e15bc8279/complexipy-7.0.0-cp312-cp312-win32.whl", hash = "sha256:df8b3795f7bbd86badff27359e7fc055e80ccc6e6095bf3b800eb0e5298ee1d3", size = 2085186, upload-time = "2026-08-10T20:19:26.4Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e4/236d24c1b21a405b7749e5c7f1c8b5589039a2cfb34c70bbbd50960fae11/complexipy-7.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:459c504769b880e9cfe0bd18b3c81b99045423fa170868dcc5475072defae1e3", size = 2250423, upload-time = "2026-08-10T20:19:28.116Z" }, + { url = "https://files.pythonhosted.org/packages/97/4b/e702108f7cea411801e401fe9cee4e32a965cf6fd8bf2afa739fedef7337/complexipy-7.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:186085b6f911d24174c396e0540d02d1828bda1f544805df1b8f2749e623fe37", size = 2383630, upload-time = "2026-08-10T20:19:29.388Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ac/819e7f46fc974ce15fd6928e1b56605ae3839021cb82871df569ae491c1c/complexipy-7.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:513c4e15357fcab515943ab4b502e07dce3699a7e083ca39f3b4d10dc6d99bac", size = 2320874, upload-time = "2026-08-10T20:19:30.761Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/596dadcedad5d371a4ed11c7cec98e1aeb95d8dd462622c5353209d888ea/complexipy-7.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172057334515ed013e5eb6873af3562a1984eeb10dfccfb9f5b1c745bb48ec3a", size = 2524393, upload-time = "2026-08-10T20:19:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/77/e2/1d57864a0687d140e9ed4d9e1dc83f5ae7db270e5b36c00e59519f164707/complexipy-7.0.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f85104ecc8cb2b70df534e6a9652bd1e91a2291615befa80163f00e201f2af3", size = 2459213, upload-time = "2026-08-10T20:19:33.794Z" }, + { url = "https://files.pythonhosted.org/packages/04/a7/3ca7dac1f82b9661f646646fce1c30c3ba6cbcaea335c7d423fea9adc4b2/complexipy-7.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67aaa1b6df8870ef50b20af18e2fef36410959e3e821e79712623d922d9450e6", size = 2687375, upload-time = "2026-08-10T20:19:35.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/35/8fbba5f5bdec3dcc0875bac79bdac2c600e70db9482c688008f2d1aac81e/complexipy-7.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39c44867a9a1775afb5338392fe0c2e3d3db614cf08dfbdc16f389c1a04d716f", size = 2919234, upload-time = "2026-08-10T20:19:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ec/f18846edbb9c48f4a815b84ab100f3ef6f6bcd60fd69908ec73816dcc013/complexipy-7.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1793eb686f1d1e93cfe3ed55cdbbb47bf685abe7bc4a0cf98c2829482c666829", size = 2585904, upload-time = "2026-08-10T20:19:38.596Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a0/7b0ba6a8c3d404c476000906e0f728f87c23ba8893ba3a1b5685be8f37ed/complexipy-7.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020110d25866d275f14c33e2d43045c432c673855f74d8a86847d5aed97c0c71", size = 2550276, upload-time = "2026-08-10T20:19:39.978Z" }, + { url = "https://files.pythonhosted.org/packages/18/b0/6c42be696c822ddc7b86abd7db52681cb8af5a5dc90562ef1d3a8a81235d/complexipy-7.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50f1a74306eb4a84006077fd2054873e24f81291270f7c323ec8bcb2f11278f9", size = 2704982, upload-time = "2026-08-10T20:19:41.554Z" }, + { url = "https://files.pythonhosted.org/packages/3e/24/b304d306f718330607288cabef58dc46a817d941cebd11bb8bdbb9c6fb9b/complexipy-7.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a75ddd087a95cf5ce6e57647ddbdd539e4c8d4da72b2e82242e8e6cc523e01e0", size = 2807092, upload-time = "2026-08-10T20:19:43.016Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/0b8d6ba98471e30bae748dcc28115ee22695d52856f154011d2bcc18c74b/complexipy-7.0.0-cp313-cp313-win32.whl", hash = "sha256:9281f72e79b0f4b8e7b50b7a58be17f687bfea2a5763520a66a5da42aea77e22", size = 2085123, upload-time = "2026-08-10T20:19:44.51Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/751561694af01813800cc28a978651c2738890ba09fa9c65da46bb7a5f30/complexipy-7.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:9127ade1785064cbd354bdd6442a4d0151ccf476398675c7ec1edd5d5f9cdd60", size = 2249900, upload-time = "2026-08-10T20:19:46.65Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7e/22bb23227173fe9a021db703f9e0c09411c887ce6433db2391e3c7f847ec/complexipy-7.0.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:29ebe4cbca7aead35b27a8b67785e0c9cafca0263e64dd0fa886bee37997d18a", size = 2384910, upload-time = "2026-08-10T20:19:48.75Z" }, + { url = "https://files.pythonhosted.org/packages/87/b0/8fc756be7ddc7b47247341fe5d32c9a8ecfe6273ab70e698a9939b4380b9/complexipy-7.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b45e860daa0dc49394a769c409040caea4bed831fcead22b85aabd373ae4c23a", size = 2321947, upload-time = "2026-08-10T20:19:50.031Z" }, + { url = "https://files.pythonhosted.org/packages/53/ac/7464fe0b68b7a2fd312a52c8897ff91c0f9f6b46593f6488c135be6a998d/complexipy-7.0.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b224473e031340286b5723a8649f3c0d0f2bfc5f3028aa183a1027eb1ade6a", size = 2526000, upload-time = "2026-08-10T20:19:51.385Z" }, + { url = "https://files.pythonhosted.org/packages/e0/86/f8ce1850f33a1f95a8ed99ff48feafc369bd2ee718de804a697ad56bb572/complexipy-7.0.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a402812a08683cb2788dd781f332a855fb474ffd3ad8e62a7b17c4041c0e6b75", size = 2460213, upload-time = "2026-08-10T20:19:52.852Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a6/5383b478020f8ae24acbb6b9c224e78b55be2135d25d692ca6f5f9293c5c/complexipy-7.0.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5d53747c0e0db306cab4951804c1ca387a9ede7be4fc232159ee3f36ef7488b", size = 2687076, upload-time = "2026-08-10T20:19:54.319Z" }, + { url = "https://files.pythonhosted.org/packages/38/f4/59686552c6e422309a8f725eabab53828ea1fa24ca7ca8bc91c182691266/complexipy-7.0.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6011036c1e7addbe2afdd0055aecda9e63859d60358ba57388f0a7b96088e87", size = 2922975, upload-time = "2026-08-10T20:19:55.772Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2b/c7e7da119eefb61d75ebcc64c3fd0ff6ab134beeaad17f27da0721b6de84/complexipy-7.0.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae9c0d5e49be1fd204398a056a83b301c239d67f3729b0279e0f274773cf6167", size = 2586564, upload-time = "2026-08-10T20:19:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f7/58529f12f6b78000693c6fbd14f69928d214972bf0d709edb58fe835a762/complexipy-7.0.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342511db5817c4725c06c81e8c80256b967f6e0ea1b2fc65f10d68cf95cdb358", size = 2550982, upload-time = "2026-08-10T20:19:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1a/05059778c05d78de9e20355776e549daa2b8be952cdf15f211bf947f7509/complexipy-7.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:08a0d7a1e6673eb217b9e2c90b6ba0d9756c4dc870f29e956697d823e61e5a6d", size = 2705584, upload-time = "2026-08-10T20:20:00.31Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f8/57c1148b92bbcaacde70d5196186c598f1b809cf4170775b8c4b9c4c9d40/complexipy-7.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ac0d9a82d76d3488d84da861a4538f9eef2859bb8ac4c4ad472dc3d04c4892e", size = 2808727, upload-time = "2026-08-10T20:20:02.043Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/2a272e5fcd6d0e2ba953165dadb9a38b00901acc4de17b83f2e0a91ddaf6/complexipy-7.0.0-cp314-cp314-win32.whl", hash = "sha256:f7db7f159725d195023db7f55926d8143d2e44b56d8e15c948e78b286bc7c712", size = 2085742, upload-time = "2026-08-10T20:20:03.558Z" }, + { url = "https://files.pythonhosted.org/packages/79/1e/50d6a569abe5026666b252d9373c4fd7c1bfdb3bca3c9ae329c6fc6f2f93/complexipy-7.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:af4217e9ab163d56527d0029868e810d4a0d8221a6cf66c99010e7e780b02c14", size = 2250902, upload-time = "2026-08-10T20:20:05.008Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/3142e38993e020cdab9fa1d5467a877317434ed7ada2bd362843ffe66546/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e949ad10cf092e2999bb066ef7776606c7fc5e3ce0e861dad51021f6b4401c4c", size = 2523172, upload-time = "2026-08-10T20:20:06.433Z" }, + { url = "https://files.pythonhosted.org/packages/50/f8/313d557fc9fe802e1a39d5b578a80bf5903a8bf667c474b5d389a8fbe37e/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8062c12f35a12dfba38d5f269ea8ff71fa999e10dd04c7bc0e3971a8a9f8075e", size = 2460108, upload-time = "2026-08-10T20:20:08.118Z" }, + { url = "https://files.pythonhosted.org/packages/9f/60/216b439fd7de9b5b7adaee3b53b90569ea7a7ff5898a4e3ee0e5edc5f430/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0469648687089ec695a3c316ad89792a551034e709dbfac38f144f5fe9ab7b86", size = 2686748, upload-time = "2026-08-10T20:20:09.79Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/cfe52a3d9ba2cf61bac15d9a371b5392f8edce8a1c484ccd9cdf50b4e003/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:473cdc9074073647567aeb340c7c283583a35c635ea3d78c020d1369092bcfbd", size = 2919372, upload-time = "2026-08-10T20:20:11.183Z" }, + { url = "https://files.pythonhosted.org/packages/73/74/dc0cd8cd3c8259030d7ca02e231162df50eb1d7a816548ca41011fbfa08e/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a738a47e4de2a3c4c3574995df56e57e0aec22077d8d4b0d51801e5e286c1867", size = 2586860, upload-time = "2026-08-10T20:20:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/5b08a7d3b11eece7ba93006e5ac166e6c4381420c296a8c9852eff17a6bd/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5bcd4823f39254911de0d86d33adba76c20962ccdf5ce4e836baf38be6dc90c", size = 2549508, upload-time = "2026-08-10T20:20:14.167Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/406cf3c72096e0720b122f173c9f1a3bae39a7e6c2964bf12da62a0677cd/complexipy-7.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ee8b71bdf46df9a82fe53df37d667aa7f254601ce5b51a94ece01871bfcb2b47", size = 2702436, upload-time = "2026-08-10T20:20:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/71/d8/8bb15e335a8dceddd50c41347741201313ae65d25ee4a87d209a4bb52f54/complexipy-7.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:242eb4b9993ef3c88e7cf2e4e94a2a9f92335dc3efa6658b817a733a494ab377", size = 2806788, upload-time = "2026-08-10T20:20:17.365Z" }, + { url = "https://files.pythonhosted.org/packages/b9/68/caa9d8275b86d4a46dc5d00a70a92271ab1382b08f0684a9d648d0f69483/complexipy-7.0.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26dea418e38f29fbf6ca1e8673b8546df86ab08053a2894ff477a757f8f6dc13", size = 2690132, upload-time = "2026-08-10T20:20:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/07/03/daef3297c8f1ada255c86d27c03947f9f46ecba1c861b133833a2d13c9a2/complexipy-7.0.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65fd45380a623543e8d586513771798f21e4dd08cea058ca4196966f3d189876", size = 2550878, upload-time = "2026-08-10T20:20:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ff/74a1a508b49dada5c93c98c13e6aae7c78df9ade8bd7b6268e8478284b0b/complexipy-7.0.0-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6de6cc1dbcf3d33de4f149fa6497b820729116e139945f7dc642b92eda79ea3", size = 2689340, upload-time = "2026-08-10T20:20:21.725Z" }, + { url = "https://files.pythonhosted.org/packages/03/6d/957964a6fa1c9c9e66df756f2b82d59bd4ce87d02c0a321f4016ab2f9f97/complexipy-7.0.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5b50a9161809f2d47000fcce075436d8167308dbd813e06884dcc8583a65336", size = 2552076, upload-time = "2026-08-10T20:20:23.322Z" }, ] [[package]] @@ -888,71 +888,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/45/78dbf9604ee5b3db24efbf26bed1cb58862fb40480cba821963c69348751/coverage-7.15.3.tar.gz", hash = "sha256:ae7ea5a4614acf399ef0483c4cb34f8f8f01df848d8fcbe7d3ce0865733f1c4d", size = 935592, upload-time = "2026-08-02T18:50:17.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/6c/bac99d9d4c6abe856e93bf3f5212982ac0bfac126dd4a042753bd53bc5af/coverage-7.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:79a3e32e83227d83d9684459ed579769b56c369ac2d7313099b2d9e031d2e10f", size = 222499, upload-time = "2026-08-02T18:48:15.018Z" }, - { url = "https://files.pythonhosted.org/packages/aa/bc/cb9a39b083bc1aa70586482dab25c9be20bab0ec6c155340e50d9066bb1e/coverage-7.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:767feb87c5886d781d0a69fafd450a20826ddab7b79bce1665deb64d21441b60", size = 222866, upload-time = "2026-08-02T18:48:16.884Z" }, - { url = "https://files.pythonhosted.org/packages/58/fb/beaa453d62000a0a5b39838bee2a137afe609a50a71f55e83c73461e513b/coverage-7.15.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50951e37033c40548d777b8a8454a2cd622dba1136780065678dccaec307c47f", size = 254367, upload-time = "2026-08-02T18:48:18.507Z" }, - { url = "https://files.pythonhosted.org/packages/66/64/43e72500ed6815cef189f9193f29d7af4b078830337c95ea976cd0c0d427/coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:63a4ff67364afb2cac826b8bbd78a5c50ce656a7b7137436b44d7b96a9271088", size = 257103, upload-time = "2026-08-02T18:48:20.172Z" }, - { url = "https://files.pythonhosted.org/packages/66/3a/2893e2937adfe02f45fd38e4a8a0a0d8b7a02ff9e012ac3d009bee3c4f16/coverage-7.15.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95e42856509675fe26560310313a6117640e96f9a1e19bb3d220116a27c94c", size = 258220, upload-time = "2026-08-02T18:48:21.963Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d5e6e2eb1a62961083734291304b1f85df72e2abe95c76eb88a7f472afd0/coverage-7.15.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abad631cba27094b4631993f4c72e89ac0ca1b3a0236c7abaf8ca79aea619851", size = 260481, upload-time = "2026-08-02T18:48:23.682Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c9/9b72c5c6a9798a9a12cf65f66e077cc1fdd396e61915c862688f9afe1cae/coverage-7.15.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b0807f1f051dd82a234ad6acdb6f1425baede60be1e84e862496c8cc9262ab9", size = 254749, upload-time = "2026-08-02T18:48:25.32Z" }, - { url = "https://files.pythonhosted.org/packages/92/20/e1c2f759e2dbce559ba85c40c0e4acfecc6cff4b740c294c88e41ccc6111/coverage-7.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8d6df7aeb5bc464040bbc9ae173d875785d3677ebc4307817997d622d74225e", size = 256138, upload-time = "2026-08-02T18:48:27.064Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ab/48cc7e760f769e86ae290a125ea6e7209dfbdbbbb7ff4f5d9d1ee7a45d57/coverage-7.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:974471c506c9f5758808b47c1ebf7949ecd0848f5c1020e78675fefe5ff46866", size = 254283, upload-time = "2026-08-02T18:48:29.082Z" }, - { url = "https://files.pythonhosted.org/packages/15/26/39529a68154f99b3a1829debd8b25eac384effeec890a293b5bbdcb49186/coverage-7.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5cba0c9c13e35c86df7998f1afaf6b1da224a3a39e4da59bdabf60c148046dcb", size = 258352, upload-time = "2026-08-02T18:48:30.892Z" }, - { url = "https://files.pythonhosted.org/packages/91/2f/55b82aa3d8d7dd8023a56e7c5c2a70e39a3c44b3353c6cf3faec9ad51566/coverage-7.15.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4d608dc36a364dce33acbf4fc3a50f9d2054c945f233bb0a2cdb4b90bfa17646", size = 253852, upload-time = "2026-08-02T18:48:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6d/839f4045124cd3518ecf2c58967e58a911202834e7c5a03cfdf2ab0b29f6/coverage-7.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2395869280554a1941da904423c12660c39f721315e1c02d076a7fe0971382f0", size = 255725, upload-time = "2026-08-02T18:48:34.848Z" }, - { url = "https://files.pythonhosted.org/packages/75/21/d25e3e2a9e327798078c877f469dfb6def860bf6e25036529046227d3e15/coverage-7.15.3-cp312-cp312-win32.whl", hash = "sha256:24f3b21840c3eb76cef3cc70b2bf6649010c64471a84a446538a39306e1ba04d", size = 224566, upload-time = "2026-08-02T18:48:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0f/df90cc1e8d095ce263968a93e04829821b2afb31ac2752c06a2e0a8e3c13/coverage-7.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:fa7b17902c3c1dd8a7adb52679b7f6340bba08443d710c8838e04db8cf62be2a", size = 225098, upload-time = "2026-08-02T18:48:38.941Z" }, - { url = "https://files.pythonhosted.org/packages/65/c7/ec49e43c58967a07163e2d1c6bbd58112b825b2772ab66784afd6a5400ba/coverage-7.15.3-cp312-cp312-win_arm64.whl", hash = "sha256:fcbe83fb7258eacd293bf5322d88807acb35ed12a5cfa99dd8215c083e3b0235", size = 224485, upload-time = "2026-08-02T18:48:40.682Z" }, - { url = "https://files.pythonhosted.org/packages/68/6e/62ae61e1fc434956bec38ed1d5b1c494f58cf579dbd998e77abffe7b3e6b/coverage-7.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1182eed05674c63d40951fae27c43e822749f04d25f75df64c2e4fa3168678de", size = 222522, upload-time = "2026-08-02T18:48:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/13/ff/c74c673d81e0e77b6608c3d21331e3db42e30daeb3c8a0a8860d4c9e2e14/coverage-7.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c0c4b0d7c4cd56e470d0c9d8441f42e8a96cdfd95050fec027f1d4dd9f11006c", size = 222894, upload-time = "2026-08-02T18:48:44.274Z" }, - { url = "https://files.pythonhosted.org/packages/a1/91/ccb30f5ffafd7d69d0b18e5162f9b711a5654e807b7b0c13497f0826b33f/coverage-7.15.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5c9fce9f4998b0d50a753da765b9215a14decc7863822c89d72da7a89ca625b3", size = 253890, upload-time = "2026-08-02T18:48:46.097Z" }, - { url = "https://files.pythonhosted.org/packages/29/c6/e92a66cda49a2751b09826d51258f199b92aa0cb005bc5f34e9729a52a9c/coverage-7.15.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a47e2a0a0ace9241e70ee00e44520f88b843094603dd54303f1bafecd929c30", size = 256484, upload-time = "2026-08-02T18:48:47.846Z" }, - { url = "https://files.pythonhosted.org/packages/96/7a/730929164b457cf25cf76c23898b90f9039a104a647890801b6586797b14/coverage-7.15.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95bad94f83807ae60ed76f3ac012f69b2605ac9ea81bee959a5a483f7fa09c10", size = 257723, upload-time = "2026-08-02T18:48:49.664Z" }, - { url = "https://files.pythonhosted.org/packages/9e/be/04cb5672cb19f5c389eda81ba22d89807699a949653d3625b0e0fda169da/coverage-7.15.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:228e172a76c428bb17d1ab78a2ff188990b0597e5dbd291f52a4edf7412de049", size = 259854, upload-time = "2026-08-02T18:48:51.413Z" }, - { url = "https://files.pythonhosted.org/packages/96/25/5e7fd6af39f6507071455944b8906dd1fe5b7b6bffb6a163ceb20afa0d13/coverage-7.15.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cea9fb33887c99349996266f1fd60abe5af3577a90633392001d27ef46b4b66e", size = 254085, upload-time = "2026-08-02T18:48:53.158Z" }, - { url = "https://files.pythonhosted.org/packages/23/c8/55e58a853f1e61163a6e755897bd14a059d78411e86560f39d9951c019b5/coverage-7.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81760de3155d7f52c21860c4046628dc6bed182f72e3c028e2b4fd46f65aa040", size = 255850, upload-time = "2026-08-02T18:48:55.031Z" }, - { url = "https://files.pythonhosted.org/packages/be/74/8bcec66dbcf3d22bea2a0b2b77ee2fa6f766a647d0023d4eabbc4f2b2756/coverage-7.15.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b47ea0a1d3a3d089826c6cbfad8429d7d8872e28e86baa95ddef330f6875da21", size = 253818, upload-time = "2026-08-02T18:48:57.163Z" }, - { url = "https://files.pythonhosted.org/packages/ce/06/450b673fdfece0997b4e16a31d6bde6b18889c578f1013ddd34c962ac6f9/coverage-7.15.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5459ba486b2a5d58a6c05254779ecdf525e7f20174d0210ceda75ba40fdb8f2c", size = 257973, upload-time = "2026-08-02T18:48:59.098Z" }, - { url = "https://files.pythonhosted.org/packages/56/fd/3ec7409aec0ddc943132452b65672f065f043b844f1830e1fe173c98b3ab/coverage-7.15.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c59209f80a08dbfcdd5109a80dc623cd3b9d22895c85757d34f57a6e6e95570f", size = 253638, upload-time = "2026-08-02T18:49:01.199Z" }, - { url = "https://files.pythonhosted.org/packages/75/20/30a8dabb194123631c93f860fdd86401ad405d56cfb1841873afbfe4e92b/coverage-7.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f863856c1779d4a5bb6a94698a2f9073e09c6706501f76f3e7780e72df97d21c", size = 255407, upload-time = "2026-08-02T18:49:03.143Z" }, - { url = "https://files.pythonhosted.org/packages/13/4d/e14365b1953b43653341412f9088b0d752614c626a73a705ff9af400f3a3/coverage-7.15.3-cp313-cp313-win32.whl", hash = "sha256:00cbdc5e322927dc30c5e42b863819b1bb867cc66f26ab5372c585850876ab93", size = 224575, upload-time = "2026-08-02T18:49:05.011Z" }, - { url = "https://files.pythonhosted.org/packages/1c/64/88f762ea80de2070207246faef514513be874486b2773528f2cc2b4b515c/coverage-7.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:835528518a1d823cf336740324b2f335f7c01e609e74abcb5d5163b3e66661e3", size = 225116, upload-time = "2026-08-02T18:49:06.894Z" }, - { url = "https://files.pythonhosted.org/packages/ab/66/03c34c53a319f522554cd29d4f2e16c5eab61aa4cdcf55753129fd7d926c/coverage-7.15.3-cp313-cp313-win_arm64.whl", hash = "sha256:0d2e1f2cbbf36b842f3e2aff8d118c60d677adb498bc6c7fa9c6838738f82767", size = 224509, upload-time = "2026-08-02T18:49:09.129Z" }, - { url = "https://files.pythonhosted.org/packages/35/6f/8c2dc014357618b3226c90f731b8282766c3685786f422558991dc49fbf2/coverage-7.15.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1e3bb08ad574bd9fb6a991f645728f70d333c1c1958dd5fcde65e24cb862813d", size = 222571, upload-time = "2026-08-02T18:49:11.242Z" }, - { url = "https://files.pythonhosted.org/packages/07/50/d867c7ceae9d56b7e74ee61ea834f1aa4f9a1e1c7f0ce39393ba573b1c12/coverage-7.15.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e5860eaff02a0b7f1b73304bdf846596ee62ab3a78d25c68044ebf684cb1fef", size = 222902, upload-time = "2026-08-02T18:49:13.448Z" }, - { url = "https://files.pythonhosted.org/packages/62/77/4f6dfc490c5f2bcacb2d296d9aa4d1e128c43b48e94ad313fec7f49f09ad/coverage-7.15.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:60874e5bd67f0b1bdbe42ab42c7bafa66a6fb8de88721af6df3f7a02713960cd", size = 253947, upload-time = "2026-08-02T18:49:15.304Z" }, - { url = "https://files.pythonhosted.org/packages/16/8a/6777f192af264165103e2a3d3768dbadb9894a0a2359a16877141d9ae8f5/coverage-7.15.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9147be876e9d83765e0b82176674dc248a6b9283e25e01e7462611b97e9b731", size = 256452, upload-time = "2026-08-02T18:49:17.801Z" }, - { url = "https://files.pythonhosted.org/packages/7d/7b/3d7ac46a0234bc684f41ee42be95e29b2b6525695adb04083609d5ac2149/coverage-7.15.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61a01f8c3804760fcc5a3d31c4f3cab792d660d44e17bf7adeaf0ea51e07821e", size = 257798, upload-time = "2026-08-02T18:49:19.878Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1e/c6ee59c29afcb5fdb35f936381340d1a06429a07c48f20e809646647acbe/coverage-7.15.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95bf3e7f26f792e25eb185f85a5a659d48479265176dcfe22b6f334fd0081b5c", size = 260112, upload-time = "2026-08-02T18:49:21.858Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e1/e8ea39a46e89e3a143312ee5f80336e992e3ae8fe44bf9c76b83fefeed42/coverage-7.15.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44c41eff9e413fed8740eca75d5438ebeb9d3e45e7cd37c67329213e7a72c764", size = 253944, upload-time = "2026-08-02T18:49:23.926Z" }, - { url = "https://files.pythonhosted.org/packages/95/67/31ab5f6a37fd887d1386f81f0da9306851ad2264e9baaa9c7f606e0b3e17/coverage-7.15.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54146bafb61f3ba9895b43af0dd17eba01561d586d44ce84ea221b0cbbee5a9e", size = 255805, upload-time = "2026-08-02T18:49:25.973Z" }, - { url = "https://files.pythonhosted.org/packages/fb/6a/ee505a80c8fd89620fb337c0596daecff87f33171fbb4ee3015fc3d7331f/coverage-7.15.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:af000dd1bb859ff8066fda4c79512ff938c798116540307226b373099c7b151f", size = 253769, upload-time = "2026-08-02T18:49:27.883Z" }, - { url = "https://files.pythonhosted.org/packages/b0/41/6ab0f81c9e89660230d8f3f581d4732e5ddb75a885b0a5dfc73d315dc94f/coverage-7.15.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a1b82490577f3889950b5a04f18712aef0207243e0749d60fe28c3c73ebfd5fd", size = 258045, upload-time = "2026-08-02T18:49:30.201Z" }, - { url = "https://files.pythonhosted.org/packages/bc/62/c995e91cae28cf31d6defab3bfb553dda5ac83ac7381b0f2b121264c307a/coverage-7.15.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c4fc90a60154c3e4b8a2dc206d6dbe852f1c235c249e0dc0cef909d032c9591a", size = 253587, upload-time = "2026-08-02T18:49:32.349Z" }, - { url = "https://files.pythonhosted.org/packages/84/df/f2049980f82d6890321f2065f9e66216eabbf4b2001815db958bc543f40a/coverage-7.15.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f25bb884814a892948b4c20394db3f2364dd452d9492736479e7a493e63b0eb6", size = 255243, upload-time = "2026-08-02T18:49:34.324Z" }, - { url = "https://files.pythonhosted.org/packages/1d/82/2c841b67a978c0eb9c3707630b68f93f9e7585d78bb906bc8823ec6b07a5/coverage-7.15.3-cp314-cp314-win32.whl", hash = "sha256:722dbf8e7828fbcfe0dc8586167dc0a5ce85ad6ea171dbb21ed3f8d6581d3cb8", size = 224759, upload-time = "2026-08-02T18:49:36.326Z" }, - { url = "https://files.pythonhosted.org/packages/b3/78/5c93ec43784fd3e404ca23cd0584ae24bc1732de4a3fc194b68c3be88db0/coverage-7.15.3-cp314-cp314-win_amd64.whl", hash = "sha256:64d0845f9c3ed47302bed265c15ab4dbb64aa4ec1490839b8e328f4e7fa914d2", size = 225246, upload-time = "2026-08-02T18:49:38.366Z" }, - { url = "https://files.pythonhosted.org/packages/9d/77/813a054371f3b018cc63c6bdb46a3c35d5e95d4e3ed4f1449d4196106db5/coverage-7.15.3-cp314-cp314-win_arm64.whl", hash = "sha256:69bc14684f8fbbee9f9dbaa4fe79719b0da9725fc37956785c06ec365acf6926", size = 224673, upload-time = "2026-08-02T18:49:40.552Z" }, - { url = "https://files.pythonhosted.org/packages/8f/63/8c9f36cc71178d26db930baa03a4494abcc516d8d41bf820d0d85ef1d80b/coverage-7.15.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f92df943c24b96cb215ca26b4f6a2283e63c5db80f1635aceea7fff11311917b", size = 223298, upload-time = "2026-08-02T18:49:42.634Z" }, - { url = "https://files.pythonhosted.org/packages/54/66/211f24d058ce9f56ebf1420d55b7574fdae924f6da3836f83c8bd4793e38/coverage-7.15.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:66591c46bdd2971d3ae2bc503a5f0459c2edcaf6b7e045b292000cc95bc6cb95", size = 223568, upload-time = "2026-08-02T18:49:44.706Z" }, - { url = "https://files.pythonhosted.org/packages/dd/bb/9c2ad5574a0d6420a96c6cade4f8a683931b9e79fe609f8924d7b6964616/coverage-7.15.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa64458b81b18bfc67cdf1f6dc02b23e3edc672f2f8e11771fad75865415a43", size = 264932, upload-time = "2026-08-02T18:49:47.153Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/938c39e77bdd5a0a440412f975609ce3702dabbda6ac715719d93ca45a7b/coverage-7.15.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:447f5421ccf5475956cf516d4ca1d575f487947b6f4e11f9d80c6aefe24b3dc8", size = 267052, upload-time = "2026-08-02T18:49:49.324Z" }, - { url = "https://files.pythonhosted.org/packages/b0/a3/7b431a98af35d9cc6394e54cde9435b33b8591672fbece6a4931267d7a8e/coverage-7.15.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a0c77ef8cd483a4987a5d12d1d9d5f7ee598dfdc6c0844417d847e5768dc779", size = 269473, upload-time = "2026-08-02T18:49:51.599Z" }, - { url = "https://files.pythonhosted.org/packages/32/58/dbc9951dce46be47a732823a1c571f62bcabdd54a68d8c281489a1a55cfb/coverage-7.15.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0b273f4ff657446a06c2d85bf80e134fa869a92852ba5f87854a70e1fb44da77", size = 270591, upload-time = "2026-08-02T18:49:53.865Z" }, - { url = "https://files.pythonhosted.org/packages/71/bd/1d610772c7c0889bfe477a59c46ee66ea53e271f3f06951e9d55b317f7c6/coverage-7.15.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daea8c4fafa22488600405be2c2be525a9406fba3fc0a83acc726db3e14e2005", size = 264007, upload-time = "2026-08-02T18:49:55.875Z" }, - { url = "https://files.pythonhosted.org/packages/69/97/852eb3dcdba156b1a9078503f098499916bf889f964b61ad4a08223ac169/coverage-7.15.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93ff57c530f3fa7aa69f92fb9b8892b8aa82712aa970842f4abf28657f42fb57", size = 266926, upload-time = "2026-08-02T18:49:57.944Z" }, - { url = "https://files.pythonhosted.org/packages/52/f8/b72cd238757fba2b587fc7dee047efe6e10b0c18343509faaaf502dd4680/coverage-7.15.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4df21bef8b800eebda9018f53d49c9ace3aeb0090c850139b27923aafcb83e91", size = 264529, upload-time = "2026-08-02T18:50:00.035Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0a/6c52ec4b7fb007cb6433d1fcfda4080cb15d75ad37ef9c31025f3427293e/coverage-7.15.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:db567b02685f26034adcbd85055f80d12cdf02111b8ed00886093d98b2874ce2", size = 268263, upload-time = "2026-08-02T18:50:02.161Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4e/f1f9aa3efd109a04353563a43fb5155340c1fdcdeaa6296ebed3b6f510ea/coverage-7.15.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5318dd51b8600b947e058cf5a4fe54d183d9d13c49b97b64ca7be05a34df9bef", size = 263377, upload-time = "2026-08-02T18:50:04.243Z" }, - { url = "https://files.pythonhosted.org/packages/dd/fb/6b268a0b2728ef1c379ad656b899274477a5f6bed1bf6765b4b387fb0601/coverage-7.15.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c995bfa383c54704839b6c4c2627a1c00895597ada0e5e8190c81d8bd620555c", size = 265688, upload-time = "2026-08-02T18:50:06.428Z" }, - { url = "https://files.pythonhosted.org/packages/29/54/1a3ea96e5d5e7cd41dc432597bfc60692910e635d05e1cc25a8ccc243581/coverage-7.15.3-cp314-cp314t-win32.whl", hash = "sha256:6433fafb8da0e1d02eb53411e0ecdadb6b88f0224fdc23317e703c0e88937d42", size = 225066, upload-time = "2026-08-02T18:50:08.533Z" }, - { url = "https://files.pythonhosted.org/packages/31/9d/a7b0d9afd18ed5274dd00651a78e7810a931c70d94b79996f150bec1a30f/coverage-7.15.3-cp314-cp314t-win_amd64.whl", hash = "sha256:fe578952b1b29fe8c777f43f241d49efac4b56724a3434f5d22ebe3c208df429", size = 225897, upload-time = "2026-08-02T18:50:10.572Z" }, - { url = "https://files.pythonhosted.org/packages/ca/11/34c5ae40b945e69aa72b87dc268135b7049905f3824af573b7073acbb946/coverage-7.15.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d2e1acb7aee29dfa8f3e48c23f36670898baca1209d9bdd3985a50c7f982165e", size = 225212, upload-time = "2026-08-02T18:50:12.63Z" }, - { url = "https://files.pythonhosted.org/packages/37/e7/7069b3d6c018917f49ba2e1c5fb910e498c7fefa3a1b78cb1b79e61ff45d/coverage-7.15.3-py3-none-any.whl", hash = "sha256:da78fa6fc7dafe4212839173133ee85afcf42c5cd5f3e47fa7c1c210453b445e", size = 214297, upload-time = "2026-08-02T18:50:14.709Z" }, +version = "7.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3f/f0642a372f494bd0d7dad3b497083b910194a5f1c88be2c94fef707c3b59/coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2", size = 257145, upload-time = "2026-08-06T13:47:41.931Z" }, + { url = "https://files.pythonhosted.org/packages/71/17/8b46d0ed68251016002ec972c8fc0119961a765d0984cafb8bf317c43758/coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931", size = 258257, upload-time = "2026-08-06T13:47:43.527Z" }, + { url = "https://files.pythonhosted.org/packages/30/b8/8498a0e72d0adbe15477dd07463d2b3bb2c9f6a4815e8589e50939e2c3ae/coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8", size = 260517, upload-time = "2026-08-06T13:47:45.121Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/7dce19c3bdb1e3dd63e769508216500edad81bd5f69a26d724e32aceaf78/coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9", size = 254785, upload-time = "2026-08-06T13:47:46.541Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b1/e1494703c675a2561723cd9b89f45c9168782c31280c611b1f767851e57c/coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839", size = 256176, upload-time = "2026-08-06T13:47:48.155Z" }, + { url = "https://files.pythonhosted.org/packages/73/76/a5629d270fb638a43a4b10466f51e2f49d532c1aa4da2913cbbb150bbe0a/coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72", size = 254321, upload-time = "2026-08-06T13:47:49.757Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/9c44447218435d5766b911534f9d798144a5560f85e9a54ebe5f3f5d19f9/coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52", size = 258390, upload-time = "2026-08-06T13:47:51.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/36/c1e127616fb3fa18a9ff71e76c417f2fd7424332a4870015ac224ef4c039/coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c", size = 253894, upload-time = "2026-08-06T13:47:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b9/fdb92c8ae7a8bb9b850cc253b7b3b9c8526f68130002048b5671cd510d09/coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4", size = 255763, upload-time = "2026-08-06T13:47:54.296Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/a7d51b2587c7bdb76e71b0896d2565bf7d60436b5122fc83e511adb1f7cd/coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b", size = 224597, upload-time = "2026-08-06T13:47:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/49/b9/5c5f80cc55f5acaaca6dee677626bfcec8c87204a7809b438b08e84f4571/coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd", size = 225135, upload-time = "2026-08-06T13:47:57.52Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/2a4561f89ff6bf7c925c287d0f2cce8bdf139c3a33735c87e3203401cf94/coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f", size = 224515, upload-time = "2026-08-06T13:47:58.977Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/651a9310859673aaa3b3203f1aa1641ca60fcf2494683e1c9474c7172780/coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921", size = 222565, upload-time = "2026-08-06T13:48:00.796Z" }, + { url = "https://files.pythonhosted.org/packages/82/f9/4dcf700137e8af550670f4d74d1b63828ce93e1e2b05e5f10710eb2ea987/coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e", size = 222936, upload-time = "2026-08-06T13:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/07/4a/612ff1e780b3fbfd637486f542f84adc5503873d8b5d279dec1ffeef9414/coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36", size = 253926, upload-time = "2026-08-06T13:48:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/b0/04/d1cff1c2ead4708a6a79c01d3736b6a25bd38a36678398f72a8dd33dfad9/coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4", size = 256523, upload-time = "2026-08-06T13:48:05.996Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/d34e13fb4b293cbdb9665838cf5522077b8ad14ef947550631a4bced36a5/coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c", size = 257759, upload-time = "2026-08-06T13:48:08.036Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e7/2c5fe7636fdb0732fe0f09f308a5b066864078b7fc61f6678e8478554f2e/coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7", size = 259890, upload-time = "2026-08-06T13:48:09.834Z" }, + { url = "https://files.pythonhosted.org/packages/92/28/9689f0858dfff59c2ea688938ab9fa2925631235df67126a42b6c5c70ae1/coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25", size = 254121, upload-time = "2026-08-06T13:48:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/785077c230c157243eb5aa9a26c3be260ecd02001bead54a3cada3df8e03/coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b", size = 255891, upload-time = "2026-08-06T13:48:13.209Z" }, + { url = "https://files.pythonhosted.org/packages/d4/90/e20371b17b40f912f21305c2db2f30efa3de306f7320fc916804872c85a4/coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78", size = 253859, upload-time = "2026-08-06T13:48:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/05/49/25371987ee459a5f67c0427fb75c74f9358e65f2c71fe75bf41c1b6c5fcb/coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f", size = 258011, upload-time = "2026-08-06T13:48:16.464Z" }, + { url = "https://files.pythonhosted.org/packages/30/6e/32e67467f6154bf4f1c4f63b05acc5097cba4237d45bbeeea446b52e8ac1/coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d", size = 253676, upload-time = "2026-08-06T13:48:18.493Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/8b24192e89286399765155251f99ee9f070a9d637109018ac23d99b99f6f/coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff", size = 255453, upload-time = "2026-08-06T13:48:20.057Z" }, + { url = "https://files.pythonhosted.org/packages/16/6f/8b41ebdf67c87854e17c035336a90f1cfbad0c14c2a584301be6ff148718/coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c", size = 224605, upload-time = "2026-08-06T13:48:21.655Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e2/2946c7f0b42b152ecb21ff1bdad72e3d301e790c0c487e4a86e8c9f69347/coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4", size = 225148, upload-time = "2026-08-06T13:48:23.376Z" }, + { url = "https://files.pythonhosted.org/packages/9e/83/3f4a69957f48ae7a0aba76c34743f88963d607b19e03f3f8e66f91cae0f9/coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf", size = 224536, upload-time = "2026-08-06T13:48:25.117Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ac/748cf29eeb2d6be34a3176ce26a4f49e38085ee08e8935f05f6f26ed7e0f/coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f", size = 222608, upload-time = "2026-08-06T13:48:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/0b/02/1abbf5c984677b0aa439cdacaccbf38d248939d8ef8fe1cc7a50d73edb77/coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c", size = 222940, upload-time = "2026-08-06T13:48:28.432Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e1/ff8f9f53d9fcf586125b55d0b1f04ec1c14955fee41e83d5814bee141bb5/coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082", size = 253985, upload-time = "2026-08-06T13:48:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/a1/26/595759762e514e81be1d7d01ed03444303bcd152226a6529998d253f9201/coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac", size = 256492, upload-time = "2026-08-06T13:48:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/b79aabac54d482be23b5fcdd4f4662bff24a78edc4ee29201726929936d5/coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734", size = 257837, upload-time = "2026-08-06T13:48:33.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/0f/bf7f297885a5bf6fd71e5782404e0ff059ca09e8711ceb3a08544abde45a/coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d", size = 260152, upload-time = "2026-08-06T13:48:34.75Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f1/296744e854ff8368542343457414380465e9ceefb9192342feb9d3bc461d/coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf", size = 253978, upload-time = "2026-08-06T13:48:36.434Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/bbdb2e9057493e66220a2e149ca2d301ba0e3a58a83bd6b90de9826d16f3/coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b", size = 255846, upload-time = "2026-08-06T13:48:38.317Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/38015b2b6d21258713bd17e76b59d033b191efb5703589cffd037dfbca20/coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25", size = 253808, upload-time = "2026-08-06T13:48:39.993Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/0d515c1e60ee6fbfd1a0e79c07cd87d388a233b7adc37758735677203808/coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303", size = 258081, upload-time = "2026-08-06T13:48:41.971Z" }, + { url = "https://files.pythonhosted.org/packages/91/71/04d9e7a3642146c6351338aef4ef85ab11dbbb54744c13245caba1aad1c0/coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f", size = 253624, upload-time = "2026-08-06T13:48:43.731Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a7/6c28b74c81ebff66987b0e2522ba5cffa3e90b0c33cb6a2eb264d4ee8cf1/coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5", size = 255280, upload-time = "2026-08-06T13:48:45.58Z" }, + { url = "https://files.pythonhosted.org/packages/52/af/bc19996a7014b98d7bbb0f0939453c67074af65784a3aa16a789a07381fa/coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7", size = 224768, upload-time = "2026-08-06T13:48:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/219484e476d6e101ba0a444852579e05f5b75c37c611a42ed1190f73ef62/coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425", size = 225259, upload-time = "2026-08-06T13:48:49.513Z" }, + { url = "https://files.pythonhosted.org/packages/b7/66/fa77daf4e383e5f776dac62c2409b6af81910ae6fe326bd5170dba74cc63/coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8", size = 224684, upload-time = "2026-08-06T13:48:51.235Z" }, + { url = "https://files.pythonhosted.org/packages/58/5b/f03bf0ce362bbf3f785fa5219620d00778d4ac6fc9e407734828e9c672f6/coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8", size = 223338, upload-time = "2026-08-06T13:48:52.896Z" }, + { url = "https://files.pythonhosted.org/packages/0f/76/e77d0ae22501831cc9f92193e8a957a5caa1dd177f90a6d1d9b106242d92/coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a", size = 223609, upload-time = "2026-08-06T13:48:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/82/1a/b1f089da8d38ac612fa2dd6dc7f4a1a7657d12f3e261d2996edd3a838d0b/coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b", size = 264970, upload-time = "2026-08-06T13:48:56.403Z" }, + { url = "https://files.pythonhosted.org/packages/bf/31/e66d98d6e9c7fcc88470f1e234eaf6b1950dc0dfbf797f7282c1c861da24/coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5", size = 267088, upload-time = "2026-08-06T13:48:58.41Z" }, + { url = "https://files.pythonhosted.org/packages/59/a1/ae94eb2c541add426378408379f233591e069040b1e2cdb33df9498a0682/coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba", size = 269508, upload-time = "2026-08-06T13:49:00.42Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c7/88a10694a1c6a213569766aba9f25847b28155d4ac731b13226db216356d/coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982", size = 270629, upload-time = "2026-08-06T13:49:02.234Z" }, + { url = "https://files.pythonhosted.org/packages/b3/34/d8b8232e5e55169933b59aabcef2fedfa4b9d8897361bb80fcbda146505f/coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c", size = 264043, upload-time = "2026-08-06T13:49:04.102Z" }, + { url = "https://files.pythonhosted.org/packages/7e/35/58b009dbf8c471c7224716478b9fed4a7e1af15320e1ed41660978504663/coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57", size = 266963, upload-time = "2026-08-06T13:49:05.821Z" }, + { url = "https://files.pythonhosted.org/packages/62/aa/57fbda1b42c892968273c56b6ee9dc0f1310850859230a507bc7873b1f65/coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26", size = 264569, upload-time = "2026-08-06T13:49:07.706Z" }, + { url = "https://files.pythonhosted.org/packages/98/8a/360e6e7f24d477b7e889703af0afa878d15b6d4d8d2a822b2835c169a879/coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429", size = 268299, upload-time = "2026-08-06T13:49:09.587Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/6f701261aee21b6b5fa8f7872229406dc917e125069448292223bf213606/coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017", size = 263413, upload-time = "2026-08-06T13:49:11.604Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0f/6f04036edc260ed425af83e834f627fad48941ce97b50bfe6edd8b6fa623/coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839", size = 265725, upload-time = "2026-08-06T13:49:13.38Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ce/d19b5d4d5c49a7bfb925fd74310fee7d28bc99520ac3367ccbc54e662518/coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85", size = 225079, upload-time = "2026-08-06T13:49:15.265Z" }, + { url = "https://files.pythonhosted.org/packages/26/bb/7aa1b3b173faee0679037ca950bbbe1247273656697994d8d13f80f8d4b4/coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e", size = 225911, upload-time = "2026-08-06T13:49:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/4ea9e47426d80038d9222db3c4534cb6021a74b237d3ff97ffd33b6600dd/coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e", size = 225219, upload-time = "2026-08-06T13:49:19.293Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/dc5d2ac8f9142e7ec7de66e7bf0591db29d78955a040bd915870d9c0e657/coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9", size = 222604, upload-time = "2026-08-06T13:49:21.279Z" }, + { url = "https://files.pythonhosted.org/packages/70/39/33e63df81fe2ee100897451841c821467635923e58e37c6bd4b46dd8106c/coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a", size = 222944, upload-time = "2026-08-06T13:49:23.187Z" }, + { url = "https://files.pythonhosted.org/packages/99/1f/ef3ffb5557febc75a0d97aa459d0266d7d741110265121cc6d8539343d44/coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54", size = 254050, upload-time = "2026-08-06T13:49:25.008Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f5/1f0f6f77698c3601ca0ae7431e34b24c62ca2f06fecb23b73ed1f651d2be/coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb", size = 256967, upload-time = "2026-08-06T13:49:26.896Z" }, + { url = "https://files.pythonhosted.org/packages/03/7a/2ed9bed79925f4367c83c77f66a89e5ca7229c288d2d19ad5f36d1ca0070/coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed", size = 258587, upload-time = "2026-08-06T13:49:28.692Z" }, + { url = "https://files.pythonhosted.org/packages/45/8c/fa34044f71b7cc4ecb6da9c2408770959b0591fa9b5fb6fb6bca38f94298/coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce", size = 260785, upload-time = "2026-08-06T13:49:30.472Z" }, + { url = "https://files.pythonhosted.org/packages/4f/54/d5727ce36b4524a7394ab9f5f1df378e1f23affcdab01037dc8655185cc7/coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c", size = 254545, upload-time = "2026-08-06T13:49:32.271Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e6/6e3783e576719590194bdffb6dd6d85490801785b7c331e35a245d8cb8b5/coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced", size = 256682, upload-time = "2026-08-06T13:49:34.089Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/bacdbde18b69ed2de424fcf64d9fb0a4913753d4f0eca8bae9daad69f4bd/coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800", size = 254560, upload-time = "2026-08-06T13:49:36.052Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a3/1fb927196e3477c1b48831169ab58ba08f451ba87ae311ff1de68b26a616/coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3", size = 258792, upload-time = "2026-08-06T13:49:38.01Z" }, + { url = "https://files.pythonhosted.org/packages/41/58/30d4c149c69053de0edfe325614c1d28d508f62b1783e0e4a234d2e49136/coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768", size = 253968, upload-time = "2026-08-06T13:49:39.934Z" }, + { url = "https://files.pythonhosted.org/packages/89/e4/77f639371b918aad30dda4051f95404b43578f7f2e2f87ba73e02ed1ff37/coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753", size = 255893, upload-time = "2026-08-06T13:49:41.825Z" }, + { url = "https://files.pythonhosted.org/packages/5c/62/13be29b3ddab35f14c87967a4820a05106d2a3eccb4fa4ff550bf30b75e0/coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2", size = 224768, upload-time = "2026-08-06T13:49:44.08Z" }, + { url = "https://files.pythonhosted.org/packages/a1/70/af0c6be0f964af6954f6b74bc109b0dbca02824696d2520fb17fe1ab06e3/coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809", size = 225242, upload-time = "2026-08-06T13:49:45.899Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2d/f3bd3aab899fc9efc18b53133ee68f5f98574ef480649b23e12962226387/coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d", size = 224674, upload-time = "2026-08-06T13:49:48.322Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ca/f69251cd63eabc6438321aea22148754cce758a26bde07dd490e3fe7cfc5/coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d", size = 223333, upload-time = "2026-08-06T13:49:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a7/037b53b2885b0d8447064432491a4d5a1014cd9f97a594d53acd0c04541a/coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26", size = 223630, upload-time = "2026-08-06T13:49:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/80/4f/152b8a4779ae90da11bb24f7467df8a59f0be48a5c52acb856325ca48289/coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645", size = 264489, upload-time = "2026-08-06T13:49:54.52Z" }, + { url = "https://files.pythonhosted.org/packages/10/2d/84b4b9e0e1dd6528a51920ff7031f35b789382e467a28ec6a5a578cb8812/coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a", size = 267567, upload-time = "2026-08-06T13:49:56.721Z" }, + { url = "https://files.pythonhosted.org/packages/53/fc/ba01cc25299f9f8a2c8b02d3b28c53f3543d9fbfbe4e74fa2760b48f163e/coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624", size = 270123, upload-time = "2026-08-06T13:49:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d0/db2647cbf40b14f8c308f94ff7bf89c06d564e59f396906edf50086ec788/coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa", size = 271107, upload-time = "2026-08-06T13:50:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4d2d17924552c458bb4f77dd631f0e3bc92fbbdf2d2d916cd4b33bbfd5b1/coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f", size = 264955, upload-time = "2026-08-06T13:50:03.023Z" }, + { url = "https://files.pythonhosted.org/packages/ee/de/dc010c7a3691f396d93bbc26bfcafa1c2a3a351cd520470f15faf5795bd5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f", size = 267949, upload-time = "2026-08-06T13:50:05.557Z" }, + { url = "https://files.pythonhosted.org/packages/78/ea/dc96a11375e83c045c2f7c61fb6918277cfe9401db7c0f7b1d111a84b2e5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67", size = 264421, upload-time = "2026-08-06T13:50:07.612Z" }, + { url = "https://files.pythonhosted.org/packages/c8/86/b77131a0f9503ce461cd577076147d7a9040f0c5dda772686f729e2cc9cb/coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc", size = 269121, upload-time = "2026-08-06T13:50:09.58Z" }, + { url = "https://files.pythonhosted.org/packages/24/24/944bc35007862955e7ebf05754e645419dcf5d7526c52735cfa2715e8ebf/coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88", size = 264565, upload-time = "2026-08-06T13:50:11.722Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cc/a3bb9f93e7e740659163e2ea584f8196ddcd2c456a5dbe15f6c50105fec1/coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc", size = 266522, upload-time = "2026-08-06T13:50:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/dd/e0e40f3560d878d888c580698ff5ad1179f5e1c3ac949684ef66b41a3817/coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a", size = 225068, upload-time = "2026-08-06T13:50:15.825Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/37732ea80eebc30e976e4cdab15c190bc42d96959a42e38ddf6f8c60468f/coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b", size = 225895, upload-time = "2026-08-06T13:50:17.928Z" }, + { url = "https://files.pythonhosted.org/packages/c6/08/1e00f7923eaaba45fb3d51dd794125fc766304b1df264f3a9c6557bfb30e/coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278", size = 225213, upload-time = "2026-08-06T13:50:19.981Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, ] [[package]] @@ -1063,29 +1093,29 @@ wheels = [ [[package]] name = "django" -version = "6.0.8" +version = "6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/28/5fee292e588dbdb782582436d1eb62ac8809ec55b98ef27f5b9676a00e9a/django-6.0.8.tar.gz", hash = "sha256:cb0bd962d27fc866f3c514b20aae6a7df56ec80b488f9899da46d675cd051526", size = 10924565, upload-time = "2026-08-04T15:03:39.469Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/42/6cb20996733984c1f6661daeda3877990836c76c633c6c8879d39f7120eb/django-6.1.tar.gz", hash = "sha256:86a2aacd59b817e4d6ac2ebfe22356c58f66f7b24e503f71b7c2fead677ee48b", size = 11223034, upload-time = "2026-08-05T19:21:53.789Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/b4/23e74d261eebfa9c8baed5e8810f09858b9afd613d1037f42ff7aa95c87f/django-6.0.8-py3-none-any.whl", hash = "sha256:9b98b7e1902e0e575ea4f42c175fc9512784f7f2580898286aee3388b322219d", size = 8376956, upload-time = "2026-08-04T15:03:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/ce847620134cfab903e75690c498af73b46abbede2912ea89bd76d5c1e76/django-6.1-py3-none-any.whl", hash = "sha256:6c132cd980c9392b06807d4ca52d72530d631dc65a85d9dacede00a780cefbbe", size = 8417399, upload-time = "2026-08-05T19:21:47.285Z" }, ] [[package]] name = "django-allauth" -version = "65.18.0" +version = "65.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/10/2d33c5bc3c0f9d7db1276a1babcaadd147e73eb416ccb1bd9523c6ac925f/django_allauth-65.18.0.tar.gz", hash = "sha256:afb82e2c545b9a5539370ad120468faaed84715e095a7b6e76972dacdc6376fe", size = 2246988, upload-time = "2026-05-29T13:01:26.071Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/b7/6107ed71b0136a6d2a6647dbfe125dbc6c2fa764fdb19e98bda9bd52b0e6/django_allauth-65.19.0.tar.gz", hash = "sha256:9a615fd7f48f31985e1e3d886f8983e12d09e189f8d00d7a9750b3d300b7b366", size = 2283106, upload-time = "2026-08-06T14:27:15.296Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/75/5a4c2bf050e38ab16549395a7cd751ddcf3a18305bff4852cfd6c0d94998/django_allauth-65.18.0-py3-none-any.whl", hash = "sha256:e508640c83b94eaf1a9b4e9ac5332dc310dcdc83fca40d58a3f670e15bad1e84", size = 2061080, upload-time = "2026-05-29T13:01:16.532Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ec/3595bf98e7a2d161cab30c79f6bb2e395ff4f707110ef55ea0d4c04348cf/django_allauth-65.19.0-py3-none-any.whl", hash = "sha256:e37efea604d0bf40a6b16edd5b00e1560414e2c3ed07c2431b6e5bb058e2a771", size = 2073767, upload-time = "2026-08-06T14:27:06.411Z" }, ] [package.optional-dependencies] @@ -1097,14 +1127,14 @@ socialaccount = [ [[package]] name = "django-cachalot" -version = "2.9.0" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/d6/2f4033cc3443b7ce15459247488050cd0b8b56f15a03fcf2650b2483026e/django_cachalot-2.9.0.tar.gz", hash = "sha256:67d3d19a3f6deab7dc5b081b5f78ea926318f64a7b3afb06f28e61d8d31e02b3", size = 76922, upload-time = "2026-01-28T05:23:30.318Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/6b/e9df6b94965f783f660a2c469f2b1fd2ffb15fb3d6953bcec9652030c8f8/django_cachalot-2.9.1.tar.gz", hash = "sha256:7d3b12022abc811e344deac8830228e2d027ed8dcf762e1116dccde74d360512", size = 77071, upload-time = "2026-08-10T19:37:48.455Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/bf/af8ad2aa5a402f278b444ca70729fb12ee96ddb89c19c32a2d7c5189358f/django_cachalot-2.9.0-py3-none-any.whl", hash = "sha256:b80ac4930613a7849988ea772a53598d262a15eaf55e5ec8c78accae7fdd99ff", size = 57814, upload-time = "2026-01-28T05:23:28.741Z" }, + { url = "https://files.pythonhosted.org/packages/7f/75/58c631f44f41f9eab7ab5dc29cfcdebeecabb63ee9487654990e1b7ac31e/django_cachalot-2.9.1-py3-none-any.whl", hash = "sha256:cd015b9e5235b74d6c9dc8cc984165aea876be02b51ce76d534870dbdd81dab0", size = 57818, upload-time = "2026-08-10T19:37:46.482Z" }, ] [[package]] @@ -1186,14 +1216,14 @@ wheels = [ [[package]] name = "djangorestframework" -version = "3.17.2" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/35/c96055e700fdff25da3a7b7756cfd1d4dc54f38b9bc6d6c5e19e3a0fdc20/djangorestframework-3.17.2.tar.gz", hash = "sha256:89ed713b6dc83e1539f214b7d10808ae19bb8511004beba886225da6d5c9dafa", size = 906683, upload-time = "2026-08-05T07:47:22.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/bc/de04e3d4dc65e8b926700956ee70d4f084f2005603d21122d4d0683006fd/djangorestframework-3.18.0.tar.gz", hash = "sha256:2323a5111837e0b784dcb8323abc78ecc54fa2a5af7aff2677cf50cdd849477f", size = 913667, upload-time = "2026-08-07T14:53:33.571Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/46/c14108e400b208c394325eb63fbae06c81341b6447fa1a6f9da718b17fe7/djangorestframework-3.17.2-py3-none-any.whl", hash = "sha256:cb0546a7415d5b46c04e0f4fe0a54b2109f4fdd5e83ca773c8c6183a6493d042", size = 899109, upload-time = "2026-08-05T07:47:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/7461738e819b853275b35fd35712a85806c09a327e1fd5f8ff766bdd317e/djangorestframework-3.18.0-py3-none-any.whl", hash = "sha256:381fc44d3249c9565c5f723850855b734e99030eb30957a49f506d3fe11d7dcb", size = 900032, upload-time = "2026-08-07T14:53:31.881Z" }, ] [[package]] @@ -1227,7 +1257,7 @@ wheels = [ [[package]] name = "djlint" -version = "1.44.0" +version = "1.44.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1237,46 +1267,65 @@ dependencies = [ { name = "pathspec" }, { name = "pyyaml" }, { name = "regex" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/24/8589327d057b67ebef2c775f9ddd2d44f8c7599f0a335bcdb0fc4b7bf3e1/djlint-1.44.0.tar.gz", hash = "sha256:9a52a3bafeff0eb6fb9a68798c9ea25c6522b40fba5c139ffef0dbc961ce0b59", size = 81320, upload-time = "2026-08-04T23:37:23.588Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/2c/03989fccdceff7f04bc3cd768428c5a399260dcb3a8ee35bdec6ea8ce998/djlint-1.44.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36c84fd7c909874f2ad52c23741d4efa2a0f39036e92ef37e2c46eee3b04a8e3", size = 806977, upload-time = "2026-08-04T23:36:39.454Z" }, - { url = "https://files.pythonhosted.org/packages/4a/de/ff8620da5b2e26d1918c47b645a3b6f8da88bb8081796ef2e38c60fb7fb6/djlint-1.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3e72c3d900b5bd5efd4eb6301ebb97ba271fef2e33872dfe0171a67d1dc5ae6e", size = 756029, upload-time = "2026-08-04T23:36:40.43Z" }, - { url = "https://files.pythonhosted.org/packages/d1/74/c941cc4165baead55994b864c725949c651babed108e4c060e6ae118c7a8/djlint-1.44.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95e5b031c925504d6c8bfe989d80fb9e686cedbc174be32e4b0e91a741766df0", size = 796458, upload-time = "2026-08-04T23:36:41.841Z" }, - { url = "https://files.pythonhosted.org/packages/07/d2/1ef8810563ab3e74546e05571b0ae6d2794bc6af13153fadef2283a2c0e0/djlint-1.44.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f26549bea48f31e2b40fc429ee868a53883854fbbdc3046570de758271a9d3a", size = 835735, upload-time = "2026-08-04T23:36:42.913Z" }, - { url = "https://files.pythonhosted.org/packages/df/4e/aeeb9b5c22455294c84246dcdfafbd9f8224cb398a0f3624d74769ae82d6/djlint-1.44.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eab32aac420c370f1d980b6053dd35195cb0919b52d293a90215cedf922605c9", size = 805751, upload-time = "2026-08-04T23:36:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/7c/db/7ce8a783146a8378150e876b7990cbeea0bf7cb6b58826688d6cdfd583ee/djlint-1.44.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb43cb30c5fef2eeadd0f75a65b2db30c0c47ba735791e208cd98ea4472602db", size = 850548, upload-time = "2026-08-04T23:36:45.334Z" }, - { url = "https://files.pythonhosted.org/packages/80/54/850353f3c7501e0a49f1eb55ae38f1c8bfa271ca92e38c873b9b6d0d1a5d/djlint-1.44.0-cp312-cp312-win32.whl", hash = "sha256:d5f17cbd6b1f9f0ff1560f2ce0beec59b84fce5ea13068919ec0a1ce560231b7", size = 611159, upload-time = "2026-08-04T23:36:46.552Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e0/13ddf7e392eb21e68ca6adfc2596fb5e91ab7d2f96027fb6c0845f80be4b/djlint-1.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba7ae53f53f8005367ac45bef90428d926f897b59e715da3a45ab6604acc53de", size = 690568, upload-time = "2026-08-04T23:36:47.595Z" }, - { url = "https://files.pythonhosted.org/packages/71/f8/cad12adf1baf03aa8030c427acef55bc7026fdfc1da7c9d09928457051d3/djlint-1.44.0-cp312-cp312-win_arm64.whl", hash = "sha256:9a10c631af7231aa43841d2acd0602de99e1ba7e292a7c91cd7fd4878e09d9aa", size = 607658, upload-time = "2026-08-04T23:36:48.696Z" }, - { url = "https://files.pythonhosted.org/packages/7c/09/af7607c6d6cc503c61c8fbbf305fe4548e110439c5a7102cbcbbbf7e899b/djlint-1.44.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd816cd84d7652c847d41e1393ee0618b09d6330b2322a64504b8a863239504f", size = 799694, upload-time = "2026-08-04T23:36:49.918Z" }, - { url = "https://files.pythonhosted.org/packages/68/c8/1662853ef863060c2cb4915bec38e434d292db639ef9850ae5cd9cd39bad/djlint-1.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:595602fa18a1fd8c236ac353ca3e013bc09e438888dd9bb2af650f783d61aa24", size = 748158, upload-time = "2026-08-04T23:36:51.064Z" }, - { url = "https://files.pythonhosted.org/packages/47/09/837358b537cd64c675f5a2b3c08253865f4c30fadb9444292359334e2691/djlint-1.44.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b43475383dab66c212cf42daf206a5eb39cfc43654898af099fc14ed4ef6fc2a", size = 792562, upload-time = "2026-08-04T23:36:52.275Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9b/9b64c5a62ddf4b7d398bd49bd61795870687f39f41c3c67bf32ec105641d/djlint-1.44.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:198407a4e561bb9135013516a05e1385cd943f4199167a3f6df8fd4e74d3d75b", size = 832562, upload-time = "2026-08-04T23:36:53.662Z" }, - { url = "https://files.pythonhosted.org/packages/21/0b/7f9df254773ba97388990b1b0907931ccb4700c1d548b55323980dc1fbda/djlint-1.44.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ef1ee5cac7bd4fbf56c175652ae111cb8dda7e0a791008d9bc29e0cc16ca", size = 803170, upload-time = "2026-08-04T23:36:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/95/15/870c19a8cc6e88436bede24c0f4384ce7475ec55e2ba09e51ed51b8ce2a9/djlint-1.44.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cef398778562128c08b91355023a0158d4e8f3042b95f8dbfe0437054203be47", size = 846928, upload-time = "2026-08-04T23:36:56.235Z" }, - { url = "https://files.pythonhosted.org/packages/91/7e/22c1c2f7423926afa8d632a71e15c839c5ea3593bf7fff13fb2adb6b27bb/djlint-1.44.0-cp313-cp313-win32.whl", hash = "sha256:8eeaacadf5365c388f2aef4706f521f57e062dacbf95b691ce0d6d69f9a466dc", size = 611690, upload-time = "2026-08-04T23:36:57.346Z" }, - { url = "https://files.pythonhosted.org/packages/36/81/551ff68f8ced6f5816a19702588c956d3467531f9c720a01c38f5447107b/djlint-1.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:a811ff1feb6497874bc3af537582c3648dd7b90b450773433d6be093fc34878e", size = 692250, upload-time = "2026-08-04T23:36:58.535Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/73a0b2a6685b74a761d4a126d7273a9a5449dbfe635e15602d00dde0d34d/djlint-1.44.0-cp313-cp313-win_arm64.whl", hash = "sha256:0046c523f27023bbdb9ec2ff601195d80bb122eb50235453df37a2d034835e4e", size = 608274, upload-time = "2026-08-04T23:36:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/12/6b/938ce125079b118ba5e93c3f1d8fb96c90779cbb0c2ec10dcb656ac37aeb/djlint-1.44.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c0c76743399acdfd649d0dac194a2dac0bed78656231134a7d618192cf8761c", size = 794735, upload-time = "2026-08-04T23:37:00.841Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e7/83b9e166d309fdb86c38a4159b159502dfb541965f0ffd440c8f9de5127e/djlint-1.44.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d17b2691ed2f833d84855b8d8e7b5104fbb5086056d18126e7b18dab605ff0d4", size = 748728, upload-time = "2026-08-04T23:37:01.939Z" }, - { url = "https://files.pythonhosted.org/packages/36/70/4d2b43779191f2fa3020fc3fe0452f5ebfa2cea2de9084a84ed7511cfa09/djlint-1.44.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f320fcc02eb074535f75adf17212ed8ab926341ca30c8e727eeec0401eadc6fc", size = 800822, upload-time = "2026-08-04T23:37:03.026Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f7/a3b43c8df9ac8bca30487572750c7069bce4b560c63a465e1aaafaab13b2/djlint-1.44.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10e3eafdd5f06e1db3c713a167c1cb9541f10287d259fae164cbe76c6a2b3212", size = 831214, upload-time = "2026-08-04T23:37:04.319Z" }, - { url = "https://files.pythonhosted.org/packages/2a/28/e6a9931302ef6a27bcaf9acd9d12458754e3cc9f298c4d67cb18842d2f37/djlint-1.44.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6ff96db13b8a0495f9cd89aba55c598592a16aaf9bad11fcf629f800145be10a", size = 809360, upload-time = "2026-08-04T23:37:05.783Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d8/3ef6dc7ff7baa3bdc44bfc3243b653e1f2e6810ada7e0e5332f8d8c065d9/djlint-1.44.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:617cb06e307b0ee724c5738d02869adfd0da63906c07a9cea44b2f1d2ff46bb1", size = 846109, upload-time = "2026-08-04T23:37:06.931Z" }, - { url = "https://files.pythonhosted.org/packages/95/69/03caf40bdc508d43a94530da7fac4ab4d2e2ae8e8b0a75bfc4d37c2b854a/djlint-1.44.0-cp314-cp314-win32.whl", hash = "sha256:824d91c636d644baccde7f30c421df5be89b686f5b06df85ee33e7d83ccdc3ea", size = 619403, upload-time = "2026-08-04T23:37:08.075Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b6/f868d840b5faa5f24a2bae50ea86d575008f79e1114555b1cc23e456a99b/djlint-1.44.0-cp314-cp314-win_amd64.whl", hash = "sha256:b327a42ce7a6202f06216e26caadc88caa982ab8dccc20b74394373213faf563", size = 703482, upload-time = "2026-08-04T23:37:09.215Z" }, - { url = "https://files.pythonhosted.org/packages/43/c1/09428687d4955f0de8da7f67672f8f6e5615595ba7c1859d3382a87af75d/djlint-1.44.0-cp314-cp314-win_arm64.whl", hash = "sha256:e3da3aefbdecb1c821f9d9e8b7cb0167bf1089ec37ed7dee2c6fef301920e7e3", size = 621782, upload-time = "2026-08-04T23:37:10.339Z" }, - { url = "https://files.pythonhosted.org/packages/95/f7/6f03c3878d06df554f72ee2b1cabe131e2b744c8b8494d0f839b29ffacb2/djlint-1.44.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb5b33854effafc23b6c5bacaa7bc311158bfc4c636437416b8e7f87e3a8393", size = 859322, upload-time = "2026-08-04T23:37:11.441Z" }, - { url = "https://files.pythonhosted.org/packages/95/63/681f08cd4c7b80ca847a5c951c84a4c5f1caa19fe7d71db5103a9871ada6/djlint-1.44.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:831a299a0538bdc2d59444f29a6f3c3577c782a8acf8a1680de4f4caca5da14d", size = 807326, upload-time = "2026-08-04T23:37:12.701Z" }, - { url = "https://files.pythonhosted.org/packages/12/b5/f465ad93a139c337a36e180f156b0ff9d63ca3ac69ef315b0d9caaf7aa99/djlint-1.44.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3398964848b797c7c67e684fde7a2e88febe33135bc068e51bd7b2ac5903de4", size = 833802, upload-time = "2026-08-04T23:37:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/25/eb/899306ddadab53156aa6e55ee828591bacea7205f46da377debc6a16d985/djlint-1.44.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b01666e18fbdc09b9fe203988603ca541b2e641997d0f5b820ae616502331b66", size = 866227, upload-time = "2026-08-04T23:37:14.966Z" }, - { url = "https://files.pythonhosted.org/packages/8e/46/d7a766df06bf4e0bf3f51b7c68e314d86b6abcbe845657e1e2ca636c6a1d/djlint-1.44.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2e464b2afb873b8de37ecf115ae8101476126dbacc466d1eb6f81068a032d393", size = 841068, upload-time = "2026-08-04T23:37:16.221Z" }, - { url = "https://files.pythonhosted.org/packages/49/9e/65d10e87b8c9a08c4d8658248397a7d8168259a7a87923d4c23673c8f36f/djlint-1.44.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b52031af5f5fa9714943bb65c9b2f393180317d42be83a993a2e247e57bc6d44", size = 879814, upload-time = "2026-08-04T23:37:17.433Z" }, - { url = "https://files.pythonhosted.org/packages/95/5e/c317c2ab010391953e72b2e530cf354300f93864ade983979bca5d8f1982/djlint-1.44.0-cp314-cp314t-win32.whl", hash = "sha256:0e89a7881d0f2b808dc7b94927883b7f748950f614d78dd4770e745c19b085b7", size = 653753, upload-time = "2026-08-04T23:37:18.608Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d8/a248ea60f09c6d9e14ea49702ac79d83233722f2e2a6568636ce96f53ff6/djlint-1.44.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e1e944cb721a04880b7bb120ad186abed1be0a5e009312b5156bbcc23f962acd", size = 731414, upload-time = "2026-08-04T23:37:19.957Z" }, - { url = "https://files.pythonhosted.org/packages/56/72/111106a997beb32a85eac520b71c7b95b3b38f3680cb66a66b223a49cb97/djlint-1.44.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d2902a48be3861530b98078c2614b6cfec04b270530d108d3d5511009eb595d2", size = 649139, upload-time = "2026-08-04T23:37:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/3b/21/b2d83bb61808d3cc62c603431a7a20d8326d3246e919b60ca9200dfbd785/djlint-1.44.0-py3-none-any.whl", hash = "sha256:84a36fb6af2f3b55853af4eaf6659634338b948b567b8e521385c8813276f869", size = 94844, upload-time = "2026-08-04T23:37:22.645Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5e/61/b8ad7ed080dd3e6a119e37a3ae2bf26e8a4dc4a06b4d97a22243fa50225a/djlint-1.44.2.tar.gz", hash = "sha256:29944d512818b59d9a1c4ab31851143b456edd327f892329bd3ee3cf7d5da4e9", size = 84909, upload-time = "2026-08-08T15:56:03.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/40/f4011fc73fe854dcb8abcaefdcf79bd94c997f377471df38dc8ffbe38641/djlint-1.44.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4e910def22f288496d2856873d4fe2453318a79189086a53d4a0228b0b891bd6", size = 834902, upload-time = "2026-08-08T15:54:40.91Z" }, + { url = "https://files.pythonhosted.org/packages/0c/04/37c3edf68952926283ade547bc4b425e5ce27785cca749e5c1ae9b6bb723/djlint-1.44.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aac33684f55074680e34db93b14bdcd5254928686ee6a077c57dbabeb1ae4e12", size = 781021, upload-time = "2026-08-08T15:54:42.244Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a1/8d35e39b4b897c1e1cffeedd4c9a9b1076856e787b0fdc424cf3a0577c40/djlint-1.44.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:886f91f6deb54c89aed6e0bf756667835a92b5f3a06d9db9a1d0e3462805f1eb", size = 817176, upload-time = "2026-08-08T15:54:43.46Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1d/65b42ef3dc858d0a7ce15a68e1515259d9b0b40b55a4d2efa3135b8b1a71/djlint-1.44.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f1311796c33bb1bfa1487bcf4af023da8b87e8879d00fdadb1856fad98c2f53", size = 860184, upload-time = "2026-08-08T15:54:44.731Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/4efba5c20b110e194e135cee70c748c01501411741e71c760fd01bef5a45/djlint-1.44.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:27d5a5c87f3653fc274318edae439a8565468699563bc79456f161a17152b9c7", size = 828582, upload-time = "2026-08-08T15:54:46.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/43/ab54d5ec8e7888ef1fd5049a5bc8966b5f61c5a8d7c2448cbf6a3f5d1dc5/djlint-1.44.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eac3a4e52a0318d762063b9b3d490abd2a14aff435eb6b5d89310daf0a3a3287", size = 872738, upload-time = "2026-08-08T15:54:47.672Z" }, + { url = "https://files.pythonhosted.org/packages/eb/97/2bf2510ea1cad93aa1244e25cf471c91c8f77df4be7e0a14e7d294585d8f/djlint-1.44.2-cp312-cp312-win32.whl", hash = "sha256:01d3def79866623b0f0617b652389142228c13ec6e7857d7c3d5ba8c1c71725f", size = 627749, upload-time = "2026-08-08T15:54:49.347Z" }, + { url = "https://files.pythonhosted.org/packages/52/89/277feee1dcf9a0e5aa739149ba1823dff5aa10053d5b5aa5a1cf1c873437/djlint-1.44.2-cp312-cp312-win_amd64.whl", hash = "sha256:05a7af5f21e09c72834e28975248a1c3a9bc93e2d35d3b97f240cdcacdf37bbc", size = 709236, upload-time = "2026-08-08T15:54:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/53/6d/b59c72a3048fb407514f2a2fcb3b6bd9573f26b60cf387d0350adf69bcd5/djlint-1.44.2-cp312-cp312-win_arm64.whl", hash = "sha256:509862f1edb4f66f7f6bc6a1fd6a4edb3a4b9a14e05dc7b016fa5a819239ee2d", size = 623773, upload-time = "2026-08-08T15:54:51.983Z" }, + { url = "https://files.pythonhosted.org/packages/81/68/56b92d968ed929a6208307bc3ff0d6354d3978beb379cd3c286211a46422/djlint-1.44.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:40e9eb4d3087f932a8c928b9fbb874dd246879527314b165b239bf6b67c1ec34", size = 826192, upload-time = "2026-08-08T15:54:53.51Z" }, + { url = "https://files.pythonhosted.org/packages/20/14/48eb11032fca97067fa60435758557006738d21eb0b24ba5348d955406cf/djlint-1.44.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a80ba03b2518ddb21d7f8f32d29617bb776dd50ce218a87f920f6ae4214ad4b", size = 775111, upload-time = "2026-08-08T15:54:55.229Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/b8d9be9037e25095588a67aafdb80a13a9855eb175819143cc010427856b/djlint-1.44.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7cdf7402b91d2fd2862c6c4ce03cf16b8467ff4024954537b4bbd7f934562eff", size = 814956, upload-time = "2026-08-08T15:54:56.427Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d7/19aa60cff7b2e68cc6c5814f7029d78a39efaff10697095eec20fe63f572/djlint-1.44.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6e1076ed929d2ee6a71912f9abdb0db8e90bdfffd3e62696f45802f876ab215", size = 857663, upload-time = "2026-08-08T15:54:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ad/d88bfa8bbc647fd601a3bb79c697dd50f81f0921d0c025a00cc76809e581/djlint-1.44.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b9136c27e95029a288f9af3dc07f970a6f12bd6baeaa53eb1a52d8dc370dc4d5", size = 824787, upload-time = "2026-08-08T15:55:00.714Z" }, + { url = "https://files.pythonhosted.org/packages/ab/26/5404c6e7e03b39fab8875f1b38a885ccfe98bc4c5ebb9a498a1fb1559a41/djlint-1.44.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4eea4169df64156d04d27d7e944c9a358bd7dc2e1a46f9a4a0fa2bf2b8528b52", size = 869647, upload-time = "2026-08-08T15:55:02.169Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/24e015b54c7ef36087713c372ce1fbe3ed313bf154075115bdec798af421/djlint-1.44.2-cp313-cp313-win32.whl", hash = "sha256:a1099c0408a9bcb2cf06466a2c1fac7e10f0a7d94b89425c7c2f686dc4510b4e", size = 628482, upload-time = "2026-08-08T15:55:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/49/78/fe1956582e3959631883bb6e5613b4c1dce7426eb2d137e013690442540b/djlint-1.44.2-cp313-cp313-win_amd64.whl", hash = "sha256:8e35060b6532f601baeeccfc9f5826a026f1326240dca699913cd65091bc2ccd", size = 710829, upload-time = "2026-08-08T15:55:05.087Z" }, + { url = "https://files.pythonhosted.org/packages/31/29/34aac75c4b0d37df4ed9d15d5d0e5e887761781e5a03bf0e6108e8e900b8/djlint-1.44.2-cp313-cp313-win_arm64.whl", hash = "sha256:db14443b74121fa675f39a4807a1c5172f2d7e62f27aeca367bfb74724751209", size = 624677, upload-time = "2026-08-08T15:55:06.266Z" }, + { url = "https://files.pythonhosted.org/packages/41/ec/27b2426d30283ba687dbbf04203fea74b04b52ca6839bc15eaa7d451bac8/djlint-1.44.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06f9adbb03fe3cd9805e6ae472f5af7b70d8c4c67f321547ad062cc11134e36c", size = 821816, upload-time = "2026-08-08T15:55:07.537Z" }, + { url = "https://files.pythonhosted.org/packages/4f/38/bd71c0694c1220a6016c5234bdb63be90f8f28e0d14da839866529845ff2/djlint-1.44.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:09775e2cc224fc8240eba77738a0f06c7bb0c41bd52c228fabe25a105f9fc057", size = 774259, upload-time = "2026-08-08T15:55:08.898Z" }, + { url = "https://files.pythonhosted.org/packages/01/4d/15238b621aef74799432721a4039ec54cef68b31361f47b39af5bc96c27d/djlint-1.44.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:212b546434c083ac7a7ae71f6e013d0f6b01c4e6bd809fafb81d6649a50a8202", size = 822879, upload-time = "2026-08-08T15:55:10.362Z" }, + { url = "https://files.pythonhosted.org/packages/7d/42/8e254a7040fffff3e8fdbe23c5dc2a577ceb6df380aa4b4c769ea56b6174/djlint-1.44.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2243dfb99fe97d21ca927f13fcbb175fbd11f341041b8e3af30e79d668461966", size = 856815, upload-time = "2026-08-08T15:55:12.001Z" }, + { url = "https://files.pythonhosted.org/packages/04/ed/e8bf4eecc94747c393ab77554add0d47467ec38adeeeb31846f613a8de74/djlint-1.44.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:40a9a4aa9422f7b9f9e6a092e7e1793be3732cd65cc14940c0ca85e50778c94b", size = 832691, upload-time = "2026-08-08T15:55:13.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/804d73326328d9f0c2e30083189fba99a63796c5c58a7a44a3e81d5a6cd0/djlint-1.44.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ca0f8d40cbcca8d33ef080e06b30a8365c3fd1f2c83d697b09f093426367a568", size = 869169, upload-time = "2026-08-08T15:55:15.2Z" }, + { url = "https://files.pythonhosted.org/packages/09/d8/b6aefa95a699f7eecf9bc4a7c65aff9776ee5fbb1a7809355bb354837a7d/djlint-1.44.2-cp314-cp314-win32.whl", hash = "sha256:8485babc0f81fa3d3a1e54eb0ce5b00807cc1c5efc7cd129ca41ef3e1f760ecf", size = 635496, upload-time = "2026-08-08T15:55:17.061Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7c/c9f76e3dc862179f132f9f3518603385434e7b06501d04401392949df396/djlint-1.44.2-cp314-cp314-win_amd64.whl", hash = "sha256:4cafd3c308503d2fb4e53933ab1ee7c5cd42c4ac9f696e244348666310ab4e2d", size = 722297, upload-time = "2026-08-08T15:55:18.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/2b/668478cb195b775b58a7a0b72b58334c7133bd5886c53ce605021a5d2d2e/djlint-1.44.2-cp314-cp314-win_arm64.whl", hash = "sha256:339a17b7f196802db1bc610705d042b61ae194f570c29ee0d9917b018d57f19c", size = 637150, upload-time = "2026-08-08T15:55:19.78Z" }, + { url = "https://files.pythonhosted.org/packages/90/68/808826f3d1e73cf9638e9a6c837eca5aee807e0d4e379ea1171e86223452/djlint-1.44.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f47abbf415f88bcac812b177a20b07629a420a1798b0a2b16fe2abd30ef87e9d", size = 888588, upload-time = "2026-08-08T15:55:21.306Z" }, + { url = "https://files.pythonhosted.org/packages/b6/34/94f745299dc49beee39d1e55f9d42c268e4b5a683d69b8db365211ef4e9d/djlint-1.44.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5fa3c85c37865c1664b132e098ad1b67a1ea3e462f31a425e0c97c1697e8ff14", size = 832883, upload-time = "2026-08-08T15:55:23.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e6/8e1aa9e2ae2bf0acee424a359b3b8de161c59b679e30f5aee4f6ccbc26dc/djlint-1.44.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45e69495c93a0def1bad3ead987cadd68493d56b4ab975f9089249386885043e", size = 857857, upload-time = "2026-08-08T15:55:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c7/2639592a95c5825e56fcfcbe19a42cfd5fe974ee6d4c59a617d29a177680/djlint-1.44.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0f623ff17a9c50159ff97e16b1607ca2dc3ca5334356e3871e1c0d44e3874f5", size = 892240, upload-time = "2026-08-08T15:55:25.989Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/cbe8cde7c8f452f922d663a691e53abdfcd1c5c80667230e0995366a2390/djlint-1.44.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48c81aba76bdebaad12509cf8986ce1499ea6dd4cd67bafb99df2871b06c8c0a", size = 866967, upload-time = "2026-08-08T15:55:27.504Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/a16f051ff66d0b43f5d514c186ba6269abd1c3a63602b649c63001f35b7e/djlint-1.44.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ae895d3e1618d531c8d2a2eb68c01bec70c24c713234ac69f65572375f366497", size = 904116, upload-time = "2026-08-08T15:55:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/91/e2/06d2a97640d2207efe832f357c071bf0a9642431a1a1f72da4be6d532e25/djlint-1.44.2-cp314-cp314t-win32.whl", hash = "sha256:4ade5f74ac3aaaaefbec597c4fee05b2b699c120af516aad6025a6a3042c82db", size = 670703, upload-time = "2026-08-08T15:55:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/19/2e/4eda641e6fc209d6c5326032aecbbfba382f7c72461d4cc55e0fe0d50acf/djlint-1.44.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d60d43999dc2e9bb9a7c95138df891dba3196f14eb4814d7b094e4e2711f671d", size = 750902, upload-time = "2026-08-08T15:55:31.976Z" }, + { url = "https://files.pythonhosted.org/packages/5c/63/58547ebe324cfeba8557280b473725939579b5519ab27e522f00d6ade981/djlint-1.44.2-cp314-cp314t-win_arm64.whl", hash = "sha256:df000fb0c19f569c97d9255ed08c5216e0c5521587a6e4ac6d3b2d3fa0a4c08a", size = 666912, upload-time = "2026-08-08T15:55:33.553Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/2339a6298ac418ffa0cf7d0d66575d820c21e3c0d4d4dd106edabbbbd872/djlint-1.44.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:0cd040b7d405182d1a8283571be6908f4aa645963b6ca0f5826670a135a2ec30", size = 821865, upload-time = "2026-08-08T15:55:34.954Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b3/a5a2600531e08066fd022650f0f420188252ace5830e22f6352ba82064e4/djlint-1.44.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:55e16f1d22b0de536ccfcc8253a7438f84736b6aa14825d1c0abdfd2a65ea68b", size = 774285, upload-time = "2026-08-08T15:55:36.316Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/dc8660295b1d73e9be1e8fa7ba4d8372ad8e2edf8107a1a993bb31683c31/djlint-1.44.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18653051f59f1f6bb743a05d78ca8196d3e94c92a7d7238f0adfb9588a4bda38", size = 824222, upload-time = "2026-08-08T15:55:37.805Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/9ec0954a6842f3a0902cf28feb45b2c0bb350910e7039eae1bfe2af07ce1/djlint-1.44.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92669608da737c537352566a22c2ac51342a03e3d2c0d84fa8d92cc927bc486c", size = 861869, upload-time = "2026-08-08T15:55:39.233Z" }, + { url = "https://files.pythonhosted.org/packages/90/fa/86544e5f53971e172695144d16e54ca5752a83bd28c78e9f7ef85eadc652/djlint-1.44.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:b63f19c3f0632543c8c7cd9db298e174a3ecb87ad6b02af2f6dd4fdd67af89a7", size = 835299, upload-time = "2026-08-08T15:55:40.783Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/d274ce0787a8d13e171651fb2a230dbd5363c73a8906f53964069fea6515/djlint-1.44.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:399d3ab3f05c8a2577d5355032888a8c0c37435229d89130942349ecda773e86", size = 875812, upload-time = "2026-08-08T15:55:42.175Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/7c50a13eb499d23020523739f01b6857adc879cd16cfc11e87caee267006/djlint-1.44.2-cp315-cp315-win32.whl", hash = "sha256:5c41ede9d15f065bccfdaa5c7248cd72d4e312234f39739da79ae4c6bd0b47b8", size = 635234, upload-time = "2026-08-08T15:55:44.716Z" }, + { url = "https://files.pythonhosted.org/packages/6d/29/128abcdb46d700881f54df0645250ef75cb667eb3b23bb0934e5cb6929ac/djlint-1.44.2-cp315-cp315-win_amd64.whl", hash = "sha256:d69bedf279df3aaec19c5778adf0e197df767479361117e68e5813428200a6a3", size = 722170, upload-time = "2026-08-08T15:55:46.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/7b/ee785b6d3ce5f1790a9b2b79ed2644a3a4c97a92d265592a9725aed5a079/djlint-1.44.2-cp315-cp315-win_arm64.whl", hash = "sha256:1e660a1a8fed5c69345b52cee7d7d40421f2228be9a03157a1dda9743d3c1987", size = 636959, upload-time = "2026-08-08T15:55:47.688Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5f/a29f8d207c52d9db5187885966d742d9a221de43223ed1105866f5a19449/djlint-1.44.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3fb9da9d3521017b86f878494d79b078b978517ccf7238d9105d380b94230705", size = 888208, upload-time = "2026-08-08T15:55:48.944Z" }, + { url = "https://files.pythonhosted.org/packages/48/e8/f9aeb73ee0ae1724bf678a552819810a5062804e8540ed2be5de27cd4821/djlint-1.44.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:59b571b896604384388b06bbc84e2a527444a60cee4d3884f735a1d4dbccdbbe", size = 833518, upload-time = "2026-08-08T15:55:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/08/8b/a2fe4a6c8c909a8f15069ec0e290a7d08c0d50e3be06ff32ee62454391e3/djlint-1.44.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02a61eb3c08527ca1c08d6f2276e4ef2143b0ce8beeadf4e5381b8c181a16c57", size = 855943, upload-time = "2026-08-08T15:55:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/4f/16/2c0acbeb7940e97c22551e1cf908e4e83de9df7fb416fa8d438470d1465b/djlint-1.44.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d6f6a22a41addc661481350282eb30c241f7d0b5621a0ed47dfdac19cf73df9", size = 893185, upload-time = "2026-08-08T15:55:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/7450ea51bbd4f60bd30b6fe1cd062a73d31ba830679138efee23dd851ee8/djlint-1.44.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:a5073a59a601846d1cd8a84c38b6e4453d877e61e1f645aa50d9e757719ff132", size = 866342, upload-time = "2026-08-08T15:55:54.949Z" }, + { url = "https://files.pythonhosted.org/packages/92/ab/1106a906f1344331d760f34e9c5af67fd012835caef6b0f9c9ffa277b2d7/djlint-1.44.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:550d02e381047be9e42b85ca766ac02d562e65b2fdf2b815772ff84cf4f1350b", size = 905531, upload-time = "2026-08-08T15:55:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/f0/61/cddab2679e53a5dc7049c11256a5c53eb94e8456c1547e889c251e61ae95/djlint-1.44.2-cp315-cp315t-win32.whl", hash = "sha256:e4143c0dff2674b903f0fa887a8043a1a4bf6a2ed47d4c288c796b3acef73b1f", size = 669129, upload-time = "2026-08-08T15:55:58.249Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d1/9b16c37e19f1d4671e36af8e977b63c7117371bfc119c0926a3055162858/djlint-1.44.2-cp315-cp315t-win_amd64.whl", hash = "sha256:eca7fefb7562db7adc7697b6b4afe63580b5f2f5cdd0e9ba696503e56f36f793", size = 749603, upload-time = "2026-08-08T15:55:59.716Z" }, + { url = "https://files.pythonhosted.org/packages/2c/37/d035049d165a301f246c017e7c9ddbbfadb509ad54cf10440f1fd1c5bdb0/djlint-1.44.2-cp315-cp315t-win_arm64.whl", hash = "sha256:bcfcb30a8929622b9f00994a61080b63a7a37ab1dec2933656641d6d0f8d910f", size = 666220, upload-time = "2026-08-08T15:56:01.103Z" }, + { url = "https://files.pythonhosted.org/packages/82/90/175b20b73e2915224c3d337b1b3329e20c7e6656d565e2d3c8818ca4b768/djlint-1.44.2-py3-none-any.whl", hash = "sha256:34c024c039ac39f7244335aaac92ae24d17eaedb7866d8e7160d1a2150afb320", size = 98848, upload-time = "2026-08-08T15:56:02.355Z" }, ] [[package]] @@ -1307,11 +1356,11 @@ wheels = [ [[package]] name = "elementpath" -version = "5.1.3" +version = "5.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/18/32c3a5cb3d1e5ce691757cd627bd15ae19aafb45a7c966fce16022ff6b70/elementpath-5.1.3.tar.gz", hash = "sha256:35e98cc2e26332305a49b32eaaebfb7289d1963c88d3989ff5ccb02c46809820", size = 378039, upload-time = "2026-06-28T11:24:02.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/9d/766386c47959bb8b014242af8770c869350884a3c3f7054375576e11f30a/elementpath-5.1.4.tar.gz", hash = "sha256:3c912a9c41311ac5244c038867670d7c0b0fec0e444813608e5b846dbb3428ac", size = 381114, upload-time = "2026-08-08T20:29:22.963Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/11/317824213350e6bf7af8aa4238cb78bec9ca014b89fdd5e25283f1c40d98/elementpath-5.1.3-py3-none-any.whl", hash = "sha256:c46f5e0e36c149b892308843e1e394bdee17ceafbc18cfa39250403dd8475a4e", size = 260297, upload-time = "2026-06-28T11:24:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/42/b0/b709cc33ccb0288f41f2d92dfd754d2ccf925cb9277a66effbc132cd004c/elementpath-5.1.4-py3-none-any.whl", hash = "sha256:5ec8d7bb44b16c98414a6a95e20cb150f3cbfc3cac65edc14773288c7a937376", size = 261881, upload-time = "2026-08-08T20:29:20.649Z" }, ] [[package]] @@ -1466,69 +1515,69 @@ reload = [ [[package]] name = "greenlet" -version = "3.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/74/b13368064b09053253555d3f2839cc2684d22d5aed0d2ccffbf7a6736558/greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20", size = 206538, upload-time = "2026-07-22T12:47:14.468Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/04/81bd731d6d1e3a469d9a4c36f5eb069bcf0cbb2d5d342c9fec22245b91fc/greenlet-3.5.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3d66250e8b09f182ede05490998c818b5961f7a3640332d44c4927caec7bbfe4", size = 295909, upload-time = "2026-07-22T11:38:09.261Z" }, - { url = "https://files.pythonhosted.org/packages/cc/dd/f5f22903a6ae70f5ea328ed0beaec92ad903f0e3b7d2845133b354abc4b8/greenlet-3.5.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c90e930c9c192e5b3ee9fb8bcd920ea3926155e2e3ded39fc697323addecee17", size = 612011, upload-time = "2026-07-22T12:26:40.69Z" }, - { url = "https://files.pythonhosted.org/packages/8e/10/92a4a88d12b915d74ea5b6d288e4afefda4771647caa34442c156f7a454f/greenlet-3.5.4-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:791fdfeeb9c6e0c7b10fa151bf110d2a6974866f13dcb5b1c7efae698245893a", size = 624299, upload-time = "2026-07-22T12:29:02.089Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f9/03e26be3487c5238e81f2b84714959a86ea8515a869828cf41f4fc54b34e/greenlet-3.5.4-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b7c895310363f310361e0fe2072af85269d2a2a285cd04c0c59e79a5e3670dcf", size = 629603, upload-time = "2026-07-22T12:43:43.456Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/0b14bb9db2989f32cd9fe7f76afedea01ee8bee3f87c07e69f24adfe7e63/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f88193799d43dbf8c8a806d6405c9c52fe2af40bf75072a606357b33cc336c7f", size = 621541, upload-time = "2026-07-22T11:51:09.464Z" }, - { url = "https://files.pythonhosted.org/packages/57/6b/7c55ca72ef80d57c16c4a55210f82582622462dc4485799a30f4ec6f3372/greenlet-3.5.4-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:13b980043cb1b3134e81ea469da1250ddcc6bfe6d245bbaa59168d9cdc8f228f", size = 432554, upload-time = "2026-07-22T12:39:51.379Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/25e9a2d9eb6b2e8b7ca4e80a3a26cb887cce6c8e0a87c921164f11bc5574/greenlet-3.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7a5f095767c4493afcd06067f2bb3b8716e3f3f9e92b99c88e7e99f885b3d4d", size = 1581444, upload-time = "2026-07-22T12:25:03.818Z" }, - { url = "https://files.pythonhosted.org/packages/b9/96/4c9bf2e2c408dcc0556edce69efa9f802e82223573c53240136a086821f1/greenlet-3.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42afdc1ab5f66da8c586c32af9224a74a706b4f0ea0dc3a4188a0860a09c65c9", size = 1645842, upload-time = "2026-07-22T11:51:12.295Z" }, - { url = "https://files.pythonhosted.org/packages/b5/41/303ecb26a3a56122c0f4d4073ee078881847bd6b6f463ae0ec57ec20223b/greenlet-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:60149df8f462d1b230038e6590c23c3b4768bb5d6c022b3b6e82532b34b0b8a3", size = 247169, upload-time = "2026-07-22T11:38:19.893Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e3/ef56864b4c35fcb3eb3b41b869f6cc46f4cd3f5e2c68e74acde8ac433951/greenlet-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:77d6ce04fed0d9aeed42e0f37923cc43eba9b027bdd9c34546bb4ccd143d0fe0", size = 245565, upload-time = "2026-07-22T11:38:27.061Z" }, - { url = "https://files.pythonhosted.org/packages/c0/9a/e51225dcd58713f16ccbdcc501a8da21098ea14515b7870f1f94459e5ff5/greenlet-3.5.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:24e61b88cb7e1b1d794b32a10cc346ac779681d6d74ff137a3e0a444d2bf1f02", size = 294831, upload-time = "2026-07-22T11:38:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ea/de50a50fadf979713ab18b46f22ad5ff5f2dcfc637a3ebdecf669801e1a5/greenlet-3.5.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:870d730fec833f5a06906a32596cc099b9161594642a92a520b7a88911c95356", size = 614619, upload-time = "2026-07-22T12:26:42.282Z" }, - { url = "https://files.pythonhosted.org/packages/db/c7/2aae27fea41205b8650294c301f042a2a4bb6155eea48c995b890a92f2c1/greenlet-3.5.4-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec5ff0d1878df6af3bf9b638a5a92a7d5693291de77c91bff10fa48519c604ef", size = 627021, upload-time = "2026-07-22T12:29:03.445Z" }, - { url = "https://files.pythonhosted.org/packages/1b/80/fb4d4788bbc8e54761f1fc88533af9523a6e86299fa113d6e8a8503ed9fc/greenlet-3.5.4-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07bd44616608d873d06735b63ef1a88191d6ca57c8d291d6559c71bc14c0893c", size = 632845, upload-time = "2026-07-22T12:43:45.19Z" }, - { url = "https://files.pythonhosted.org/packages/eb/56/79fd826f9ccaae0b84e1b4ef68dabba5e105bb044ffcd448a0b782fcba9a/greenlet-3.5.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d84d993f6e575c950d91a23c1345d18fe1a4310d447bf630849d7809196b52f0", size = 624002, upload-time = "2026-07-22T11:51:11.391Z" }, - { url = "https://files.pythonhosted.org/packages/42/e3/6086fa578ebb72772722cdc4bcd628459814b42e0c2db1e3cbd6552b3271/greenlet-3.5.4-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:3529a8a933582ad19e224792cac7372489526576b75b4c124e8e4f29948f4861", size = 435053, upload-time = "2026-07-22T12:39:52.715Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1a/27319f97e731298513dcba1a2e91b63e9d8811d9de22130f960b129b1bf1/greenlet-3.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58023945f421093de5e6fa108c0985a8659d43f49e0216da25099369a121bcbd", size = 1581533, upload-time = "2026-07-22T12:25:05.322Z" }, - { url = "https://files.pythonhosted.org/packages/b1/6d/24240bf562e9786dd2799ee0a4a4dadb4ded22510f41b20245099159ac8c/greenlet-3.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bae2728e1897aa8df8cb1af38cd48b3a743aefe29372de7b8b7a9f532501e69f", size = 1645781, upload-time = "2026-07-22T11:51:14.805Z" }, - { url = "https://files.pythonhosted.org/packages/c1/5a/442ab1a9ef7ca6bf7210e5397a95972206a91a31033a03c8900866a10039/greenlet-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:ca5726c0b08ca35ae873557266a78b2c3f3b2b7d7401aa5ff886c2045dd0111c", size = 247133, upload-time = "2026-07-22T11:39:20.661Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e6/9160210222386b1a378ff94db846b9508ca24a121cf684991561fdb69280/greenlet-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:7c1303791d603080cac6fc3b34df51c3b75b723739c282c8029e48a0d241672f", size = 245500, upload-time = "2026-07-22T11:40:22.185Z" }, - { url = "https://files.pythonhosted.org/packages/a5/a7/6ab1d4f9cd548d15ab90da29947f2076100130bb179b0bde59f795a459e3/greenlet-3.5.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7e8afa5eac028f8140ceafe5ceec66e6aa127ddcb21452d2a564dcd2900b5f22", size = 295410, upload-time = "2026-07-22T11:40:35.747Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7a/422f63b4715cbc0b24385305407adf38b48f6bb68b3e6b04090e994d0f5a/greenlet-3.5.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73b37afe369021423ea53dd3123e04bffa7e93ac64429b9f50835b2e4fcae7cf", size = 661286, upload-time = "2026-07-22T12:26:43.8Z" }, - { url = "https://files.pythonhosted.org/packages/d0/31/5a1cac663bf5582190c5a714ef81364f03cde232227f39748f8ae4c11da5/greenlet-3.5.4-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ef964f56dfcb6f9bbef2a190d9126795eac408716aeae47b5e7c73c32aafca9", size = 673517, upload-time = "2026-07-22T12:29:04.815Z" }, - { url = "https://files.pythonhosted.org/packages/9c/bf/250c2921c7b585dde12f5239e313ca2dcbc464d161ecca36e4e6ef21762d/greenlet-3.5.4-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cef589bc65fae02d10bca2ac341191c5b33acc2967892ebf4fcbd10eabb7a74c", size = 677968, upload-time = "2026-07-22T12:43:46.788Z" }, - { url = "https://files.pythonhosted.org/packages/15/4a/2a82a1e3f8aaca020853ac8d12211280ca2b231aa08ea39f636f1060c319/greenlet-3.5.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c53ff01a5c53a40f2c16820ebc56d7c61a77f5fbe009dadd96292d5682f80f8", size = 670917, upload-time = "2026-07-22T11:51:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/18/40/10bfcf6513558d82f7b95dd728001c63bd388259fe27d3e30ae01f103430/greenlet-3.5.4-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:dfc41ae893d9ceaf22c824f2153a88b30651b20e8758c2cd9ac143f23640563c", size = 480643, upload-time = "2026-07-22T12:39:54.149Z" }, - { url = "https://files.pythonhosted.org/packages/68/b0/e379a152b17bfdfa95795af4049e37c0fd1b4d81f020d426db104ed07c77/greenlet-3.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecca4d80d55a01ad6b23b33262662956149fbb7b2c6be2910f1705921958cbf3", size = 1628478, upload-time = "2026-07-22T12:25:06.678Z" }, - { url = "https://files.pythonhosted.org/packages/5e/43/bffdfa64f7317f954c5c1230b5dd5922676ce198689a68c1ac1ed4b1b1a5/greenlet-3.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffbc533e0eaf8e80d8471411646ab88fe58f641d508c0b02b24494479f4d9ec", size = 1692021, upload-time = "2026-07-22T11:51:17.008Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/f799f9637e2c6e9b0b716015e339040598b058cf7654dfc0d67468b177ed/greenlet-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:305f69e6c4523d7f6979ed001cff4e5853c063e5da04880296603aa0227e544c", size = 248031, upload-time = "2026-07-22T11:40:11.007Z" }, - { url = "https://files.pythonhosted.org/packages/05/75/625bcdd74d5e6b2dca1ecba3c3ac77bcf8a026c21a649a46cef23e421f97/greenlet-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:f260930bbbbcf9caee661211235a5111c86dfe5832fdf6ae4570da1e0995320f", size = 246892, upload-time = "2026-07-22T11:40:27.357Z" }, - { url = "https://files.pythonhosted.org/packages/ec/69/35c62ed49c320cb4d98e14698ccca5467d3bfe683984172be9cb564d9ce3/greenlet-3.5.4-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:41ddab54e4b238f4a6c323f39b4e59e176affd5a94d461a9fb7583dac74240a3", size = 305571, upload-time = "2026-07-22T11:40:31.659Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6c/64d60216b3640dcb0b62d913dd9e0d80030c09115bb2e4ba70c95d10ca45/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3dabe3e2809013052c68bdf0b7fa5f5f2859c43a80803131ad61af9cabd7867", size = 672568, upload-time = "2026-07-22T12:26:45.298Z" }, - { url = "https://files.pythonhosted.org/packages/5c/de/ba3ab0a96292e53039530333b0d2ae18d9e508f3a325cd7bf15f8172944c/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27d3f00718634d4520a3a150154ac5da36f257869d41321953375b90bfbbc72c", size = 680076, upload-time = "2026-07-22T12:29:06.125Z" }, - { url = "https://files.pythonhosted.org/packages/ae/db/24a10af12bf8e639cec46c38b9ce1a282543ba42ff4fb0b31a970f1ab603/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39169a11d87a6a263afda3e9a27d1df16d0f919d40a4837cc73986c9884c0dd8", size = 681690, upload-time = "2026-07-22T12:43:48.109Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/aeada79083c6f1c15f45d77a332f9c441af263ee298e3eb17522cd337d22/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbd60b5763c6543c1827e48faaf14ea9bfbad245f52b1a4d76a2a2d8884c6c66", size = 676733, upload-time = "2026-07-22T11:51:16.027Z" }, - { url = "https://files.pythonhosted.org/packages/f4/60/44a2eca7b9fd71ae0fae7ff184da1cd3169d176652b97aa1cffcbb0ef961/greenlet-3.5.4-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:bd3d1145f603b2db19feb9078c2e6855eb7c67e15580c010ed815cee519b86fd", size = 510263, upload-time = "2026-07-22T12:39:55.678Z" }, - { url = "https://files.pythonhosted.org/packages/e9/10/2392fc3a98948652ef5fd1e7275c04f861dd13f74b78a2b4309f4ee4d090/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00f910f0e7b35416c63b23ad78b769aeccfc1775f712b43c4ee525624a2eef7", size = 1637327, upload-time = "2026-07-22T12:25:07.879Z" }, - { url = "https://files.pythonhosted.org/packages/55/c6/e7237a3dfa1f205ed0d9ea1e46d70bd2811b32d516266399fb59d28ab90a/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91c26423753b92caf41ab3f98fd547d7374d4d9fc2d85be041886c1579d9255e", size = 1697493, upload-time = "2026-07-22T11:51:19.214Z" }, - { url = "https://files.pythonhosted.org/packages/55/e3/4ba8154ba2a3d43729e499f72471b4b5c993f3826d3e24da81d5f06d6572/greenlet-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:ee032b91fd8ec29ec6c4cea2b8c561b178435134bd0752c7334b94e9c736c132", size = 251637, upload-time = "2026-07-22T11:40:37.44Z" }, - { url = "https://files.pythonhosted.org/packages/90/03/e3f96dfc100261a29545ddc8270cafe58f9195b6651466910e820910de77/greenlet-3.5.4-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:178111881dd7a6c946471fda85485ec796e1043c2b939f694b096e2ecf986809", size = 296076, upload-time = "2026-07-22T11:39:38.364Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3d/da52d208e5c977bce8667e784729e584e38b5785f4c1ec0f4c836e9a1c42/greenlet-3.5.4-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d92df08dd65fede97fc37aad36c2e9dcda3b31c467f8e0c2c096456cb818e927", size = 666870, upload-time = "2026-07-22T12:26:46.691Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8a/7e6dee25cb8a8cf9b362c8e597cc269593378bd916f16c736c059a52e85a/greenlet-3.5.4-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99e8f8c4ebc4fd80aa26c1280ae9ad43a0976e786349703a181cf0bae60413e5", size = 677678, upload-time = "2026-07-22T12:29:07.508Z" }, - { url = "https://files.pythonhosted.org/packages/51/a7/dafc7415d430b0a43a16396eb49ecb3b62fd720877fb259cc4dcfaf5f31e/greenlet-3.5.4-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f17e362d78e37559e0506c5a7d066bdd45073c36a0127a543e8a0df27242ff3", size = 681428, upload-time = "2026-07-22T12:43:49.623Z" }, - { url = "https://files.pythonhosted.org/packages/6c/21/5a38699fa45de749e3857d93b8f07e4c20489e77c2d35d915a2e1c456606/greenlet-3.5.4-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:394de08dad5ffcb1f50c2159d93e398d9d2da3ed437645eaa54771fa720db9f0", size = 676067, upload-time = "2026-07-22T11:51:18.163Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d9/6298f3432de301d4718766cf934bd73c418c73f81fbb77247319364b0d96/greenlet-3.5.4-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:cd320d998cbaa032932830448e39abf3c6a12901295e386e8114db926e10cffb", size = 487446, upload-time = "2026-07-22T12:39:57.044Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ba/863116ab8ff1ca7a729e327800268939d182db47aa433db70e216e7d9194/greenlet-3.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c883d61f2282d72c767a14936641b3efcbde9d82f1080712aaea0b1d3126cb88", size = 1633489, upload-time = "2026-07-22T12:25:09.605Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/7466ced82818d6132462d7f26b3f83c66ea15d2b193a6c0088d558ed7d95/greenlet-3.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2a924f15d17957e252a810acefcb5942f5ca712298e8b6fcaed9a307d357522c", size = 1696584, upload-time = "2026-07-22T11:51:21.304Z" }, - { url = "https://files.pythonhosted.org/packages/f9/4d/55b638489260065de9ffce606c8b5d04507bef705de4b212a0c3d6a1a0df/greenlet-3.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ed17e5f3420360d5b459de8462efb52060399a5326a613d4cde31cef63ef95da", size = 248297, upload-time = "2026-07-22T11:42:04.055Z" }, - { url = "https://files.pythonhosted.org/packages/bb/08/9dd4ae635da93d41dc268bc34bd62a9d711ed8b8825c5d22ac910c7d6e6d/greenlet-3.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:f908898d6fa484ce4b6f447ce70ea99b52c503fee419e53cf74d60a16bc9e667", size = 247423, upload-time = "2026-07-22T11:44:00.764Z" }, - { url = "https://files.pythonhosted.org/packages/19/66/7c87ed9cdbf1d49c2c6cd1c7b9dd4d16c33b24235ca03972293a1876b30c/greenlet-3.5.4-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:1833637f17d5e7472548a48575c394fe39f1b1890d676d162d86593610f44d8c", size = 306487, upload-time = "2026-07-22T11:41:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/5b/05/0a4201e7c0054866eefc05da234f236dd4c950d0fbf9ca0517141f01b269/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12cda9122e03341f1cb6b8207a19d7a9d375e52f1b4e9243918375f40fd7b4b9", size = 676479, upload-time = "2026-07-22T12:26:48.129Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c0/4b6b8c5a3aec70f0649cd89662d120fdd6421e2bdc8e3b15c3ab5ec568d8/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d83ae0e32d14957ab7170785a20f582635c8474deab1bfbb552b17e769a6ce25", size = 684321, upload-time = "2026-07-22T12:29:08.925Z" }, - { url = "https://files.pythonhosted.org/packages/88/15/0b167aeea95285b0e654ddce651922f666c089363c2ec528ca8b9a9ba74f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:123aa379c962ed5fe90a880327e0c3066124ac64ec99e12a238be9fd8eb3db3d", size = 685995, upload-time = "2026-07-22T12:43:50.993Z" }, - { url = "https://files.pythonhosted.org/packages/24/c9/b49c31c9a972eee91e260445770e922244a7efc542697f51a012ec046d0f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f1467de1bb767f75db0aa34c195e3a496d8d1278c796e70c24ce205d3e99cde", size = 681293, upload-time = "2026-07-22T11:51:20.43Z" }, - { url = "https://files.pythonhosted.org/packages/de/90/c023ec337f32ff505be7db759c80d98f0532bb94d0c6fa13645efe9bee2e/greenlet-3.5.4-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:adf2244d7f69409925a8f22ed22cc5f93cdfe5c9dc87ff3476be2c2aaae61a05", size = 516928, upload-time = "2026-07-22T12:39:58.359Z" }, - { url = "https://files.pythonhosted.org/packages/95/6f/7f2d4653770500eee667866016d42d7a68e3d3462f80df6b8e3fcd48a0eb/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0fa53040b78b578120eecdc0265e3f1051487cc425d11a2b7c761daadf4feaa8", size = 1642474, upload-time = "2026-07-22T12:25:10.819Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d8/8cba31036a4caae448087ba5d150660ab03b4a0f54d9150f6495a3be7262/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:60e0bc961d367df506660e9ac0177a76bc6d81305300704b0977d1634f76efe2", size = 1701012, upload-time = "2026-07-22T11:51:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cd/3f77a4cce3bae631b08eb52f53a82a976669600337e21dfdba811cb50267/greenlet-3.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f680e549edb3eaf21eea4e7fe101e15ec180c74b7879ab46adc080f22d4015d2", size = 251977, upload-time = "2026-07-22T11:41:38.125Z" }, - { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, +version = "3.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/d8/7cc97c142388aef03f622e001c572c4f84e9252a439549d483f555771970/greenlet-3.5.5.tar.gz", hash = "sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c", size = 207585, upload-time = "2026-08-10T15:09:36.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/7e/9ecd0285e3153532ae07aeb88063c43c72b4221cf0d4d123b02f3682e3ff/greenlet-3.5.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380", size = 295809, upload-time = "2026-08-10T13:25:34.023Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/60e4bbcc89252037b18087f2ec16405d5b2d5be42dde191bbf3667e96102/greenlet-3.5.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053", size = 611910, upload-time = "2026-08-10T14:14:35.18Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/cd5134be659cd4a443e7a61ae670dabec165a814c51162916d637b6dd38e/greenlet-3.5.5-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95", size = 624198, upload-time = "2026-08-10T14:27:25.229Z" }, + { url = "https://files.pythonhosted.org/packages/9b/30/87c212b5c684d0e72974f1063b7a9687631e8985902c06e1016542c874e7/greenlet-3.5.5-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ca5d6ae0739e5764f2cfcfaa562ac5a990cbdaedca93251c5e3cf07c362371f", size = 629504, upload-time = "2026-08-10T14:30:07.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/ac/5c5b959999b6f09c3026b5dfe171575bc3121c5236ce74f495096f25b203/greenlet-3.5.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:147b25a42e5ca5be3d42356e8f608b37af715a1c196e9bf9d1627f3341adfe1d", size = 621439, upload-time = "2026-08-10T13:40:49.391Z" }, + { url = "https://files.pythonhosted.org/packages/63/2c/eb487fafc9f50ffff2b1e0b697f70fb34bf150821c08ab225aacf5583a7e/greenlet-3.5.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:1b5ed9162c0c098e0bbc2cf88a94f433c1b8926f831745252e099e5d83e17759", size = 432462, upload-time = "2026-08-10T14:30:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6acf112ed8aee499f25b4d6949820fb02ac950ff9c1f3d793bd5be0599f2/greenlet-3.5.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:27493374cff1d1b7919dc8126547f2aea582737e3046147b434b1e12de56389b", size = 1581342, upload-time = "2026-08-10T14:15:05.653Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/734e5f198888876b42d7616ff6644c075baf6b8a2412deadd6b0e1b8b20c/greenlet-3.5.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:12e2ee66c2aba86133f10fd99d6a8856c6d351ffb7be0e4d52ef2cc5fbb705b2", size = 1645744, upload-time = "2026-08-10T13:40:30.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/30/1f42b88dc587b5899ee50616ad56ee40cafaf225df4fb829f10183c62a5c/greenlet-3.5.5-cp312-cp312-win_amd64.whl", hash = "sha256:49ddacd36af37735fab103846f4ee4d18a492dde72730d1699c0c8ebe30d9f18", size = 324171, upload-time = "2026-08-10T13:28:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/76/e5/4dee4d8d2e603fe5fdd7b444e63219f7b9bd852c60c6214511c7157cbe88/greenlet-3.5.5-cp312-cp312-win_arm64.whl", hash = "sha256:5f1b1ff4828cdc1aba4266aff814085d04a1d07959287219af021b838b265d52", size = 308362, upload-time = "2026-08-10T13:26:46.839Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3d/8cef5f724ec0d4add2af8961d504535ec60c3cca9e464f6d03bdba29d85b/greenlet-3.5.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b79fd2a5bc099b5e744f34c4c9a58954a5f4cb7529fb4b6e8446057d61b6edaa", size = 294730, upload-time = "2026-08-10T13:27:51.206Z" }, + { url = "https://files.pythonhosted.org/packages/88/4b/8e7aa3f514273aecff30a16ab1bac09ff54cfc7e6860fdd8058c37ff2499/greenlet-3.5.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:634cf15a233a949136879dd388e25d3296e16f3f1e217d2456797b8579ebc6ed", size = 614536, upload-time = "2026-08-10T14:14:36.589Z" }, + { url = "https://files.pythonhosted.org/packages/85/48/4e95e9dd5a8a397dc6a6345dd7f1935113d0fca4f85e89d3976da9cd988d/greenlet-3.5.5-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:499adea519f748407fc6806d20eedabac2884fd73b9f38d81236e190ba20dfef", size = 626924, upload-time = "2026-08-10T14:27:27.048Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/eaa476d6bf3816828d0d70e80dcc36bf30a058233bd889e707e693f6e860/greenlet-3.5.5-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7278591501941bb2456af102bb9cd59aab48c6cfd6e2dd68fa1290bb0c49a42", size = 632726, upload-time = "2026-08-10T14:30:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/89/5d/398a1c71fa7a277deeb376c999979de6786f08fc2d5747a0b9d6e11738dd/greenlet-3.5.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2eabb980975cba5b93a95f6f69287d05fc05ac955bfd6a320a7c083eeb52c0b0", size = 623906, upload-time = "2026-08-10T13:40:50.501Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/0cc2849ede68579291e9c59b3ab6ec1958f98681cca5b14d8fc75bf674a4/greenlet-3.5.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:4dfc7c4470354e7b09184d1a3a985761053a2fd694ddb5b5c80242afc2c8c90b", size = 434966, upload-time = "2026-08-10T14:30:03.729Z" }, + { url = "https://files.pythonhosted.org/packages/04/1b/745450fc5ea9e0cb17d840d248f284db3363de736d362c7d2d883e3eadba/greenlet-3.5.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03115c2e0a371999bf8ae616aa8d653f96641d4705c457aebaa187276e9f7537", size = 1581430, upload-time = "2026-08-10T14:15:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/d4/29/d51b296e3191bb15d3d81ec375af1909e4466c0f395d744ed475801798a9/greenlet-3.5.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4441153ffba21b90d3ca89fe3d31f5c093ae6c0bf0cfdfc98f54cde22f95b62e", size = 1645684, upload-time = "2026-08-10T13:40:32.133Z" }, + { url = "https://files.pythonhosted.org/packages/12/63/369f1a1625e64e9e31df3963c6044056e3fdfa3fa3fdba3c54ffefa6e987/greenlet-3.5.5-cp313-cp313-win_amd64.whl", hash = "sha256:95c5b1f4b3a193f8a0c2de4bfdcb48d119f7f1063941f1de1f2168051b3e52dd", size = 324075, upload-time = "2026-08-10T13:26:58.974Z" }, + { url = "https://files.pythonhosted.org/packages/45/78/649cb5c09d4d81f6dd1444e75474a7206784743283a21d24171562ac4899/greenlet-3.5.5-cp313-cp313-win_arm64.whl", hash = "sha256:1af90aa4bc129883b340cdd6957a3bc74f60528a4993bbd1f53aaebe1d9981cc", size = 308260, upload-time = "2026-08-10T13:27:50.795Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8c/080e881fa2be95ff1ddbd6994b2bab3b1a78df3b3fcab39306011764fcc7/greenlet-3.5.5-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d4a389a852e392a6366058651a20fa5ba40d979865aa81bea2ccbdc44805070d", size = 295309, upload-time = "2026-08-10T13:26:03.032Z" }, + { url = "https://files.pythonhosted.org/packages/25/cc/0ac614e6586c0e42d4cc281a5819150f4f43685744a4c5ff77139286409d/greenlet-3.5.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70b157cd319873e8b544ddc2de158f55bbd0a9b0218c8ce9332039801518e328", size = 661185, upload-time = "2026-08-10T14:14:37.867Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b9/6808725354be8ad305dfe5172377664fc9642d4fc043be246b3314cf4482/greenlet-3.5.5-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8bdfd1424abcf26832961e766570cae79efdb9599d709088c9cb6ef82b194926", size = 673419, upload-time = "2026-08-10T14:27:28.652Z" }, + { url = "https://files.pythonhosted.org/packages/eb/52/f005d579acde46c3d1cc3cab1c9f3d5708c8a3006a4120e8cf5da801afe9/greenlet-3.5.5-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d98ef6f92e67c6dbf299dbfd8facc1b0d2d9cedf91e325e73b3d0373fe4309d8", size = 677863, upload-time = "2026-08-10T14:30:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/42/2e/40c509967da7f254680826a2fa0dd22138ec79946c70b97542d74cde8b43/greenlet-3.5.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:182de51c6b572a705f2fafaab2e783bcf7d2760940229dfe73086cbae037af3e", size = 670822, upload-time = "2026-08-10T13:40:51.833Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8a/a75f8a2bdcef3c358a3147cdc9db3aa83755f0a038f766ab0bedb66f512c/greenlet-3.5.5-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:159df1942d88e8f784cbb38d6f18bdb365cd11319cfbb3e89623de2b97892d53", size = 480554, upload-time = "2026-08-10T14:30:05.171Z" }, + { url = "https://files.pythonhosted.org/packages/2d/22/c3c2eee4a8fe191d6d1d183086c56133d646024e3d70bfd414829f64560b/greenlet-3.5.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8fec3f165dfe332e490c3247c0f6c23b0bfc45f06496ad7f00ddb00e3d35e4dc", size = 1628469, upload-time = "2026-08-10T14:15:08.11Z" }, + { url = "https://files.pythonhosted.org/packages/f7/87/25babd09b94cb1f03e71db815fde463f0262e40cfbd953d58a8d77311351/greenlet-3.5.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c6ce25fee6cabc8bf22cb8b52e642cbb821be5b9aec8094d07ff03378141b8e9", size = 1691952, upload-time = "2026-08-10T13:40:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/5cc9701117ea4dc0eb7bf1f4f9b7888a6e2e5277ddfae095805ace50f2b6/greenlet-3.5.5-cp314-cp314-win_amd64.whl", hash = "sha256:7dffc5c859fe6059974df1e37d7923d654a83e2ae18fdd616994270e001115e1", size = 327458, upload-time = "2026-08-10T13:27:02.868Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/594fa2de7fae7629168a404a4305d7d7e31a5742c50a801b1839543cb93d/greenlet-3.5.5-cp314-cp314-win_arm64.whl", hash = "sha256:5e2afcfc4d4305dd715809b03da5cbe437c8984f61d8917751eb5fe4aefa3e07", size = 311146, upload-time = "2026-08-10T13:27:25.046Z" }, + { url = "https://files.pythonhosted.org/packages/24/e0/50cd600b469e5734c72709b6b1838b6bc63f307b573c772c3132d6ecfe92/greenlet-3.5.5-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:0e5a7de979d764aea1f5b6e95cf92b5b37741b9823702041f34b126e7f690277", size = 305471, upload-time = "2026-08-10T13:26:20.568Z" }, + { url = "https://files.pythonhosted.org/packages/75/a3/77acd66dfc6387b5219b2080806c0cabb73c10eb1bb44b413c40a62015ba/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fef01bd457f11fc158b130ca0027a3c365693280e8e231b65bdaf57999f39f5b", size = 672470, upload-time = "2026-08-10T14:14:39.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/71/0d178142dca3ec19f46fb2212ae73d30ad53b9d548dc64804086033a7089/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5173a72310725a74afc82c164f0e52cb8ad0de62f2bb623f24f6c0cc07d80272", size = 679973, upload-time = "2026-08-10T14:27:30.072Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ac/0d7887aa4bbfc9eba075cc428244dfc96f623478454d5ec81180d0d6bd5a/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5e9ec2e7c98e895fcea0c5cc57b2606cf86ece6d0a56578f3eb225e2af4f0387", size = 681587, upload-time = "2026-08-10T14:30:13.519Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/46eb8567302eaf787abf88d09df014e14ae3baf460af1b8b0efdbd3efcd5/greenlet-3.5.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44f08341873200ba8a60a8bc14ace3d91f1754f7fa7bc66157714a8cd420a476", size = 676634, upload-time = "2026-08-10T13:40:53.004Z" }, + { url = "https://files.pythonhosted.org/packages/4f/18/8d58ba1c429b0383e3219a3d0e0bba241d0444d8ed05b73349953c7d7c7b/greenlet-3.5.5-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:102817506f6090b5176c746a82603341a549b40e5c3d5b72a4c672228a918c41", size = 510175, upload-time = "2026-08-10T14:30:07.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e9/b88bbf5b29970cb84172dc2c32aa3e5e579ceb94c808e81c826454138850/greenlet-3.5.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d246c0db9a2513cd45f019ba178ea4d4d4705bd210ee465e2c15d76a1ab13874", size = 1637320, upload-time = "2026-08-10T14:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/6d/8c/7631ed29cc6f0392f11830076e172ce4885e70b0bc2c1bce1731176d4b4e/greenlet-3.5.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:72507285b5caa1d17904a3f7c322ca780823a54170a0e04ec3f37bcc60d4db71", size = 1697412, upload-time = "2026-08-10T13:40:34.924Z" }, + { url = "https://files.pythonhosted.org/packages/da/0f/f7dd935f9c4cb1be49098770587f54d8a78518e55c89bce86c4fb4109057/greenlet-3.5.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7805655781fb8f28a55d05fe57ed61f5f10f1892fb587673e3bb5264f28041f0", size = 331514, upload-time = "2026-08-10T13:29:20.611Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e5/681b01f8fbc1b55232822f99e8f8afeb78a55a7c76a7bf9dbdc7ccb03a6d/greenlet-3.5.5-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:c0db80fcd5b8aece93f66c64f78a786bbb6b96c5fe63ef5a5a4581ecf8bab206", size = 295975, upload-time = "2026-08-10T13:28:45.985Z" }, + { url = "https://files.pythonhosted.org/packages/11/f2/69b488cd9e7267bf4b0fe8cdebf25d8d6df680d21bdf41150d23e23d6652/greenlet-3.5.5-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b241c32f912ada659808d68e308c568baf577eebf757d15471472de0c18cfad", size = 666823, upload-time = "2026-08-10T14:14:40.222Z" }, + { url = "https://files.pythonhosted.org/packages/84/d4/d5bc2fdebbdda0c94555925ba79948b8395d75a7f6a36cc85dce5bab9f11/greenlet-3.5.5-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ef6a08349401d8eaf3cb12688ac8557de95788556b8631ef17555a4a173022c0", size = 677613, upload-time = "2026-08-10T14:27:31.543Z" }, + { url = "https://files.pythonhosted.org/packages/65/53/4e13642efc4d7ad6554ecb2242a5be42666b2e1a067323e88dfc0124a04b/greenlet-3.5.5-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37faa97daccb6d9f4c2141ce3118d023c3c5506864a7d8bdf726f665018c1f76", size = 681436, upload-time = "2026-08-10T14:30:14.839Z" }, + { url = "https://files.pythonhosted.org/packages/bd/93/542d8a3a90f3b35c6ad8bf7e56a03010287f2cafa289a5b7985b5207db39/greenlet-3.5.5-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2e3d061b8e13aec2f0441689b3c71b244a20e5d274a52cb0f7e31bd1d139552", size = 675930, upload-time = "2026-08-10T13:40:54.205Z" }, + { url = "https://files.pythonhosted.org/packages/cd/32/188447c9a468d6977d2989397226b0c6b65ab6f4cf943f931643328512fc/greenlet-3.5.5-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:b18007dc2473a7942fd157366b55f01da6fed7ce85318591005b419e0a439474", size = 487404, upload-time = "2026-08-10T14:30:08.903Z" }, + { url = "https://files.pythonhosted.org/packages/52/b5/89c9f2e8460d71101037d47a1feed11928615a5edd42370be290e0657eeb/greenlet-3.5.5-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9ab5f5b93655e77fe0d6c2dfd22b5eac751bb1f876d8ec21761b7c1fb9266007", size = 1633878, upload-time = "2026-08-10T14:15:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/b8/60/297de93f3b02ac78a5e04d32bb8bbe3080f4a73d8ed95016561463b70618/greenlet-3.5.5-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f0e5a21bd4452a88cf032fc43c4a5b307ab1380eacb63b5988f9c0317885e773", size = 1696597, upload-time = "2026-08-10T13:40:36.252Z" }, + { url = "https://files.pythonhosted.org/packages/18/25/54c6eaff4f337fb670215e89eb2d00d9499487b658e709d4b477be4a342e/greenlet-3.5.5-cp315-cp315-win_amd64.whl", hash = "sha256:469dbb0a78625642f4a626cfd0c6e8bccc0385b5e49189b6308bbe849ec88a8e", size = 327700, upload-time = "2026-08-10T13:28:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/67/67/857e88a36301caa0e029870132c2478bd55d896630321432afab03a3115f/greenlet-3.5.5-cp315-cp315-win_arm64.whl", hash = "sha256:2d57406c3efd32d7a81e17a674314e8bd00792cdab49ea3228a49aa1bfb2e769", size = 311750, upload-time = "2026-08-10T13:34:08.815Z" }, + { url = "https://files.pythonhosted.org/packages/10/e2/3144c0a116067ac1e30457b0139a94d60d1d36a86e015de68e9ac87cb3bc/greenlet-3.5.5-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:68184dfcf50ccaa8e864770fe0633a7e27250ea9329f8192ef47ee9ecfd78e1c", size = 306387, upload-time = "2026-08-10T13:27:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a1/cb4223a7e9b9f43b8807e8eb212358bfe2dfaa174a9ea2889eb1714dcba2/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ec0dc0e59dc9c61af5c47348365ccbbd7addfafe0a93b00336ff3da2907bdc6", size = 676472, upload-time = "2026-08-10T14:14:41.417Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cd/a154b4498e5d8f12ada291cfb3b8d596eadde2177f5bf09a9be699d2a446/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e604f58e35833fc46ef20302bcb314dddbfd3fcf33a4f936216d51dd678d63ae", size = 684238, upload-time = "2026-08-10T14:27:32.946Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f4/e450a68a152f819491d8c7df6a8254e761d87e6a78759268961f8c5bd4dd/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2888a3a38bc5ee5bb6c438372197152e815837e4fab7ed7a1f86ef18ffd58ad1", size = 686022, upload-time = "2026-08-10T14:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/bf/bb/b0031d260c2968a3c87deebc51d80c64e499377f993aafe06ee3b7488cc2/greenlet-3.5.5-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40239b5384f96da3963585cc6d7eaa9b56f8ae67e8d92cc82dd9e202fc847de3", size = 681246, upload-time = "2026-08-10T13:40:55.402Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/17e63d6bf3b9c9b9dbea981b7f643a71f79603bdfb4f1c3a9cf353e22aed/greenlet-3.5.5-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:1e8d9391fe77f15649589a907cef972dbbd6352ef7ff7dc0492f658c0c26495f", size = 516951, upload-time = "2026-08-10T14:30:10.907Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/da554b71ab88e649da146e1065d86a48a5c5d92e50ab74ef41b504aa7f56/greenlet-3.5.5-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:a1eaccf5c3a1d3e46dead602c72e6836731e8e245c9de6a27764567b6b62d4c0", size = 1642735, upload-time = "2026-08-10T14:15:11.92Z" }, + { url = "https://files.pythonhosted.org/packages/78/76/26a3782a051677668af9d92beaa47cd87ba9dd5072f762961144a03dd4c6/greenlet-3.5.5-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:19e4e026fe20691f333b8eb1a3bc9625eceba8c3f9d62ec5a6f8581afbc6b5a5", size = 1700925, upload-time = "2026-08-10T13:40:37.656Z" }, + { url = "https://files.pythonhosted.org/packages/28/d9/fe7baf4190c2ae71f267efb9de21b3172bb35bc0ed1ef53dd6027d658e33/greenlet-3.5.5-cp315-cp315t-win_amd64.whl", hash = "sha256:712aee154f648bde84634654bb38bb78c69ac640c37a45c9effed800735049d8", size = 331829, upload-time = "2026-08-10T13:26:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/df/af/419a4e383bd600858a9b67e9b280a60fdc383ee3f2fe5b6c0c1ef04e74d1/greenlet-3.5.5-cp315-cp315t-win_arm64.whl", hash = "sha256:7f049911ee81a16a03c33d5450d8d5867d27f596ca5fb201b86f4524e874468b", size = 315093, upload-time = "2026-08-10T13:29:34.949Z" }, ] [[package]] @@ -2071,11 +2120,11 @@ wheels = [ [[package]] name = "marshmallow" -version = "4.3.0" +version = "4.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/7e/1dbd4096eb7c148cd2841841916f78820bb85a4d80a0c25c02d30815a7fb/marshmallow-4.3.0.tar.gz", hash = "sha256:fb43c53b3fe240b8f6af37223d6ef1636f927ad9bea8ab323afad95dff090880", size = 224485, upload-time = "2026-04-03T21:46:32.72Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/d7/611e68d57e6a903c29cb33b5afec0f93b4baecacc6c6c62e33cde9eb9dcb/marshmallow-4.3.1.tar.gz", hash = "sha256:fb6b8048af08d4ab061610d5b7d3696a7e4c95337dbda880edb9f95812cabc20", size = 218308, upload-time = "2026-08-08T14:27:29.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/e0/ff24e25218bb59eb6290a530cea40651b14068b6e3659b20f9c175179632/marshmallow-4.3.0-py3-none-any.whl", hash = "sha256:46c4fe6984707e3cbd485dfebbf0a59874f58d695aad05c1668d15e8c6e13b46", size = 49148, upload-time = "2026-04-03T21:46:31.241Z" }, + { url = "https://files.pythonhosted.org/packages/43/57/4526ca0e214a3d158690e3393f6d547a2f8070f88d6388ab21d5b0aac8a1/marshmallow-4.3.1-py3-none-any.whl", hash = "sha256:e65accfbe277546df92ed7996a678c90e063e9a7c2a2f5e03f7d0b90e3768c42", size = 49219, upload-time = "2026-08-08T14:27:28.078Z" }, ] [[package]] @@ -2382,53 +2431,75 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, - { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, - { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, - { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, - { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, - { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, - { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, - { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, - { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, - { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, - { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, - { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, - { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, - { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, - { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, - { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, - { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, - { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, - { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, - { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, - { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274, upload-time = "2026-08-09T13:47:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846, upload-time = "2026-08-09T13:47:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892, upload-time = "2026-08-09T13:47:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309, upload-time = "2026-08-09T13:47:20.456Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850, upload-time = "2026-08-09T13:47:24.365Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664, upload-time = "2026-08-09T13:47:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749, upload-time = "2026-08-09T13:47:31.541Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495, upload-time = "2026-08-09T13:47:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696, upload-time = "2026-08-09T13:47:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324, upload-time = "2026-08-09T13:47:41.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466, upload-time = "2026-08-09T13:47:44.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947, upload-time = "2026-08-09T13:47:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331, upload-time = "2026-08-09T13:47:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336, upload-time = "2026-08-09T13:47:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387, upload-time = "2026-08-09T13:47:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096, upload-time = "2026-08-09T13:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730, upload-time = "2026-08-09T13:48:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686, upload-time = "2026-08-09T13:48:09.627Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727, upload-time = "2026-08-09T13:48:13.744Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775, upload-time = "2026-08-09T13:48:17.543Z" }, + { url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559, upload-time = "2026-08-09T13:48:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103, upload-time = "2026-08-09T13:48:24.21Z" }, ] [[package]] @@ -2451,20 +2522,20 @@ wheels = [ [[package]] name = "oxipng-pybind" -version = "10.1.1.post3" +version = "10.1.1.post4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/9b/eb6b06f24b765f0dcb7d85c2834541fae9ce6f0be85e2c8eab7a636f1ec0/oxipng_pybind-10.1.1.post3.tar.gz", hash = "sha256:ca9d9cfa015a1bc0fcb341eb5e7c08ef8cb6577917d6d72eee70a10087e076b2", size = 187238, upload-time = "2026-07-03T22:32:24.849Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/05/4ef6bcdf0cca970ea9cd5484e5cf74a7e2f1878f14bf367ba131fd8e1a4d/oxipng_pybind-10.1.1.post4.tar.gz", hash = "sha256:45a68eeface94e202abf2ed85db02234aae0a1dcb36ad6fe04a5e62ac657c418", size = 190104, upload-time = "2026-08-08T11:31:50.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/0d/b0c330b7df466a34bd1450d8a948c38b2b1c5a16ca180060d0eb71b2204a/oxipng_pybind-10.1.1.post3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:04849d2d0f2014836c9e5031b4806684e5c8671e1f32aa47fb1995e1212ca3b4", size = 516855, upload-time = "2026-07-03T22:32:08.104Z" }, - { url = "https://files.pythonhosted.org/packages/c2/72/45ff201144278aa0a41a4acb38de46b0cad7fda2bdb048987e6f08db7670/oxipng_pybind-10.1.1.post3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:6fe7a719572d0135c3b512c877a2104c0ff287d3eda9e114492649ad6506eef5", size = 487366, upload-time = "2026-07-03T22:32:09.807Z" }, - { url = "https://files.pythonhosted.org/packages/8b/8e/1a829ef08a7c89e788988f7120aeebe859d6a43c99bb860a02858aa8d0a0/oxipng_pybind-10.1.1.post3-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2dfc6b40abebd58ee643a09d084c7977f9951ea3e56515f8cb9c96a4c09e3975", size = 531147, upload-time = "2026-07-03T22:32:11.679Z" }, - { url = "https://files.pythonhosted.org/packages/79/04/33c4272d73aedc99bef561091623a43d62f3a6de942e47e71943cd652f0e/oxipng_pybind-10.1.1.post3-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ed13db1dcc530ebeeb0d36fb422f945017edf4dee50304182c23665ec9d466be", size = 559186, upload-time = "2026-07-03T22:32:13.366Z" }, - { url = "https://files.pythonhosted.org/packages/16/bc/819547edcd3cab84517672d214dbeb3caa094bcbf4183c1888bbc9c68b1a/oxipng_pybind-10.1.1.post3-cp310-abi3-win_amd64.whl", hash = "sha256:dc7313b75855047499318897a58d761e5883c6a98fa119010ab2a100fc1b785e", size = 461254, upload-time = "2026-07-03T22:32:15.102Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c0/c15f18e7a1b5528e84d453c12c54ee95754a12452b3328c0f5195789eea8/oxipng_pybind-10.1.1.post3-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0de38c64cef6551c94481f9953e2edddd8d9c7ae5387103ff9e0e3c86bfd29c4", size = 515594, upload-time = "2026-07-03T22:32:16.737Z" }, - { url = "https://files.pythonhosted.org/packages/37/91/acf8bf94a762677fe0101e35c45472ee173717ab7395654b947e9a52b4a5/oxipng_pybind-10.1.1.post3-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:f20745f1c9c7e88ab2056442a2184d3ab619c9108e3771169aeb559b85046b9a", size = 486006, upload-time = "2026-07-03T22:32:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e1/338fccf1a0991889bd72b2e346f46935e95abf19e7002a7fc7132c4b5a39/oxipng_pybind-10.1.1.post3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a2d473e6333f90f02d1b400c890edac745d1f95cb27aaa6521a77a9a14a2525a", size = 528312, upload-time = "2026-07-03T22:32:20.167Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/5bd5b6510567c64563967d38d36b9d7aec62df48f3a17299f6fe63e338db/oxipng_pybind-10.1.1.post3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b5b441203f1675b42d0049aa000cc1c5fd672c1ae8604f86c89a880ba60a9751", size = 557812, upload-time = "2026-07-03T22:32:21.937Z" }, - { url = "https://files.pythonhosted.org/packages/11/91/5b6c5661dc6abb2df1b7ed3932c483c9c842aed5fd6148644a8fb48bfe8b/oxipng_pybind-10.1.1.post3-cp311-abi3-win_amd64.whl", hash = "sha256:8de00b373808def0f9090670d429ae3c2e05fefe1b0307a6e60290355f44394c", size = 460466, upload-time = "2026-07-03T22:32:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/bb/27/9815b87366d4a040169ee058f55c427df7aca2ce308d1eece28fbc8bb7bf/oxipng_pybind-10.1.1.post4-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:302c7b4ab55c6f25e4a910d6f86f300a7ec167d4f6188f29fc6fdd15f527e2c9", size = 517491, upload-time = "2026-08-08T11:31:33.978Z" }, + { url = "https://files.pythonhosted.org/packages/4b/58/689d430b0f230729d1ed5cf4904d6d599d220a1300fa33c796db7f2da2a3/oxipng_pybind-10.1.1.post4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8ad3d2d6173b9ec96f5b78e28fdd51c9113a698d1281f19e599b635693abb0f6", size = 485649, upload-time = "2026-08-08T11:31:35.867Z" }, + { url = "https://files.pythonhosted.org/packages/6b/45/a792ad1fbd171498abd0979572b6ce96d50b3b9b5a9f78944debc8e6ef27/oxipng_pybind-10.1.1.post4-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:653e5b624eeb412e07911a5c35685f3ddc86730b8c985ee2bd876bc14c73775a", size = 528420, upload-time = "2026-08-08T11:31:37.388Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a4/5c94dd33f1b227cc7badd1de8b8c94b8db76dab414f07cea198a13e117d1/oxipng_pybind-10.1.1.post4-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:025b2670f5196c7a9d46f61fba9aa43877ea5c3e4c93d9393c024e1f9f40af4b", size = 559397, upload-time = "2026-08-08T11:31:38.917Z" }, + { url = "https://files.pythonhosted.org/packages/43/9b/76f427592587b0c323e14ebdc0c901f1683cd8fb26b25ba3de693b57c561/oxipng_pybind-10.1.1.post4-cp310-abi3-win_amd64.whl", hash = "sha256:977937c79f212570d98222fd398c704f76c9bde0ed4fffe5208f41c5fa97ca16", size = 460809, upload-time = "2026-08-08T11:31:40.323Z" }, + { url = "https://files.pythonhosted.org/packages/f7/8a/6f382df81f14771790995c9ff76f7daf2fc9d9014e102a346aac9f170005/oxipng_pybind-10.1.1.post4-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2eee366cdc6ce922c6d8eefffe5f3b650fffa0a49b5da0a8edd81ea82d0e9415", size = 516530, upload-time = "2026-08-08T11:31:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/08/98/3254996d3739e37dd590d0c266eaaa17d0a4d8a4fa0d024e99c32d5f6ec1/oxipng_pybind-10.1.1.post4-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:3261a017d4f3ba531bf5796e37733e2c1e0379c294aa09d2146567a2644d6b5d", size = 484759, upload-time = "2026-08-08T11:31:43.698Z" }, + { url = "https://files.pythonhosted.org/packages/b4/99/094c4e74bebe1830f135845a787b29c0839003988d2a192a54c90b3ae894/oxipng_pybind-10.1.1.post4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:979df97b76759d88bea1aa65cbae34e501351ecb4bb6220c54186afd222cb65f", size = 526783, upload-time = "2026-08-08T11:31:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/a8/88/23e8f0d1d619bb0b578df2dc3e2eaa832d04b757b26f26df7bf1aa6677a8/oxipng_pybind-10.1.1.post4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2834bf1f8a995bc2208d315e795f6123602a845e808f99c4b3f03018516c2723", size = 557262, upload-time = "2026-08-08T11:31:47.343Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cb/bfe0c02b4af86b1727d4ad4444e365bf5ee87ae83e6276e09b88b55373a1/oxipng_pybind-10.1.1.post4-cp311-abi3-win_amd64.whl", hash = "sha256:3c5ff67a81b5350e28c96e1b8179aade3909caf613aa8158af7fc21676a62d39", size = 459818, upload-time = "2026-08-08T11:31:48.888Z" }, ] [[package]] @@ -2686,11 +2757,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.0" +version = "4.11.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/98/0bf930c4f97d0266b58a89e36c015f56232c52b5d2f207215d48cca9e8f7/platformdirs-4.11.2.tar.gz", hash = "sha256:3a2ae5fca3520a01ab1be8b45613537f52ddf5b5f6f53d88233892dfbf0cd82d", size = 32716, upload-time = "2026-08-10T15:48:06.092Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, + { url = "https://files.pythonhosted.org/packages/49/e2/4e6eee633809c376c024821b91ade709cbfd040ec53939ffbcc292aa7eee/platformdirs-4.11.2-py3-none-any.whl", hash = "sha256:7f89089b6ea71bda7962953edcf784b2e2d9d285b40ad88be2bb75c6e9d82ab4", size = 23361, upload-time = "2026-08-10T15:48:04.855Z" }, ] [[package]] @@ -2970,19 +3041,20 @@ crypto = [ [[package]] name = "pymupdf" -version = "1.28.0" +version = "1.28.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/e9/6d6c5d6c0a3551bffd47681a6240caf941727f195b45593cf20ab36f018f/pymupdf-1.28.0.tar.gz", hash = "sha256:e53f3567403a92da15caa9e7ae0164327fff48817e9f40175367fb9de524258d", size = 87637751, upload-time = "2026-06-29T09:08:47.547Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/fb/b6761fa2d5266f2cdb24c3b91f4023070ab7848381417678e7a289a1d52a/pymupdf-1.28.2.tar.gz", hash = "sha256:5e0be7908a715aa20333caddd73f1d6f01e4cd0c26e869fa2dd0b7f344da2249", size = 87903557, upload-time = "2026-08-06T21:43:23.321Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/b7/88043e38cc7529de070f0c9bd267fa258035cca0b4ad5260536b994594a7/pymupdf-1.28.0-cp310-abi3-macosx_10_15_x86_64.whl", hash = "sha256:892b89ba88e8f98b53133b62877a9dc9b5e7dc6a4aeb837b612db56a8d2e03ac", size = 24597385, upload-time = "2026-06-29T09:03:30.608Z" }, - { url = "https://files.pythonhosted.org/packages/33/f4/23775bbda0781b61fc398cc75079a2b0e64696d8fcf93271748883e9627e/pymupdf-1.28.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:4d692dcf44d3566ae96bc6f6346c6ad432274a29ba617bf7a9fe18009e24adb4", size = 23828292, upload-time = "2026-06-29T09:03:46.129Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f5/bf75fc7a415722f8b33662054f82d88520c0cbfd4c36d0e08aeaec605e49/pymupdf-1.28.0-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:47a5c29ed4eb0744de9c4e37bb49b1259b18d4d75fcc8a7c130f7c9fa15956f6", size = 25045507, upload-time = "2026-06-29T09:04:03.86Z" }, - { url = "https://files.pythonhosted.org/packages/58/69/5d12c9f1f2d76f28383d6110a069c79fbfced5a4f97bb1ee6e8354f52bb7/pymupdf-1.28.0-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44f0973f5e5edbaec95bc34b64e71d1959d4ee90b1328de1b4f4f5b4fa78673f", size = 25716599, upload-time = "2026-06-29T09:04:19.367Z" }, - { url = "https://files.pythonhosted.org/packages/4d/b4/ec0e017bc42857cc86bd651441dbc41cc18be48d4698ecd27aac491e0c9a/pymupdf-1.28.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4d61ec323a706e153a12e262e51febfb43eeaa20977785ace135d18d48bcdc83", size = 25940489, upload-time = "2026-06-29T09:04:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/06/86/f831fef09013f33b3c9c09fb3923f2ff53e1e437f6ace14b8ae46392f558/pymupdf-1.28.0-cp310-abi3-win32.whl", hash = "sha256:caea2b3b67347fd79e5d15ed7929b0e886aac594ea228073b6d39de0078189da", size = 18489703, upload-time = "2026-06-29T20:50:30.599Z" }, - { url = "https://files.pythonhosted.org/packages/2e/5d/1a03f53eb0449900469335fcfc742ca28e3ba159b7d650e0921d50b8b308/pymupdf-1.28.0-cp310-abi3-win_amd64.whl", hash = "sha256:e01e90fd86abfeb37ceb921eddb951f988a11d45ff6ce6b7664f2039849068ec", size = 19773102, upload-time = "2026-06-29T09:04:49.773Z" }, - { url = "https://files.pythonhosted.org/packages/72/f6/1e52ce243ca792254f6223b4017c5667194c146ce9b88baf37bc5eb3d1c9/pymupdf-1.28.0-cp313-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:74c6d00ba2a9aad3a635db73b07c15db462b480741d831a34a75a56535ebc22b", size = 18357011, upload-time = "2026-06-29T20:50:50.353Z" }, - { url = "https://files.pythonhosted.org/packages/62/b1/46b5b3d8ef3cc71114667cf10c4d8b33f39af97253af32e9a0986775b638/pymupdf-1.28.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b3e1399c7a64c6914239116a369efcdaac4cfb9e838bde2656d7accc4a85c72d", size = 25753599, upload-time = "2026-06-29T09:05:09.398Z" }, + { url = "https://files.pythonhosted.org/packages/b4/51/550c9a75c4ff3245cb4ecb7bb95cbe2ab7374230b8e2b7a1f7259444150b/pymupdf-1.28.2-cp310-abi3-macosx_10_15_x86_64.whl", hash = "sha256:5fc315b425ff1f7afdd1ea2f348205cb19b806767daae7ce4d64115799c2bae1", size = 24645079, upload-time = "2026-08-06T21:37:25.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/3591f781b417b382a8487a2356e927acfe858b1043bab0ec47f6805bb109/pymupdf-1.28.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:7113846b35dbf0a033f088e4f4fb543dabeb4b0b12c112966a1ca1ee2d5eacae", size = 23875605, upload-time = "2026-08-06T21:37:40.369Z" }, + { url = "https://files.pythonhosted.org/packages/d2/86/4a68f080b71b46802178346af46486e1697508e760855ff5f3b218a6dff7/pymupdf-1.28.2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3050a233dde1211efe89ada74e2add6238436434159f46097a1423aad2842545", size = 25095554, upload-time = "2026-08-06T21:37:58.485Z" }, + { url = "https://files.pythonhosted.org/packages/c7/06/dace3e27af26690cb20bead80dbac42941b0841eb689b8aabbd67dde16f0/pymupdf-1.28.2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:397d6715c1f0df7548a92d0afd8ce370fc48fa47aeefac16be2bc04a16a8227f", size = 25762500, upload-time = "2026-08-06T21:38:17.438Z" }, + { url = "https://files.pythonhosted.org/packages/e5/61/4146dfa1d8172a1ce8d59f0eed94896ddefb8deb2274534d0522fbb8abf5/pymupdf-1.28.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f89fb2d86d07d643a269f17a093105057e20c79c1d06c103b53600067b6d2b01", size = 25986309, upload-time = "2026-08-06T21:38:35.472Z" }, + { url = "https://files.pythonhosted.org/packages/52/60/1fb6e64676f7500ebe89054b9e5bbbe14d3101c92d5f1a40ac9a35227673/pymupdf-1.28.2-cp310-abi3-win32.whl", hash = "sha256:530ef543a3885b3b81cb72a854e7c5a625a9233201221132bb6c31698c6a2bdb", size = 18525353, upload-time = "2026-08-06T21:38:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/4a/61/d563bbccba262f9dd6d2d35ccb72593648184d886188efb12d9ce8f34dd6/pymupdf-1.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:ebd244918798502d7b4504c90410d1711a4d7675a32584ca30f1bab419ecbffe", size = 19826532, upload-time = "2026-08-06T21:39:00.213Z" }, + { url = "https://files.pythonhosted.org/packages/e2/93/08f404a1f0155fe24137cf2d3aabd3e2b4b08c62053ed89c60f2611be3e9/pymupdf-1.28.2-cp310-abi3-win_arm64.whl", hash = "sha256:ffe91a24edc75c80da2a4b62f50fc0f54632d34fc8fe4cbc48e5c7ff07cf8fb4", size = 19759252, upload-time = "2026-08-06T21:39:12.937Z" }, + { url = "https://files.pythonhosted.org/packages/58/8c/d897dcd32a25b58186c968b15ce4324ca029e9d96460de12325314e390be/pymupdf-1.28.2-cp313-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2e1b574c0fd2cb238021033fd3c0f9c4388816638df064e4bfb56d9d81736dc8", size = 18399403, upload-time = "2026-08-06T21:39:25.008Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f1/de34a1c53fe2bf8c6e71db84b0ced782d408970c9810d2b456a2ae96814c/pymupdf-1.28.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:fd481ed48bef56305c41fb7e05a055c03345c899c7b101dad086258b438f8168", size = 25802333, upload-time = "2026-08-06T21:39:41.426Z" }, ] [[package]] @@ -3112,14 +3184,14 @@ wheels = [ [[package]] name = "pytest-django" -version = "4.13.0" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ae/5ed1ab2d12090bd794392d798c14d092f0470f6446eaa6d2af4183f4aa2b/pytest_django-4.13.0.tar.gz", hash = "sha256:2316e3f63e3bc799deb4212830e39b2ef1dcc3b2d8688060f1d0217db6a56ebe", size = 94200, upload-time = "2026-08-05T21:36:23.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/f6/3851312120c2bf2f19cafff931e75059aad1ba670703cd751e2fde9bc942/pytest_django-4.14.0.tar.gz", hash = "sha256:26787dd3f422cfbab8f55b80a776e2edea7a11092cb74e960bef1312515708ef", size = 94700, upload-time = "2026-08-10T14:13:08.319Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/88/52db7a840fdbd74934f8df11b7450c9919ac00f61c02e88827f3d58180f7/pytest_django-4.13.0-py3-none-any.whl", hash = "sha256:09374c12d947ca0f99e33726bcb720a6d0124f67058e1130586372bd109493e3", size = 27066, upload-time = "2026-08-05T21:36:21.763Z" }, + { url = "https://files.pythonhosted.org/packages/9c/03/850bffad2b581c440ca51c039d74504d5a422c94bda0bdb8a8ba5068d48b/pytest_django-4.14.0-py3-none-any.whl", hash = "sha256:c533b08d89cc675efcd5398eea270b34547e35f9a3608e2c9748dd88428ea187", size = 27067, upload-time = "2026-08-10T14:13:06.998Z" }, ] [[package]] @@ -3749,27 +3821,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, - { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, - { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, - { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, - { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, - { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, - { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, - { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, - { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, +version = "0.16.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, ] [[package]] @@ -4108,27 +4180,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.68" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/8d/d5658a08ae59f0aac8cb542a0f23c1db7072b177915e5f91f645623e2c8c/ty-0.0.68.tar.gz", hash = "sha256:789b5c7d31ac188c3c0fb5c90ed10f969f9cfac67f6acbbd7e5c80f7b37ad661", size = 6557062, upload-time = "2026-08-06T01:04:27.941Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/57/8f27d6866e3ba50c84c3e3d208c707647c0d68de4aa7aea18bda57d0c4f6/ty-0.0.68-py3-none-linux_armv6l.whl", hash = "sha256:12399eba3b0c6d81a2f6ace177bcdfbb7163a78673fe6048b044175d2f49222f", size = 12389838, upload-time = "2026-08-06T01:03:50.012Z" }, - { url = "https://files.pythonhosted.org/packages/a9/86/ba58ba672d176cc10266492064f2561f9afc1dd614058fa21350a1cd121a/ty-0.0.68-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4b98ac03e74265b90feaf8cd01bfb5be71b2a5ca9cfbd178a0c5c925604d49f8", size = 12022356, upload-time = "2026-08-06T01:03:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/31/a4/c6d84d017d8714e33d8531297b5d169763bcbc02d2c0e8f02ce4b77589bf/ty-0.0.68-py3-none-macosx_11_0_arm64.whl", hash = "sha256:12fb0ada6e8a1a30ae5d86fb28a24596e6b72bfb710dad76e9d1703fdd0f63a3", size = 11522087, upload-time = "2026-08-06T01:03:54.793Z" }, - { url = "https://files.pythonhosted.org/packages/a8/e5/6b58df41be1720171025841ce45c0f15d07612ff2ed6f80c9f16869d9e2f/ty-0.0.68-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac5e7dadab73c20b354fd20dda71ec2e8a97fbcbd9c14f1bc101dbcf2a1534c7", size = 12097819, upload-time = "2026-08-06T01:03:57.207Z" }, - { url = "https://files.pythonhosted.org/packages/69/df/6345493a8394a9d5afe177c5f6cb27358a41c278409f13721a223c180be9/ty-0.0.68-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d19bbcbb5e366ade22f230c97b80f84bb9738e8e516c05d5997f13c7e5a6434", size = 12172745, upload-time = "2026-08-06T01:03:59.579Z" }, - { url = "https://files.pythonhosted.org/packages/9a/56/bb3955d926e397cfe5aece433e270663878a4580b291be8e53bbb8cb2103/ty-0.0.68-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d50d5d35a856d9c15c56b5fc15a2f9a3ea34c450ff674a4f2da1240de7212d65", size = 12909444, upload-time = "2026-08-06T01:04:01.679Z" }, - { url = "https://files.pythonhosted.org/packages/0f/28/fe9e1a57907f85a60dc695953748d6090a5defb11c65c2cbe03861cc7d88/ty-0.0.68-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30110eabd84dcea99966695ed23a0cea053c35a0ad7182e620586ded4011cac6", size = 13388865, upload-time = "2026-08-06T01:04:03.804Z" }, - { url = "https://files.pythonhosted.org/packages/2b/16/8559df373ea54772b11253c8b33573120afaae7553b8b30b7b6300f9fc59/ty-0.0.68-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2621261f916958bc3326caf6b568b4a5ad12a8ee537e147811dfb26cda425813", size = 13127664, upload-time = "2026-08-06T01:04:06.037Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8d/ecaf8c14e89bf7eaf36a2f3baa3521abdd08cc3d8a1e84a0b805b7f29ca1/ty-0.0.68-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21339febe1306b48e2929743a9f89bc04cd0caf017d1d79538b6aa590cd7a81", size = 12689349, upload-time = "2026-08-06T01:04:08.281Z" }, - { url = "https://files.pythonhosted.org/packages/00/67/7eee09bfbbdd7e660474fd6a4166bc6740d61e044a96b519b0dbf04e0616/ty-0.0.68-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f719654f9050ae38561d671a2ddefee3d85e972fb9f9e9c27452f33bae88e41", size = 12940744, upload-time = "2026-08-06T01:04:10.427Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d3/4ec799ae7a3152321c45fdaee8d73f71160e10a0f510ad135fa381555487/ty-0.0.68-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0f455279f00ad0cda819311e67bc7ba3102b3b0a66949de5ccf937b60827d7a0", size = 12045945, upload-time = "2026-08-06T01:04:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/51/db/26e51c8170c3750c540ff811cb54dd53f498237e28ad0e679320fd5c2444/ty-0.0.68-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d2a4878c633d1be047551dcb9f735ee245bace3f2f4cba30e462d49a1cd6bd5", size = 12185731, upload-time = "2026-08-06T01:04:14.902Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/1403120dcee3f3cc90e5a02e7263e82d82081ee0f36834042a30bd5e1a51/ty-0.0.68-py3-none-musllinux_1_2_i686.whl", hash = "sha256:72c79892f0b9d1ea05f72c7100cdbe57851b2bf9cc5a6541a506e99fead525c3", size = 12448897, upload-time = "2026-08-06T01:04:17.006Z" }, - { url = "https://files.pythonhosted.org/packages/76/c1/507a176fc2c1d90df7b80494ef8567ccedcc7a9256d6839cd0549fb752a2/ty-0.0.68-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e7a7e2b80f4cedb72b996d2658e8e6ff24d286273dadd28048de6723c5fc26cd", size = 12787807, upload-time = "2026-08-06T01:04:19.338Z" }, - { url = "https://files.pythonhosted.org/packages/42/bc/aa5c1d3efcc5b02c8b1ffcabb136c59f1c77780c98867ea50ee2f72d4ed8/ty-0.0.68-py3-none-win32.whl", hash = "sha256:3c541c54f6ea9a571dc2eb83c01645582df2f36c4de5e978e2f8ae75c21cca04", size = 11762964, upload-time = "2026-08-06T01:04:21.382Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b4/cbdb5d54341bde1fc39f694e2517402cd8e4451cd3fe8615092646c51e52/ty-0.0.68-py3-none-win_amd64.whl", hash = "sha256:674d89e294a2de8bdf1e4133d41866df2cc3b591d971fa87ae18041707b44f16", size = 12844190, upload-time = "2026-08-06T01:04:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/24/e5/6b6a7d7bc9fdcf6bd00bf2ad91e1eb2d1344e05a396d028ef67e49e3e204/ty-0.0.68-py3-none-win_arm64.whl", hash = "sha256:ba8a886d27dff2da86521a073373e6f68782a69f6ccabf41e83f84a51f210563", size = 12178880, upload-time = "2026-08-06T01:04:25.776Z" }, +version = "0.0.70" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ed/38a8ab52f1d7c3ed701442a31b23ba774cbc5d6909f2c00da9e1f3c590f9/ty-0.0.70.tar.gz", hash = "sha256:a01bebc128b4081c16002965d906fccb21323d69bb709b9108c1f2406bcffced", size = 6601156, upload-time = "2026-08-10T23:20:26.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/c5/ddd6cc5657fd3da85591b264f4dabb1ce5e5b535e73fd5174991c294978b/ty-0.0.70-py3-none-linux_armv6l.whl", hash = "sha256:4fb2d2e55e2160c07152361be2e1a26fdd4f6261055731317d5972daf1749935", size = 12537215, upload-time = "2026-08-10T23:19:43.512Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8a/acc9b34331cde81e0d63cda92d4db9e4042def5b1efa7d47431ff1d30d17/ty-0.0.70-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9b04d4c21cb029501c05598ca21e9c0829c0b2e35a7ceba1e5b78014c1e8104d", size = 12149070, upload-time = "2026-08-10T23:19:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cd/51ac2708f4d077058a0bfeefa40d630883c5ec7a82fd2c15c86519140235/ty-0.0.70-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c605ebf2643f5e64ec4bcb269640a1aa85966d29fa888fb746039c28570369b2", size = 11625729, upload-time = "2026-08-10T23:19:48.535Z" }, + { url = "https://files.pythonhosted.org/packages/b7/99/afc4fe7e630100dc782ff0cdc8c59c01acfb05299551ff0ef49c93814320/ty-0.0.70-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c38ea76e12909c29e18fcd295ff223b63f3701c1d1c34db2cdc9736340b9de2", size = 12204810, upload-time = "2026-08-10T23:19:51.845Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/8cabc3c8ad4c3a02e585ecff12a71ff8e5881f8a00ee71745fd765201da5/ty-0.0.70-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1ee6bf4edaabf7bd0307f0c0f9ddc2204df0390820fe89f6a9de65f07722aba", size = 12308114, upload-time = "2026-08-10T23:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/44/7e/bb4e552ecd68bb490bae9368ef323e3d0c79ef85a811857139a2d0f59ddb/ty-0.0.70-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5eef3e11d7d6b800ef66da0cce8ea24f8be804d1ebf359683426cfe848729bf8", size = 13072240, upload-time = "2026-08-10T23:19:56.872Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c4/1861e1d554e5b6e0d11b3a9f36d40c372253f44a3c4e56ad4bf840f2801e/ty-0.0.70-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95e4ae7c2599197c9d89652e49ca344ab224f7d12376e6ad8beb0587e8ff83f", size = 13497678, upload-time = "2026-08-10T23:19:59.191Z" }, + { url = "https://files.pythonhosted.org/packages/77/22/3b22442133e8f641485e59e463a070a31184db0d6456851f871db8f59763/ty-0.0.70-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06aca758d1e0016c0a1f57fe9d8de7a21ff83f692306f747a0d97f32de24e27f", size = 13232510, upload-time = "2026-08-10T23:20:01.456Z" }, + { url = "https://files.pythonhosted.org/packages/84/b8/911f1e6885b5485b6e1d29aaab19ce5e6deeb3e931b6ad82296da4f22051/ty-0.0.70-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d81825524f1b57ecbcb5fce7d61fb159cb4837a6167a4569309c9fa7fc15a77d", size = 12817128, upload-time = "2026-08-10T23:20:04.275Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a6/9affc3ca11c32d75b348a66144ab83335fa7fa7100b1581356725e15a668/ty-0.0.70-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3287dfb09f7320ef28f114f5f9aae5f697b7b1a6ee37fb2a4a3be94481c1b4f8", size = 13089208, upload-time = "2026-08-10T23:20:06.574Z" }, + { url = "https://files.pythonhosted.org/packages/f0/cb/4776108ea08ec4013b71375b101be9c8a632967cd10f24abbdd4664d66f4/ty-0.0.70-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9fb1877f6401cdaac4db46c5bf762f327482ba5f891420ea462ebd15d0de4185", size = 12148890, upload-time = "2026-08-10T23:20:08.827Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/814e71f698d9231ef6412bd2c1499d81c6f86f66c2764e952c991e2d6da5/ty-0.0.70-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e78f4997dbb0db2d2270210eb3694466206e5b3a11985e24148770e702158a25", size = 12327084, upload-time = "2026-08-10T23:20:11.023Z" }, + { url = "https://files.pythonhosted.org/packages/3b/69/9f34bba534c5d1ace391d300ead4f1971f4348c5459e66dc466cfd60661c/ty-0.0.70-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cf758d3b2dad910c9b1d22d2d62fea894b5bc9acd3c00365e8a73ace6090a452", size = 12604372, upload-time = "2026-08-10T23:20:13.366Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ff/abb34674517b29a8e2489a39c4e01930fcf6aab993a7d6b33aa97f119117/ty-0.0.70-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0d338761617279a4fb6a83e7fad7e21126394637b8c45e13e196b2bb675f39b2", size = 12917800, upload-time = "2026-08-10T23:20:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1a/f8289572f4fad5cb16c883b94638548166bc78cf3eb569f0cd9199eb10a0/ty-0.0.70-py3-none-win32.whl", hash = "sha256:a45642cf09dde91f0a3ce9b9e6fffda9779ff69d73f7347c18acf4fb45007c07", size = 11921482, upload-time = "2026-08-10T23:20:18.244Z" }, + { url = "https://files.pythonhosted.org/packages/17/ae/8739d7618b4670c3ee4f52d641d53be87928bd121d8bda9c0e3450500b75/ty-0.0.70-py3-none-win_amd64.whl", hash = "sha256:33e7941a926cf39b82553911a59a6ed68ec98c3d3d5a415df633f4d1cd051e6c", size = 12986994, upload-time = "2026-08-10T23:20:20.53Z" }, + { url = "https://files.pythonhosted.org/packages/f1/44/2bc3301ba4356ad8866daac9f8cfb3687953e52d076ea204f113b9304c42/ty-0.0.70-py3-none-win_arm64.whl", hash = "sha256:0d380f735d52b1d4b773193f8f5c58c065725eab1ebb0008d38a7b830de14f47", size = 12301082, upload-time = "2026-08-10T23:20:23.816Z" }, ] [[package]] @@ -4157,11 +4229,11 @@ wheels = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20260716" +version = "2.9.0.20260807" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/9a/aefb9f80ff79641d36149352afe66ca835c5e6894deb518418f786f4b8ad/types_python_dateutil-2.9.0.20260716.tar.gz", hash = "sha256:1d55d1c3024bdb4861bb6a6622c9ec800c433d87bdc5b16fb84cdd0eed4ef2cb", size = 17516, upload-time = "2026-07-16T04:48:06.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/4e/b3fa538f9cb38dfece0d6ccf6d3d0d925bdedb144fb9c8129dfc007cd003/types_python_dateutil-2.9.0.20260807.tar.gz", hash = "sha256:e0b8a90d464c8684c66b7b8e4556d9074afdddcc56ca45323f0987134f9e7034", size = 17618, upload-time = "2026-08-07T04:17:13.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/85/cff97326a81e7e8877803e78e3ea56c840b85bb811934b9c936f7c40f185/types_python_dateutil-2.9.0.20260716-py3-none-any.whl", hash = "sha256:1ae41d51a5c5f6bbeeb7f1f34df7086d55ff1605cf88d2ed71a7a276e1a7794e", size = 18443, upload-time = "2026-07-16T04:48:05.793Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5e/3715867caea2f4cea56ccb04c851cde23ed063449c3b004c7a047f20dd48/types_python_dateutil-2.9.0.20260807-py3-none-any.whl", hash = "sha256:54aa3707350ed7a9cc0776fd2f6739679d6967d11b40150985e81edcb86df4db", size = 18486, upload-time = "2026-08-07T04:17:12.504Z" }, ] [[package]] @@ -4187,14 +4259,14 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.2" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/bc/4eae18cd40c65798a16267572ba346c11f599d44b01603dbd843342042bc/typing_inspection-0.4.3.tar.gz", hash = "sha256:c5f9ec1530b5c1e2c9bc34a84d9a3466ed1b2f3f2fa9f901368d9c5596210e4d", size = 76711, upload-time = "2026-08-10T09:39:18.063Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/42/f7/7a3935abdebd5cf18705a5f0335dd6a3a18bef3baa7cb9edc3b6b9922cc8/typing_inspection-0.4.3-py3-none-any.whl", hash = "sha256:5f42b23858a91e0b4ef521f5418f03a0da3c9216fd2995ef5e73463100e676cd", size = 14693, upload-time = "2026-08-10T09:39:16.693Z" }, ] [[package]] From 2b9051572aff30256f0f40160369d824b6892b6d Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 10 Aug 2026 18:52:16 -0700 Subject: [PATCH 78/82] fix radon complexity warnings Three items `make complexity` flagged, all pure refactors. AdminOnlineTagStartView.post (CC 11 -> C): three near-identical "request value or stored default" blocks collapse into a _FLAG_DEFAULT_FIELDS mapping plus a _resolve_flags helper. test_telemeter_privacy._vocabularies (CC 12 -> C): split by vocabulary source into _choice_vocabularies, _identifier_bucket_vocabulary and _tagging_vocabularies, with the literal and OIDC sets hoisted to module constants. The same string set is checked; the walk is not widened. test_onlinetag_session_manager (MI 17.37 -> B): 916 lines in one module, every function already rank A. Split along its seams into the scan pass, prompt resolution and credentials, with the doubles and the comic factory moved to tests/onlinetag_session_fakes.py. All 31 tests come across unchanged; the move dedupes the BulkTagWriteTask filter into write_tasks() and the prompt dict into a _prompt() factory. test_onlinetag_tag_pass now imports the fakes from their new home. make complexity, lint, ty clean; 815 pytest + 371 vitest pass. Co-Authored-By: Claude Opus 5 --- codex/views/admin/onlinetag.py | 45 +- tests/onlinetag_session_fakes.py | 172 ++++++ tests/test_onlinetag_credentials.py | 74 +++ tests/test_onlinetag_prompt_resolution.py | 327 ++++++++++ tests/test_onlinetag_session_manager.py | 709 +++------------------- tests/test_onlinetag_tag_pass.py | 34 +- tests/test_telemeter_privacy.py | 80 ++- 7 files changed, 736 insertions(+), 705 deletions(-) create mode 100644 tests/onlinetag_session_fakes.py create mode 100644 tests/test_onlinetag_credentials.py create mode 100644 tests/test_onlinetag_prompt_resolution.py diff --git a/codex/views/admin/onlinetag.py b/codex/views/admin/onlinetag.py index 8436c237e..06989ca94 100644 --- a/codex/views/admin/onlinetag.py +++ b/codex/views/admin/onlinetag.py @@ -3,6 +3,8 @@ import contextlib import uuid from dataclasses import fields +from types import MappingProxyType +from typing import Final from rest_framework.response import Response from rest_framework.status import HTTP_202_ACCEPTED, HTTP_409_CONFLICT @@ -34,6 +36,16 @@ from codex.views.admin.identifier_parse import parse_identifier_input from codex.views.admin.tagwrite import FilteredComicPksView +# Boolean scan knobs the request may omit, mapped to the field on the stored +# tagging defaults that supplies the fallback. +_FLAG_DEFAULT_FIELDS: Final = MappingProxyType( + { + "delete_original": "delete_original", + "merge_all_sources": "merge_all_sources", + "rename": "rename_files", + } +) + def _configured_sources(defaults: ComicboxTaggingDefaults | None) -> frozenset[str]: """Which online sources actually have credentials configured.""" @@ -123,6 +135,16 @@ def _comic_pks_error(self, comic_pks: frozenset[int], *, pinned: bool) -> str: return "Tagging by id requires a single comic." return "" + @staticmethod + def _resolve_flags(data: dict, defaults: ComicboxTaggingDefaults | None) -> dict: + """Take each boolean knob from the request, falling back to the defaults.""" + return { + key: bool(requested) + if (requested := data.get(key)) is not None + else bool(defaults and getattr(defaults, default_field)) + for key, default_field in _FLAG_DEFAULT_FIELDS.items() + } + def post(self, request): """Validate and enqueue a BulkOnlineTagTask.""" serializer = OnlineTagStartSerializer(data=request.data) @@ -146,35 +168,14 @@ def post(self, request): return Response({"detail": detail}, status=400) session_id = str(uuid.uuid4()) - - req_delete = data.get("delete_original") - if req_delete is not None: - delete_original = req_delete - else: - delete_original = bool(defaults and defaults.delete_original) - - req_merge = data.get("merge_all_sources") - if req_merge is not None: - merge_all_sources = req_merge - else: - merge_all_sources = bool(defaults and defaults.merge_all_sources) - - req_rename = data.get("rename") - if req_rename is not None: - rename = req_rename - else: - rename = bool(defaults and defaults.rename_files) - task = BulkOnlineTagTask( comic_pks=comic_pks, session_id=session_id, sources=tuple(data["sources"]), mode=data["mode"], prompts_mode=data["prompts_mode"], - delete_original=delete_original, - merge_all_sources=merge_all_sources, - rename=rename, ids=ids, + **self._resolve_flags(data, defaults), ) LIBRARIAN_QUEUE.put(task) return Response( diff --git a/tests/onlinetag_session_fakes.py b/tests/onlinetag_session_fakes.py new file mode 100644 index 000000000..99e08b122 --- /dev/null +++ b/tests/onlinetag_session_fakes.py @@ -0,0 +1,172 @@ +""" +Shared doubles for the online tagging session manager tests. + +Every manager test drives the same seam: a fake ``OnlineSession`` standing in +for comicbox, a fake librarian queue collecting the tasks the manager enqueues, +and one real comic row backed by a file on disk. They live here so each test +module can stay about one behavior of the manager. +""" + +from __future__ import annotations + +import shutil +from pathlib import Path +from types import SimpleNamespace +from typing import Any, ClassVar, Final, override + +from django.core.cache import caches +from django.test import TestCase +from loguru import logger + +from codex.librarian.onlinetag.session_manager import OnlineTagSessionManager +from codex.librarian.scribe.tasks import BulkTagWriteTask +from codex.models import ( + Comic, + ComicboxTaggingDefaults, + Imprint, + Library, + Publisher, + Series, + Volume, +) + +TMP_DIR: Final = Path("/tmp/codex.tests.onlinetag.manager") # noqa: S108 +PATCH_TARGET: Final = "codex.librarian.onlinetag.session_manager.OnlineSession" +FETCH_TARGET: Final = ( + "codex.librarian.onlinetag.session_manager.fetch_tags_by_explicit_id" +) + + +def double(stub: object) -> Any: + """ + Pass a test double through a strictly-typed seam. + + The manager's constructor and ``_pass_runner`` attribute are annotated + with concrete production types (loguru ``Logger``, multiprocessing + ``Queue``, ``TagPassRunner``). These fakes only implement the slice the + tests exercise, so erase to ``Any`` at the injection point instead of + weakening the production annotations. + """ + return stub + + +class FakeQueue: + """Collects everything put on it for assertions.""" + + def __init__(self) -> None: + """Start with nothing queued.""" + self.items: list = [] + + def put(self, item) -> None: + self.items.append(item) + + +class FakeCandidate: + """One search hit a deferred prompt offers the admin.""" + + def __init__(self, issue_id: int, source: str) -> None: + """Build a candidate with a summary the admin panel can render.""" + self.issue_id = issue_id + self.source = source + self.score = 0.9 + self.url = "" + self.summary = SimpleNamespace( + series="S", issue="1", year=2020, publisher="P", cover_url="" + ) + + +class FakeDP: + """An ambiguous match comicbox defers back to the caller.""" + + def __init__(self, path: Path, fingerprint: str, source: str) -> None: + """Build a deferred prompt offering one candidate.""" + self.path = path + self.fingerprint = fingerprint + self.source = source + self.mode = "auto" + self.candidates = [FakeCandidate(123, source)] + + +class FakeSession: + """Stand-in for comicbox.OnlineSession driven by class-level fixtures.""" + + deferred: ClassVar[list] = [] + tag_results: ClassVar[list] = [] + preloaded: ClassVar[list] = [] + last_kwargs: ClassVar[dict] = {} + + def __init__(self, **kwargs) -> None: + """Record the kwargs the manager built the session with.""" + self.kwargs = kwargs + FakeSession.last_kwargs = kwargs + + def tag_many(self, paths): # noqa: ARG002 + return iter(FakeSession.tag_results) + + def deferred_prompts(self): + return list(FakeSession.deferred) + + def preload_resolution( + self, fingerprint, *, action, payload=None, chosen_volume_id=None + ) -> None: + FakeSession.preloaded.append((fingerprint, action, payload, chosen_volume_id)) + + def cancel(self) -> None: + pass + + +def make_comic() -> Comic: + """Create one comic row whose path exists on disk.""" + TMP_DIR.mkdir(exist_ok=True, parents=True) + library = Library.objects.create(path=str(TMP_DIR)) + publisher = Publisher.objects.create(name="P") + imprint = Imprint.objects.create(name="I", publisher=publisher) + series = Series.objects.create(name="S", publisher=publisher, imprint=imprint) + volume = Volume.objects.create( + name="1", publisher=publisher, imprint=imprint, series=series + ) + path = TMP_DIR / "c.cbz" + path.touch() + return Comic.objects.create( + library=library, + path=path, + issue_number=1, + name="c", + publisher=publisher, + imprint=imprint, + series=series, + volume=volume, + size=1, + file_type="CBZ", + ) + + +class OnlineTagSessionTestCase(TestCase): + """A fresh manager wired to a fake queue, with the class fixtures reset.""" + + @override + def setUp(self) -> None: + caches["default"].clear() + caches["tagging"].clear() + FakeSession.deferred = [] + FakeSession.tag_results = [] + FakeSession.preloaded = [] + FakeSession.last_kwargs = {} + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={"metron_key": "t"}, + ) + self.queue = FakeQueue() # pyright: ignore[reportUninitializedInstanceVariable] + self.manager = OnlineTagSessionManager( # pyright: ignore[reportUninitializedInstanceVariable] + double(logger), + double(self.queue), + thread_queue=None, + ) + + @override + def tearDown(self) -> None: + shutil.rmtree(TMP_DIR, ignore_errors=True) + + def write_tasks(self) -> list[BulkTagWriteTask]: + """Every tag write task the manager enqueued.""" + return [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] diff --git a/tests/test_onlinetag_credentials.py b/tests/test_onlinetag_credentials.py new file mode 100644 index 000000000..c37126160 --- /dev/null +++ b/tests/test_onlinetag_credentials.py @@ -0,0 +1,74 @@ +""" +Unit tests for the credentials the session manager hands comicbox. + +``_build_credentials`` reads the singleton tagging defaults; a source counts as +configured only when it carries something that can actually authenticate. +""" + +from __future__ import annotations + +from codex.models import ComicboxTaggingDefaults +from tests.onlinetag_session_fakes import OnlineTagSessionTestCase + + +class OnlineTagCredentialsTests(OnlineTagSessionTestCase): + """Keys and legacy logins configure a source; a bare url does not.""" + + def test_metron_credentials_accept_a_key_or_a_legacy_login(self) -> None: + """Both auth styles configure Metron; comicbox raises on neither.""" + manager = self.manager + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={"metron_key": "", "metron_user": "u", "metron_password": "p"}, + ) + legacy = manager._build_credentials() # noqa: SLF001 + assert legacy is not None + assert legacy.metron_user == "u" + assert manager._source_has_credentials(legacy, "metron") is True # noqa: SLF001 + + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={"metron_key": "t", "metron_user": "", "metron_password": ""}, + ) + keyed = manager._build_credentials() # noqa: SLF001 + assert keyed is not None + assert keyed.metron_key == "t" + assert manager._source_has_credentials(keyed, "metron") is True # noqa: SLF001 + + def test_no_metron_credentials_at_all_is_unconfigured(self) -> None: + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={ + "metron_key": "", + "metron_user": "", + "metron_password": "", + "comicvine_key": "", + }, + ) + assert self.manager._build_credentials() is None # noqa: SLF001 + + def test_comicvine_custom_url_reaches_comicbox(self) -> None: + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={ + "comicvine_key": "key", + "comicvine_url": "https://cv.example.com/api", + }, + ) + credentials = self.manager._build_credentials() # noqa: SLF001 + assert credentials is not None + assert credentials.comicvine_url == "https://cv.example.com/api" + + def test_a_custom_url_alone_is_not_credentials(self) -> None: + """A url cannot authenticate, so it must not configure the source.""" + ComicboxTaggingDefaults.objects.update_or_create( + pk=1, + defaults={ + "metron_key": "", + "metron_user": "", + "metron_password": "", + "comicvine_key": "", + "comicvine_url": "https://cv.example.com/api", + }, + ) + assert self.manager._build_credentials() is None # noqa: SLF001 diff --git a/tests/test_onlinetag_prompt_resolution.py b/tests/test_onlinetag_prompt_resolution.py new file mode 100644 index 000000000..cd478d735 --- /dev/null +++ b/tests/test_onlinetag_prompt_resolution.py @@ -0,0 +1,327 @@ +""" +Unit tests for answering an online tagging prompt. + +Resolution is decoupled from the scan that raised the prompt: ``resolve_prompt`` +builds a fresh session, applies the chosen match, and enqueues a single-comic +write — no live scan required. A response that arrives *during* a scan is +deferred instead, and applied when the scan winds down. +""" + +from __future__ import annotations + +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +from django.core.cache import caches + +from codex.librarian.onlinetag.session_cache import ( + get_pending_prompts, + set_pending_prompts, +) +from codex.librarian.onlinetag.session_snapshot import ( + USER_MATCHED, + get_resolved_outcomes, +) +from codex.librarian.onlinetag.session_state import SessionState +from codex.librarian.onlinetag.tasks import OnlineTagPromptResponseTask +from codex.librarian.scribe.tagwrite_errors import get_tag_write_errors +from tests.onlinetag_session_fakes import ( + FETCH_TARGET, + PATCH_TARGET, + FakeDP, + FakeSession, + OnlineTagSessionTestCase, + double, + make_comic, +) + + +def _prompt(comic, *, path: str | None = None, candidates: list | None = None) -> dict: + """Build a serialized deferred prompt for ``comic``, as the scan persists it.""" + return { + "fingerprint": "fp1", + "pk": comic.pk, + "path": path if path is not None else str(comic.path), + "source": "metron", + "candidates": candidates + if candidates is not None + else [{"issue_id": 123, "source": "metron"}], + "mode": "auto", + "formats": ["COMIC_INFO"], + "delete_original": False, + } + + +class OnlineTagPromptResolutionTests(OnlineTagSessionTestCase): + """A pick is fetched by id and written; a replay may drift or write nothing.""" + + def test_resolve_choose_fetches_chosen_issue_by_id_and_writes(self) -> None: + """A pick is fetched by its exact issue id, not re-searched.""" + comic = make_comic() + comic_path = str(comic.path) + set_pending_prompts({"fp1": _prompt(comic)}) + captured: dict = {} + + def _fake_fetch(path, source, issue_id, _credentials, **_kwargs): + captured.update(path=str(path), source=source, issue_id=issue_id) + return {"series": "X"} + + with patch(FETCH_TARGET, _fake_fetch): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + # Prompt consumed; the exact chosen issue was fetched directly by id. + assert get_pending_prompts() == {} + assert captured == {"path": comic_path, "source": "metron", "issue_id": 123} + # The replay session was never built — no re-search to drift. + assert FakeSession.preloaded == [] + # A single-comic write was enqueued, and the outcome recorded. + writes = self.write_tasks() + assert len(writes) == 1 + assert writes[0].comic_pks == frozenset({comic.pk}) + assert writes[0].per_comic_patches == {comic.pk: {"series": "X"}} + assert get_resolved_outcomes().get(comic.pk) == USER_MATCHED + + def test_resolve_choose_unresolved_id_does_not_write_or_requeue(self) -> None: + """A pick whose id doesn't resolve writes nothing and never re-prompts.""" + comic = make_comic() + set_pending_prompts({"fp1": _prompt(comic)}) + + with patch(FETCH_TARGET, lambda *_a, **_k: None): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not self.write_tasks() + # The explicit choice is honored: no worse fresh prompt is re-queued. + assert get_pending_prompts() == {} + # The consumed-but-unapplied pick surfaces on the admin error panel. + errors = get_tag_write_errors() + assert len(errors) == 1 + assert "did not resolve" in errors[0]["error"] + + def test_resolve_fetches_against_current_db_path_not_prompt_snapshot(self) -> None: + """ + A pick applies against the comic's current DB path, not the prompt's. + + The serialized prompt path goes stale when an earlier write for the + same comic ran with rename enabled (e.g. the comic's other source's + prompt was answered first) — the DB row follows the rename. + """ + comic = make_comic() + stale_path = str(Path(comic.path).with_name("stale-pre-rename-name.cbz")) + set_pending_prompts({"fp1": _prompt(comic, path=stale_path)}) + captured: dict = {} + + def _fake_fetch(path, source, issue_id, _credentials, **_kwargs): + captured.update(path=str(path), source=source, issue_id=issue_id) + return {"series": "X"} + + with patch(FETCH_TARGET, _fake_fetch): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert captured["path"] == str(comic.path) + writes = self.write_tasks() + assert len(writes) == 1 + assert writes[0].comic_pks == frozenset({comic.pk}) + + def test_resolve_fetch_failure_surfaces_error_without_crashing(self) -> None: + """A fetch crash (stale file, source error) reports; the pick isn't lost silently.""" + comic = make_comic() + comic_path = str(comic.path) + prompt = _prompt( + comic, candidates=[{"issue_id": 764978, "source": "comicvine"}] + ) + prompt["source"] = "comicvine" + set_pending_prompts({"fp1": prompt}) + + def _boom(*_args, **_kwargs): + reason = f"{comic_path} does not exist." + raise FileNotFoundError(reason) + + with patch(FETCH_TARGET, _boom): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not self.write_tasks() + assert get_pending_prompts() == {} + errors = get_tag_write_errors() + assert len(errors) == 1 + assert "fetching chosen issue comicvine:764978 failed" in errors[0]["error"] + + def test_resolve_missing_comic_row_reports_and_never_fetches(self) -> None: + """A prompt whose comic left the DB reports instead of fetching a dead path.""" + gone = SimpleNamespace(pk=999_999, path="/gone/c.cbz") + set_pending_prompts({"fp1": _prompt(gone)}) + called: list = [] + + def _fake_fetch(*args, **_kwargs): + called.append(args) + return {"series": "X"} + + with patch(FETCH_TARGET, _fake_fetch): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not called + assert not self.write_tasks() + errors = get_tag_write_errors() + assert len(errors) == 1 + assert "no longer in the database" in errors[0]["error"] + + def test_resolve_drifted_prompt_requeues_fresh_prompt(self) -> None: + """A candidate with no issue id falls back to replay, which can drift.""" + comic = make_comic() + comic_path = str(comic.path) + # No issue_id → not an explicit pick → replay path. + set_pending_prompts({"fp1": _prompt(comic, candidates=[{"source": "metron"}])}) + # The re-search produced a different candidate set: the preloaded + # fingerprint misses and the session defers a fresh prompt instead. + FakeSession.deferred = [FakeDP(Path(comic_path), "fp2", "metron")] + FakeSession.tag_results = [ + SimpleNamespace( + path=Path(comic_path), + tags={"series": "Existing"}, + error=None, + matched=False, + ) + ] + + with patch(PATCH_TARGET, FakeSession): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not self.write_tasks() + prompts = get_pending_prompts() + assert set(prompts) == {"fp2"} + assert prompts["fp2"]["pk"] == comic.pk + assert prompts["fp2"]["formats"] == ["COMIC_INFO"] + + def test_resolve_unmatched_result_does_not_write(self) -> None: + """An unmatched replay (tags = merged existing metadata) writes nothing.""" + comic = make_comic() + # No issue_id → not an explicit pick → replay path. + set_pending_prompts({"fp1": _prompt(comic, candidates=[{"source": "metron"}])}) + FakeSession.tag_results = [ + SimpleNamespace( + path=Path(comic.path), + tags={"series": "Existing"}, + error=None, + matched=False, + ) + ] + + with patch(PATCH_TARGET, FakeSession): + self.manager.resolve_prompt("fp1", "choose", 0, None) + + assert not self.write_tasks() + + def test_resolve_skip_drops_prompt_without_writing(self) -> None: + set_pending_prompts( + {"fp1": {"fingerprint": "fp1", "pk": 1, "path": "/c/1.cbz", "source": "x"}} + ) + + with patch(PATCH_TARGET, FakeSession): + self.manager.resolve_prompt("fp1", "skip", None, None) + + assert get_pending_prompts() == {} + assert not self.write_tasks() + + def test_skip_all_clears_every_prompt(self) -> None: + seeded = { + "a": {"fingerprint": "a", "pk": 1, "path": "/c/1.cbz", "source": "x"}, + "b": {"fingerprint": "b", "pk": 2, "path": "/c/2.cbz", "source": "x"}, + } + set_pending_prompts(seeded) + + count = self.manager.skip_all_prompts() + + assert count == len(seeded) + assert get_pending_prompts() == {} + + def test_resolve_unknown_prompt_is_a_noop(self) -> None: + with patch(PATCH_TARGET, FakeSession): + self.manager.resolve_prompt("nope", "choose", 0, None) + + assert not self.write_tasks() + + def test_pending_prompts_survive_default_cache_clear(self) -> None: + """Importer-finish / CRUD cache.clear() must not strand pending prompts.""" + set_pending_prompts( + {"fp1": {"fingerprint": "fp1", "pk": 1, "path": "/c/1.cbz", "source": "x"}} + ) + + caches["default"].clear() + + assert "fp1" in get_pending_prompts() + + +class OnlineTagMidScanResponseTests(OnlineTagSessionTestCase): + """A response that lands mid-scan clears the prompt now and writes later.""" + + @staticmethod + def _seed_prompt(comic) -> dict: + prompt = _prompt(comic) + set_pending_prompts({"fp1": prompt}) + return prompt + + def test_defer_prompt_response_removes_from_cache_and_defers_apply(self) -> None: + """A mid-scan "choose" clears the cache now but defers the write.""" + comic = make_comic() + self._seed_prompt(comic) + state = SessionState( + session=double(FakeSession()), path_to_pk={Path(comic.path): comic.pk} + ) + task = OnlineTagPromptResponseTask( + prompt_fingerprint="fp1", action="choose", payload=0 + ) + + self.manager._defer_prompt_response(state, task) # noqa: SLF001 + + # Gone from the cache immediately, so a refresh won't resurrect it. + assert get_pending_prompts() == {} + assert "fp1" in state.answered_fingerprints + # The network apply is deferred, not run inline mid-scan. + assert len(state.deferred_applies) == 1 + assert not self.write_tasks() + + def test_defer_prompt_response_skip_drops_without_deferring_apply(self) -> None: + comic = make_comic() + self._seed_prompt(comic) + state = SessionState( + session=double(FakeSession()), path_to_pk={Path(comic.path): comic.pk} + ) + task = OnlineTagPromptResponseTask(prompt_fingerprint="fp1", action="skip") + + self.manager._defer_prompt_response(state, task) # noqa: SLF001 + + assert get_pending_prompts() == {} + assert "fp1" in state.answered_fingerprints + assert state.deferred_applies == [] + + def test_persist_prompts_skips_answered_fingerprints(self) -> None: + """A scan must not re-persist a prompt the admin answered mid-scan.""" + comic = make_comic() + comic_path = Path(comic.path) + FakeSession.deferred = [FakeDP(comic_path, "fp1", "metron")] + state = SessionState( + session=double(FakeSession()), + path_to_pk={comic_path: comic.pk}, + formats=("COMIC_INFO",), + ) + state.answered_fingerprints.add("fp1") + + self.manager._persist_prompts(state) # noqa: SLF001 + + assert get_pending_prompts() == {} + + def test_apply_deferred_resolutions_writes_then_clears(self) -> None: + comic = make_comic() + prompt = self._seed_prompt(comic) + # The cache entry was already removed inline; the apply re-fetches. + set_pending_prompts({}) + state = SessionState(session=double(FakeSession())) + state.deferred_applies.append((prompt, "choose", 0, None)) + + with patch(FETCH_TARGET, lambda *_a, **_k: {"series": "X"}): + self.manager._apply_deferred_resolutions(state) # noqa: SLF001 + + writes = self.write_tasks() + assert len(writes) == 1 + assert writes[0].per_comic_patches == {comic.pk: {"series": "X"}} + assert state.deferred_applies == [] diff --git a/tests/test_onlinetag_session_manager.py b/tests/test_onlinetag_session_manager.py index 3323adefa..0b3d2a42f 100644 --- a/tests/test_onlinetag_session_manager.py +++ b/tests/test_onlinetag_session_manager.py @@ -1,190 +1,68 @@ """ -Unit tests for the non-blocking online tagging session manager. +Unit tests for a scan run by the online tagging session manager. -A scan (``run_session``) runs Pass 1 and persists ambiguous matches as -deferred prompts, then returns without blocking. Answering a prompt -(``resolve_prompt``) is decoupled: it builds a fresh session, applies the -chosen match, and enqueues a single-comic write — no live scan required. +``run_session`` runs Pass 1 and persists ambiguous matches as deferred +prompts, then returns without blocking. Answering those prompts is covered by +``test_onlinetag_prompt_resolution``; credential assembly by +``test_onlinetag_credentials``. """ from __future__ import annotations -import shutil from pathlib import Path from types import SimpleNamespace -from typing import Any, ClassVar, Final, override from unittest.mock import patch from comicbox.events import AutoWritten, FileFinished, RateLimited -from django.core.cache import caches -from django.test import TestCase from loguru import logger from codex.librarian.onlinetag.session_cache import ( get_active_scan_id, get_pending_prompts, - set_pending_prompts, -) -from codex.librarian.onlinetag.session_manager import OnlineTagSessionManager -from codex.librarian.onlinetag.session_snapshot import ( - USER_MATCHED, - get_resolved_outcomes, ) from codex.librarian.onlinetag.session_state import SessionState -from codex.librarian.onlinetag.tasks import ( - BulkOnlineTagTask, - OnlineTagPromptResponseTask, -) -from codex.librarian.scribe.tagwrite_errors import get_tag_write_errors -from codex.librarian.scribe.tasks import BulkTagWriteTask -from codex.models import ( - Comic, - ComicboxTaggingDefaults, - Identifier, - IdentifierSource, - Imprint, - Library, - Publisher, - Series, - Volume, +from codex.librarian.onlinetag.tasks import BulkOnlineTagTask +from codex.models import Comic, Identifier, IdentifierSource +from tests.onlinetag_session_fakes import ( + FETCH_TARGET, + PATCH_TARGET, + FakeDP, + FakeSession, + OnlineTagSessionTestCase, + double, + make_comic, ) -_TMP_DIR: Final = Path("/tmp/codex.tests.onlinetag.manager") # noqa: S108 -_PATCH_TARGET: Final = "codex.librarian.onlinetag.session_manager.OnlineSession" -_FETCH_TARGET: Final = ( - "codex.librarian.onlinetag.session_manager.fetch_tags_by_explicit_id" -) - - -def _double(stub: object) -> Any: - """ - Pass a test double through a strictly-typed seam. - - The manager's constructor and ``_pass_runner`` attribute are annotated - with concrete production types (loguru ``Logger``, multiprocessing - ``Queue``, ``TagPassRunner``). These fakes only implement the slice the - tests exercise, so erase to ``Any`` at the injection point instead of - weakening the production annotations. - """ - return stub - - -class _FakeQueue: - """Collects everything put on it for assertions.""" - - def __init__(self) -> None: - self.items: list = [] - def put(self, item) -> None: - self.items.append(item) +class OnlineTagScanTests(OnlineTagSessionTestCase): + """run_session persists prompts non-blockingly and never re-searches.""" - -class _FakeCandidate: - def __init__(self, issue_id: int, source: str) -> None: - self.issue_id = issue_id - self.source = source - self.score = 0.9 - self.url = "" - self.summary = SimpleNamespace( - series="S", issue="1", year=2020, publisher="P", cover_url="" + def _no_op_pass(self) -> None: + """Stub out Pass 1: only the steps around it are under test.""" + self.manager._pass_runner = double( # noqa: SLF001 + SimpleNamespace(collect_results=lambda *_args, **_kwargs: None) ) + @staticmethod + def _add_issue_id(comic: Comic, source_name: str, key: str) -> None: + """Attach a stored issue-level identifier to ``comic``.""" + source, _ = IdentifierSource.objects.get_or_create(name=source_name) + identifier = Identifier.objects.create(source=source, id_type="comic", key=key) + comic.identifiers.add(identifier) -class _FakeDP: - def __init__(self, path: Path, fingerprint: str, source: str) -> None: - self.path = path - self.fingerprint = fingerprint - self.source = source - self.mode = "auto" - self.candidates = [_FakeCandidate(123, source)] - - -class _FakeSession: - """Stand-in for comicbox.OnlineSession driven by class-level fixtures.""" - - deferred: ClassVar[list] = [] - tag_results: ClassVar[list] = [] - preloaded: ClassVar[list] = [] - last_kwargs: ClassVar[dict] = {} - - def __init__(self, **kwargs) -> None: - self.kwargs = kwargs - _FakeSession.last_kwargs = kwargs - - def tag_many(self, paths): # noqa: ARG002 - return iter(_FakeSession.tag_results) - - def deferred_prompts(self): - return list(_FakeSession.deferred) - - def preload_resolution( - self, fingerprint, *, action, payload=None, chosen_volume_id=None - ) -> None: - _FakeSession.preloaded.append((fingerprint, action, payload, chosen_volume_id)) - - def cancel(self) -> None: - pass - - -def _make_comic() -> Comic: - _TMP_DIR.mkdir(exist_ok=True, parents=True) - library = Library.objects.create(path=str(_TMP_DIR)) - publisher = Publisher.objects.create(name="P") - imprint = Imprint.objects.create(name="I", publisher=publisher) - series = Series.objects.create(name="S", publisher=publisher, imprint=imprint) - volume = Volume.objects.create( - name="1", publisher=publisher, imprint=imprint, series=series - ) - path = _TMP_DIR / "c.cbz" - path.touch() - return Comic.objects.create( - library=library, - path=path, - issue_number=1, - name="c", - publisher=publisher, - imprint=imprint, - series=series, - volume=volume, - size=1, - file_type="CBZ", - ) - - -class OnlineTagSessionManagerTests(TestCase): - """run_session persists prompts non-blockingly; resolve applies via fresh session.""" - - @override - def setUp(self) -> None: - caches["default"].clear() - caches["tagging"].clear() - _FakeSession.deferred = [] - _FakeSession.tag_results = [] - _FakeSession.preloaded = [] - _FakeSession.last_kwargs = {} - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={"metron_key": "t"}, - ) - self.queue = _FakeQueue() # pyright: ignore[reportUninitializedInstanceVariable] - self.manager = OnlineTagSessionManager( # pyright: ignore[reportUninitializedInstanceVariable] - _double(logger), - _double(self.queue), - thread_queue=None, + def _capture_search_paths(self, captured: list) -> None: + """Make the (mocked) search pass record the comics it's handed.""" + self.manager._pass_runner = double( # noqa: SLF001 + SimpleNamespace( + collect_results=lambda _state, paths, **_kw: captured.extend(paths) + ) ) - @override - def tearDown(self) -> None: - shutil.rmtree(_TMP_DIR, ignore_errors=True) - def test_run_session_persists_prompts_and_returns(self) -> None: - comic = _make_comic() + comic = make_comic() comic_path = Path(comic.path) - _FakeSession.deferred = [_FakeDP(comic_path, "fp1", "metron")] - # No-op Pass 1: we only exercise the persist step here. - self.manager._pass_runner = _double( # noqa: SLF001 - SimpleNamespace(collect_results=lambda *_args, **_kwargs: None) - ) + FakeSession.deferred = [FakeDP(comic_path, "fp1", "metron")] + self._no_op_pass() task = BulkOnlineTagTask( comic_pks=frozenset({comic.pk}), session_id="scan-1", @@ -192,7 +70,7 @@ def test_run_session_persists_prompts_and_returns(self) -> None: mode="auto", ) - with patch(_PATCH_TARGET, _FakeSession): + with patch(PATCH_TARGET, FakeSession): self.manager.run_session(task) prompts = get_pending_prompts() @@ -203,10 +81,8 @@ def test_run_session_persists_prompts_and_returns(self) -> None: def test_run_session_passes_source_order_to_session(self) -> None: """The task's source order (run priority) reaches OnlineSession verbatim.""" - comic = _make_comic() - self.manager._pass_runner = _double( # noqa: SLF001 - SimpleNamespace(collect_results=lambda *_args, **_kwargs: None) - ) + comic = make_comic() + self._no_op_pass() task = BulkOnlineTagTask( comic_pks=frozenset({comic.pk}), session_id="scan-order", @@ -214,29 +90,14 @@ def test_run_session_passes_source_order_to_session(self) -> None: mode="auto", ) - with patch(_PATCH_TARGET, _FakeSession): + with patch(PATCH_TARGET, FakeSession): self.manager.run_session(task) - assert _FakeSession.last_kwargs["sources"] == ("comicvine", "metron") - - @staticmethod - def _add_issue_id(comic: Comic, source_name: str, key: str) -> None: - """Attach a stored issue-level identifier to ``comic``.""" - source, _ = IdentifierSource.objects.get_or_create(name=source_name) - identifier = Identifier.objects.create(source=source, id_type="comic", key=key) - comic.identifiers.add(identifier) - - def _capture_search_paths(self, captured: list) -> None: - """Make the (mocked) search pass record the comics it's handed.""" - self.manager._pass_runner = _double( # noqa: SLF001 - SimpleNamespace( - collect_results=lambda _state, paths, **_kw: captured.extend(paths) - ) - ) + assert FakeSession.last_kwargs["sources"] == ("comicvine", "metron") def test_run_session_prefetches_stored_id_and_skips_search(self) -> None: """A comic with a stored issue id is fetched by id, not searched.""" - comic = _make_comic() + comic = make_comic() self._add_issue_id(comic, "metron", "123495") searched: list = [] self._capture_search_paths(searched) @@ -252,19 +113,19 @@ def _fake_fetch(path, source, issue_id, _credentials, **_kwargs): # noqa: ARG00 captured_fetch.update(source=source, issue_id=issue_id) return {"series": "X"} - with patch(_PATCH_TARGET, _FakeSession), patch(_FETCH_TARGET, _fake_fetch): + with patch(PATCH_TARGET, FakeSession), patch(FETCH_TARGET, _fake_fetch): self.manager.run_session(task) # Fetched directly by the stored id (numeric), never searched. assert captured_fetch == {"source": "metron", "issue_id": 123495} assert searched == [] - writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + writes = self.write_tasks() assert len(writes) == 1 assert writes[0].per_comic_patches == {comic.pk: {"series": "X"}} def test_run_session_unresolved_stored_id_falls_back_to_search(self) -> None: """A stored id that doesn't resolve leaves the comic for the search pass.""" - comic = _make_comic() + comic = make_comic() self._add_issue_id(comic, "metron", "123495") searched: list = [] self._capture_search_paths(searched) @@ -276,20 +137,18 @@ def test_run_session_unresolved_stored_id_falls_back_to_search(self) -> None: ) with ( - patch(_PATCH_TARGET, _FakeSession), - patch(_FETCH_TARGET, lambda *_a, **_k: None), + patch(PATCH_TARGET, FakeSession), + patch(FETCH_TARGET, lambda *_a, **_k: None), ): self.manager.run_session(task) - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + assert not self.write_tasks() assert searched == [Path(comic.path)] def test_run_session_passes_pinned_ids_to_session(self) -> None: """A pinned id per source reaches OnlineSession, which fetches it.""" - comic = _make_comic() - self.manager._pass_runner = _double( # noqa: SLF001 - SimpleNamespace(collect_results=lambda *_args, **_kwargs: None) - ) + comic = make_comic() + self._no_op_pass() task = BulkOnlineTagTask( comic_pks=frozenset({comic.pk}), session_id="scan-pinned", @@ -298,10 +157,10 @@ def test_run_session_passes_pinned_ids_to_session(self) -> None: ids={"metron": 12345}, ) - with patch(_PATCH_TARGET, _FakeSession): + with patch(PATCH_TARGET, FakeSession): self.manager.run_session(task) - assert _FakeSession.last_kwargs["ids"] == {"metron": 12345} + assert FakeSession.last_kwargs["ids"] == {"metron": 12345} def test_run_session_pinned_ids_bypass_the_stored_id_prepass(self) -> None: """ @@ -311,7 +170,7 @@ def test_run_session_pinned_ids_bypass_the_stored_id_prepass(self) -> None: leave the unpinned sources with nothing to search — the pinned and searched sources have to resolve in the same lookup to merge. """ - comic = _make_comic() + comic = make_comic() self._add_issue_id(comic, "metron", "123495") searched: list = [] self._capture_search_paths(searched) @@ -325,20 +184,20 @@ def test_run_session_pinned_ids_bypass_the_stored_id_prepass(self) -> None: fetch_calls: list = [] with ( - patch(_PATCH_TARGET, _FakeSession), - patch(_FETCH_TARGET, lambda *a, **_k: fetch_calls.append(a)), + patch(PATCH_TARGET, FakeSession), + patch(FETCH_TARGET, lambda *a, **_k: fetch_calls.append(a)), ): self.manager.run_session(task) assert fetch_calls == [] assert searched == [Path(comic.path)] - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] + assert not self.write_tasks() def test_run_session_pinned_ids_land_in_resume_params(self) -> None: """A paused pinned session resumes still pinned.""" - comic = _make_comic() + comic = make_comic() captured: list = [] - self.manager._pass_runner = _double( # noqa: SLF001 + self.manager._pass_runner = double( # noqa: SLF001 SimpleNamespace( collect_results=lambda state, *_a, **_k: captured.append( state.resume_params @@ -353,7 +212,7 @@ def test_run_session_pinned_ids_land_in_resume_params(self) -> None: ids={"metron": 12345}, ) - with patch(_PATCH_TARGET, _FakeSession): + with patch(PATCH_TARGET, FakeSession): self.manager.run_session(task) assert captured @@ -363,12 +222,12 @@ def test_prefetch_keeps_comic_in_status_out_of_search_and_resume(self) -> None: """A prefetched comic shows as matched yet is excluded from Resume.""" from codex.librarian.onlinetag.session_snapshot import remaining_pks - comic = _make_comic() + comic = make_comic() self._add_issue_id(comic, "metron", "123495") path = Path(comic.path) comic_paths = {comic.pk: path} state = SessionState( - session=_double(_FakeSession()), + session=double(FakeSession()), path_to_pk={path: comic.pk}, sources=("metron",), ) @@ -382,7 +241,7 @@ def test_prefetch_keeps_comic_in_status_out_of_search_and_resume(self) -> None: mode="auto", ) - with patch(_FETCH_TARGET, lambda *_a, **_k: {"series": "X"}): + with patch(FETCH_TARGET, lambda *_a, **_k: {"series": "X"}): self.manager._prefetch_stored_ids( # noqa: SLF001 state, comic_paths, task, credentials ) @@ -396,14 +255,11 @@ def test_prefetch_keeps_comic_in_status_out_of_search_and_resume(self) -> None: assert remaining_pks(state, set()) == [] def test_run_session_never_prompts_skips_persistence(self) -> None: - comic = _make_comic() + comic = make_comic() comic_path = Path(comic.path) # An ambiguous match is available, but "never" must not persist it. - _FakeSession.deferred = [_FakeDP(comic_path, "fp1", "metron")] - # No-op Pass 1: we only exercise the (skipped) persist step here. - self.manager._pass_runner = _double( # noqa: SLF001 - SimpleNamespace(collect_results=lambda *_args, **_kwargs: None) - ) + FakeSession.deferred = [FakeDP(comic_path, "fp1", "metron")] + self._no_op_pass() task = BulkOnlineTagTask( comic_pks=frozenset({comic.pk}), session_id="scan-never", @@ -412,17 +268,17 @@ def test_run_session_never_prompts_skips_persistence(self) -> None: prompts_mode="never", ) - with patch(_PATCH_TARGET, _FakeSession): + with patch(PATCH_TARGET, FakeSession): self.manager.run_session(task) # The session was built to skip inline rather than defer prompts. - assert _FakeSession.last_kwargs["defer_prompts"] is False + assert FakeSession.last_kwargs["defer_prompts"] is False # No ambiguous match was queued for later resolution. assert get_pending_prompts() == {} assert get_active_scan_id() == "" def test_run_session_logs_outcome_summary(self) -> None: - comic = _make_comic() + comic = make_comic() comic_path = Path(comic.path) on_event = self.manager._on_event # noqa: SLF001 @@ -431,7 +287,7 @@ def _drive(_state, _paths, **_kwargs) -> None: on_event(AutoWritten(path=comic_path, source="metron")) on_event(FileFinished(path=comic_path, outcome="written")) - self.manager._pass_runner = _double( # noqa: SLF001 + self.manager._pass_runner = double( # noqa: SLF001 SimpleNamespace(collect_results=_drive) ) task = BulkOnlineTagTask( @@ -444,7 +300,7 @@ def _drive(_state, _paths, **_kwargs) -> None: messages: list[str] = [] sink_id = logger.add(messages.append, level="INFO", format="{message}") try: - with patch(_PATCH_TARGET, _FakeSession): + with patch(PATCH_TARGET, FakeSession): self.manager.run_session(task) finally: logger.remove(sink_id) @@ -453,375 +309,13 @@ def _drive(_state, _paths, **_kwargs) -> None: assert len(summaries) == 1 assert "matched 1 (metron 1)" in summaries[0] - def test_resolve_choose_fetches_chosen_issue_by_id_and_writes(self) -> None: - """A pick is fetched by its exact issue id, not re-searched.""" - comic = _make_comic() - comic_path = str(comic.path) - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": comic.pk, - "path": comic_path, - "source": "metron", - "candidates": [{"issue_id": 123, "source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - captured: dict = {} - - def _fake_fetch(path, source, issue_id, _credentials, **_kwargs): - captured.update(path=str(path), source=source, issue_id=issue_id) - return {"series": "X"} - - with patch(_FETCH_TARGET, _fake_fetch): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - # Prompt consumed; the exact chosen issue was fetched directly by id. - assert get_pending_prompts() == {} - assert captured == {"path": comic_path, "source": "metron", "issue_id": 123} - # The replay session was never built — no re-search to drift. - assert _FakeSession.preloaded == [] - # A single-comic write was enqueued, and the outcome recorded. - writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - assert len(writes) == 1 - assert writes[0].comic_pks == frozenset({comic.pk}) - assert writes[0].per_comic_patches == {comic.pk: {"series": "X"}} - assert get_resolved_outcomes().get(comic.pk) == USER_MATCHED - - def test_resolve_choose_unresolved_id_does_not_write_or_requeue(self) -> None: - """A pick whose id doesn't resolve writes nothing and never re-prompts.""" - comic = _make_comic() - comic_path = str(comic.path) - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": comic.pk, - "path": comic_path, - "source": "metron", - "candidates": [{"issue_id": 123, "source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - - with patch(_FETCH_TARGET, lambda *_a, **_k: None): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - # The explicit choice is honored: no worse fresh prompt is re-queued. - assert get_pending_prompts() == {} - # The consumed-but-unapplied pick surfaces on the admin error panel. - errors = get_tag_write_errors() - assert len(errors) == 1 - assert "did not resolve" in errors[0]["error"] - - def test_resolve_fetches_against_current_db_path_not_prompt_snapshot(self) -> None: - """ - A pick applies against the comic's current DB path, not the prompt's. - - The serialized prompt path goes stale when an earlier write for the - same comic ran with rename enabled (e.g. the comic's other source's - prompt was answered first) — the DB row follows the rename. - """ - comic = _make_comic() - stale_path = str(Path(comic.path).with_name("stale-pre-rename-name.cbz")) - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": comic.pk, - "path": stale_path, - "source": "metron", - "candidates": [{"issue_id": 123, "source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - captured: dict = {} - - def _fake_fetch(path, source, issue_id, _credentials, **_kwargs): - captured.update(path=str(path), source=source, issue_id=issue_id) - return {"series": "X"} - - with patch(_FETCH_TARGET, _fake_fetch): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - assert captured["path"] == str(comic.path) - writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - assert len(writes) == 1 - assert writes[0].comic_pks == frozenset({comic.pk}) - - def test_resolve_fetch_failure_surfaces_error_without_crashing(self) -> None: - """A fetch crash (stale file, source error) reports; the pick isn't lost silently.""" - comic = _make_comic() - comic_path = str(comic.path) - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": comic.pk, - "path": comic_path, - "source": "comicvine", - "candidates": [{"issue_id": 764978, "source": "comicvine"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - - def _boom(*_args, **_kwargs): - reason = f"{comic_path} does not exist." - raise FileNotFoundError(reason) - - with patch(_FETCH_TARGET, _boom): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - assert get_pending_prompts() == {} - errors = get_tag_write_errors() - assert len(errors) == 1 - assert "fetching chosen issue comicvine:764978 failed" in errors[0]["error"] - - def test_resolve_missing_comic_row_reports_and_never_fetches(self) -> None: - """A prompt whose comic left the DB reports instead of fetching a dead path.""" - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": 999_999, - "path": "/gone/c.cbz", - "source": "metron", - "candidates": [{"issue_id": 123, "source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - called: list = [] - - def _fake_fetch(*args, **_kwargs): - called.append(args) - return {"series": "X"} - - with patch(_FETCH_TARGET, _fake_fetch): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - assert not called - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - errors = get_tag_write_errors() - assert len(errors) == 1 - assert "no longer in the database" in errors[0]["error"] - - def test_resolve_drifted_prompt_requeues_fresh_prompt(self) -> None: - """A candidate with no issue id falls back to replay, which can drift.""" - comic = _make_comic() - comic_path = str(comic.path) - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": comic.pk, - "path": comic_path, - "source": "metron", - # No issue_id → not an explicit pick → replay path. - "candidates": [{"source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - # The re-search produced a different candidate set: the preloaded - # fingerprint misses and the session defers a fresh prompt instead. - _FakeSession.deferred = [_FakeDP(Path(comic_path), "fp2", "metron")] - _FakeSession.tag_results = [ - SimpleNamespace( - path=Path(comic_path), - tags={"series": "Existing"}, - error=None, - matched=False, - ) - ] - - with patch(_PATCH_TARGET, _FakeSession): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - prompts = get_pending_prompts() - assert set(prompts) == {"fp2"} - assert prompts["fp2"]["pk"] == comic.pk - assert prompts["fp2"]["formats"] == ["COMIC_INFO"] - - def test_resolve_unmatched_result_does_not_write(self) -> None: - """An unmatched replay (tags = merged existing metadata) writes nothing.""" - comic = _make_comic() - comic_path = str(comic.path) - set_pending_prompts( - { - "fp1": { - "fingerprint": "fp1", - "pk": comic.pk, - "path": comic_path, - "source": "metron", - # No issue_id → not an explicit pick → replay path. - "candidates": [{"source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - } - ) - _FakeSession.tag_results = [ - SimpleNamespace( - path=Path(comic_path), - tags={"series": "Existing"}, - error=None, - matched=False, - ) - ] - - with patch(_PATCH_TARGET, _FakeSession): - self.manager.resolve_prompt("fp1", "choose", 0, None) - - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - - def test_resolve_skip_drops_prompt_without_writing(self) -> None: - set_pending_prompts( - {"fp1": {"fingerprint": "fp1", "pk": 1, "path": "/c/1.cbz", "source": "x"}} - ) - - with patch(_PATCH_TARGET, _FakeSession): - self.manager.resolve_prompt("fp1", "skip", None, None) - - assert get_pending_prompts() == {} - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - - def test_skip_all_clears_every_prompt(self) -> None: - seeded = { - "a": {"fingerprint": "a", "pk": 1, "path": "/c/1.cbz", "source": "x"}, - "b": {"fingerprint": "b", "pk": 2, "path": "/c/2.cbz", "source": "x"}, - } - set_pending_prompts(seeded) - - count = self.manager.skip_all_prompts() - - assert count == len(seeded) - assert get_pending_prompts() == {} - - def test_resolve_unknown_prompt_is_a_noop(self) -> None: - with patch(_PATCH_TARGET, _FakeSession): - self.manager.resolve_prompt("nope", "choose", 0, None) - - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - - def test_pending_prompts_survive_default_cache_clear(self) -> None: - """Importer-finish / CRUD cache.clear() must not strand pending prompts.""" - set_pending_prompts( - {"fp1": {"fingerprint": "fp1", "pk": 1, "path": "/c/1.cbz", "source": "x"}} - ) - - caches["default"].clear() - - assert "fp1" in get_pending_prompts() - - def _seed_prompt(self, comic) -> dict: - prompt = { - "fingerprint": "fp1", - "pk": comic.pk, - "path": str(comic.path), - "source": "metron", - "candidates": [{"issue_id": 123, "source": "metron"}], - "mode": "auto", - "formats": ["COMIC_INFO"], - "delete_original": False, - } - set_pending_prompts({"fp1": prompt}) - return prompt - - def test_defer_prompt_response_removes_from_cache_and_defers_apply(self) -> None: - """A mid-scan "choose" clears the cache now but defers the write.""" - comic = _make_comic() - self._seed_prompt(comic) - state = SessionState( - session=_double(_FakeSession()), path_to_pk={Path(comic.path): comic.pk} - ) - task = OnlineTagPromptResponseTask( - prompt_fingerprint="fp1", action="choose", payload=0 - ) - - self.manager._defer_prompt_response(state, task) # noqa: SLF001 - - # Gone from the cache immediately, so a refresh won't resurrect it. - assert get_pending_prompts() == {} - assert "fp1" in state.answered_fingerprints - # The network apply is deferred, not run inline mid-scan. - assert len(state.deferred_applies) == 1 - assert not [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - - def test_defer_prompt_response_skip_drops_without_deferring_apply(self) -> None: - comic = _make_comic() - self._seed_prompt(comic) - state = SessionState( - session=_double(_FakeSession()), path_to_pk={Path(comic.path): comic.pk} - ) - task = OnlineTagPromptResponseTask(prompt_fingerprint="fp1", action="skip") - - self.manager._defer_prompt_response(state, task) # noqa: SLF001 - - assert get_pending_prompts() == {} - assert "fp1" in state.answered_fingerprints - assert state.deferred_applies == [] - - def test_persist_prompts_skips_answered_fingerprints(self) -> None: - """A scan must not re-persist a prompt the admin answered mid-scan.""" - comic = _make_comic() - comic_path = Path(comic.path) - _FakeSession.deferred = [_FakeDP(comic_path, "fp1", "metron")] - state = SessionState( - session=_double(_FakeSession()), - path_to_pk={comic_path: comic.pk}, - formats=("COMIC_INFO",), - ) - state.answered_fingerprints.add("fp1") - - self.manager._persist_prompts(state) # noqa: SLF001 - - assert get_pending_prompts() == {} - - def test_apply_deferred_resolutions_writes_then_clears(self) -> None: - comic = _make_comic() - prompt = self._seed_prompt(comic) - # The cache entry was already removed inline; the apply re-fetches. - set_pending_prompts({}) - state = SessionState(session=_double(_FakeSession())) - state.deferred_applies.append((prompt, "choose", 0, None)) - - with patch(_FETCH_TARGET, lambda *_a, **_k: {"series": "X"}): - self.manager._apply_deferred_resolutions(state) # noqa: SLF001 - - writes = [i for i in self.queue.items if isinstance(i, BulkTagWriteTask)] - assert len(writes) == 1 - assert writes[0].per_comic_patches == {comic.pk: {"series": "X"}} - assert state.deferred_applies == [] - def test_mark_rate_limited_sets_retry_and_eta(self) -> None: """A rate-limit event arms the retry countdown and pushes eta out.""" from codex.librarian.onlinetag.status import OnlineLookupStatus status = OnlineLookupStatus() state = SessionState( - session=_double(_FakeSession()), + session=double(FakeSession()), match_mode="auto", sources=("comicvine",), total_comics=10, @@ -842,7 +336,7 @@ def test_unmatched_scan_result_is_not_batched(self) -> None: from codex.librarian.onlinetag.tag_pass_runner import TagPassRunner path = Path("/c/a.cbz") - state = _double(SimpleNamespace(path_to_pk={path: 1}, collected_tags={})) + state = double(SimpleNamespace(path_to_pk={path: 1}, collected_tags={})) batch: dict = {} unmatched = SimpleNamespace( path=path, tags={"series": "Existing"}, error=None, matched=False @@ -855,62 +349,3 @@ def test_unmatched_scan_result_is_not_batched(self) -> None: ) TagPassRunner._store_result_tags(state, matched, batch, flush_writes=True) # noqa: SLF001 assert batch == {1: {"series": "New"}} - - def test_metron_credentials_accept_a_key_or_a_legacy_login(self) -> None: - """Both auth styles configure Metron; comicbox raises on neither.""" - manager = self.manager - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={"metron_key": "", "metron_user": "u", "metron_password": "p"}, - ) - legacy = manager._build_credentials() # noqa: SLF001 - assert legacy is not None - assert legacy.metron_user == "u" - assert manager._source_has_credentials(legacy, "metron") is True # noqa: SLF001 - - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={"metron_key": "t", "metron_user": "", "metron_password": ""}, - ) - keyed = manager._build_credentials() # noqa: SLF001 - assert keyed is not None - assert keyed.metron_key == "t" - assert manager._source_has_credentials(keyed, "metron") is True # noqa: SLF001 - - def test_no_metron_credentials_at_all_is_unconfigured(self) -> None: - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={ - "metron_key": "", - "metron_user": "", - "metron_password": "", - "comicvine_key": "", - }, - ) - assert self.manager._build_credentials() is None # noqa: SLF001 - - def test_comicvine_custom_url_reaches_comicbox(self) -> None: - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={ - "comicvine_key": "key", - "comicvine_url": "https://cv.example.com/api", - }, - ) - credentials = self.manager._build_credentials() # noqa: SLF001 - assert credentials is not None - assert credentials.comicvine_url == "https://cv.example.com/api" - - def test_a_custom_url_alone_is_not_credentials(self) -> None: - """A url cannot authenticate, so it must not configure the source.""" - ComicboxTaggingDefaults.objects.update_or_create( - pk=1, - defaults={ - "metron_key": "", - "metron_user": "", - "metron_password": "", - "comicvine_key": "", - "comicvine_url": "https://cv.example.com/api", - }, - ) - assert self.manager._build_credentials() is None # noqa: SLF001 diff --git a/tests/test_onlinetag_tag_pass.py b/tests/test_onlinetag_tag_pass.py index 68878b84a..9c7ffac75 100644 --- a/tests/test_onlinetag_tag_pass.py +++ b/tests/test_onlinetag_tag_pass.py @@ -1,9 +1,8 @@ """ Unit tests for the online-tag pass runner and stored-id prepass. -Split out of ``test_onlinetag_session_manager`` (which covers the session -manager itself) to keep each module focused. The shared test doubles and -the comic factory live in that module and are imported here. +One of the online tagging manager modules; the shared test doubles and the +comic factory live in ``tests.onlinetag_session_fakes``. """ from __future__ import annotations @@ -19,12 +18,7 @@ from loguru import logger from codex.models import Identifier, IdentifierSource -from tests.test_onlinetag_session_manager import ( - _TMP_DIR, - _double, - _FakeQueue, - _make_comic, -) +from tests.onlinetag_session_fakes import TMP_DIR, FakeQueue, double, make_comic class TagPassRunnerFinishTests(TestCase): @@ -51,7 +45,7 @@ def tag_many(self, _paths): msg = "rate-limit budget exhausted" raise RuntimeError(msg) - state = _double( + state = double( SimpleNamespace( session=_BoomSession(), cancelled=False, @@ -66,9 +60,9 @@ def tag_many(self, _paths): ) ) runner = TagPassRunner( - _double(logger), - _double(_FakeQueue()), - _double(_FakeStatusController()), + double(logger), + double(FakeQueue()), + double(_FakeStatusController()), lambda _state: None, lambda _state: None, ) @@ -94,7 +88,7 @@ def update(self, _status, **_kwargs) -> None: status = OnlineLookupStatus() status.subtitle = "rate limited by comicvine" status.retry_at = now() + timedelta(seconds=30) - state = _double( + state = double( SimpleNamespace( completed_comics=0, total_comics=10, @@ -104,9 +98,9 @@ def update(self, _status, **_kwargs) -> None: ) ) runner = TagPassRunner( - _double(logger), - _double(_FakeQueue()), - _double(_NoopStatusController()), + double(logger), + double(FakeQueue()), + double(_NoopStatusController()), lambda _state: None, lambda _state: None, ) @@ -126,7 +120,7 @@ class BuildStoredIdMapTests(TestCase): @override def tearDown(self) -> None: - shutil.rmtree(_TMP_DIR, ignore_errors=True) + shutil.rmtree(TMP_DIR, ignore_errors=True) @staticmethod def _identifier(source_name: str, id_type: str, key: str) -> Identifier: @@ -137,7 +131,7 @@ def test_maps_issue_ids_in_source_priority_order(self) -> None: """Issue ids parse (incl. comicvine long keys) and order by priority.""" from codex.librarian.onlinetag.stored_id_prepass import build_stored_id_map - comic = _make_comic() + comic = make_comic() comic.identifiers.add( self._identifier("metron", "comic", "123495"), self._identifier("comicvine", "comic", "4000-67890"), @@ -154,7 +148,7 @@ def test_ignores_unrequested_sources_and_missing_ids(self) -> None: """Only requested sources count; comics without an id are absent.""" from codex.librarian.onlinetag.stored_id_prepass import build_stored_id_map - comic = _make_comic() + comic = make_comic() comic.identifiers.add(self._identifier("comicvine", "comic", "4000-67890")) assert build_stored_id_map([comic.pk], ("metron",)) == {} diff --git a/tests/test_telemeter_privacy.py b/tests/test_telemeter_privacy.py index e4d6a2c4b..bf5bda506 100644 --- a/tests/test_telemeter_privacy.py +++ b/tests/test_telemeter_privacy.py @@ -7,7 +7,7 @@ leaf to be a number, a boolean, or a string drawn from a closed vocabulary. If a new stat legitimately needs a new kind of string, add its vocabulary to -``_vocabularies`` deliberately. Do not widen the walk. +one of the ``_vocabularies`` sources deliberately. Do not widen the walk. """ import json @@ -59,47 +59,75 @@ ) -def _vocabularies() -> frozenset[str]: - """Every closed vocabulary a payload string may be drawn from.""" +# Strings the payload may carry that belong to no enum: the empty value, the +# collapsed-unknown buckets, and serialized booleans. +_LITERALS: Final = frozenset({"", "other", "true", "false", "unknown"}) + +# OIDC client authentication methods, a closed vocabulary from the spec. +_OIDC_AUTH_METHODS: Final = frozenset( + { + "client_secret_basic", + "client_secret_post", + "client_secret_jwt", + "private_key_jwt", + "none", + } +) + + +def _enum_values(*enums) -> set[str]: + """Every member value across the given enums.""" + return {member.value for enum in enums for member in enum} + + +def _choice_vocabularies() -> set[str]: + """Collect the enums and choice tuples codex defines in its own source.""" from codex.choices.browser import ( BROWSER_ORDER_BY_CHOICES, BROWSER_TABLE_COVER_SIZE_CHOICES, BROWSER_VIEW_MODE_CHOICES, ) from codex.choices.reader import READER_CHOICES - from codex.models.age_rating import AgeRatingMetron from codex.models.choices import FileTypeChoices - values: set[str] = {"", "other", "true", "false", "unknown"} - values |= {member.value for member in Collection} - values |= {member.value.lower() for member in FileTypeChoices} - values |= {member.value for member in IdentifierType} + values = _enum_values(Collection, IdentifierType) + values |= {value.lower() for value in _enum_values(FileTypeChoices)} values |= set(BROWSER_ORDER_BY_CHOICES) values |= set(BROWSER_VIEW_MODE_CHOICES) values |= set(BROWSER_TABLE_COVER_SIZE_CHOICES) - for reader_choices in READER_CHOICES.values(): - values |= {value.lower() for value in reader_choices} - values |= {mode.value for mode in ComicboxTaggingDefaults.MatchModeChoices} - values |= {mode.value for mode in ComicboxTaggingDefaults.PromptsModeChoices} - values |= set(AgeRatingMetron.objects.values_list("name", flat=True)) - # Identifier buckets are ":", both closed vocabularies. + return values | { + value.lower() for choices in READER_CHOICES.values() for value in choices + } + + +def _identifier_bucket_vocabulary() -> set[str]: + """Cross the two closed vocabularies an ":" bucket key joins.""" from comicbox.enums.maps.identifiers import ID_SOURCE_NAME_MAP sources = {source.value for source in ID_SOURCE_NAME_MAP} | {"other"} - kinds = {member.value for member in IdentifierType} | {"other"} - values |= {f"{source}:{kind}" for source in sources for kind in kinds} - # comicbox online source names, used as tagging bucket keys. + kinds = _enum_values(IdentifierType) | {"other"} + return {f"{source}:{kind}" for source in sources for kind in kinds} + + +def _tagging_vocabularies() -> set[str]: + """Tagging modes, plus the comicbox source names used as bucket keys.""" from comicbox.formats.base.online import SOURCE_NAMES - values |= set(SOURCE_NAMES) - # OIDC client authentication methods. - values |= { - "client_secret_basic", - "client_secret_post", - "client_secret_jwt", - "private_key_jwt", - "none", - } + return set(SOURCE_NAMES) | _enum_values( + ComicboxTaggingDefaults.MatchModeChoices, + ComicboxTaggingDefaults.PromptsModeChoices, + ) + + +def _vocabularies() -> frozenset[str]: + """Every closed vocabulary a payload string may be drawn from.""" + from codex.models.age_rating import AgeRatingMetron + + values = set(_LITERALS | _OIDC_AUTH_METHODS) + values |= _choice_vocabularies() + values |= _identifier_bucket_vocabulary() + values |= _tagging_vocabularies() + values |= set(AgeRatingMetron.objects.values_list("name", flat=True)) return frozenset(values) From e9454ac774a22723ba641da7842de64769f229d5 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 14 Aug 2026 14:58:58 -0700 Subject: [PATCH 79/82] update deps --- bun.lock | 12 +- frontend/bun.lock | 162 +++++++------ frontend/package.json | 10 +- package.json | 4 +- pyproject.toml | 2 +- uv.lock | 535 +++++++++++++++++++++++------------------- 6 files changed, 398 insertions(+), 327 deletions(-) diff --git a/bun.lock b/bun.lock index 083a79ac0..274ab5f56 100644 --- a/bun.lock +++ b/bun.lock @@ -24,15 +24,15 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", - "eslint-plugin-package-json": "^1.7.0", + "eslint-plugin-package-json": "^1.7.1", "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", - "eslint-plugin-regexp": "^3.1.1", + "eslint-plugin-regexp": "^3.2.0", "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", - "eslint-plugin-unicorn": ">=73.0.0", + "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-vue": "^10.10.0", "eslint-plugin-vue-scoped-css": "^3.1.3", "eslint-plugin-yml": "^3.8.1", @@ -404,7 +404,7 @@ "eslint-plugin-no-use-extend-native": ["eslint-plugin-no-use-extend-native@0.7.3", "", { "dependencies": { "is-get-set-prop": "^2.0.0", "is-js-type": "^3.0.0", "is-obj-prop": "^2.0.0", "is-proto-prop": "^3.0.1" }, "peerDependencies": { "eslint": "^9.3.0 || ^10.0.0" } }, "sha512-kYJhgZkiZIavu/wIwrO+n4GemQcMX53kWCNZNr7nGMkRD1aBFLkDpBivEYP7nIJINCo9fzPbFjrpeX5kr2Qbww=="], - "eslint-plugin-package-json": ["eslint-plugin-package-json@1.7.0", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-6kc/2obmBs1JZ2/m65iZjDWFxC4n0OVDKTEhsnWCE6syL5qAJb0rzQtYFNaSmFe9hUWqhaTpr2+OnHQlU1X5kQ=="], + "eslint-plugin-package-json": ["eslint-plugin-package-json@1.7.1", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.3", "eslint-json-compat-utils": "^0.2.3", "jsonc-eslint-parser": "^3.1.0", "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^4.0.0" }, "peerDependencies": { "@eslint/json": ">=1.0.0", "eslint": ">=9.0.0" }, "optionalPeers": ["@eslint/json"] }, "sha512-nlIlX7thB20+uExdYL0QGAmyjHXI+AgIAWpwTs45be4XkJFaOGjSrShxw42b51aUHWGtNrA+eLSkeA1RYm+ugA=="], "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.10.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.65.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Kprsp9Us0GqAesYaAIzUViw57xYp5WBqzXrcE0Mtww++E5fexWXYBipMuuD7yvyH4vvpBH0+oJ+OMAmZ0oYXkw=="], @@ -412,7 +412,7 @@ "eslint-plugin-promise": ["eslint-plugin-promise@7.3.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" } }, "sha512-6uGiOR0INuujr6PEQmeSSP7GbIMJ/ebEXXiEzb/nOj68LknH5Pxzb/AbZivmr6VE6TkTE8rTjRK9zhKpK6HsRA=="], - "eslint-plugin-regexp": ["eslint-plugin-regexp@3.1.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^7.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-MxR5nqoQCtVWmJwia0D2+NlXX1xzdpkslsVOZLEYQ4PQWEaL65PCZXURxaBc3lPnkNFpNxzMIRmYVxdl8giXRA=="], + "eslint-plugin-regexp": ["eslint-plugin-regexp@3.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^9.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-4yq47CnLxyfHFKJEo5kvNaiJ0aDtBxSJWk4M2LiamplUXdWxdlzDYqImb/ZVCp+2BqkQjBAmw4Xc07Q8SXVDZg=="], "eslint-plugin-security": ["eslint-plugin-security@4.0.1", "", { "dependencies": { "safe-regex": "^2.1.1" } }, "sha512-/lZCkOxPOWaf1jXAqgICrS8St3BMBccIPvhOSUYuV6VCr1o5nFVG998FnTLt6w2Nxb8Uo0nM8fzmnhp+GY/aEg=="], @@ -558,7 +558,7 @@ "js-types": ["js-types@4.0.0", "", {}, "sha512-/c+n06zvqFQGxdz1BbElF7S3nEghjNchLN1TjQnk2j10HYDaUc57rcvl6BbnziTx8NQmrg0JOs/iwRpvcYaxjQ=="], - "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@7.3.0", "", {}, "sha512-DoyJXo7x/n48M3NsGOs9QnEws0ft0tV3YsSgvWMNxz2hZtz+Q6fpqUD96lVYX4a+jcEkzHeFNDJOn68TzXfbdA=="], + "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@9.1.1", "", { "dependencies": { "@types/estree": "^1.0.9" } }, "sha512-kojSbQb9iQM2bk2PmHiKa3reG0U4v2gN9U8D8+eCQq+4KM5ZXBUyBVeF8KmR0RiwqyrW4+uVWYWTOLnpsavnkw=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], diff --git a/frontend/bun.lock b/frontend/bun.lock index ea83fbe65..46061d43b 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,17 +5,17 @@ "": { "name": "codex", "dependencies": { - "@unhead/vue": "^3.3.1", + "@unhead/vue": "^3.3.2", "@vueuse/core": "^14.4.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", - "pinia": "^4.0.2", + "pinia": "^4.0.3", "pretty-bytes": "^7.1.1", "text-case": "^1.2.11", "vue": "^3.5.41", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", - "vuetify": "^4.1.8", + "vuetify": "^4.1.9", "xior": "^0.8.4", }, "devDependencies": { @@ -23,12 +23,12 @@ "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", "@types/node": "^26.2.0", - "@unhead/bundler": "^3.3.1", + "@unhead/bundler": "^3.3.2", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", "happy-dom": "^20.11.2", - "rollup-plugin-visualizer": "^7.0.1", + "rollup-plugin-visualizer": "^7.1.1", "sass": "^1.102.0", "sass-loader": "^17.0.0", "toml": "^5.0.0", @@ -58,11 +58,11 @@ "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], - "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + "@emnapi/core": ["@emnapi/core@2.0.0-alpha.3", "", { "dependencies": { "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g=="], - "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + "@emnapi/runtime": ["@emnapi/runtime@2.0.0-alpha.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@2.0.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], @@ -128,7 +128,7 @@ "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.100", "", { "os": "win32", "cpu": "x64" }, "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw=="], "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], @@ -204,35 +204,35 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.1", "", { "os": "none", "cpu": "arm64" }, "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.2.1", "", { "dependencies": { "@emnapi/core": "2.0.0-alpha.3", "@emnapi/runtime": "2.0.0-alpha.3", "@napi-rs/wasm-runtime": "^1.2.0" } }, "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], @@ -290,9 +290,9 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@unhead/bundler": ["@unhead/bundler@3.3.1", "", { "dependencies": { "magic-string": "^1.1.0", "oxc-walker": "^1.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.3.1", "@vitejs/devtools-kit": "^0.4.1", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0", "unhead": "^3.3.1", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "@vitejs/devtools-kit", "esbuild", "lightningcss", "oxc-parser", "rolldown", "vite", "webpack"] }, "sha512-F9gEgqpUKqHFzOv+7Pgm3TbmXhUdLX+WjZiPAK/huLDzblzZmvAUE9vRDymWaOST7jqGT/tPKkwl2kqbgb3nPw=="], + "@unhead/bundler": ["@unhead/bundler@3.3.2", "", { "dependencies": { "magic-string": "^1.1.0", "oxc-walker": "^1.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "@unhead/cli": "^3.3.2", "@vitejs/devtools-kit": "^0.4.1", "esbuild": ">=0.17.0", "lightningcss": ">=1.20.0", "oxc-parser": ">=0.98.0", "rolldown": ">=1.0.0", "unhead": "^3.3.2", "vite": ">=6.4.2", "webpack": ">=5.0.0" }, "optionalPeers": ["@unhead/cli", "@vitejs/devtools-kit", "esbuild", "lightningcss", "oxc-parser", "rolldown", "vite", "webpack"] }, "sha512-FgddDfNva/2n/BPRsqNJVNJ26TVnO5ywqrWYeC8xXV4c1CLQRABDwmmyy7hEsKH1YXg9bDb8IkZzyNBkJ59LTQ=="], - "@unhead/vue": ["@unhead/vue@3.3.1", "", { "dependencies": { "@unhead/bundler": "3.3.1", "hookable": "^6.1.1", "unhead": "3.3.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-iS+eiE1NehbV/eF7wzR7UUuUVTv8fIphFOCekQvEMuPqfKPCB8f3wf7sgPgUngYX6mdgM6PmkGmaOQgTBxqwbw=="], + "@unhead/vue": ["@unhead/vue@3.3.2", "", { "dependencies": { "@unhead/bundler": "3.3.2", "hookable": "^6.1.1", "unhead": "3.3.2", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2", "vue": ">=3.5.18", "webpack": ">=5.0.0" }, "optionalPeers": ["vite", "webpack"] }, "sha512-WMmKtBxEzYrs2GBG3nuFFguuq8CdbQ6s7NX8TxBNev++WGh8otHa6KvmlMwNCawu+iH83aRWuCaxLpCKAuCDkQ=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], @@ -652,7 +652,7 @@ "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - "pinia": ["pinia@4.0.2", "", { "dependencies": { "nostics": "^1.1.4" }, "peerDependencies": { "@vue/devtools-api": "^8.1.5", "typescript": ">=5.6.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g=="], + "pinia": ["pinia@4.0.3", "", { "dependencies": { "nostics": "^1.1.4" }, "peerDependencies": { "@vue/devtools-api": "^8.1.5", "typescript": ">=5.6.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA=="], "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], @@ -676,9 +676,9 @@ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], - "rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], + "rolldown": ["rolldown@1.2.1", "", { "dependencies": { "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.1", "@rolldown/binding-darwin-arm64": "1.2.1", "@rolldown/binding-darwin-x64": "1.2.1", "@rolldown/binding-freebsd-x64": "1.2.1", "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", "@rolldown/binding-linux-arm64-gnu": "1.2.1", "@rolldown/binding-linux-arm64-musl": "1.2.1", "@rolldown/binding-linux-ppc64-gnu": "1.2.1", "@rolldown/binding-linux-s390x-gnu": "1.2.1", "@rolldown/binding-linux-x64-gnu": "1.2.1", "@rolldown/binding-linux-x64-musl": "1.2.1", "@rolldown/binding-openharmony-arm64": "1.2.1", "@rolldown/binding-wasm32-wasi": "1.2.1", "@rolldown/binding-win32-arm64-msvc": "1.2.1", "@rolldown/binding-win32-x64-msvc": "1.2.1" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw=="], - "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.0.1", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^18.0.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg=="], + "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.1", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-ThaGiHTU8XW02OkK80TrTHATraJmM9OAduU4otal+7gyXLpYEtmGBLfx5kW+EHvvLwn03YGW2NnwKUIqsYlJAA=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -698,7 +698,7 @@ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "source-map": ["source-map@0.8.0", "", {}, "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -708,7 +708,7 @@ "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -782,7 +782,7 @@ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "unhead": ["unhead@3.3.1", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-eqHlbLyuvIXw898WopQmosTml4PdYW5ZhXDom/sf7ysAqQB9uvYrw2/dNvbrmkJTufSkmNmgGCjoQcvoT2mS/A=="], + "unhead": ["unhead@3.3.2", "", { "dependencies": { "hookable": "^6.1.1", "unplugin": "^3.3.0" }, "peerDependencies": { "vite": ">=6.4.2" }, "optionalPeers": ["vite"] }, "sha512-nys63ms9LcERQX4Z6UV5jrGDiHMhccKb8Jsm9Q7uCWGqYKSKNygiIGi0AsEBTDz5mmaS8J4yXva6U3qZg4FdBQ=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], @@ -820,7 +820,7 @@ "vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="], - "vuetify": ["vuetify@4.1.8", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0 || ^3.6.0-0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-003D/8b5462uZCD5CMRTZF3smADmOn47mzthNY0aP/xkslovR5yIc1qXjPdFN0LHcu+p3GEAVmMQabldaIQ5pg=="], + "vuetify": ["vuetify@4.1.9", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0 || ^3.6.0-0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-2NlpUiSwkUAGxKmkKwoRCst3IJF6EPP7msNJd+VxFjcAN9xCG+RFODzsfXfO2ypl9aSj3T/PJ65BBjQeTGgFKA=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -846,7 +846,7 @@ "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], - "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + "yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="], "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], @@ -864,6 +864,12 @@ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "@oxc-resolver/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@types/ws/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], "@vitest/mocker/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -914,6 +920,8 @@ "buffer-image-size/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "cross-spawn/path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], @@ -928,14 +936,14 @@ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "rolldown/@oxc-project/types": ["@oxc-project/types@0.142.0", "", {}, "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ=="], + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/rolldown": ["rolldown@1.2.1", "", { "dependencies": { "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.1", "@rolldown/binding-darwin-arm64": "1.2.1", "@rolldown/binding-darwin-x64": "1.2.1", "@rolldown/binding-freebsd-x64": "1.2.1", "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", "@rolldown/binding-linux-arm64-gnu": "1.2.1", "@rolldown/binding-linux-arm64-musl": "1.2.1", "@rolldown/binding-linux-ppc64-gnu": "1.2.1", "@rolldown/binding-linux-s390x-gnu": "1.2.1", "@rolldown/binding-linux-x64-gnu": "1.2.1", "@rolldown/binding-linux-x64-musl": "1.2.1", "@rolldown/binding-openharmony-arm64": "1.2.1", "@rolldown/binding-wasm32-wasi": "1.2.1", "@rolldown/binding-win32-arm64-msvc": "1.2.1", "@rolldown/binding-win32-x64-msvc": "1.2.1" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw=="], - "vitest/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "vitest/vite": ["vite@8.1.4", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.16", "rolldown": "~1.1.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ=="], @@ -948,6 +956,8 @@ "vue-router/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -958,6 +968,8 @@ "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + "@vue-macros/common/@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.40", "", { "dependencies": { "@babel/parser": "^7.29.7", "@vue/shared": "3.5.40", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ=="], @@ -988,42 +1000,12 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.142.0", "", {}, "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ=="], - - "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA=="], - - "vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA=="], - - "vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ=="], - - "vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg=="], - - "vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA=="], - - "vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg=="], - - "vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A=="], - - "vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg=="], - - "vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ=="], - - "vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw=="], - - "vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A=="], - - "vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.1", "", { "os": "none", "cpu": "arm64" }, "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.2.1", "", { "dependencies": { "@emnapi/core": "2.0.0-alpha.3", "@emnapi/runtime": "2.0.0-alpha.3", "@napi-rs/wasm-runtime": "^1.2.0" } }, "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww=="], - - "vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw=="], - - "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ=="], - "vitest/vite/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "vitest/vite/postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="], + "vitest/vite/rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], + "vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], "vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], @@ -1048,12 +1030,6 @@ "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@2.0.0-alpha.3", "", { "dependencies": { "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@2.0.0-alpha.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw=="], - "vitest/vite/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], "vitest/vite/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], @@ -1078,14 +1054,50 @@ "vitest/vite/postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + "vitest/vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], + + "vitest/vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], + + "vitest/vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], + + "vitest/vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], + + "vitest/vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], + + "vitest/vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], + + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], + + "vitest/vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], + + "vitest/vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], + "vue/@vue/compiler-dom/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@vue-macros/common/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@2.0.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ=="], + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "vitest/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], } } diff --git a/frontend/package.json b/frontend/package.json index 6d4331749..940e5b343 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,17 +16,17 @@ "test:ci": "vitest run --reporter verbose --update" }, "dependencies": { - "@unhead/vue": "^3.3.1", + "@unhead/vue": "^3.3.2", "@vueuse/core": "^14.4.0", "dequal": "^2.0.3", "eslint-import-resolver-oxc": "^0.15.0", - "pinia": "^4.0.2", + "pinia": "^4.0.3", "pretty-bytes": "^7.1.1", "text-case": "^1.2.11", "vue": "^3.5.41", "vue-pdf-embed": "^2.1.5", "vue-router": "^5.2.0", - "vuetify": "^4.1.8", + "vuetify": "^4.1.9", "xior": "^0.8.4" }, "devDependencies": { @@ -34,12 +34,12 @@ "@mdi/js": "^7.4.47", "@pinia/testing": "^2.0.1", "@types/node": "^26.2.0", - "@unhead/bundler": "^3.3.1", + "@unhead/bundler": "^3.3.2", "@vitejs/plugin-vue": "^6.0.8", "@vue/test-utils": "^2.4.11", "@vue/typescript-plugin": "^3.3.9", "happy-dom": "^20.11.2", - "rollup-plugin-visualizer": "^7.0.1", + "rollup-plugin-visualizer": "^7.1.1", "sass": "^1.102.0", "sass-loader": "^17.0.0", "toml": "^5.0.0", diff --git a/package.json b/package.json index cf82639a4..0ed33bb22 100644 --- a/package.json +++ b/package.json @@ -82,11 +82,11 @@ "eslint-plugin-no-secrets": "^2.3.3", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-no-use-extend-native": "^0.7.3", - "eslint-plugin-package-json": "^1.7.0", + "eslint-plugin-package-json": "^1.7.1", "eslint-plugin-perfectionist": "^5.10.1", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-promise": "^7.3.0", - "eslint-plugin-regexp": "^3.1.1", + "eslint-plugin-regexp": "^3.2.0", "eslint-plugin-security": "^4.0.1", "eslint-plugin-sonarjs": "^4.2.0", "eslint-plugin-toml": "^1.5.0", diff --git a/pyproject.toml b/pyproject.toml index 6df8f1437..6601d6804 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ dev = [ "toml-cli~=0.7", "tomlkit~=0.14", ] -build = ["resvg-py~=0.3.1"] +build = ["resvg-py~=0.4.0"] lint = [ "basedpyright~=1.38", "codespell~=2.4", diff --git a/uv.lock b/uv.lock index b2539ae87..152a8d3f8 100644 --- a/uv.lock +++ b/uv.lock @@ -8,16 +8,16 @@ resolution-markers = [ [[package]] name = "adrf" -version = "0.1.13" +version = "0.1.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-property" }, { name = "django" }, { name = "djangorestframework" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/12/0ad45c9d1f61d85ae97b3261a6ad5500e552bd22e8897f390828c95f0790/adrf-0.1.13.tar.gz", hash = "sha256:769c1b1e9e2abb7f46c83bc066e672ce832e8debfdaa455a995b906c41e168b7", size = 19262, upload-time = "2026-06-24T05:06:03.756Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/f3/2e4647d679c1c3cb8f7316eabc85d4fafe396318a5aa389f2ef14a2df103/adrf-0.1.14.tar.gz", hash = "sha256:c6ded6771a4a2a65c8dad3d3bf027cf0bb7b01025f8e9dff18c9a58920edeac6", size = 19256, upload-time = "2026-08-11T23:39:39.527Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/cb/a1d0826cd96ae8bfe07aaa58b151f548bdf32f1db7df6e67e8bc469b827c/adrf-0.1.13-py3-none-any.whl", hash = "sha256:684c082725b68d41d776d5f03102177c74d15af29dd3f748d329efad152e7f5a", size = 22765, upload-time = "2026-06-24T05:06:02.318Z" }, + { url = "https://files.pythonhosted.org/packages/38/30/9c482ba6256b0c4b57a4ad6a5da918f57064689d0d3d9595515707222ff9/adrf-0.1.14-py3-none-any.whl", hash = "sha256:dcf03cb6fbeb5d37dcb819740c17dd40db36481bbbb049f9fa8f39675747607b", size = 22763, upload-time = "2026-08-11T23:39:38.412Z" }, ] [[package]] @@ -180,14 +180,14 @@ wheels = [ [[package]] name = "basedpyright" -version = "1.39.9" +version = "1.39.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodejs-wheel-binaries" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/4b/c1f4e211e50389304d6af32b9280e026a7133e3ad59bbdf8f7a3250f8bee/basedpyright-1.39.9.tar.gz", hash = "sha256:32cbea5fc8273e89df3db20daea56cb7286e419ccdfdc479c64759d2dc071901", size = 24412216, upload-time = "2026-06-27T02:19:49.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/43/ad2999f3b09eb2b1e59931d88fac0f7bcc9c17fc18c903268779bd10cc97/basedpyright-1.39.10.tar.gz", hash = "sha256:c8eaf5302f3265e275c7df4fba194d7afa7c1cb53fbfd448e90098360aca2c2e", size = 24740347, upload-time = "2026-08-13T17:09:02.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/d4/e1fa108710d0498a18c77b1e13897f31eab47c69aa8cfe2d2a4df746541e/basedpyright-1.39.9-py3-none-any.whl", hash = "sha256:6b0837b9eba972c71895167ab9b127e6afdbc17abc92312e3f8d15ca82a5611c", size = 13374276, upload-time = "2026-06-27T02:19:54.431Z" }, + { url = "https://files.pythonhosted.org/packages/be/2a/a224054d75a58786c482f63b8ff2a09fc3362268bd1ebd0a61fb3f982153/basedpyright-1.39.10-py3-none-any.whl", hash = "sha256:cbd75d83c0be841329bcfef2d2f1182f152a6d975b8eb199e75cf5b8e9a3de78", size = 13482322, upload-time = "2026-08-13T17:08:59.074Z" }, ] [[package]] @@ -485,63 +485,133 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, - { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, - { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, - { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, - { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, - { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, - { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, - { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, - { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, - { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, - { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, - { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, - { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, - { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, - { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, - { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, - { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, - { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, - { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, - { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, - { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, - { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, - { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, - { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, - { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/31/4971872b3ed8715346231fb6eb4da8fcba65a4143c189db151ee28a2812b/charset_normalizer-3.5.0.tar.gz", hash = "sha256:49bd5feb59b0bf3cbf6ebcf4352e371c95b9da9bacd4449f8b64d0ad2c10a26e", size = 169295, upload-time = "2026-08-12T14:35:31.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/3c/045ea64ea5a550870dd8ab60b2242870328d53f17d2be593b4f9f3121474/charset_normalizer-3.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:98820e1ceb25c6df7a80c4fd8efa59cb121f99bc7c4c1693ad94a2caff5b311d", size = 343861, upload-time = "2026-08-12T14:32:27.254Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1b/7502be709db899d5b4801509829188b3a5a10969411da9c846115a5f1b70/charset_normalizer-3.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:608553f476fca509537e804c4a71f5eb166ce63b75141f89c2c686ce1aa36956", size = 237550, upload-time = "2026-08-12T14:32:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ce/66392661375148d9455c17bee25509a54e28c39969e34befa48ec8777936/charset_normalizer-3.5.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6753de11eef42f1c321b26d682957d92c7f7bbce6530f34bbe0f9291dd37cc6f", size = 229673, upload-time = "2026-08-12T14:32:29.668Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/f58c32a8d4ecf55b82ee61ee9aa6a664d4afcd36c72feb4c926fd6fe9af8/charset_normalizer-3.5.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f76dc0a47f94cb9b69d86f01e477f4b0371ca70208b9ccea7e063c41eed9046", size = 260768, upload-time = "2026-08-12T14:32:30.891Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ea/36c90e59a96386174377e855479ec154221ef001e96637e0b23be92489c4/charset_normalizer-3.5.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c387c6bf91b4774e359a48a179e2872b8e8bf741e4fde06ba8d1665eb9a4760a", size = 257880, upload-time = "2026-08-12T14:32:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/23/35/5b85772eb82528ef22ba29487ad544a7049dfd27f35b1a5a55dbc0843048/charset_normalizer-3.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14f6904a3cf870abf044df3a8c4924ac6c8ef77e9896586fd37e73ae96cff2af", size = 247547, upload-time = "2026-08-12T14:32:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/b6/14/ba11a99c2a22ab04c2d5383a700b378cb463a78ab15f36444cabc10cd671/charset_normalizer-3.5.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cce46dd29d73e135e8087b96eb62a4aca6d69391b7f97808c6588ebed3178f3", size = 243326, upload-time = "2026-08-12T14:32:34.794Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4a/cadba3f2400b45aa1d62a4ae0298bf58a3b30b1158baf15c338c7ce5b601/charset_normalizer-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b476cdb63df22da2b91837593380be3ddbe406f36c506c1c91d80e7196b66288", size = 238820, upload-time = "2026-08-12T14:32:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/47/41/d5188b9342d75b72c2b05d3ee373f01a691397e770f001ee05e3b37925f5/charset_normalizer-3.5.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1f56ce84b317ef2a59d7d3461891c7597c79247d2192bb8114c68a1a1debfcc0", size = 231661, upload-time = "2026-08-12T14:32:37.191Z" }, + { url = "https://files.pythonhosted.org/packages/13/5f/df38fa972c4e945c3d8cee2bc4e610613af522fd359c7dc7a74c419f0278/charset_normalizer-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9ce0f885239357379d92fd9a5fddbe20f0e30e0527c29ba69f8e99eeb1304a76", size = 261459, upload-time = "2026-08-12T14:32:38.369Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d7/043ff7720067a3beee05523465ac9c1c846c68b7884930dd483f72ee5ab6/charset_normalizer-3.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:96ae7ab5d8155fde927aa0864fbc8ba3cc4fde6d41ab0c7cea9d6012b4978603", size = 242300, upload-time = "2026-08-12T14:32:39.505Z" }, + { url = "https://files.pythonhosted.org/packages/19/f6/33980b7b802a048e546a6d9ad2ea783a6cf6b10a86aaccd10db462d8b913/charset_normalizer-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf91921009025e96ce57a03ced6d14604fc3baf0530351638e9504a55da6fa3b", size = 259101, upload-time = "2026-08-12T14:32:41.02Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b7/0d19bde844bff9165377c1da9ef3c4792a4c24bd49b5b7094d9e6f6ab58b/charset_normalizer-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0b2e44e6d42d1a4ff78ccc219a93c5449105d10b16198d1aea581080df8073f9", size = 249246, upload-time = "2026-08-12T14:32:42.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cc/2c34fdfaacdf0e96e880ef562cbf80a9b5f8ea97e0dd9e57ba348a9c65cf/charset_normalizer-3.5.0-cp312-cp312-win32.whl", hash = "sha256:deb99535e9bf0bea8e274c6413eb939a21be35a3f492678dba4d5b1f4d70f142", size = 178025, upload-time = "2026-08-12T14:32:43.909Z" }, + { url = "https://files.pythonhosted.org/packages/76/d0/c34dbd1df23bcbdc1b5d2f48256340d72fa747f1eb03924a9d2fa35ed85b/charset_normalizer-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54dd1a66fa4bce0ccaf0db9dde336e49b3eec646dc4c1c0991279369d373a14", size = 200143, upload-time = "2026-08-12T14:32:45.254Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/4e6bf1465d60c3d8f488d5bde140d0cb91cd23ccab0dc2895cc6c6982047/charset_normalizer-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:b8ea208b304587d47931b36481342d20336e0d338ab052f8b4305926482598d6", size = 180046, upload-time = "2026-08-12T14:32:46.545Z" }, + { url = "https://files.pythonhosted.org/packages/1d/be/cc7b7b6fc41984902c0d31b06f5d9297e67705c1dae9352608e5540fad09/charset_normalizer-3.5.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:5c23fa4f6eccdd601949cb00f3988c01d64e671d8faba356397971077022e144", size = 211050, upload-time = "2026-08-12T14:32:47.81Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ae/e3ec8f17313609f43f7b323012fdb1ee37b83432277ca4eceba83e00366c/charset_normalizer-3.5.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:07f6f42b5a6325df35b458004fb5f9f29bf502d89287a33c7cdef3590e31de0f", size = 222768, upload-time = "2026-08-12T14:32:49.027Z" }, + { url = "https://files.pythonhosted.org/packages/c5/50/9f9c0d7ccc1512d49e27a0e7c12c58ec71dfe91698fa4326f058c33e1f1b/charset_normalizer-3.5.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8efc3f1563ed431882dd0dc0411b5f8ace1b1b89074981deaf6bd8af77dbe1bc", size = 193907, upload-time = "2026-08-12T14:32:50.414Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/cfe7b745ef7f4c3b7214581955b5a0869ba2ac551a58fc11036281ae167c/charset_normalizer-3.5.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:368eb2fc9482158b3a3386e8f01fa61f479c968e9a19ceab8f0188b86b312991", size = 197135, upload-time = "2026-08-12T14:32:51.605Z" }, + { url = "https://files.pythonhosted.org/packages/28/55/30fafdcfca9ba616bc394240545e4cd52f4f66dea43ded81b7d2d5274fde/charset_normalizer-3.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:826a295a039178479a325be1ae60eded1f0b10f7dda749df59e2440de8f61d64", size = 339892, upload-time = "2026-08-12T14:32:52.82Z" }, + { url = "https://files.pythonhosted.org/packages/f4/08/bdca5fc2bdc36ee443673dc7d12b23885a5a7b282bef85a1a4c3b325b40e/charset_normalizer-3.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70ff1c16eb0eb5ee6bb12739292347f981a5ba764cc4df1bc2e69b0405d4ac3b", size = 239439, upload-time = "2026-08-12T14:32:54.058Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9e/506c8d7a7722bba7c8cdd78c1b5ef23bda92bfbe0b3e28ea84673d519a0f/charset_normalizer-3.5.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cfdab178a4add5483e26a9bb1c16d8018ccf39b4be7a3aea6c3979e6828f2ee", size = 227896, upload-time = "2026-08-12T14:32:55.326Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1a/dd828f2b1d6f4bf10821b9a74d866be05ffcdbfcddfc501d6fe6428762a7/charset_normalizer-3.5.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6083d10a846218502d664375b9448508d9fa580bd834567423156c6abfbe899d", size = 262548, upload-time = "2026-08-12T14:32:56.484Z" }, + { url = "https://files.pythonhosted.org/packages/be/81/196d26f6bd78b93e0d451b69082a71027ceeddd4b0be9170b81bb038f824/charset_normalizer-3.5.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0f211c21aa316cb6e2662e54a1194633a79d98a50a876addacfce7ba5b34b09f", size = 259986, upload-time = "2026-08-12T14:32:57.661Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/5b964a1eb0f9075ecd45083eeb21aaec215334f98bac3d400302ea73875d/charset_normalizer-3.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b08ebf9488c7ff5eff038e48e6ea938178dfd9dcc8598b5ca941e4ae27b20be", size = 249853, upload-time = "2026-08-12T14:32:59.123Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b4/aee3a9d82edd0e931091ef3e9f03e46491ae3590e96e998d0975dadbe17c/charset_normalizer-3.5.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95450fce59f00c6d08eff6572ec2e736e5054c9450253afd5748f8416f2eb9", size = 244217, upload-time = "2026-08-12T14:33:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/22/3e/33f72ca11c1b619b220fd9f35905ebd171cbd0e0470f2357e467b9e861ee/charset_normalizer-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5780a29823e1d2bec69b7a104ead4195a43f3e97782efaedbf1f79a0157af715", size = 241307, upload-time = "2026-08-12T14:33:01.589Z" }, + { url = "https://files.pythonhosted.org/packages/78/27/6029dccba958621c7f3a65136f87c5512d712aef9e890f09512cc171bd03/charset_normalizer-3.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:054420b5db984971d886e5e4e2c37c760ae6682aedbd066687ff0949d9ed5f08", size = 233305, upload-time = "2026-08-12T14:33:02.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/691c967be459153fe9faf49bf78bc95639ef8bf6dd008f38cc6389a349eb/charset_normalizer-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d016dc857136c726958102c3b8a3986acdc65ace6fbf12cfdc09cc4bfa2935b2", size = 263465, upload-time = "2026-08-12T14:33:04.166Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2d/9202221be5c90b2a835924191e362690ed8dc8c7d6606100c2bd03fe0f8c/charset_normalizer-3.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f2ce3d39fb4a9d674e6639dd5d3146b2e273475d2260f10163228d66fc04433d", size = 245060, upload-time = "2026-08-12T14:33:05.325Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9a/298772fd0a0cbccadf36451a1cd7eef4b66a11e99b4a7f6fafc47cc62c75/charset_normalizer-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a5613a3a82c974227bde18f03409e30c467f8065cb56d822e3eb83708a5f223d", size = 261091, upload-time = "2026-08-12T14:33:06.475Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/1a11fe66e555dbe2f5714ade6ba74fa29edc9155d9cf1001d4d6ed096aa7/charset_normalizer-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fded2e82ff082e5d8e017e2ddcc1411bd8cb83b8585097fc401ef574f756b888", size = 251857, upload-time = "2026-08-12T14:33:07.784Z" }, + { url = "https://files.pythonhosted.org/packages/17/fc/73b817e8af3f1d25ec5cf458d405abba5a144cf9812238a61530f5eac186/charset_normalizer-3.5.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:8f006866047c6ec4b627ec144b1e0bbc7427cb31fd7c08d19897d0ac9032af3d", size = 139745, upload-time = "2026-08-12T14:33:09.123Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fb/ddb66303c86f7dc5043a457dad9fa82b4d6d0cb97094f9bcdde21693fd58/charset_normalizer-3.5.0-cp313-cp313-win32.whl", hash = "sha256:196e270c4e80827b5072eed7d6aa661d133afada94fe366669f9609e718d305e", size = 177217, upload-time = "2026-08-12T14:33:10.305Z" }, + { url = "https://files.pythonhosted.org/packages/fb/88/6018cc8d76ea2b7cb02918f37e23e86c261d1a102713d7e88d2cfb8b211c/charset_normalizer-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:72982d9958a42f8132bf2d6b90214ed66477295ef1188731f98ae3511c6eeb5a", size = 198896, upload-time = "2026-08-12T14:33:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/04c0bbfc8d9abf91959f7a3d207d45cbf63a8116984caae2381890019bb5/charset_normalizer-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:0b373bab0b867b68b8eb249da9478cab9181a42993437cd2f5dba5fb0b4fbd1b", size = 179193, upload-time = "2026-08-12T14:33:12.726Z" }, + { url = "https://files.pythonhosted.org/packages/43/14/d098868dac5ff27e0258f548b1c74c6484be528384965d8fcf8fc6a4011d/charset_normalizer-3.5.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d95244906ed69d0f79f190893c65e336c15959003e21449256dc05c001b52ea2", size = 211664, upload-time = "2026-08-12T14:33:14.153Z" }, + { url = "https://files.pythonhosted.org/packages/e7/da/a944b32a46601ae5a4c3499e8d64ecd14fe82313f00da74dcdf00273a0b4/charset_normalizer-3.5.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d788e2ded0c4c47efa4d73cfe59eaf975ee32f425219873d2cb3e3fbaa00f636", size = 224375, upload-time = "2026-08-12T14:33:15.472Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/eabb5996be2f529744755e7b2fc9396eff4a64961f034e7fd49d54b9afb2/charset_normalizer-3.5.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:f9f91d3e8382900f3a68fa0ce94294479de9cd2de6bc0c70acd0f0dfd511836b", size = 194364, upload-time = "2026-08-12T14:33:16.607Z" }, + { url = "https://files.pythonhosted.org/packages/78/65/4ad3c5be108930310d8003f5602861d5b89f728293b9f09c3a4837f7ba10/charset_normalizer-3.5.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d54625cbf4e6b60bf0639728cb8b4cb541e340f6d7cafae5806051a40ddf4c45", size = 197643, upload-time = "2026-08-12T14:33:17.88Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8fee3201b98d52289be60a775797d69be05a04fb6cfb48c1587dad33e649/charset_normalizer-3.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1f99a8c3a1da5d955edbad18208b3d627bdd54c48a6e739fa877bdca98c686d6", size = 341384, upload-time = "2026-08-12T14:33:19.239Z" }, + { url = "https://files.pythonhosted.org/packages/a7/dd/9e757101d1f76c35c0643684ba499ac3a181fb2b264c68174bf727d627e8/charset_normalizer-3.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf1e75dc07a3850b53d1e5f75e04d3ae12afe56284be7821771eaa2466350c73", size = 241637, upload-time = "2026-08-12T14:33:20.619Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/7857023015400bc4aa0a82fbcca29fa2dc7ec25f971a130764cb2dc7a589/charset_normalizer-3.5.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ac5a9cc079c67d75f4ddf343276031879eadbb333d1bb231cce297b8d7b9aae8", size = 226170, upload-time = "2026-08-12T14:33:21.773Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ae/8b52935b304f7b6bbf33151ed2b75266b09aa4b6f8f04230d948885b2577/charset_normalizer-3.5.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0dfe83c1b4d00abbf433998117a14f56a5c2bc68226c0d331709eed0d1ce539b", size = 265093, upload-time = "2026-08-12T14:33:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/6e838f6bb059f2c0afc60a4e7f294252f043c254656ad4114c50302cae4d/charset_normalizer-3.5.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e8fa586df2208ef040684751345f10f503834a757c9a74ecd19c1a2f9b1ccd", size = 262789, upload-time = "2026-08-12T14:33:24.214Z" }, + { url = "https://files.pythonhosted.org/packages/c2/08/189b27e51fddc9d6b3695331da0e31792c1d88b953ad854e57f06e9b2cc8/charset_normalizer-3.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82cc5835997ec78afe293a192e385099355770a7db94b2fb1239d36b32796f1c", size = 250580, upload-time = "2026-08-12T14:33:25.707Z" }, + { url = "https://files.pythonhosted.org/packages/ac/55/64854e99b25841f83e8e37d9df2f3d1f96f693439f80e5fabd542a7e47ab/charset_normalizer-3.5.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:19e52bda45086df8a4be4bb5910af6f5d9d3b538c78712c8ae09ef10b85bf458", size = 245008, upload-time = "2026-08-12T14:33:26.971Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/7720c904fa635d4260b4dced6029cf3d298c57b26741365d5a8d28c54043/charset_normalizer-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3418edd0ecb72a0a3861cf72f31be0ad9b7fe338ce2b58fb5cc80b9aeb792700", size = 243892, upload-time = "2026-08-12T14:33:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/70/50/7bfcb327631d4870c720872b548745f6ec8baa044d51c21b5d1d32ac4e3a/charset_normalizer-3.5.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:125ee619611019471b177c70bc3e9d4cda9fad7e01d93523501d3b188df0193a", size = 230996, upload-time = "2026-08-12T14:33:29.511Z" }, + { url = "https://files.pythonhosted.org/packages/24/51/40c45d6d940c04005ed721aa54bdebf1ebb2930f8a2ae537e8d60484fb27/charset_normalizer-3.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a3ad0e3da22852533858663848608f3f24c0d35e5cde415a4903476f2b4c88ec", size = 265834, upload-time = "2026-08-12T14:33:30.689Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d4/ef7a227ef89d215b47f9df79c3966610b17faa13bb2f236989207a631622/charset_normalizer-3.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4ebebb410bc517e1d284c52a123e82704b21e4e7e26a21ebecf7439d0647b8a3", size = 245544, upload-time = "2026-08-12T14:33:31.859Z" }, + { url = "https://files.pythonhosted.org/packages/37/a9/a4ca9156964ded61c7718eba410ce11be2fd2b263fda4bcf08367b6578cd/charset_normalizer-3.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:91f9f7c151e772acebe489eaec96e96a2877202d7dd144e3f96b8676881715a0", size = 264110, upload-time = "2026-08-12T14:33:33.13Z" }, + { url = "https://files.pythonhosted.org/packages/38/6a/838364bb8702229c6e5f8b23f80ff0f052a12dfaf3113a12fd6acbe92a44/charset_normalizer-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:401ea6e7af9e7852ed818f64714b579c1935482049670847ca3bd7ba45dc63fb", size = 252303, upload-time = "2026-08-12T14:33:34.98Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5f/d88032edce951f499a2321cf7ae0d35a043c74be12bc22d81084cc7afbcc/charset_normalizer-3.5.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f7496aed56b06325a1ad419c5bf23c6dd042558e874f71dd1b958f3e255f3053", size = 139964, upload-time = "2026-08-12T14:33:36.195Z" }, + { url = "https://files.pythonhosted.org/packages/37/ae/1c4a46b6b00d1c34d2ee355ef99ad6173674166800d1af0f05f85028d513/charset_normalizer-3.5.0-cp314-cp314-win32.whl", hash = "sha256:606a86c1c3196f3738de39a67a7490bbd61cb31c0e0436070bd0c6a48170b38e", size = 179790, upload-time = "2026-08-12T14:33:37.356Z" }, + { url = "https://files.pythonhosted.org/packages/01/51/f94dcf34fa8eba48c1fb89b6490a5f1426e19488fe5f38aac6c648c99057/charset_normalizer-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ec6c464cf45867f66a2273e2214d9199a8fbad5cb95ca0fd45f6a2fe1d9d2cf4", size = 203723, upload-time = "2026-08-12T14:33:38.639Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ba/91d386870b5d9e4b0d8c4034f63877cc2e47b99c81ef05f3e6d42bf9a53f/charset_normalizer-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:dc28949de1bb5f7f30a46f15d74ce7ac5aaa63e03c5de04d68f571c7423af834", size = 183423, upload-time = "2026-08-12T14:33:39.899Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c9/534ecb17b7fb95f9052c4a44cf316316a27d4a8f73e8475ff55e778dcdd7/charset_normalizer-3.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:68b7e84ae8239a94f8d2c8f3f3a3a81bcde54805ec8f42a34de927d155688ec6", size = 368967, upload-time = "2026-08-12T14:33:41.093Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/ad7c3242d7fe55cd55126c22c65cb1b49779782cdf8932fd01d12232d86a/charset_normalizer-3.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58ca5dc0a0ef99f2801ec0574214c978e9574055bc783830bbb6e7433218609f", size = 239478, upload-time = "2026-08-12T14:33:42.428Z" }, + { url = "https://files.pythonhosted.org/packages/7e/62/77f0b850048e430fc350ec58876b0c020f5c8d0d3956fd1a4d6ae2fa292f/charset_normalizer-3.5.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6c57af4084c10cb3286688d65e4c654190ff5edcbc2411d08cdca0a8a44c59a1", size = 227036, upload-time = "2026-08-12T14:33:43.635Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ba/47d951e1a51dddbaad0a1410baf49fb1d897ceb00281568f1183b79bce9a/charset_normalizer-3.5.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1619a3cc174a7e3963dd34348e6fceb6e50db0ddeb0031bd7c73a58286454fa", size = 260772, upload-time = "2026-08-12T14:33:44.96Z" }, + { url = "https://files.pythonhosted.org/packages/b0/61/8c7ff4c81b2a88271126acf4b83ab3e31f6d63868b0f01d331eaa0f9cb67/charset_normalizer-3.5.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1328cc57dd4372be1265f68232cee890e087416e3e6e93e6ffb32c2bad4d36a4", size = 259273, upload-time = "2026-08-12T14:33:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fd/36129689be08dc287b951306946657ff70d76e287dd57018861f86d0e474/charset_normalizer-3.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06f4fb62a9139bef056b8b2da6773c94c2f259f90e4b8e53b166f3d0372d7cf6", size = 248086, upload-time = "2026-08-12T14:33:47.54Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f8/bcae67f994c8fd31dda445e5ebf84045823c31443fe46f0e9ee6aca99aa0/charset_normalizer-3.5.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:478650a70a750d75d5add401606c77f77069c32e4ba2c9131dc6cee566962ca0", size = 242671, upload-time = "2026-08-12T14:33:48.746Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/0472cdad1061c2f0e4d3aee29973eb6e81bb8fe256ff2860cf115b15f1c9/charset_normalizer-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48920bf6fe83eb2226756ac623fa54940487154eb18f80889d5735cf234965c0", size = 241311, upload-time = "2026-08-12T14:33:50.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/89/04a03de5d27c77c624d9fcf6287073754bd438df1b58cb7d030c57c2824d/charset_normalizer-3.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:168a0cb536b5123a77bc42ecf5e0bf6f923d0d9ae43c42a14eb0677c19ac6c19", size = 229898, upload-time = "2026-08-12T14:33:51.523Z" }, + { url = "https://files.pythonhosted.org/packages/42/a2/639c4278adcb7ed1f4db608dd9ac19b6774fa2285a96b1c0bdb9c124ccbd/charset_normalizer-3.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f278e131afa96a3622cef9211c406ea2ad1b68eb06f8837cd443684a40e0ae50", size = 262852, upload-time = "2026-08-12T14:33:52.924Z" }, + { url = "https://files.pythonhosted.org/packages/9d/95/02e34c97bedfd0c5574efb9179c850591acc7f967ba039ed8dd29d332b73/charset_normalizer-3.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d6100f877d2ed95f0856a3fde25334153add94bf2224c43f45f88e7039262aaa", size = 242913, upload-time = "2026-08-12T14:33:54.18Z" }, + { url = "https://files.pythonhosted.org/packages/a3/64/0946aeab6462dad9f160a50dfb4704d3f58a5ee708f085abc2105fbbff0c/charset_normalizer-3.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4c440122e1ea68b1f8b44a631ebf49c39180f6869b1da22d76e8a724208ec6e9", size = 257938, upload-time = "2026-08-12T14:33:55.802Z" }, + { url = "https://files.pythonhosted.org/packages/79/77/36787d41ead124746506a4425c729f4f17c68280af8a6a5baa0a598cae86/charset_normalizer-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e5f834965c2fe589837bac1002e07e25734ff70381903ccd95b3d649e22bfa40", size = 249467, upload-time = "2026-08-12T14:33:57.081Z" }, + { url = "https://files.pythonhosted.org/packages/65/10/d9f6c5589cd24198d4ce6cd2948191c18e657272f433e5a00d258d9f5c22/charset_normalizer-3.5.0-cp314-cp314t-win32.whl", hash = "sha256:076cf9d3f3c7e410295c09d96355cf3b1bcae74990034d80e4371e20fe1ba4c6", size = 190624, upload-time = "2026-08-12T14:33:58.449Z" }, + { url = "https://files.pythonhosted.org/packages/6c/81/43e0584a802051a22c725795ebe1df78263abc7de858eef6cdc9b36637e9/charset_normalizer-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3288a560dc3114d5d2ebe309b1ef43f8af355eafe25856832415c2a8196c9db3", size = 215902, upload-time = "2026-08-12T14:33:59.753Z" }, + { url = "https://files.pythonhosted.org/packages/30/f3/af6a1160fef0eac4510d035241e11eccf78e5350e4cd4de79e79fe02a5e5/charset_normalizer-3.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a284c36b9c6616bf0a8aa4aabba668a0c75ba65ccf40a79868aeaa69ad996897", size = 193452, upload-time = "2026-08-12T14:34:01.017Z" }, + { url = "https://files.pythonhosted.org/packages/42/a4/dee470afb7a55c4f78b6fef37306c51fed17ebf94dbe530798c91d394350/charset_normalizer-3.5.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c38d1e9bc2073b0984d2099ea647fd7f6c0d8f83a1e14e0cd32926f16e4c44ce", size = 341595, upload-time = "2026-08-12T14:34:02.4Z" }, + { url = "https://files.pythonhosted.org/packages/6a/32/9c3126dc429c6d9d7f79c52681a7c4453ed20a26267c9a8275d7ab620aba/charset_normalizer-3.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9f45186390aee4d1f26f723c615b67df346766c3b16df000d84d6e374f06757", size = 242177, upload-time = "2026-08-12T14:34:03.741Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9d/5b616a887301ff4cc0916b39ba44257390d3da80deeed6e8b6f2f26b14a8/charset_normalizer-3.5.0-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f0fde5e5100c735b2274ab898f0742a5dcde492796296cfbe7e0ad6a4cd1a396", size = 236730, upload-time = "2026-08-12T14:34:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/d6d3e70be93ebe5fabef65e4c7ac113e1d1705cbaeb5fb72467e713aca17/charset_normalizer-3.5.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d00e18e7bbf47e332ab63903d18bae31efc701b1d8cca0382b97784a621fc44", size = 265158, upload-time = "2026-08-12T14:34:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/3f1fafa87e2643257474f9c4eec609f2193a61d907dce7dd4f3f2390ebd5/charset_normalizer-3.5.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b81980668800dd1c69faad8aea6e85a8cee0e13bcd3bba7671695ff16260293", size = 262931, upload-time = "2026-08-12T14:34:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/0a/df/ebeb224a949d91829e5e114c6b64372a3c792b00762a9e951ce416f3a32d/charset_normalizer-3.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb3e0d1345b9c0fe73673ea656375f38a78ec679c2edeae0c24800f04798a85", size = 251388, upload-time = "2026-08-12T14:34:08.949Z" }, + { url = "https://files.pythonhosted.org/packages/a0/64/9a6ce2e7acc5cf1b4636f78f82e89ff581e06a0216a40678b28bd4d832c4/charset_normalizer-3.5.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2401f7671242e921e604f609d429f6b282ea4ca787a6ffd22ed7372011ddb9d1", size = 251821, upload-time = "2026-08-12T14:34:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b1/6e69b8056f615e5ccff6b91ca16db2d47922251f016821a300c115267fef/charset_normalizer-3.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:96720f2aeed3434bc48f4d52fbad64ecc820cfed88915d664780ed9ba09ede78", size = 244507, upload-time = "2026-08-12T14:34:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/0f/34/02c15d6a0aa6b934dcdc136b111da63ae857b9fd51cf5505b0736337c2eb/charset_normalizer-3.5.0-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:c455829625df983f716cbaecbba77f2d1dc2e0e0ed1638c059cece15a279344b", size = 240951, upload-time = "2026-08-12T14:34:12.991Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/0fe893d3e1c7d111280bd6c4bd4c1e431487a1124a1bcbce78dfeda3a3a8/charset_normalizer-3.5.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:c41b067eddcfa5ee6b1169c287605be7fb6b0ea22bba6474c5bb978a668def4f", size = 266162, upload-time = "2026-08-12T14:34:14.232Z" }, + { url = "https://files.pythonhosted.org/packages/55/ea/eca03527307670f5d102c295671a800c404ca958cf94fefd10fc963a72f0/charset_normalizer-3.5.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:e31786a947b136329bfdc458c82c06d4ec539b4a4436b7da4df4aafc9902ee80", size = 251835, upload-time = "2026-08-12T14:34:15.48Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/fee5633081e595fe9e191df6f215106c791ad596eddf5e41e39b8ea0f2e2/charset_normalizer-3.5.0-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:c5c6d47a865147e0ae3322ce92e7fb52ba3169d94b447deda56897ea2aa6fac9", size = 264314, upload-time = "2026-08-12T14:34:16.679Z" }, + { url = "https://files.pythonhosted.org/packages/cf/fb/17f47ae6ca35b562fb6e6f4b05f7aec6034217353eb4a23aaa3566dc7340/charset_normalizer-3.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c75191e3c8052045179646cb40e280800a4e0bdfda34d9c949c2f268d44e80e4", size = 253194, upload-time = "2026-08-12T14:34:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/59/88/f2b0f7ebb92493e925889ff29239b3b0073ffafd91230dbfc69e5cf9389c/charset_normalizer-3.5.0-cp315-cp315-win32.whl", hash = "sha256:83b62410bd36bb1178a7d563e2ee0cf21eb1c980c912ab99c2c78f06227f1731", size = 179800, upload-time = "2026-08-12T14:34:19.409Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f0/afb5bfdea52fd943b1960403847a276b8e900c6e4cd6a38752321b4eda64/charset_normalizer-3.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:e3b9eaa99a6d8c9ace4cd303915947ef55088d4cd87c6676874f98c5c03aa040", size = 203726, upload-time = "2026-08-12T14:34:20.656Z" }, + { url = "https://files.pythonhosted.org/packages/fc/71/219783eb691aa2ec879c0e521afdfe2b826f9678eed51b9c039d03e0db2b/charset_normalizer-3.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:fec352b793cdc183cc9e7e0b6c10fd7bff38ec54ba44cc43599b9b56f7f3db2e", size = 183428, upload-time = "2026-08-12T14:34:21.975Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d0/14aef3b9f80f2593c039d897e89034635b9eb0eb44b6ce5173bbd79ff338/charset_normalizer-3.5.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:c9bde7a960720c8b8e1b5ef7afaa0c9a2f3b55c44abd635b2b29dd066b298e3a", size = 368728, upload-time = "2026-08-12T14:34:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/10/fc/b249466ddbbeffa448b6597631e9091d1f01b5132ff8e7a0e21a6eb72b63/charset_normalizer-3.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8cd1283a9fe6c2065c807e9d5da81afe5e1e004caef39adc0d8ae86dd883698", size = 240925, upload-time = "2026-08-12T14:34:24.504Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b9/c17e72aaa1b3e1ca6c184e8025cf138ed492d01a54f85286ff7d31253a4b/charset_normalizer-3.5.0-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1c010dd86d3f4c4433c9634d33ce8147393b270dfa54f217f965540b8ae8e075", size = 234932, upload-time = "2026-08-12T14:34:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/f84ef0966bbe216f71029e34e7fa425a16b1682e2a40265e679dedf2b655/charset_normalizer-3.5.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5e68229977b2dea28e7061c0c0630a23f2f9f6e9c6fb38d77d3d6dbfe3768b74", size = 261733, upload-time = "2026-08-12T14:34:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/3b/73/3e887fa0781a395339355ed934ab6561ceb5bb52574160f070224039c630/charset_normalizer-3.5.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a60773eb5fda796e6e6f76b9c152d270fe59f9788a51a6ff8ba44082d8548ae4", size = 258460, upload-time = "2026-08-12T14:34:28.431Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/52ebd9849bf9e35d0b21fff115cb6543162a8e1f2f564e8f87121a336b8c/charset_normalizer-3.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9419f44e568f7fafcdc0b3b5c766a2364e705a9b34fb8a56b431e0d1f3f4258", size = 249894, upload-time = "2026-08-12T14:34:29.698Z" }, + { url = "https://files.pythonhosted.org/packages/85/f3/9366492b8a5fe0187de282e001d61345740cf79eb4a5f20181d769be02b5/charset_normalizer-3.5.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75e243abbb528c1a774390ed71e3f868a9f37b1373442e4bbadd401cfc505ff4", size = 249540, upload-time = "2026-08-12T14:34:30.96Z" }, + { url = "https://files.pythonhosted.org/packages/0b/af/28bb5e5dbd3e67cb9196a62781ac2b6d79492f4fc7a069b6ca7d6d6c8d58/charset_normalizer-3.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:54c963ce6404e52255b737e8a06d356fc762d59096ae566203a67cf2b7d050f2", size = 242734, upload-time = "2026-08-12T14:34:32.481Z" }, + { url = "https://files.pythonhosted.org/packages/26/d6/7ccfa62b53b40fc06b2d3504825aa400764740bd10cf248fdc4272441b93/charset_normalizer-3.5.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:63ea0cc840c66670183578c2630d138c0e944aeadfc33f25173ee240f5db780d", size = 239580, upload-time = "2026-08-12T14:34:33.916Z" }, + { url = "https://files.pythonhosted.org/packages/0b/82/71c0c9b046697b8da66b3acefa8d5f92d00a9ef433ad7c3522b971d0369a/charset_normalizer-3.5.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6c06875a1d4a7537bef70f659b55c6b55b9a47ec3ba8f2db610350c2d9915e6e", size = 263281, upload-time = "2026-08-12T14:34:35.333Z" }, + { url = "https://files.pythonhosted.org/packages/d6/01/d027583c869f40ba980c1c76994adbd522c360a6327e72beb44d7c267385/charset_normalizer-3.5.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:4253da1b4456b633651a8d59eb1dc7a8a8fa38241014dd7c217b353e547ae394", size = 250027, upload-time = "2026-08-12T14:34:36.991Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e9/6475d739e0ec8bb1236e06263dc3affaffdf947d8114ad27024932f325da/charset_normalizer-3.5.0-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:f044cb1cf44012184715f46584658993b5fee9344d71c4b0c455a17a299730c0", size = 257547, upload-time = "2026-08-12T14:34:38.277Z" }, + { url = "https://files.pythonhosted.org/packages/a5/60/d1f502fcaa048a2aca3ab80bfef8407659c131e4f1792fa805fec14b4960/charset_normalizer-3.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a17864853f7c518ae7d4b368af98f427f9396805476af40af8698560f09d7d97", size = 251718, upload-time = "2026-08-12T14:34:39.562Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/76343dcf4381a698807ff8a20d89f66bbdd9f6222b0b17740f77ab764335/charset_normalizer-3.5.0-cp315-cp315t-win32.whl", hash = "sha256:9e726478d7a213847860219d74665a6892a643ac93b8f76580f6cf9ed39996b7", size = 190757, upload-time = "2026-08-12T14:34:41.053Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ea/d18147626a1667cc773c42104ab155a4ca5d6d4d174b7a35e01062213ea5/charset_normalizer-3.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:d7229a99120c6c2792d96f4857c2648ce5530e93667a2c2388c5ef69a6b84775", size = 215431, upload-time = "2026-08-12T14:34:42.501Z" }, + { url = "https://files.pythonhosted.org/packages/47/21/4869598aae0872d94faa5933918a4fe37ab2c5af9d095786e241f9506fed/charset_normalizer-3.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:527e28a5e751d9e11369b9c5f9ab35c748eb9c109101920c7deb40d6eadf8d03", size = 193205, upload-time = "2026-08-12T14:34:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f3/7b523d807cb5e73562ef8acf21d39cdb9d704955327362c781bc3478a73d/charset_normalizer-3.5.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:5a4ee37248dfac25107c758bda99d545ce73e60b44d2dd39e4a2bb9f2831e9f5", size = 330840, upload-time = "2026-08-12T14:34:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/f0/de/fc68978fe78ca97063c96d764e41ff92ca639948f319271e0ff450e577a2/charset_normalizer-3.5.0-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a864bdcacd8bff58bb4845304e031f821a3ec64b2b7259f2d409cd49c9e59ca3", size = 251862, upload-time = "2026-08-12T14:34:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cb/82b41a0ab7fb1a88065f1d78ad32696ad88ea3fe8e25b8189d08833938de/charset_normalizer-3.5.0-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84b736e3b391601bc47b86da381c749c0f894e9191aaca9f31f30c2632206df3", size = 239484, upload-time = "2026-08-12T14:34:47.869Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0c/19608b631f4538f908098d4a2d56a8f79a665e27cc58e9d90479761a9227/charset_normalizer-3.5.0-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6abb1f356fb865baeb6ebc3fadd843e9a96fbf49b9adcca55037f3cceccb7438", size = 230602, upload-time = "2026-08-12T14:34:49.265Z" }, + { url = "https://files.pythonhosted.org/packages/29/db/f648eb30e14eba301aed61e11672156f137905c1bdbb530151abe8065943/charset_normalizer-3.5.0-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d366548d2ee28a8cfdcc4296363978cc644a728333be9824d2de4652e83df0a", size = 259208, upload-time = "2026-08-12T14:34:50.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e0/ed2c8bdbac484d69614d6993143aeb6cb0f4dd1561c883402517b623c8ef/charset_normalizer-3.5.0-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d672f329ae504ee240eb39b6effb3318aa8e7e8924c0ce8eee5760b3fad98539", size = 253659, upload-time = "2026-08-12T14:34:52.11Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/b4907cb9ec5b521d9d024ced13611240b86ef065c2eb15b3ad2334dc9940/charset_normalizer-3.5.0-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c54036a518748b6c02e666f6d46c3817561998fb904c3be25b56fb4fe3dc5706", size = 248821, upload-time = "2026-08-12T14:34:53.399Z" }, + { url = "https://files.pythonhosted.org/packages/12/b2/e2d1abcfbc05822f0030869efb4e9f8a3658e13b4821796d4b62da917327/charset_normalizer-3.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:22a1889f1c9b752c63c36758a0c2145458e3cadb20fced7a0790002e9dd12b26", size = 240271, upload-time = "2026-08-12T14:34:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f9/4ba127ad610542fa3eabfa41c45bf12d357860a815b3566374ec0188e213/charset_normalizer-3.5.0-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:b7eb3eab5c646d3de7dcb14a7c9caebace5249c5767da39e1761cb1576e521a3", size = 232155, upload-time = "2026-08-12T14:34:56.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/68/40613182366d00bd6dbd5f6c84a926cbd120960e038a8269e9ae7d782762/charset_normalizer-3.5.0-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:2080aa129a28267984cdc902898993d788c995c384e285d0d19199f56760d52e", size = 259674, upload-time = "2026-08-12T14:34:57.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/53/4574a14fa4c9de4a6c9f31725354bfa40b67f653e6d594ce1654f9a41b32/charset_normalizer-3.5.0-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:fd68c825548a611158230e2f9222e210ceb2e3391995c0aa5865cbdf3ab4bd49", size = 246122, upload-time = "2026-08-12T14:34:59.337Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/bd8030d13d92c058ca7b2b9615bbb3169569e144db64d65c149cd45abf5e/charset_normalizer-3.5.0-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:d8a9316f4da85e937242642b537c6d55d7e9287dd38e5634732f8233932aff45", size = 255221, upload-time = "2026-08-12T14:35:00.71Z" }, + { url = "https://files.pythonhosted.org/packages/77/9d/10ecd3bcbe2666b3d4d4026c97b48f73990682815db516052a1e8f4a31c5/charset_normalizer-3.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d90254c8f609338c53ec180fcd4c4f9c16502e238e3fc88ca7fd4c2f38d445b8", size = 253450, upload-time = "2026-08-12T14:35:02.217Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/d044c4872c0938a84f87b5027a698c0e61bacfc5c3551a4e749ca9b7bc5c/charset_normalizer-3.5.0-cp37-abi3-win32.whl", hash = "sha256:8b3e9e29b8b07cc461b9ce7768db7693a93979d0dadf22046f6f3555ded2f516", size = 173594, upload-time = "2026-08-12T14:35:03.845Z" }, + { url = "https://files.pythonhosted.org/packages/10/6b/6046773901f1944b9a89436351529811ee958afc7b774563be9d74a6f0c3/charset_normalizer-3.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:0c8953d9d1617794cfc40d81179571c9ba3805dd029623a15c93f1fb70e60a74", size = 198959, upload-time = "2026-08-12T14:35:05.187Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/b57708ac92aefc8e8389d51d5178129b81f03196da61ee2c23e687b8178a/charset_normalizer-3.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:562d24ca7797c1af8852994950c2e623a907b201fc4b0ed29e92af173d3828ca", size = 267055, upload-time = "2026-08-12T14:35:06.533Z" }, + { url = "https://files.pythonhosted.org/packages/22/c7/754d09943a616937df61e4ba367c409ded2a987e872972098d51a6fcf73b/charset_normalizer-3.5.0-py3-none-any.whl", hash = "sha256:993dfcbe75a85a3784abb5084f2c41b915767c90546fcc92803cffa28611baea", size = 67943, upload-time = "2026-08-12T14:35:30.363Z" }, ] [[package]] @@ -683,7 +753,7 @@ requires-dist = [ ] [package.metadata.requires-dev] -build = [{ name = "resvg-py", specifier = "~=0.3.1" }] +build = [{ name = "resvg-py", specifier = "~=0.4.0" }] ci = [] dev = [ { name = "django-silk", specifier = "~=5.4" }, @@ -815,62 +885,62 @@ wheels = [ [[package]] name = "complexipy" -version = "7.0.0" +version = "7.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tomli" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/17/42a5d52d74e9acb1ffd1496006f4b5c49318f516fbc12aa287fe31ba3791/complexipy-7.0.0.tar.gz", hash = "sha256:2cd8251532f8d9f560266179ff80353427f79604c3bdf9e3640ed32dca9b4e5c", size = 441274, upload-time = "2026-08-10T20:21:15.513Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/ed/53d0ea342fda7768eb98d1584403ce80258cf920cea5758dc0ea57e03990/complexipy-7.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ec9d47a4a662b234a0c365fb92e0685ad54eeb1ed6d18b1c1e5922de0bcf0773", size = 2384248, upload-time = "2026-08-10T20:19:12.306Z" }, - { url = "https://files.pythonhosted.org/packages/96/b6/0755f74366397fa8a6156f045ba94c6251930830654bc74c88529ac7b704/complexipy-7.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d722f7fe6f7ce06f74353085521edd67db8a1fafa5905c0f91c744a7508b63a2", size = 2321164, upload-time = "2026-08-10T20:19:13.667Z" }, - { url = "https://files.pythonhosted.org/packages/e1/67/05b3cc5928f50c6622e8350283c621b54e263fe0df722e945232b57a174e/complexipy-7.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e7754d6e5ba3866b0775f75e7b3ce69216af9faa2debe75c794ce2581e821e", size = 2524667, upload-time = "2026-08-10T20:19:15.058Z" }, - { url = "https://files.pythonhosted.org/packages/39/93/6d94fc163fa0a36ad3456e5eec866c1047d2011286125f5365f4f9270856/complexipy-7.0.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81190504987362d37ae8c24322bb5bfa026ae04734b5fedade7f1f2689d456e2", size = 2458564, upload-time = "2026-08-10T20:19:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/91/49/aee95aaa534446147108d391de1a0e6837ed9dc56f36be736862ed9fdcf7/complexipy-7.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0f448a1f0e8fe060eaa1cd6b92a89f01026c97701f86b7de3f6a11e7c131ff1", size = 2688406, upload-time = "2026-08-10T20:19:17.82Z" }, - { url = "https://files.pythonhosted.org/packages/ac/51/66a1dec58f844f9a208b8d43304d2a5cc0813e246c77432d41b24903db76/complexipy-7.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fbc1ab3e21b0291ae3a929bc85d688c02094781727ee3a42c8020cd9b7ec7b2", size = 2918803, upload-time = "2026-08-10T20:19:19.172Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c5/db2d5f31635b5fdfd5ac4d05d84a64606ab6945b08145bf406b69b7ccae2/complexipy-7.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73eb093258a3842b33f9230fb43ceb05373ca4cb94a5ebefd71d70f34e6a54d2", size = 2586235, upload-time = "2026-08-10T20:19:20.862Z" }, - { url = "https://files.pythonhosted.org/packages/85/34/c51f8c5b7be4cf7fde506315817be3bc8b426c65d37991bfaf13d3bb148d/complexipy-7.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c96a70e8f75abfbf4f3fd86de33644068c1378fdb2d17919844d4e134611549", size = 2549725, upload-time = "2026-08-10T20:19:22.148Z" }, - { url = "https://files.pythonhosted.org/packages/31/c8/78e65675fee73cd5eb52ed2090869e6436c13334d3724615ef528cf46a4a/complexipy-7.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78e5b57e1c23b8262bc659b6902ca0daba224c4f23c8e5d21a2a9ebb1aaeb05d", size = 2705292, upload-time = "2026-08-10T20:19:23.594Z" }, - { url = "https://files.pythonhosted.org/packages/b4/d2/47f707af4bdd443e2d2d3275b5b0e3e882065d56e9f514f04de97f60da96/complexipy-7.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e0ce95a7f9afed083dcba81f50b13ab024bc16c1c4595908df251e6d32be49", size = 2807342, upload-time = "2026-08-10T20:19:25.002Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e0/e32df61e990224286fb34b487312973baf76ccd0e9feb96de16e15bc8279/complexipy-7.0.0-cp312-cp312-win32.whl", hash = "sha256:df8b3795f7bbd86badff27359e7fc055e80ccc6e6095bf3b800eb0e5298ee1d3", size = 2085186, upload-time = "2026-08-10T20:19:26.4Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e4/236d24c1b21a405b7749e5c7f1c8b5589039a2cfb34c70bbbd50960fae11/complexipy-7.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:459c504769b880e9cfe0bd18b3c81b99045423fa170868dcc5475072defae1e3", size = 2250423, upload-time = "2026-08-10T20:19:28.116Z" }, - { url = "https://files.pythonhosted.org/packages/97/4b/e702108f7cea411801e401fe9cee4e32a965cf6fd8bf2afa739fedef7337/complexipy-7.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:186085b6f911d24174c396e0540d02d1828bda1f544805df1b8f2749e623fe37", size = 2383630, upload-time = "2026-08-10T20:19:29.388Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ac/819e7f46fc974ce15fd6928e1b56605ae3839021cb82871df569ae491c1c/complexipy-7.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:513c4e15357fcab515943ab4b502e07dce3699a7e083ca39f3b4d10dc6d99bac", size = 2320874, upload-time = "2026-08-10T20:19:30.761Z" }, - { url = "https://files.pythonhosted.org/packages/c9/30/596dadcedad5d371a4ed11c7cec98e1aeb95d8dd462622c5353209d888ea/complexipy-7.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172057334515ed013e5eb6873af3562a1984eeb10dfccfb9f5b1c745bb48ec3a", size = 2524393, upload-time = "2026-08-10T20:19:32.16Z" }, - { url = "https://files.pythonhosted.org/packages/77/e2/1d57864a0687d140e9ed4d9e1dc83f5ae7db270e5b36c00e59519f164707/complexipy-7.0.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f85104ecc8cb2b70df534e6a9652bd1e91a2291615befa80163f00e201f2af3", size = 2459213, upload-time = "2026-08-10T20:19:33.794Z" }, - { url = "https://files.pythonhosted.org/packages/04/a7/3ca7dac1f82b9661f646646fce1c30c3ba6cbcaea335c7d423fea9adc4b2/complexipy-7.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67aaa1b6df8870ef50b20af18e2fef36410959e3e821e79712623d922d9450e6", size = 2687375, upload-time = "2026-08-10T20:19:35.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/35/8fbba5f5bdec3dcc0875bac79bdac2c600e70db9482c688008f2d1aac81e/complexipy-7.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39c44867a9a1775afb5338392fe0c2e3d3db614cf08dfbdc16f389c1a04d716f", size = 2919234, upload-time = "2026-08-10T20:19:36.956Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ec/f18846edbb9c48f4a815b84ab100f3ef6f6bcd60fd69908ec73816dcc013/complexipy-7.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1793eb686f1d1e93cfe3ed55cdbbb47bf685abe7bc4a0cf98c2829482c666829", size = 2585904, upload-time = "2026-08-10T20:19:38.596Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a0/7b0ba6a8c3d404c476000906e0f728f87c23ba8893ba3a1b5685be8f37ed/complexipy-7.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020110d25866d275f14c33e2d43045c432c673855f74d8a86847d5aed97c0c71", size = 2550276, upload-time = "2026-08-10T20:19:39.978Z" }, - { url = "https://files.pythonhosted.org/packages/18/b0/6c42be696c822ddc7b86abd7db52681cb8af5a5dc90562ef1d3a8a81235d/complexipy-7.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50f1a74306eb4a84006077fd2054873e24f81291270f7c323ec8bcb2f11278f9", size = 2704982, upload-time = "2026-08-10T20:19:41.554Z" }, - { url = "https://files.pythonhosted.org/packages/3e/24/b304d306f718330607288cabef58dc46a817d941cebd11bb8bdbb9c6fb9b/complexipy-7.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a75ddd087a95cf5ce6e57647ddbdd539e4c8d4da72b2e82242e8e6cc523e01e0", size = 2807092, upload-time = "2026-08-10T20:19:43.016Z" }, - { url = "https://files.pythonhosted.org/packages/ba/da/0b8d6ba98471e30bae748dcc28115ee22695d52856f154011d2bcc18c74b/complexipy-7.0.0-cp313-cp313-win32.whl", hash = "sha256:9281f72e79b0f4b8e7b50b7a58be17f687bfea2a5763520a66a5da42aea77e22", size = 2085123, upload-time = "2026-08-10T20:19:44.51Z" }, - { url = "https://files.pythonhosted.org/packages/9e/58/751561694af01813800cc28a978651c2738890ba09fa9c65da46bb7a5f30/complexipy-7.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:9127ade1785064cbd354bdd6442a4d0151ccf476398675c7ec1edd5d5f9cdd60", size = 2249900, upload-time = "2026-08-10T20:19:46.65Z" }, - { url = "https://files.pythonhosted.org/packages/2b/7e/22bb23227173fe9a021db703f9e0c09411c887ce6433db2391e3c7f847ec/complexipy-7.0.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:29ebe4cbca7aead35b27a8b67785e0c9cafca0263e64dd0fa886bee37997d18a", size = 2384910, upload-time = "2026-08-10T20:19:48.75Z" }, - { url = "https://files.pythonhosted.org/packages/87/b0/8fc756be7ddc7b47247341fe5d32c9a8ecfe6273ab70e698a9939b4380b9/complexipy-7.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b45e860daa0dc49394a769c409040caea4bed831fcead22b85aabd373ae4c23a", size = 2321947, upload-time = "2026-08-10T20:19:50.031Z" }, - { url = "https://files.pythonhosted.org/packages/53/ac/7464fe0b68b7a2fd312a52c8897ff91c0f9f6b46593f6488c135be6a998d/complexipy-7.0.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b224473e031340286b5723a8649f3c0d0f2bfc5f3028aa183a1027eb1ade6a", size = 2526000, upload-time = "2026-08-10T20:19:51.385Z" }, - { url = "https://files.pythonhosted.org/packages/e0/86/f8ce1850f33a1f95a8ed99ff48feafc369bd2ee718de804a697ad56bb572/complexipy-7.0.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a402812a08683cb2788dd781f332a855fb474ffd3ad8e62a7b17c4041c0e6b75", size = 2460213, upload-time = "2026-08-10T20:19:52.852Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a6/5383b478020f8ae24acbb6b9c224e78b55be2135d25d692ca6f5f9293c5c/complexipy-7.0.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5d53747c0e0db306cab4951804c1ca387a9ede7be4fc232159ee3f36ef7488b", size = 2687076, upload-time = "2026-08-10T20:19:54.319Z" }, - { url = "https://files.pythonhosted.org/packages/38/f4/59686552c6e422309a8f725eabab53828ea1fa24ca7ca8bc91c182691266/complexipy-7.0.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6011036c1e7addbe2afdd0055aecda9e63859d60358ba57388f0a7b96088e87", size = 2922975, upload-time = "2026-08-10T20:19:55.772Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2b/c7e7da119eefb61d75ebcc64c3fd0ff6ab134beeaad17f27da0721b6de84/complexipy-7.0.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae9c0d5e49be1fd204398a056a83b301c239d67f3729b0279e0f274773cf6167", size = 2586564, upload-time = "2026-08-10T20:19:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f7/58529f12f6b78000693c6fbd14f69928d214972bf0d709edb58fe835a762/complexipy-7.0.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342511db5817c4725c06c81e8c80256b967f6e0ea1b2fc65f10d68cf95cdb358", size = 2550982, upload-time = "2026-08-10T20:19:58.758Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1a/05059778c05d78de9e20355776e549daa2b8be952cdf15f211bf947f7509/complexipy-7.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:08a0d7a1e6673eb217b9e2c90b6ba0d9756c4dc870f29e956697d823e61e5a6d", size = 2705584, upload-time = "2026-08-10T20:20:00.31Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f8/57c1148b92bbcaacde70d5196186c598f1b809cf4170775b8c4b9c4c9d40/complexipy-7.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ac0d9a82d76d3488d84da861a4538f9eef2859bb8ac4c4ad472dc3d04c4892e", size = 2808727, upload-time = "2026-08-10T20:20:02.043Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/2a272e5fcd6d0e2ba953165dadb9a38b00901acc4de17b83f2e0a91ddaf6/complexipy-7.0.0-cp314-cp314-win32.whl", hash = "sha256:f7db7f159725d195023db7f55926d8143d2e44b56d8e15c948e78b286bc7c712", size = 2085742, upload-time = "2026-08-10T20:20:03.558Z" }, - { url = "https://files.pythonhosted.org/packages/79/1e/50d6a569abe5026666b252d9373c4fd7c1bfdb3bca3c9ae329c6fc6f2f93/complexipy-7.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:af4217e9ab163d56527d0029868e810d4a0d8221a6cf66c99010e7e780b02c14", size = 2250902, upload-time = "2026-08-10T20:20:05.008Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/3142e38993e020cdab9fa1d5467a877317434ed7ada2bd362843ffe66546/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e949ad10cf092e2999bb066ef7776606c7fc5e3ce0e861dad51021f6b4401c4c", size = 2523172, upload-time = "2026-08-10T20:20:06.433Z" }, - { url = "https://files.pythonhosted.org/packages/50/f8/313d557fc9fe802e1a39d5b578a80bf5903a8bf667c474b5d389a8fbe37e/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8062c12f35a12dfba38d5f269ea8ff71fa999e10dd04c7bc0e3971a8a9f8075e", size = 2460108, upload-time = "2026-08-10T20:20:08.118Z" }, - { url = "https://files.pythonhosted.org/packages/9f/60/216b439fd7de9b5b7adaee3b53b90569ea7a7ff5898a4e3ee0e5edc5f430/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0469648687089ec695a3c316ad89792a551034e709dbfac38f144f5fe9ab7b86", size = 2686748, upload-time = "2026-08-10T20:20:09.79Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ba/cfe52a3d9ba2cf61bac15d9a371b5392f8edce8a1c484ccd9cdf50b4e003/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:473cdc9074073647567aeb340c7c283583a35c635ea3d78c020d1369092bcfbd", size = 2919372, upload-time = "2026-08-10T20:20:11.183Z" }, - { url = "https://files.pythonhosted.org/packages/73/74/dc0cd8cd3c8259030d7ca02e231162df50eb1d7a816548ca41011fbfa08e/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a738a47e4de2a3c4c3574995df56e57e0aec22077d8d4b0d51801e5e286c1867", size = 2586860, upload-time = "2026-08-10T20:20:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/5e/1e/5b08a7d3b11eece7ba93006e5ac166e6c4381420c296a8c9852eff17a6bd/complexipy-7.0.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5bcd4823f39254911de0d86d33adba76c20962ccdf5ce4e836baf38be6dc90c", size = 2549508, upload-time = "2026-08-10T20:20:14.167Z" }, - { url = "https://files.pythonhosted.org/packages/48/83/406cf3c72096e0720b122f173c9f1a3bae39a7e6c2964bf12da62a0677cd/complexipy-7.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ee8b71bdf46df9a82fe53df37d667aa7f254601ce5b51a94ece01871bfcb2b47", size = 2702436, upload-time = "2026-08-10T20:20:15.897Z" }, - { url = "https://files.pythonhosted.org/packages/71/d8/8bb15e335a8dceddd50c41347741201313ae65d25ee4a87d209a4bb52f54/complexipy-7.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:242eb4b9993ef3c88e7cf2e4e94a2a9f92335dc3efa6658b817a733a494ab377", size = 2806788, upload-time = "2026-08-10T20:20:17.365Z" }, - { url = "https://files.pythonhosted.org/packages/b9/68/caa9d8275b86d4a46dc5d00a70a92271ab1382b08f0684a9d648d0f69483/complexipy-7.0.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26dea418e38f29fbf6ca1e8673b8546df86ab08053a2894ff477a757f8f6dc13", size = 2690132, upload-time = "2026-08-10T20:20:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/07/03/daef3297c8f1ada255c86d27c03947f9f46ecba1c861b133833a2d13c9a2/complexipy-7.0.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65fd45380a623543e8d586513771798f21e4dd08cea058ca4196966f3d189876", size = 2550878, upload-time = "2026-08-10T20:20:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ff/74a1a508b49dada5c93c98c13e6aae7c78df9ade8bd7b6268e8478284b0b/complexipy-7.0.0-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6de6cc1dbcf3d33de4f149fa6497b820729116e139945f7dc642b92eda79ea3", size = 2689340, upload-time = "2026-08-10T20:20:21.725Z" }, - { url = "https://files.pythonhosted.org/packages/03/6d/957964a6fa1c9c9e66df756f2b82d59bd4ce87d02c0a321f4016ab2f9f97/complexipy-7.0.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5b50a9161809f2d47000fcce075436d8167308dbd813e06884dcc8583a65336", size = 2552076, upload-time = "2026-08-10T20:20:23.322Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5a/a1/25719a32617e791fa47d8c2659b3b23dae131795260617ef5c80f85354f7/complexipy-7.0.1.tar.gz", hash = "sha256:e9aeafb66d9f6e04e98feb1ce14cd31b114ae567578ed2ceaf0e6bbac7fa29fa", size = 442243, upload-time = "2026-08-12T22:19:39.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/62/3413d5d3b919895ff9d5b3a877fda9249597139d89148ff0f7f722bb0fdf/complexipy-7.0.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:692ee07f34c746d5a1d078f1d9c2024dcb7a810457e0ba9da1528ede6081c56c", size = 2384285, upload-time = "2026-08-12T22:17:59.132Z" }, + { url = "https://files.pythonhosted.org/packages/fb/96/1789e7a98cc735d5ad0c162a12f5c7eaf281f413bb03a5843d81ab09d17a/complexipy-7.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97147e5686c96096e2c819da682bdc97e994b2557357600db8e58d590cb03d5d", size = 2321330, upload-time = "2026-08-12T22:18:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/f3/be/f21e27810da7c14a5870ce5c89bdee846738f09f6a49066ae21d47754e1d/complexipy-7.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4843184024bcccc58b43e4c32d0df712c5fcfc594e76400747ff755aa023d2", size = 2525449, upload-time = "2026-08-12T22:18:01.484Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1f/1ec0104c6b98105022f7b754b15996bf0cd73e6a5204c86477d7bc837de3/complexipy-7.0.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b9d17418030f5f14a560a5cfd547373efb10bfd9ed6f4bee02ea55dc3cf5537", size = 2459124, upload-time = "2026-08-12T22:18:02.734Z" }, + { url = "https://files.pythonhosted.org/packages/47/20/ae1eb020427f0c7d67d5a738a87c8a392a9a2325dcce400bdc866d6d8d47/complexipy-7.0.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ab52c536c86a9eaac274fc3874ea0c58ccee64ef9379d3458c05e73b59db946", size = 2688249, upload-time = "2026-08-12T22:18:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/1b/2e/5099b51cd771ea34fe0606385848071adae2af0a25c0e8ba5a0b349d1535/complexipy-7.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb8faf364e140f19c9231d60ddd2219b18758b7a7911d5b8e977d99837ae08d", size = 2917963, upload-time = "2026-08-12T22:18:05.724Z" }, + { url = "https://files.pythonhosted.org/packages/95/ea/5114915a34c34ddd3af9767587bc2ae87c34222da887688ff4796369a43f/complexipy-7.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:067bfd58b17dd90efcf50851cab6fc7560cb6411c0a38f5a91cbbed9ae412707", size = 2586349, upload-time = "2026-08-12T22:18:06.92Z" }, + { url = "https://files.pythonhosted.org/packages/24/45/35c6e4568d1ec5c3964a85f101e6c3a2714a2dcdb2743af75b326c015786/complexipy-7.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4040fac7a28e168d42f7c40315b7418e134ab8aada7e3f8da061bc1e9fd46a14", size = 2549744, upload-time = "2026-08-12T22:18:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/28/7b/31290e7adab2af9703dc4c6ef27c9778c125a621084fd473695ce3e61c99/complexipy-7.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:04cfcaddaf05e987917e105f8852d7a9dd4365d6c33b12d47a7d9f23d9368da8", size = 2705040, upload-time = "2026-08-12T22:18:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8a/f77927559fd9516ec946371ad1dea44a4c95211e53d8376b54fcb0ff28a2/complexipy-7.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee2a8b611d5df3c706f7ab386b31a211cb9d911a2e36292cef5dcd042bfbfbb4", size = 2807634, upload-time = "2026-08-12T22:18:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/72/a0/6d3e1a98439f8aa4eaaf0bbbffcc571519b51c3fc5ef3e53ebe400f417d8/complexipy-7.0.1-cp312-cp312-win32.whl", hash = "sha256:7df938e6c1d76353f7c1e38140fab549a88e951c6e46222d3e85e607d58ff5c0", size = 2085240, upload-time = "2026-08-12T22:18:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f7/a1f01cdb35762e2af0c08f1802aba625f0f8b225f6ca3354606f7b439ea7/complexipy-7.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f753f842599664b5ab6b1ebd27ff306833997b5aeac28b1551fe7e2fc78be28", size = 2250602, upload-time = "2026-08-12T22:18:13.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/9c/49ccd33634d155bfb4c98a6c8be392d2beb9aa6e10e0ce16ba66864ce993/complexipy-7.0.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b960ababd486b62f18e548c252f5b5b37931c1a236923f04b626f283f1df590b", size = 2383639, upload-time = "2026-08-12T22:18:14.491Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/e1e3f0b088d6efedee48246cf016a073ccde8535f7b5b8d732f6244a02d7/complexipy-7.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4582c730c592bbd0a4c248523d8573305af9cc02d7677757088d5e5ff9695eba", size = 2320805, upload-time = "2026-08-12T22:18:16.031Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e3/c86048d3fba3b3d6c888de45080fda26261f2c0599b9e323ea3255e79ddb/complexipy-7.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84012b74389b0bf0d482a52a9e98f5e3086907c0d962eba9935df709759439f3", size = 2525534, upload-time = "2026-08-12T22:18:17.254Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/033fd088c4561f81a8eab367be76197ff67dda73bae227d9423b901e3e7b/complexipy-7.0.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c37fe24325f98ee02716a23765ed6d64e5652283b8873cf128e13f71562e5185", size = 2459412, upload-time = "2026-08-12T22:18:18.715Z" }, + { url = "https://files.pythonhosted.org/packages/d5/26/fbd52521467ac4315046961f84c509fe05be6d57e8702d4570ee560cc15b/complexipy-7.0.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95341957ee010e44cd9a231df0aa66cfeb40bd822e7a15c3483b7aafd35fb0c1", size = 2687847, upload-time = "2026-08-12T22:18:20.017Z" }, + { url = "https://files.pythonhosted.org/packages/8b/db/439dd0325ff262a848e64be8ed6f17a88860ddea48923fe1271d68f6ecbf/complexipy-7.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf52b78fe4c15ecbe0c39ca4b8c741f3db757d561976a9dd14ab6d221dad4ebf", size = 2918373, upload-time = "2026-08-12T22:18:21.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d6/231bcaa34829ef86d313c5066271169b21ddefcfdeeb1f29860453e24a05/complexipy-7.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6e1038060c990c0c5f8afdb7d0827afdc61a8b8c07b36c507e5d93b42f13a24", size = 2586778, upload-time = "2026-08-12T22:18:22.383Z" }, + { url = "https://files.pythonhosted.org/packages/db/85/bd15c109459a9b34050a5965c5b3fa5da40f010572c556796441eef99112/complexipy-7.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc12b194a36299bfd9f72a2aafda718c6038b9ee747f316d9564fa58ec0f9827", size = 2550325, upload-time = "2026-08-12T22:18:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b2/10a2ab0adad9b65d11a17ca185c4675a2b53eb6ba88b6585e10010cd5fb3/complexipy-7.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1ebcb1c90e162c18c51b4551adcf5a3dd8076d629899b6e97e7851bc080a87e1", size = 2705490, upload-time = "2026-08-12T22:18:24.657Z" }, + { url = "https://files.pythonhosted.org/packages/6f/72/a319d0683904efff91fe288910fa7d4f2f954c160a575c09912b997f1f2e/complexipy-7.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:560f38758ca4118cdb3d3997557a615a22c98fe86208e69429e04b92ba920fff", size = 2807148, upload-time = "2026-08-12T22:18:26.05Z" }, + { url = "https://files.pythonhosted.org/packages/39/9b/3433d83595f4a8608404944c3d13e7891894d075a11b125e2169e78ac4b5/complexipy-7.0.1-cp313-cp313-win32.whl", hash = "sha256:d0472c69790d1acfb54acbe0f4074e810fa5aea0aafc97cd91433187ff504e29", size = 2085182, upload-time = "2026-08-12T22:18:27.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/9e/e8d107a796b91690b2b763e8d2d49152cda02c583376942f97bba1569e7b/complexipy-7.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:54650d957684333852005e8cc26b61eb3979f83428a1051e872545733387ad9b", size = 2250129, upload-time = "2026-08-12T22:18:28.269Z" }, + { url = "https://files.pythonhosted.org/packages/f5/40/7e71997c42350239346544fa96c1bdaf24487f341d5b54286007e017ee87/complexipy-7.0.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cbacaa05423a78765f2c71cca3c22a7bb60aed478e1aff40c21708a124422e5d", size = 2384964, upload-time = "2026-08-12T22:18:29.682Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f4/4d073477cbbe036a4d4e6cf2f5b7dd88496c4a19ca643658251fe4d52881/complexipy-7.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3923526fd3005848376ec64e2be15cdb7d8391093301c9ec3693d7510fdbf3c9", size = 2322023, upload-time = "2026-08-12T22:18:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/fb/19/c89a5d8b1c7552d6b4546d9f1e4fbd5c0f00d8f7ef3ac6676da084548231/complexipy-7.0.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a41144de681a8fbaa8ee4fa0035cee6331c9dced8539b1664349481da911b1f2", size = 2526081, upload-time = "2026-08-12T22:18:32.265Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a7/dc16525a990706c3a71bf183001b1a4f3fc7d04898891825520e758212c7/complexipy-7.0.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1a597ffbbdd4eaed3aa59ec258e0f8097cb75d899d017e7d3f52e0181c2fe650", size = 2460165, upload-time = "2026-08-12T22:18:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/04/bb/b98c40de9d581c5ec6e9dcf26b6acbff63a52194b67f57ee77f62945ac86/complexipy-7.0.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2da01466b2c302bf9b3bd3e73bb8bbc1616e07cebb4834e4f61b1edfde6578ab", size = 2686829, upload-time = "2026-08-12T22:18:34.681Z" }, + { url = "https://files.pythonhosted.org/packages/fb/17/f50c0c4d3ef908363c95311a9e0a52ac80e5ceaa74f4b93b7d865da7ef17/complexipy-7.0.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78933708b59719c4829a338209d24c8816576c882aaf9de1a0bbd0e538bc5bff", size = 2921406, upload-time = "2026-08-12T22:18:35.87Z" }, + { url = "https://files.pythonhosted.org/packages/56/7b/579098e787eed0d02443175dfd74b1a292c5c8ada02792a0095efd805263/complexipy-7.0.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16d46d5ea559421c24c552d78f8b29e2858278fd940e707bb5fdf137c4e300d", size = 2586715, upload-time = "2026-08-12T22:18:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f7/f100ac59a722c076043789b6c30c15cecbec66253dd99c720052115b4daa/complexipy-7.0.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6560ddb896270f5575d01bcef52a4d7e4fe7445a7812bbc70fc6af5ac522142a", size = 2551173, upload-time = "2026-08-12T22:18:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/7b/54/efeb021c17c9cfc02622626500e8fbdde0a1f6c003c3e03880ba9c5aff4b/complexipy-7.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dcc304acccd9ab1685a84c14a708c373dd4679e7132dd71cc2d7129e5826c9ed", size = 2706114, upload-time = "2026-08-12T22:18:39.487Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f2/c4072e96bf516291ee7c9d8e8602998b9567497c9cda0995790c40d7e14d/complexipy-7.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:160fbbe5197f720ff28b2940d59286154c3b93797fe950f11216a7906e9c1e0b", size = 2808532, upload-time = "2026-08-12T22:18:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/67/ac/aedfdb359028f2616e479b7d43050d0a5b8af0ae9d1c904eed6332dd1482/complexipy-7.0.1-cp314-cp314-win32.whl", hash = "sha256:337665732690599805178230923cf653c823cf55c231afde60ff8a6a922339be", size = 2085776, upload-time = "2026-08-12T22:18:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2a/d6d43a0ac1ac50ed673a35eb28ba077f9168d4c919f17b56dc3284db84e7/complexipy-7.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:f1ccdf3aaf55bf477187bdaa7c4b765a7d91033ed3187f1090e510ab2d468ab9", size = 2250961, upload-time = "2026-08-12T22:18:43.238Z" }, + { url = "https://files.pythonhosted.org/packages/f2/3e/5a2deeb4a2a177127e6bc00f4ec27abf6ae02f697ca3aca08824b1296f17/complexipy-7.0.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e68809dcd7cb752cb3f88b336bfe44c9f82079ae5f5a323fdda20ddb5bf89385", size = 2523596, upload-time = "2026-08-12T22:18:44.407Z" }, + { url = "https://files.pythonhosted.org/packages/70/ce/98c826e4e8c25ce075bf1ba4a230b11cf253e57dec9b82d8509aa6292e08/complexipy-7.0.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d49be5a9ab8b2e9f20a63570b7ed6a53b3e052897dfd135453ab867cf8c9e4d7", size = 2460428, upload-time = "2026-08-12T22:18:45.609Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/af44aa7d6e32ee426d591c29440ceff4d07b335b48000d0b372067c04592/complexipy-7.0.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb1e4381d91cf233616652528bd702b5c1d8ee7e7d1eac237d07bed9f0b4df7", size = 2686732, upload-time = "2026-08-12T22:18:46.868Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/fdddb7366313dbccb1e0da559824cb7098b1c2ee46fc558c181083299fda/complexipy-7.0.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3202989419df622dffb1ecd24fdf9acc6fcf2354981d63cdea4ffe2d8a92610", size = 2919040, upload-time = "2026-08-12T22:18:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/55/d3/c55c463eccedbd61179efc552c750abb5961eadfb6024b4be078b4639707/complexipy-7.0.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0d7b9494b36b65b12dbc0e3be4417c0e1409b908dc252d60c418c20cb254c09", size = 2587702, upload-time = "2026-08-12T22:18:49.244Z" }, + { url = "https://files.pythonhosted.org/packages/dd/84/450b73146a0c02d3f13cb6aad6f86da66afa205c3109d2f35d8c2ca37257/complexipy-7.0.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c04019e3b5d4ba4ebc769096bded578f40ae3bfe0674e5a62b19ad8e6f4bb063", size = 2550030, upload-time = "2026-08-12T22:18:50.644Z" }, + { url = "https://files.pythonhosted.org/packages/08/4a/5c360db78099c29c29ff6c10164f088ea5fc4b9e29031a1cb0eccac24523/complexipy-7.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:37c42db8e1255f002e7bd66f3c13683bf857c45971fec809c89fbad1cff63d92", size = 2702458, upload-time = "2026-08-12T22:18:51.827Z" }, + { url = "https://files.pythonhosted.org/packages/dd/69/6543a6b801fd4298b2baefa0b8668206dfdd369df9b16b9dc0aad933289d/complexipy-7.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1e399b22d7e0c4ec7f6785bd0797339419d98a1ee1b4952055d515138df35bf", size = 2806439, upload-time = "2026-08-12T22:18:52.924Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/bcb789e44eeb5f3cde2de28da3a39e45d6b7aee3c160a75af10e8e50d17c/complexipy-7.0.1-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cf6026e149f62a96adeccd120813897599af865c290b5eae3db2b1c6910d7f0", size = 2690105, upload-time = "2026-08-12T22:18:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/237597ef59d2a6a1249c8e7741dab9dbb5f626aa2175ab1a9cb26b7e7a79/complexipy-7.0.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cc8f3028cddee9fbd3a8258765e00b4b06990d311e803122f71ba5faa167c59", size = 2550932, upload-time = "2026-08-12T22:18:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ea/e6b137f2017893ff6868773e33c0fb5c0cd34ae65a20bad9e061e1718cc2/complexipy-7.0.1-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b26c9674cc71642b0b7c6a519b34a5c381c86f72883a96dd746aafcd1ff41d15", size = 2689690, upload-time = "2026-08-12T22:18:56.939Z" }, + { url = "https://files.pythonhosted.org/packages/35/6d/9045128929c116c82ae6e87b7d9f6f13f4e492ce861eb72b572051cc3cd8/complexipy-7.0.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74bc6d1018308a9269ce3daf3079b828b7c375bc6580993d09d98133eaecad3", size = 2552183, upload-time = "2026-08-12T22:18:58.406Z" }, ] [[package]] @@ -1107,15 +1177,15 @@ wheels = [ [[package]] name = "django-allauth" -version = "65.19.0" +version = "65.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/b7/6107ed71b0136a6d2a6647dbfe125dbc6c2fa764fdb19e98bda9bd52b0e6/django_allauth-65.19.0.tar.gz", hash = "sha256:9a615fd7f48f31985e1e3d886f8983e12d09e189f8d00d7a9750b3d300b7b366", size = 2283106, upload-time = "2026-08-06T14:27:15.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/93/776a61df6ce71f82e98afa1938c6e540ec239a8f80b2747c3c33cb9581cc/django_allauth-65.19.1.tar.gz", hash = "sha256:999d01d5c422a82734cf5804df9f5a0d1d4ddd6acbb2f807db976ed7d49f9cb3", size = 2286662, upload-time = "2026-08-13T10:02:17.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/ec/3595bf98e7a2d161cab30c79f6bb2e395ff4f707110ef55ea0d4c04348cf/django_allauth-65.19.0-py3-none-any.whl", hash = "sha256:e37efea604d0bf40a6b16edd5b00e1560414e2c3ed07c2431b6e5bb058e2a771", size = 2073767, upload-time = "2026-08-06T14:27:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/77/f6/3c5f838f2e66903b422ae45f8f44b9e5da6457a4d4da125d426b339d86c8/django_allauth-65.19.1-py3-none-any.whl", hash = "sha256:5c507b53c45d2b7cbfcac7643d0123d5f0a7df7d43a882edcdbedb5f6b396421", size = 2074420, upload-time = "2026-08-13T10:02:26.339Z" }, ] [package.optional-dependencies] @@ -1178,16 +1248,16 @@ wheels = [ [[package]] name = "django-silk" -version = "5.5.0" +version = "5.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "gprof2dot" }, { name = "sqlparse" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/3e/db3bda7cf1327aa3800d32dca80f0d077954b52f61dc32cf76f605a28767/django_silk-5.5.0.tar.gz", hash = "sha256:41fcabe65d59d31ccdb69daeb3c3e6d30879ab2c00b0875b111fda0f69aec065", size = 4495794, upload-time = "2026-03-08T05:00:57.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/f4/e5f50497db9a3e59121237e3f74d6b2a2e5fc09ae0d242a659f3e0ca33c6/django_silk-5.5.2.tar.gz", hash = "sha256:c488ee1eab763a6f3d0db09a969e7a91b637743f04159cd8d10141386157ec0d", size = 4498731, upload-time = "2026-08-13T04:25:18.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/54/c56950e8b9279d2c6156d46571a64c1a808c07bddde571819e2bdbb8d5e3/django_silk-5.5.0-py3-none-any.whl", hash = "sha256:82b5a690d623935be916dd145e2f605a4ac9454d54e03df6a7ffcf38333c8444", size = 1944887, upload-time = "2026-03-08T05:01:13.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/da/48607c4a756df4a3d37752006824004fe6ecb5e0a2b34864c97a9903ea08/django_silk-5.5.2-py3-none-any.whl", hash = "sha256:6f11ae724699192bdda247513aa40160fd5e7e3de3083ec307b7151d801d1d51", size = 1945106, upload-time = "2026-08-13T04:25:29.316Z" }, ] [[package]] @@ -2757,11 +2827,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.2" +version = "4.11.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/98/0bf930c4f97d0266b58a89e36c015f56232c52b5d2f207215d48cca9e8f7/platformdirs-4.11.2.tar.gz", hash = "sha256:3a2ae5fca3520a01ab1be8b45613537f52ddf5b5f6f53d88233892dfbf0cd82d", size = 32716, upload-time = "2026-08-10T15:48:06.092Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/d7/e7bfbc86e9f99ff7807e24de7703f032e9c9ba80bb355cf26e0e9bc5a75e/platformdirs-4.11.3.tar.gz", hash = "sha256:66a73d38a849810252df809a3d8bcbda8e26f6c189920e7535ad608a48dbb5ab", size = 33050, upload-time = "2026-08-13T22:43:27.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/e2/4e6eee633809c376c024821b91ade709cbfd040ec53939ffbcc292aa7eee/platformdirs-4.11.2-py3-none-any.whl", hash = "sha256:7f89089b6ea71bda7962953edcf784b2e2d9d285b40ad88be2bb75c6e9d82ab4", size = 23361, upload-time = "2026-08-10T15:48:04.855Z" }, + { url = "https://files.pythonhosted.org/packages/19/a9/c34aebedd3a4c9afe5101b1b8713710b3fec18087c8a36c35d2f909861bd/platformdirs-4.11.3-py3-none-any.whl", hash = "sha256:5ed065d443751de711da036041a7a214122efc4a4de393b3f4137ba5576540e7", size = 23491, upload-time = "2026-08-13T22:43:26.121Z" }, ] [[package]] @@ -3579,69 +3649,57 @@ wheels = [ [[package]] name = "resvg-py" -version = "0.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/31/38e2aa968d1d294469c5beba43c1256dab435f328c172d7b91ec310bb87d/resvg_py-0.3.4.tar.gz", hash = "sha256:a398b46979a23ff0699e7009f9ba12bbcab93769aea58a3bb3ad94534afa4131", size = 1746109, upload-time = "2026-08-02T23:11:30.307Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/29/766f6eed8fcb4d6298950de250135c635b6cdf801d041e0081cacdf3f9b2/resvg_py-0.3.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b256b132d06ba05ba634fcf5d360eee2f6197b410d578b90f008ce53387fd50b", size = 1201042, upload-time = "2026-08-02T23:10:04.615Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0b/61907558f3b09ed6678a54f11474bd7b1639b5d189001a90a6c2e26e6f6b/resvg_py-0.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e6c691c541360180e174f0169e1e65651cbe4a5acd0f3887251921198104005", size = 1147038, upload-time = "2026-08-02T23:10:05.894Z" }, - { url = "https://files.pythonhosted.org/packages/ab/d6/b3b2563459e06efb25d3ff556d55f5c34af7d043077f9418d920091cd0ad/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07bc0340f60095802883b5344205e8b2f4e14502e827fd3792f7767dd4ddaa19", size = 1343304, upload-time = "2026-08-02T23:10:07.083Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9c/9af1121fdf01d567808c16d62a4499312b2427b500959d73ff13f7e6906a/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d95b6de22a7852d05c05c03149d104e8662605bf39c4043a9622cf2f930fb6df", size = 1267162, upload-time = "2026-08-02T23:10:08.136Z" }, - { url = "https://files.pythonhosted.org/packages/34/53/91a20a3aa0d26ed4ae7ff5b66524cd5d9c74d4aed3756d793efb7ac1ab22/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc1abb404a5c4390a1ebe57e0f3c100ac9e88bcb3e246e3c79786e44aedc725", size = 1440905, upload-time = "2026-08-02T23:10:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/15/7d/0bdd4e8ccc69a7d9da9f828d16f0659b7a3fc065a7556ef85304bc4b55be/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b1d253c5becce29c2b5574b74ad30fc7bbca0b27febadd317b326b9fd09053", size = 1398000, upload-time = "2026-08-02T23:10:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/d3/75/1d35b767ca110a47476fe2813792a53788e57548734e838b043d44c4c421/resvg_py-0.3.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c646bbd2a01683527e7df3fcac47b8f9c4fe8a84b9cb26f6c41fe21f4dfbe134", size = 1343934, upload-time = "2026-08-02T23:10:12.255Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4b/e76ae56f47e7507a6f24a34e5e3e87ad8d06f1049d0cbe33e34bc7a159b8/resvg_py-0.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff104ea3de66bc5e9b9548b67b3f35bb74d92a84a5671e0143338c139757a2e", size = 1425791, upload-time = "2026-08-02T23:10:13.418Z" }, - { url = "https://files.pythonhosted.org/packages/85/3c/3b5231f21fdd66d907419c41bf297d52f35d190eeda2f94dd4bc0d037a92/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:45d460432ffcbdf38c3ef6623d2ea5e3c711d737c29dc5aff42c8ec8624f7d04", size = 1520883, upload-time = "2026-08-02T23:10:14.551Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b3552e4d99d23f828ae084841b839cd6d51fa15993da1f9e2f7dd281d12e/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b36f62616004a6825e045b8211ac3c2c0a20f0f5fde13f4f9f572511747af053", size = 1544173, upload-time = "2026-08-02T23:10:15.659Z" }, - { url = "https://files.pythonhosted.org/packages/89/ee/d7432ed0a1c2192126d1ffbf2820e11f924a4ee365eb625ed0f95af53d68/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a5d801e1c49537f713fac90e22345bc00120ab81264e821b717a9129d5cfca07", size = 1587768, upload-time = "2026-08-02T23:10:16.792Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d4/db8cd9268ce2762a1469c83c20b10a865eb0510d2822748b5dfc80268709/resvg_py-0.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7e8ddb925c854a600ab3e1f676d472252cd3971a86304faf67f6cc46220a79", size = 1560670, upload-time = "2026-08-02T23:10:18.015Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f4/36e2cdd2e68e0857673adc530eb7d35d7a8580267db1b9f25fc23427b5bf/resvg_py-0.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:5250a9296e3e96bfcccce3920ed1a90d76e8dfa153ca4273143917b2f1edbbcb", size = 1142998, upload-time = "2026-08-02T23:10:19.235Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/5699c0f9db7b15d5be43d1acf63b1b24728bc90e05b38467e12c42565cb2/resvg_py-0.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:c58e12e14c890e5754177e6dc68b921a73bfc047627371fd9362bcdae3d275b6", size = 1061655, upload-time = "2026-08-02T23:10:20.286Z" }, - { url = "https://files.pythonhosted.org/packages/aa/41/eb1a9fbe03624dda18e22c9b14fa1c3d6efd0663734e352e2efedc019246/resvg_py-0.3.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2482bf877984b6da7a6502c7c154486a43401d391087b8b521a3aed373d7fa60", size = 1201226, upload-time = "2026-08-02T23:10:21.505Z" }, - { url = "https://files.pythonhosted.org/packages/ae/7f/3ed80c16df81aae5672dbb9299a73e0efe54e93bd9c5b6e9db0606918c1c/resvg_py-0.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a4e76a1709d0c34d96402f8e53ee7b8fbfecbdcb5d4fc5289d326c629a5ecfb8", size = 1147174, upload-time = "2026-08-02T23:10:22.701Z" }, - { url = "https://files.pythonhosted.org/packages/40/f8/d705c618839261cc8952324755bab871f91525f91d495c10d25fc643bacd/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c84d954db4314181a6f3ce55f3231ff4d658111ac5cc5d1ebde140db84da5921", size = 1343258, upload-time = "2026-08-02T23:10:23.806Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/331807cf83ccdc16a9bedb89a82753bdc6e436c802937a4a7c3bab3c6e3c/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2145320fb78966d241e2d8ea9a8ca15ea1b1481219e6fbdc9b9e38dae5f2f82", size = 1267206, upload-time = "2026-08-02T23:10:24.887Z" }, - { url = "https://files.pythonhosted.org/packages/65/99/466af312185b71fb58bad7b4784c1dcd6d3afb1d8ca6e1e164dac504bffb/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:626a00d21b7dc87b3ad46f5ecbdda5f6ef12c9425516294413307ecca01552b2", size = 1441009, upload-time = "2026-08-02T23:10:25.959Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9d/725b30a5960372d837cc0decf2710789caffd27735beaa23c44e51591778/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9aeaf72f10067c714ca1945f6d77991c06265ac12d546857f98326d0c8fc4594", size = 1398078, upload-time = "2026-08-02T23:10:27.613Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e4/fbba3141ea69b3ab5b9dc7a11c5a0e64b29f8d2fd1caf18825432e67a94c/resvg_py-0.3.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c496c5ecbe7ce2e02905a89f40a0f4d577e3c35429c2714828f45dd3ffe3598b", size = 1343997, upload-time = "2026-08-02T23:10:28.732Z" }, - { url = "https://files.pythonhosted.org/packages/03/06/4c0090073d53fa43bf3aee089ba99b02a3ec55e0a44007f39cdfa2157867/resvg_py-0.3.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a25b7c9a6ebe2c35196941bbebc10d3902aefe915ec9ab2682701cacfddcf9a3", size = 1425766, upload-time = "2026-08-02T23:10:29.823Z" }, - { url = "https://files.pythonhosted.org/packages/54/5c/b2721a2be2ef0db935a6a595fc0902dea7acc08ac5a6095056ada77b2a94/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a6076d7275faf0c5e4ccb0472d09911402c99ca1d1237a4f1fba5cb97a8dc8e", size = 1521063, upload-time = "2026-08-02T23:10:30.916Z" }, - { url = "https://files.pythonhosted.org/packages/2f/72/b531fa672c566404c6dd498766b834f25b267a99988d293c19e9548b2b1d/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d94f5e800e869944af328ecf40fb2667013a205afd6f8da7b78608325bd5b0b4", size = 1544232, upload-time = "2026-08-02T23:10:31.992Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f2/89a4469672fee1219cfb477971eb1f4d719253d99a403b4ca764a1f7452f/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cd9f7a586c51b58ce3dd27767ab204f4e42c1bef787ec71cde68416959fac8da", size = 1587702, upload-time = "2026-08-02T23:10:33.323Z" }, - { url = "https://files.pythonhosted.org/packages/6e/07/58079a9f1402e53162d7fcd9ed98cef39f8db2af5b354a2d49902d3a317e/resvg_py-0.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2f517d86d159e9265a35c1d854adff8d2d60e5a5433f9b9a5e1c79ae56853e6", size = 1560847, upload-time = "2026-08-02T23:10:34.533Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e1/0e89adb5cbaeb82b7137521233dfadae15a10a29d5001a5dfdd1d37a00a3/resvg_py-0.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:fa8c36ff008cfe6281d2865a0dbdd1f5dd3b779a17fd948f3db6183ddbfc51e4", size = 1142940, upload-time = "2026-08-02T23:10:35.665Z" }, - { url = "https://files.pythonhosted.org/packages/3c/88/417fca98314a02eb3172b359b6d4afb0412e0cff353b6c4b054ad567ccea/resvg_py-0.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:753d3630bb3ffcbf0a66635cb045f35f588c42e7f9ed7a0dfd33dd5d56d316f3", size = 1061619, upload-time = "2026-08-02T23:10:36.94Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2a/6e60f8d433bd54acb1d609492075853967bc49c0e89dbfc7dfe65eee16b4/resvg_py-0.3.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:51a1ff58e44161e6a9f038f75d1732aaad4c578ec0c9308fa41c041861830a04", size = 1307381, upload-time = "2026-08-02T23:10:38.272Z" }, - { url = "https://files.pythonhosted.org/packages/5e/51/0ec352ee090dfdd63b3399aa51a2231e00eb9b2b121816594628bc68aa78/resvg_py-0.3.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d5905c0dc20e61e379d91b36773c938fb045d7c7cba930523f7a641589f74b81", size = 1312914, upload-time = "2026-08-02T23:10:39.368Z" }, - { url = "https://files.pythonhosted.org/packages/a5/dc/7ea87665848296ae20f1295d061afbd475eaed35190886a993960f9f8af3/resvg_py-0.3.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:856ad94a8bc96f68e50f0045896b680abb0521ca9900c10078068123cf895d2e", size = 1201764, upload-time = "2026-08-02T23:10:40.546Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f7/cde835d58a971c51af664924ac7df1e3b03045f12fce930bc2a9a2b83654/resvg_py-0.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b99941a95889eacc63c34f5259b0db48c856e1106390073dd2c8f10d991c5c84", size = 1147810, upload-time = "2026-08-02T23:10:41.681Z" }, - { url = "https://files.pythonhosted.org/packages/97/0e/f4ea7cf44a81eb6e0186f7b3da489611cb467502a5425eb0a15100d85d13/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7c6bdc37a5e0ce8903ce13eeae5f8538071bfb42203ed20af446a95f4acdf4", size = 1343936, upload-time = "2026-08-02T23:10:42.785Z" }, - { url = "https://files.pythonhosted.org/packages/4f/f5/6b18873f054df55f5168e42af74906afd1773d1d985778c4ab9f888eebb2/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e482f490dbc96687c024770858b6b8854f0d18d94c439e27b653b78374ec6a2b", size = 1267539, upload-time = "2026-08-02T23:10:43.91Z" }, - { url = "https://files.pythonhosted.org/packages/79/5d/b34e513c96be0a75c56013bb593a4f35b5b02d3952356cff8a9eeb359844/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:313d056d64ba9bdbb014539ca68b8b5d31cfaba3deb94b7dd8ed89d0db5547c0", size = 1441991, upload-time = "2026-08-02T23:10:45.318Z" }, - { url = "https://files.pythonhosted.org/packages/2c/4c/2cfd1f01dfbc0eeca38f831d848e216346eda7bfe40ce1156fb4016140c1/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c360aff07c307b7a8648e7e2b4e5d17f96632afcb730c85e5e6b97ee718519ee", size = 1398593, upload-time = "2026-08-02T23:10:46.608Z" }, - { url = "https://files.pythonhosted.org/packages/aa/14/af4525f0911fcaee39fe9876894e62ff4ddba24c5ecf0492ef264fe22875/resvg_py-0.3.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:740b2a1bf1026b63c200bc02a8e9eb1363ad5f9ad679f4700db5fa5342cf4fd7", size = 1344806, upload-time = "2026-08-02T23:10:47.699Z" }, - { url = "https://files.pythonhosted.org/packages/1e/06/f403db4b157ae38d3e96d27ef06780a9309ceacd6114390713f376d69537/resvg_py-0.3.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4e9ddc3053d6c1684e9991ed67c6399d1824f2db9884470ab28b789b361365de", size = 1426797, upload-time = "2026-08-02T23:10:49.076Z" }, - { url = "https://files.pythonhosted.org/packages/b8/3c/26543d7816c68caf83727dea887bbaa7a4e42c1dcbc63ae97f4001cd9f8e/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5a7d7058eea552ec2a5a467871e983996240aafd7318b14ae45441b099d2bc7d", size = 1521642, upload-time = "2026-08-02T23:10:50.325Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6a/891d38412414dc40a7f02b7a195913e37b52a06c84b6f3ed729fe9ba6d4f/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:e9176711eb6e35f2e1bff3d29f2d88751668263717b072070261bad1666dc2e1", size = 1544524, upload-time = "2026-08-02T23:10:51.452Z" }, - { url = "https://files.pythonhosted.org/packages/db/df/e91f2f8342ee730425cd009d636191517bbce9d10d3d3c3d9d628ed2135d/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:eaf735fad2a766bfb85cd48cce29a87136b679f423bb97d48b5025a5a5076bbf", size = 1588438, upload-time = "2026-08-02T23:10:52.69Z" }, - { url = "https://files.pythonhosted.org/packages/38/03/cfc0c8b9e9ae997304b13f073b60640da89ff8b68071cac9abb9b3c23cf5/resvg_py-0.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b6679a1e90cb805c95e0a5983a75f8a6b83a491d847e6da11a40d40b30b6f147", size = 1561531, upload-time = "2026-08-02T23:10:53.868Z" }, - { url = "https://files.pythonhosted.org/packages/7a/00/f14bcebd3b649f610d3bcee63fe2c2f5cf7614131eeca3decf70100d6ff8/resvg_py-0.3.4-cp314-cp314-win32.whl", hash = "sha256:399d2fe4675c41624a785206b4929dc3b3949b3d471b593a516e62e88473e316", size = 1125195, upload-time = "2026-08-02T23:10:55.095Z" }, - { url = "https://files.pythonhosted.org/packages/6f/04/33983346c599e4ed0ee33d6b87b985dcf03ad29e1fffa1635fd976116617/resvg_py-0.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:bc29c6c70acb56d4c4a6d624eb306b044e974db124dec0dec673b7d4ca572e7b", size = 1143434, upload-time = "2026-08-02T23:10:56.315Z" }, - { url = "https://files.pythonhosted.org/packages/03/51/9fb4c51f56775b453ac46b1e3002e92fe12939694e4ec755b1719d01fa83/resvg_py-0.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:feda5d1eb3e5268c1062b35d953907b2e35b92262c851abce270f3b42696a1ef", size = 1062193, upload-time = "2026-08-02T23:10:57.87Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c6/065cbf979bae9b78b3b27d7a46b9d969c40ed67fb74b474fbdaf7e4eb148/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61ec5cbb44a3a250368e474153c58309e162f10ee636628e740d5062724b4a65", size = 1343946, upload-time = "2026-08-02T23:10:58.981Z" }, - { url = "https://files.pythonhosted.org/packages/4e/76/33d2dded2d70f38eedb3164ef0795c2ea1f85997399c0ddf754a39b17715/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5b734365c6e70eb3b28de15a3fc7b82facab384d7c11590674cea186f9c6103", size = 1267388, upload-time = "2026-08-02T23:11:00.054Z" }, - { url = "https://files.pythonhosted.org/packages/a5/64/59362bd36a3533a44a101858c19a4249e0109e9d763aabc209037d6ddb32/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c3135ecd0a56c61e797b05fd977928f4e3dd0c9e0f688579e6e4e7d2118c0fb", size = 1442131, upload-time = "2026-08-02T23:11:01.205Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/e3300cbfa2c2aa787fb04e0d18ef16a9c3fa26b9b0b05081bc1f63b917f7/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26bc305d56f6268afa9304a41168e3f275fd63bd4a2d1a98b99d17ff68066208", size = 1398530, upload-time = "2026-08-02T23:11:02.525Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/ba22fae05074d841ccd91f18afe4c1f87564b5de2449bb5884d272ea99bc/resvg_py-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c9e12339f1fccb2cbd0b7ef96a7cd0dba6c8e134aa8f8ba77297251e7292614", size = 1344910, upload-time = "2026-08-02T23:11:04.236Z" }, - { url = "https://files.pythonhosted.org/packages/02/1d/22e7933ef9eeffd7264a444b4070fb2a6961c1249fa7cd31058ec01e61e3/resvg_py-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42bb20e4e4c7cf7dc2d2d9ca5e73ff536df1faa2ecefc7d5822d213eddc65949", size = 1426964, upload-time = "2026-08-02T23:11:05.599Z" }, - { url = "https://files.pythonhosted.org/packages/00/d3/fc04f814ae93f2bff14abe3886acf1b7e57a6538f0f3dcbc8f180c1c4fb2/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c30955d4fb8fcabc39f64a704a4370c23aa1f248a3e6bc089e1d45dcd24b4ff", size = 1521457, upload-time = "2026-08-02T23:11:06.918Z" }, - { url = "https://files.pythonhosted.org/packages/79/c1/95288f004e3c21bc4bf122c12b7926e5f41c3bb6695c3e9bf918bc7452b7/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:459c809a5b5fa0c715baaf986c5b8f7ca9486788931dcf32f30595ffd16e5555", size = 1544417, upload-time = "2026-08-02T23:11:07.998Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/f93d7c95a5b9a3e34534be3bb4237e00540f2fbdd4c179aee825af33fa93/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3493ea58465d1bf4eb24143d147d3de5f011aaaaebd9b68669949d1d06bff76a", size = 1588351, upload-time = "2026-08-02T23:11:09.37Z" }, - { url = "https://files.pythonhosted.org/packages/60/ff/21a3b80d6b7c5b1733dc10f7b40bd0ea1f2d6585360aa2a64026addf8749/resvg_py-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00117ddb9829ae479540a1885b4d88bbc0e3b5d77197ac0e289b7dd0c00c6b74", size = 1561626, upload-time = "2026-08-02T23:11:10.626Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/a0b36d5263048c15840b549610bfef5873f538274c5fa6d53d0c266600fd/resvg_py-0.3.4-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:168c669311f83836dbd643a03f0ae564d4489baaa3be86a77ae40261e7952039", size = 1344910, upload-time = "2026-08-02T23:11:11.685Z" }, - { url = "https://files.pythonhosted.org/packages/70/a4/88b5574250dcf5d8e06b812868aa4c8ee03f79113ae5ae86bba3cd8640ca/resvg_py-0.3.4-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:434e442871756358e4243037485be890615a3c802985efac4fc4d1be862a41c3", size = 1426954, upload-time = "2026-08-02T23:11:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/27/1c/cc59160651004f163c1d1925de94eb1df64868ff1a0303db10e3b1ef86fe/resvg_py-0.3.4-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b55849172c67417acc142f6eb6cc11278968a5756cba4b3c89dae51299048efa", size = 1344996, upload-time = "2026-08-02T23:11:14.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/bd/046456598595dad61c76923cea0110cd57b413334686cbd32524b079cb2d/resvg_py-0.3.4-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1a89721ffecd83c673f048b8d7abb298e5010e764b0ac59938bab71afebed3f", size = 1427098, upload-time = "2026-08-02T23:11:15.477Z" }, +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/4e/26c3b88f8d2c2ef96c2d9bf48823f484a91275011c58cbeddbc98b32a0cd/resvg_py-0.4.0.tar.gz", hash = "sha256:7b93183f2179bb23411e8451dc6201ad3bceed9b7e35dca2107faf6d8b157c43", size = 1747412, upload-time = "2026-08-12T16:01:33.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/49/df4bd6106527281ef72b3916cb949ca760e170b7a01b84a36ecf1c85eac5/resvg_py-0.4.0-cp310-abi3-android_24_arm64_v8a.whl", hash = "sha256:7302bb18376c27b055f692c97b9e73f716bfb0e628715b8eb49b78bdf8489e8b", size = 1307910, upload-time = "2026-08-12T16:00:24.957Z" }, + { url = "https://files.pythonhosted.org/packages/7e/74/ef6cfea97f5774dfe3dc189134932e43bf6c0f0f34036145e847e97be7b9/resvg_py-0.4.0-cp310-abi3-android_24_x86_64.whl", hash = "sha256:22dc18024ec4b3dbeec4c144c299a3ea829726ca1ff666b1a0bb79c674c92ffd", size = 1313463, upload-time = "2026-08-12T16:00:26.663Z" }, + { url = "https://files.pythonhosted.org/packages/63/ba/46e9eb4da8b85b4a3615025414022773d0b65abf886be8fc69e8b0e7ab31/resvg_py-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:facd4639edf0cbd800adee3c0f20583331f47f1bf5921953c8564c74081b79bb", size = 1202090, upload-time = "2026-08-12T16:00:27.805Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/0456a51ee69d40ffdbd49f4a953b7eb93f09eabbd64c1ff4124464270fb6/resvg_py-0.4.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:663f1e77fef38354afa28214cbe2f9512a317b66aae1b5571624f6a9c820f070", size = 1148361, upload-time = "2026-08-12T16:00:28.863Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e1/49f08214fa4df8b24e009c70b373067d702d5b5f9cb5b03757e6b0ca5f65/resvg_py-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b1675fef0c5c28e3981082d51e7402bc7d47358b75e8e65f8be1f533d891b15", size = 1344871, upload-time = "2026-08-12T16:00:29.921Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/2fa8f7373d9fc08ee296522d553e72bf18d41f0813be6a6b12d637d7e711/resvg_py-0.4.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0da2aa6e580ff6d9a984da96faeab3e08d7e5328a0e0a93a9eb1563e5143ab1d", size = 1268440, upload-time = "2026-08-12T16:00:31.406Z" }, + { url = "https://files.pythonhosted.org/packages/98/dc/e76e0b870bcb543ed109dfba1893757d1ef690150c3a57928720c244f14f/resvg_py-0.4.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47b8b3e65d8abb48bdd3a4a1442abd1b7ba29a3e2783a7b772fe118c10b5f994", size = 1443392, upload-time = "2026-08-12T16:00:32.483Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c5/4a0f0e954faf36e31f7abab2420ef82c7b0ff3c7165e5772403f6a593937/resvg_py-0.4.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:055dc6e2da744af6cc977f78cbf8ac1cdb544ef1fc9f638dd3da86100ff6f04e", size = 1398857, upload-time = "2026-08-12T16:00:33.713Z" }, + { url = "https://files.pythonhosted.org/packages/ca/99/058b1188f980116787b9c189f1bc15f4000b8148665f22fbd4c619d62ffb/resvg_py-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa319c50a1cf11ec868faff88b06233cc4e6959251657cbc809a90e459aabb05", size = 1345682, upload-time = "2026-08-12T16:00:34.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/45/5f68775c5e9d603d417f1753fe076a44b4cfe4774bf7b31911671ba56e13/resvg_py-0.4.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7675c10e7fcb282186b9e167ee29d4fbfa75af17ba3892df185fd387c6216878", size = 1426219, upload-time = "2026-08-12T16:00:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/e8e824c32c9257b70745a160569f56fa964c75df55f45ecf4e8e8e66f3dd/resvg_py-0.4.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c50ed47c2d16cd8cee9f2692523f501d24b431a0ec654ad9137c66805738d564", size = 1523681, upload-time = "2026-08-12T16:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/26/54/9eeda766ac87452524d7f6b2bccef8b0e245277e95c9501280aa7df61fc6/resvg_py-0.4.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4c6456670ea732611e5ff1019544f09e1f09fa0e90a6fc35b4e96b3c22943b4c", size = 1545125, upload-time = "2026-08-12T16:00:38.949Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e9/04c66e1fbd7fb75e2c28e504652632ae2d8d1880c96d52b9811163b13a8b/resvg_py-0.4.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:74c1ceea6cfa29b79992834aa3a238600d399f00aeb5df5d02289fd1f4560b96", size = 1588426, upload-time = "2026-08-12T16:00:40.227Z" }, + { url = "https://files.pythonhosted.org/packages/45/5f/6b5a57f796b83a45b97d8f6ebdf0ac7e200e8d33851a48ddf9a5eb07e46f/resvg_py-0.4.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3274d9e168b22dc5c3241619965a64c2d1451685871002329b707b2aeb397b9b", size = 1562330, upload-time = "2026-08-12T16:00:41.3Z" }, + { url = "https://files.pythonhosted.org/packages/60/57/5863b4d6a257f9033f1d187fd921553c4ed2e7f0e71c0d4d0ac21f48b82c/resvg_py-0.4.0-cp310-abi3-win32.whl", hash = "sha256:fee3af4e045049a7da46e7e40ca3cd97e5ea2e5dc66aa9f9769d73d69d300904", size = 1125848, upload-time = "2026-08-12T16:00:42.306Z" }, + { url = "https://files.pythonhosted.org/packages/43/7f/b8f825cab25a132c0119b5ad2110dd0dfa6e7e110afae3affb6cfb3b5a0e/resvg_py-0.4.0-cp310-abi3-win_amd64.whl", hash = "sha256:e889d57d51ac913c23e8d1843a29f0356a473519983959b34c6177698e4a3aef", size = 1144555, upload-time = "2026-08-12T16:00:43.574Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/ad5b1afeea46401c36c6a016dba7e8b05d0a38e7a1a2ddf3129416bc05d1/resvg_py-0.4.0-cp310-abi3-win_arm64.whl", hash = "sha256:81b837f1915b352aa9b2663730331c7e7c675880b5fdad341d14046ba9a3b4af", size = 1063229, upload-time = "2026-08-12T16:00:44.841Z" }, + { url = "https://files.pythonhosted.org/packages/16/e6/7a60a429a6eae82dde9373c9c120661a5c92583f9cf4fffbdfd7872c9736/resvg_py-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:0751cfe9c50645f1aa3121bb884569c4e11df9969d847a40d3f7ff3de4500501", size = 1201701, upload-time = "2026-08-12T16:00:45.903Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9e/cafee6cd7712532b0fd3b9eb6d863f5e45be3b2ae38ce196fb28c6e9d2ac/resvg_py-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5f676582ee94110cb55db5540e04a38f9f8d555206d4962b1d243fe052c241f", size = 1147724, upload-time = "2026-08-12T16:00:46.951Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/e4cbdcad3e30a291abf57f6e2ed80b8f76193100724a0ceb82844ca4ca27/resvg_py-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c25a0b44ba2c10d5c2719cbaf08770608f022c7ef50e314fc1c8106e2a0b9dbb", size = 1343680, upload-time = "2026-08-12T16:00:48.155Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ec/ce06b06697f019402ddba7ce7a0e8e799659726f26da828398655265f180/resvg_py-0.4.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e58e4a9992e80199b9fb06fb14c243cb6f483f7e3c2afec7b406160132d9c62", size = 1267404, upload-time = "2026-08-12T16:00:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/eb/35/16f42ac26296df9b8766cccb72f745797974cd8c471b01c290348ee9bfb8/resvg_py-0.4.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344daca15585fbb2dd1ab789684a4703615d536ea4f5bd4ff628c710bf801eb9", size = 1441912, upload-time = "2026-08-12T16:00:50.524Z" }, + { url = "https://files.pythonhosted.org/packages/44/ba/b442c809e0d22ad55e0bd29919535158b04ba1dbaec77c13261b944d3a1c/resvg_py-0.4.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33cc5e166e0a3e4f0212bf0753f04183329e999e1d1d03c71d75cf89f4453057", size = 1398498, upload-time = "2026-08-12T16:00:51.887Z" }, + { url = "https://files.pythonhosted.org/packages/03/2b/af2a6c6b67d9a89f72a0801c3696e9a6d6984eaf87f67f024419c54b5992/resvg_py-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deacd4457907919cf762e8324cf4789385e24c55dd5a723fcac32ae80272229d", size = 1344822, upload-time = "2026-08-12T16:00:53.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/94f273a117e13225892287a400bc6521c28c14f8f934cc3d3d8fdb0fd180/resvg_py-0.4.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d53e2c91696e03685b9dfa3b89d3d4182d4faac7a4862d8d5d3978db0e57ed41", size = 1426738, upload-time = "2026-08-12T16:00:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/b2/86/b7a51241e68fc7d2c9df24b518d57f81a9affc63d681c5b343ecd1304902/resvg_py-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b891d9c4f943af4e21acc15119392119d16877888011ef41ad6fec90809ff523", size = 1522502, upload-time = "2026-08-12T16:00:55.39Z" }, + { url = "https://files.pythonhosted.org/packages/df/a0/d6104c5f305751fe8ba39aa59d80b80b80d7e301a8956f82de1d54b4b9ae/resvg_py-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:5ee11fdfa319e2c6aa7f0c00b3abe7777261d1acb55c1188f652232b83457990", size = 1544616, upload-time = "2026-08-12T16:00:56.537Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/e603777adabd0eaacd3da5cb9600a856bb5a0e69195ba58c474969a80037/resvg_py-0.4.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6f43408d8fd4a63fc941bdf6c9f537a62bf23813e474ef4ef1a8738d2bca5727", size = 1588558, upload-time = "2026-08-12T16:00:57.884Z" }, + { url = "https://files.pythonhosted.org/packages/a1/11/059c4ded8d6568aa73289a55a695bb5a7308bfa5e2412bb0f8d0791fd12e/resvg_py-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fe56cd46f131532717b80235da87e2c59c3fe09e81823893632e859e3a21f42", size = 1561507, upload-time = "2026-08-12T16:00:59.342Z" }, + { url = "https://files.pythonhosted.org/packages/00/c2/08902697d6462654e937ef689cf30b86184f8fd016fb71eb8de32c098c69/resvg_py-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0f243e1480855ccabddd272b8219ac88586bfd329220ff34fdcbd9747ee131bd", size = 1126165, upload-time = "2026-08-12T16:01:00.451Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/b24ad9e9c16c3eedd29b55743bacb04125c03f56e5977eab36d2c4aedba0/resvg_py-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d78472d0ddb9df61844f4d146f435f4e96d2a5ac3c10ec7edd5f70e75cbcf7c8", size = 1143272, upload-time = "2026-08-12T16:01:01.643Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d8/422833eced1a443fa5231787a2eb808131b0470a355d3ea4908a305c498a/resvg_py-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:3152af22cd381edd330b52b275dd1f578aae5bb619f0a8cdd00369e8ab4fb123", size = 1061866, upload-time = "2026-08-12T16:01:02.778Z" }, + { url = "https://files.pythonhosted.org/packages/92/c5/4b82e20ba90d310bee2657ee283cf3d7e3e59b42f28b42343c5049ec2485/resvg_py-0.4.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:f28aef8ebb639a668402c8be3ca4b818f2a2b105a509682a231e0c95d3eaa2bf", size = 1201497, upload-time = "2026-08-12T16:01:03.949Z" }, + { url = "https://files.pythonhosted.org/packages/39/7a/00ee34bafff95fea7e8858ea7ceedee797d3ee7512a56e7673262dfd9057/resvg_py-0.4.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:f48e42cfea42aea01edb83125499e17059ef3539b1f6ab950d4d00480ad7011b", size = 1147730, upload-time = "2026-08-12T16:01:05.015Z" }, + { url = "https://files.pythonhosted.org/packages/64/a9/c22f254b165b8f7a7b36e494d4e60f75ff93c0eceeeab199c6d7f836f6ff/resvg_py-0.4.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bff774a5cf4ff0d5554bfb785d864cb38fa4531759672527af13e50bd6065c88", size = 1344020, upload-time = "2026-08-12T16:01:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5d/152241a069573f389a60aeb07757fe256ec2f0aa91936b69d352bb0148e0/resvg_py-0.4.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec75269c60761d4d2cccb8f80649a2ee9015806bb3b339421ae71b22e3a3c8d8", size = 1267463, upload-time = "2026-08-12T16:01:07.435Z" }, + { url = "https://files.pythonhosted.org/packages/14/7a/05a223f6e7a7535c234728b52138a35711831e02a4982533bb404917bb98/resvg_py-0.4.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:830d87b2a512c0355219e4deff5086da6299e01591fe068aeaa5d1a06c7b2450", size = 1441631, upload-time = "2026-08-12T16:01:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/a0/35/930ee65c2a1f7fe0ec17d4ba2707efddfd770b1f5769f78c3cdb5a8b07f0/resvg_py-0.4.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fbd0846f594d208f8b28df5adfe0cf5a1d2a0e365d79fa9abd694a7b0e16fe9", size = 1398417, upload-time = "2026-08-12T16:01:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/c2/af/b04b9bbc8195f5c81d4e79fc14a69acd4da9ef38dbb496d42d86cd4c8b71/resvg_py-0.4.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586e6ce8e6d368e34e00e705daca3780d506334432940a5fb3695c4e3215cbef", size = 1344689, upload-time = "2026-08-12T16:01:11.095Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d4/f2a15ac2c4324cab29234e6da98fe54c2c2229f5e55a2fda6fa0e502caee/resvg_py-0.4.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4256a7283bf827442dab20b3614dd9f98c4faf9321389d68478f51f81940d278", size = 1426312, upload-time = "2026-08-12T16:01:12.231Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6a/9068dbffee179f1b378ec5b4906edbc5c8feb6afcd5215886640d445b676/resvg_py-0.4.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:407de28dc272b48942fd7820f3dc9c64266ac7a32603763114767196a3ca8a27", size = 1522267, upload-time = "2026-08-12T16:01:13.546Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/6c4662168aad28b13398da4d1292c86aa34e4fe814dabf5eeb00bb3229b7/resvg_py-0.4.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:3614654205efbf516f3c5eaab619f931dce6a08bd41beb8bb9a786ff15bcc1c3", size = 1544578, upload-time = "2026-08-12T16:01:15.357Z" }, + { url = "https://files.pythonhosted.org/packages/61/93/1b9b50f3a72d36b2f8d454874fef1707c60495fa9a605a518feba2da122e/resvg_py-0.4.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:ea9f555f0b5212c7c773b6fdb0597d5ab95983213d93c1753b1f7929c371d542", size = 1588383, upload-time = "2026-08-12T16:01:16.457Z" }, + { url = "https://files.pythonhosted.org/packages/20/a8/0681fe9835bc7c788b237d39c5172ef51ce4efb3f78ee91fef93116d6e09/resvg_py-0.4.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:a811975def9a8a643170ae2271b67232a99a438d74d08a6e7e0665f6eb753159", size = 1561365, upload-time = "2026-08-12T16:01:17.588Z" }, + { url = "https://files.pythonhosted.org/packages/0d/05/3c327408a93ccb76cadccb02eee5afdd1b89988bd5dffe0c4bc53f47a1b9/resvg_py-0.4.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:dcd45cb27bbe955cf9b3c7b6d3a4d2a4ba43b9761f7248daba04f42d3f88d49f", size = 1125887, upload-time = "2026-08-12T16:01:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/52/f6/6f1e358cb266ab9867733d905223c0f419d7db63e980576ab0e9853b466d/resvg_py-0.4.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:a83beeebc7ed1b707bcebb4113c247ecb944551203c910aa2b1709e4498b68d3", size = 1143340, upload-time = "2026-08-12T16:01:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/c1/cd/0ee87a5ddc6f320dc827bd5e2749dfa081e8c8ddda22ea7bbb1f561e1716/resvg_py-0.4.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:25f81ea0c2b0a5edf90eec5b662933d97b89abeb62019bd5eaee37aca327debe", size = 1061705, upload-time = "2026-08-12T16:01:21.151Z" }, ] [[package]] @@ -3821,27 +3879,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, - { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, - { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, - { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, - { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, - { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, - { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, - { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, - { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, +version = "0.16.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, + { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, + { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, + { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, + { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, + { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, ] [[package]] @@ -4056,17 +4114,18 @@ wheels = [ [[package]] name = "simyan" -version = "3.0.0" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "requests" }, { name = "requests-cache" }, { name = "requests-ratelimiter" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/bb/4dbbfeabee7646150f363f79d0a4487c12a06ab447aaabc213facfe0a0ec/simyan-3.0.0.tar.gz", hash = "sha256:c8e69e0fc59509b38a42dd9d5dfa6e5beec798630253c54a487aa7c0a9f63166", size = 12827713, upload-time = "2026-07-06T08:23:30.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/c4527638102c0213ca589882d2ab65e1e3047aa973fc4962ecebd14b3cc7/simyan-3.1.0.tar.gz", hash = "sha256:e1d4defcf11a56b281ecc15e25e037148936794f74a3feed3f65bd1ca9c816b0", size = 12215650, upload-time = "2026-08-11T10:40:03.227Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/0c/1f274f82ffbbffd8f4590d5e55e25d2b66f4d835db6a7e49e5bb542cf498/simyan-3.0.0-py3-none-any.whl", hash = "sha256:4889aa17e143b53c528c8329dd1b0c45b8d5717effcb88a3baf537a6cb40fef3", size = 32110, upload-time = "2026-07-06T08:23:29.026Z" }, + { url = "https://files.pythonhosted.org/packages/6e/02/edc527e5a678d70865a08945d0aa0f9886fb529c5277dacd1ce53789459c/simyan-3.1.0-py3-none-any.whl", hash = "sha256:d5dfc313104d5df05232f0f72187e073335af7fbc9996fbb7f3b625165ba292e", size = 32542, upload-time = "2026-08-11T10:40:01.182Z" }, ] [[package]] @@ -4080,11 +4139,11 @@ wheels = [ [[package]] name = "sqlparse" -version = "0.5.5" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, + { url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" }, ] [[package]] @@ -4180,27 +4239,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.70" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/ed/38a8ab52f1d7c3ed701442a31b23ba774cbc5d6909f2c00da9e1f3c590f9/ty-0.0.70.tar.gz", hash = "sha256:a01bebc128b4081c16002965d906fccb21323d69bb709b9108c1f2406bcffced", size = 6601156, upload-time = "2026-08-10T23:20:26.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/c5/ddd6cc5657fd3da85591b264f4dabb1ce5e5b535e73fd5174991c294978b/ty-0.0.70-py3-none-linux_armv6l.whl", hash = "sha256:4fb2d2e55e2160c07152361be2e1a26fdd4f6261055731317d5972daf1749935", size = 12537215, upload-time = "2026-08-10T23:19:43.512Z" }, - { url = "https://files.pythonhosted.org/packages/8b/8a/acc9b34331cde81e0d63cda92d4db9e4042def5b1efa7d47431ff1d30d17/ty-0.0.70-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9b04d4c21cb029501c05598ca21e9c0829c0b2e35a7ceba1e5b78014c1e8104d", size = 12149070, upload-time = "2026-08-10T23:19:46.369Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cd/51ac2708f4d077058a0bfeefa40d630883c5ec7a82fd2c15c86519140235/ty-0.0.70-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c605ebf2643f5e64ec4bcb269640a1aa85966d29fa888fb746039c28570369b2", size = 11625729, upload-time = "2026-08-10T23:19:48.535Z" }, - { url = "https://files.pythonhosted.org/packages/b7/99/afc4fe7e630100dc782ff0cdc8c59c01acfb05299551ff0ef49c93814320/ty-0.0.70-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c38ea76e12909c29e18fcd295ff223b63f3701c1d1c34db2cdc9736340b9de2", size = 12204810, upload-time = "2026-08-10T23:19:51.845Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/8cabc3c8ad4c3a02e585ecff12a71ff8e5881f8a00ee71745fd765201da5/ty-0.0.70-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1ee6bf4edaabf7bd0307f0c0f9ddc2204df0390820fe89f6a9de65f07722aba", size = 12308114, upload-time = "2026-08-10T23:19:54.229Z" }, - { url = "https://files.pythonhosted.org/packages/44/7e/bb4e552ecd68bb490bae9368ef323e3d0c79ef85a811857139a2d0f59ddb/ty-0.0.70-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5eef3e11d7d6b800ef66da0cce8ea24f8be804d1ebf359683426cfe848729bf8", size = 13072240, upload-time = "2026-08-10T23:19:56.872Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c4/1861e1d554e5b6e0d11b3a9f36d40c372253f44a3c4e56ad4bf840f2801e/ty-0.0.70-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95e4ae7c2599197c9d89652e49ca344ab224f7d12376e6ad8beb0587e8ff83f", size = 13497678, upload-time = "2026-08-10T23:19:59.191Z" }, - { url = "https://files.pythonhosted.org/packages/77/22/3b22442133e8f641485e59e463a070a31184db0d6456851f871db8f59763/ty-0.0.70-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06aca758d1e0016c0a1f57fe9d8de7a21ff83f692306f747a0d97f32de24e27f", size = 13232510, upload-time = "2026-08-10T23:20:01.456Z" }, - { url = "https://files.pythonhosted.org/packages/84/b8/911f1e6885b5485b6e1d29aaab19ce5e6deeb3e931b6ad82296da4f22051/ty-0.0.70-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d81825524f1b57ecbcb5fce7d61fb159cb4837a6167a4569309c9fa7fc15a77d", size = 12817128, upload-time = "2026-08-10T23:20:04.275Z" }, - { url = "https://files.pythonhosted.org/packages/e6/a6/9affc3ca11c32d75b348a66144ab83335fa7fa7100b1581356725e15a668/ty-0.0.70-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3287dfb09f7320ef28f114f5f9aae5f697b7b1a6ee37fb2a4a3be94481c1b4f8", size = 13089208, upload-time = "2026-08-10T23:20:06.574Z" }, - { url = "https://files.pythonhosted.org/packages/f0/cb/4776108ea08ec4013b71375b101be9c8a632967cd10f24abbdd4664d66f4/ty-0.0.70-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9fb1877f6401cdaac4db46c5bf762f327482ba5f891420ea462ebd15d0de4185", size = 12148890, upload-time = "2026-08-10T23:20:08.827Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d9/814e71f698d9231ef6412bd2c1499d81c6f86f66c2764e952c991e2d6da5/ty-0.0.70-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e78f4997dbb0db2d2270210eb3694466206e5b3a11985e24148770e702158a25", size = 12327084, upload-time = "2026-08-10T23:20:11.023Z" }, - { url = "https://files.pythonhosted.org/packages/3b/69/9f34bba534c5d1ace391d300ead4f1971f4348c5459e66dc466cfd60661c/ty-0.0.70-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cf758d3b2dad910c9b1d22d2d62fea894b5bc9acd3c00365e8a73ace6090a452", size = 12604372, upload-time = "2026-08-10T23:20:13.366Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ff/abb34674517b29a8e2489a39c4e01930fcf6aab993a7d6b33aa97f119117/ty-0.0.70-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0d338761617279a4fb6a83e7fad7e21126394637b8c45e13e196b2bb675f39b2", size = 12917800, upload-time = "2026-08-10T23:20:15.841Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1a/f8289572f4fad5cb16c883b94638548166bc78cf3eb569f0cd9199eb10a0/ty-0.0.70-py3-none-win32.whl", hash = "sha256:a45642cf09dde91f0a3ce9b9e6fffda9779ff69d73f7347c18acf4fb45007c07", size = 11921482, upload-time = "2026-08-10T23:20:18.244Z" }, - { url = "https://files.pythonhosted.org/packages/17/ae/8739d7618b4670c3ee4f52d641d53be87928bd121d8bda9c0e3450500b75/ty-0.0.70-py3-none-win_amd64.whl", hash = "sha256:33e7941a926cf39b82553911a59a6ed68ec98c3d3d5a415df633f4d1cd051e6c", size = 12986994, upload-time = "2026-08-10T23:20:20.53Z" }, - { url = "https://files.pythonhosted.org/packages/f1/44/2bc3301ba4356ad8866daac9f8cfb3687953e52d076ea204f113b9304c42/ty-0.0.70-py3-none-win_arm64.whl", hash = "sha256:0d380f735d52b1d4b773193f8f5c58c065725eab1ebb0008d38a7b830de14f47", size = 12301082, upload-time = "2026-08-10T23:20:23.816Z" }, +version = "0.0.72" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/df/656e684bafb13c1d146e7d5b5f3e7978ca177232acc84998ff36427e9462/ty-0.0.72.tar.gz", hash = "sha256:ec2b8066b618df18cab4cb8e992f8da45d360332acb23fa34df7fa29cd1b9d3a", size = 6654939, upload-time = "2026-08-14T21:35:42.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/3b/f51461239a4e66565d4b362f97a3b55fe7fdba2e944068341f87c62f6743/ty-0.0.72-py3-none-linux_armv6l.whl", hash = "sha256:fda86db153ffd85ee52000cf175d6a3f1c0223772cf7c5b6f726200bf92c7b44", size = 12621989, upload-time = "2026-08-14T21:35:01.676Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/79ddf683affc679ca856f3510b5640ec3a88a842ba5f654f5d4bc78f1786/ty-0.0.72-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ceb944c612529b9023acfdc9cf4c0dcbb722549f9d17d46baecd1141baf01d7f", size = 12233910, upload-time = "2026-08-14T21:35:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/5d/45/10562a0d84802158db8fa4ec46de54aa9fdcecdeeaabbfe3639ae7042b66/ty-0.0.72-py3-none-macosx_11_0_arm64.whl", hash = "sha256:108d76218333d6c092e5f1cebf8e9b06f25738613a0236a28e2dd47c936ee52c", size = 12084108, upload-time = "2026-08-14T21:35:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dc/1fe1aef8d697e3509face271a5331700c7aa1d1e44a4b622707bdfa41d4b/ty-0.0.72-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f3943f186f741a2499a31053872169250c9264a9a49684920e48d8fcf4ef4f5", size = 12132640, upload-time = "2026-08-14T21:35:09.305Z" }, + { url = "https://files.pythonhosted.org/packages/14/46/41ceb265e96969487311a2014bd0e53abb4fbc1395efb2ebe411fcb4db62/ty-0.0.72-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf283c07dc3cc52ca48a3ad8ab100fb5aec3aebbd03ef6a12d5f910b8e596fc5", size = 12402489, upload-time = "2026-08-14T21:35:11.555Z" }, + { url = "https://files.pythonhosted.org/packages/2b/45/30bf43cb4fd505c5c2dd30fda27dde5f05208686cd21217adec77c954204/ty-0.0.72-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f3b6462c38f9f115d10cee21f47fedf715fcf2040daf36eef210359300bc7c", size = 13130835, upload-time = "2026-08-14T21:35:13.746Z" }, + { url = "https://files.pythonhosted.org/packages/31/2f/03bba754d2613f640df168335c41f83f41db150bb515839c60d80e3a7880/ty-0.0.72-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30caf658feb8ffb250d9e9e47107657a78f5f3425c227df1664d8df2ebe38880", size = 13590392, upload-time = "2026-08-14T21:35:16.839Z" }, + { url = "https://files.pythonhosted.org/packages/04/c7/03c67f00e63005ec41585653dc3096064570b1e6273742baae2798cd242f/ty-0.0.72-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27bdc012ddfbeec8948e4a6036c0dc39ac7cf2c8ec7c7d48dc7d2fd56d57b399", size = 13309629, upload-time = "2026-08-14T21:35:19.169Z" }, + { url = "https://files.pythonhosted.org/packages/c1/df/102d3b264eb7f2a58dd11952f229bb5150bb5668d176a6154976a6675981/ty-0.0.72-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:802c5970a77d7739e6f499921fbb6984fb7ad8a31d95e1ff42fd46f3642e4f3b", size = 12734028, upload-time = "2026-08-14T21:35:22.099Z" }, + { url = "https://files.pythonhosted.org/packages/61/85/d0737c8c54d0ba67366ddfb9f31d88edf0b02299e65923e6945ae60ebcb5/ty-0.0.72-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:47dce65114fdc615c68ca0edb393b433df0956447e4267df0e264137a789598d", size = 13174832, upload-time = "2026-08-14T21:35:24.71Z" }, + { url = "https://files.pythonhosted.org/packages/1e/31/497f5a96c36d9b586ab6afe0574986835c6fd5b835a89773d2bec4711b49/ty-0.0.72-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:325144fa07e2675d0faa337fcc864213c272a499eb0cfe5bde2fdc62282d27bc", size = 12215005, upload-time = "2026-08-14T21:35:26.892Z" }, + { url = "https://files.pythonhosted.org/packages/df/7d/46e65b17b4966c7cd0140f134380d33d8e84fe6efccd761533ce793dc502/ty-0.0.72-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5c9f15d0f58e43707d8848274be1821a0ef408eccb8aa7dda28a4a9eddf7640", size = 12421298, upload-time = "2026-08-14T21:35:29.301Z" }, + { url = "https://files.pythonhosted.org/packages/08/2a/12ada4ec17700b3cb1d4fd3bc3e5b1852df9e6885288429318cade87b3c1/ty-0.0.72-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee508d64b381871529cc22c412b41071bf5e908b7aa5d66a38f3f6b2573a806", size = 12669242, upload-time = "2026-08-14T21:35:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/1c/1a/4692536880790fb550ed6d44a6096778dc71bb112f2c6d615cebb01a57e5/ty-0.0.72-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3699e2ec7921d44da79d6b089f7bf239b2cc53c4e45a5a38430adc34ee9e9a55", size = 12988199, upload-time = "2026-08-14T21:35:33.749Z" }, + { url = "https://files.pythonhosted.org/packages/9a/0d/f5e5a50322e9c45865e7b7a428ba6cd6527387cf0f2472492ac3cf746243/ty-0.0.72-py3-none-win32.whl", hash = "sha256:f25f72a67bd36cd247707c4784e52fad0b6b4f42a1b7dd14804110fa95c486ed", size = 11939708, upload-time = "2026-08-14T21:35:36.006Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4e/8af3534b2e4214e6184a5a59c34101e94a68d578f081f97b995866bab1bf/ty-0.0.72-py3-none-win_amd64.whl", hash = "sha256:cdeee869341717e1736cea2e2d7856738c6957c320f584ed2f68c8f90100d2f5", size = 12643876, upload-time = "2026-08-14T21:35:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ea/a2606e654c7276bd08586391a2525b0af3f3bf60228a8c57b2d248f273f9/ty-0.0.72-py3-none-win_arm64.whl", hash = "sha256:1bd3ac3ed4424a6d6990a85dc388556aea012bd752de21349a84b685951de0d8", size = 12394857, upload-time = "2026-08-14T21:35:40.277Z" }, ] [[package]] @@ -4259,14 +4318,14 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.3" +version = "0.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/bc/4eae18cd40c65798a16267572ba346c11f599d44b01603dbd843342042bc/typing_inspection-0.4.3.tar.gz", hash = "sha256:c5f9ec1530b5c1e2c9bc34a84d9a3466ed1b2f3f2fa9f901368d9c5596210e4d", size = 76711, upload-time = "2026-08-10T09:39:18.063Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/f7/7a3935abdebd5cf18705a5f0335dd6a3a18bef3baa7cb9edc3b6b9922cc8/typing_inspection-0.4.3-py3-none-any.whl", hash = "sha256:5f42b23858a91e0b4ef521f5418f03a0da3c9216fd2995ef5e73463100e676cd", size = 14693, upload-time = "2026-08-10T09:39:16.693Z" }, + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, ] [[package]] From 39ed7cf1aa455e709933b6032791d18974d7e564 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 14 Aug 2026 15:34:38 -0700 Subject: [PATCH 80/82] fix(importer): skip comic moves onto claimed paths A move whose destination path already belonged to another comic row violated the (library, path) unique constraint inside bulk_update. The IntegrityError aborted the whole import before the delete phase could clear the stale row, so the poller rebuilt the same task and crashed on every subsequent scan. Mirror the folder guard for comics and custom covers: drop moves onto paths an existing row holds, and moves that two sources in one batch claim, which files_moved can express because it is a plain dict rather than a bidict. Skipping converges. The destination row's stat refreshes, the next scan sees two rows sharing an inode and suppresses the bogus move, and the stale source falls through to the delete phase. Also contain any move phase failure in all three move steps so a bad batch degrades to a skipped phase the next scan reconciles instead of aborting the import. This covers the same class of crash in the folder step, where converting dirs_moved to a bidict raises on the duplicate destinations the watcher can emit. Fixes #807 Co-Authored-By: Claude Fable 5 --- NEWS.md | 8 + .../librarian/scribe/importer/moved/comics.py | 86 +++++++++- .../librarian/scribe/importer/moved/covers.py | 10 +- .../scribe/importer/moved/folders.py | 5 + tests/importer/test_moved_dest_collision.py | 159 ++++++++++++++++++ 5 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 tests/importer/test_moved_dest_collision.py diff --git a/NEWS.md b/NEWS.md index e2750e125..aed1a76e3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,14 @@ width: 128px; border-radius: 128px; " /> +## v2.2.7 + +- Fixes + - A comic moved or renamed onto a path that already belongs to another + comic, like a duplicate landing in a dupes folder, no longer crashes the + scan and no longer crashes every scan after it. The stray move is skipped + and the leftover record is cleaned up by a later scan. + ## v2.2.6 - Features diff --git a/codex/librarian/scribe/importer/moved/comics.py b/codex/librarian/scribe/importer/moved/comics.py index 8c8a0936a..6ca733d5f 100644 --- a/codex/librarian/scribe/importer/moved/comics.py +++ b/codex/librarian/scribe/importer/moved/comics.py @@ -1,5 +1,6 @@ """Bulk import and move comics.""" +from collections.abc import Mapping from pathlib import Path from django.db.models.functions import Now @@ -15,12 +16,78 @@ from codex.librarian.scribe.importer.statii.create import ImporterCreateTagsStatus from codex.librarian.scribe.importer.statii.moved import ImporterMoveComicsStatus from codex.librarian.scribe.importer.statii.query import ImporterQueryMissingTagsStatus -from codex.models import Comic, Folder +from codex.models import Comic, CustomCover, Folder class MovedComicsImporter(ReadMetadataImporter): """Methods for moving comics and folders.""" + def _remove_file_move_collisions( + self, + model: type[Comic] | type[CustomCover], + moves: Mapping[str, str], + item_name: str, + ) -> dict[str, str]: + """ + Remove moves whose destination path is already claimed. + + The poller and the watcher both infer moves from inode matches + without checking that the destination is free in the database. + A destination that already belongs to another row would violate + the ``(library, path)`` unique constraint inside the upcoming + ``bulk_update``, and that exception aborted the entire import, + leaving the delete phase unreachable so the same task crashed + again on every subsequent scan. + + Two claims are possible. An existing row may sit at the + destination already (a duplicate file landing on a tracked + path, or an overwrite-rename), or two sources in this batch may + resolve to the same destination, which ``files_moved`` can + express because it is a plain dict rather than the bidict + folder moves use. Drop both, keeping the lowest sorted source + for a duplicated destination so the choice is deterministic. + + Skipping converges rather than stranding anything. The + destination row's stat is refreshed by the modified reimport or + by the poller's stale stat pass, so the next scan sees two rows + sharing an inode, suppresses the bogus move as ambiguous, and + the stale source falls through to the delete phase. Libraries + that only run the watcher wait for their next poll instead. A + source and destination that swap paths drop both moves and + reconcile as content changes in place, which a single + ``bulk_update`` could not express under SQLite anyway. + + This is normal reconciliation, not a fault. Log at INFO with a + count and at DEBUG with the paths, matching the folder guard. + """ + if not moves: + return {} + occupied_paths = frozenset( + model.objects.filter( + library=self.library, path__in=frozenset(moves.values()) + ).values_list(PATH_FIELD_NAME, flat=True) + ) + kept_moves: dict[str, str] = {} + claimed_paths: set[str] = set() + skipped_paths: list[str] = [] + for src_path in sorted(moves): + dest_path = moves[src_path] + if dest_path in occupied_paths or dest_path in claimed_paths: + skipped_paths.append(src_path) + else: + kept_moves[src_path] = dest_path + claimed_paths.add(dest_path) + if skipped_paths: + count = len(skipped_paths) + plural = "s" if count != 1 else "" + msg = ( + f"Resolved {count} phantom {item_name} move{plural} by skipping " + "the rename and leaving the destinations in place." + ) + self.log.info(msg) + self.log.debug(f"Skipped {item_name} move sources: {skipped_paths}") + return kept_moves + def _bulk_comics_moved_ensure_folders(self) -> None: """Ensure folders we're moving to exist.""" dest_comic_paths = self.task.files_moved.values() @@ -125,14 +192,21 @@ def _bulk_comics_move_prepare(self) -> tuple[list, dict, dict]: return updated_comics, folder_m2m_links, del_rows_map def bulk_comics_moved(self) -> int: - """Move comcis.""" + """Move comics.""" + count = 0 num_files_moved = len(self.task.files_moved) status = ImporterMoveComicsStatus(0, num_files_moved) try: if not num_files_moved: - return 0 + return count self.status_controller.start(status) + # Drop moves onto claimed paths before creating any folders + # for destinations that will not be used. + self.task.files_moved = self._remove_file_move_collisions( + Comic, self.task.files_moved, "comic" + ) + # Prepare self._bulk_comics_moved_ensure_folders() updated_comics, folder_m2m_links, del_rows_map = ( @@ -148,6 +222,12 @@ def bulk_comics_moved(self) -> int: self.link_comic_m2m_field(FOLDERS_FIELD_NAME, folder_m2m_links, status) count = len(updated_comics) + except Exception: + # Broad by intent. A failed move phase must degrade to a + # skipped phase that the next scan reconciles, never abort + # the import, which left the delete phase unreachable and + # the same task crashing on every scan. + self.log.exception(f"Moving {num_files_moved} comics") finally: self.status_controller.finish(status) return count diff --git a/codex/librarian/scribe/importer/moved/covers.py b/codex/librarian/scribe/importer/moved/covers.py index 90279c863..5c13a75c6 100644 --- a/codex/librarian/scribe/importer/moved/covers.py +++ b/codex/librarian/scribe/importer/moved/covers.py @@ -62,13 +62,18 @@ def _bulk_covers_moved_unlink(self, unlink_pks) -> None: def bulk_covers_moved(self, status=None) -> int: """Move covers.""" + count = 0 num_covers_moved = len(self.task.covers_moved) status = ImporterMoveCoversStatus(None, num_covers_moved) try: if not num_covers_moved: - return 0 + return count self.status_controller.start(status) + self.task.covers_moved = self._remove_file_move_collisions( + CustomCover, self.task.covers_moved, "custom cover" + ) + moved_covers, unlink_pks = self._bulk_covers_moved_prepare(status) if LINK_COVER_PKS not in self.metadata: self.metadata[LINK_COVER_PKS] = set() @@ -83,6 +88,9 @@ def bulk_covers_moved(self, status=None) -> int: count = len(moved_covers) level = "INFO" if count else "DEBUG" self.log.log(level, f"Moved {count} custom covers.") + except Exception: + # Broad by intent, as in bulk_comics_moved. + self.log.exception(f"Moving {num_covers_moved} custom covers") finally: self.status_controller.finish(status) return count diff --git a/codex/librarian/scribe/importer/moved/folders.py b/codex/librarian/scribe/importer/moved/folders.py index 64094ad0c..927628e41 100644 --- a/codex/librarian/scribe/importer/moved/folders.py +++ b/codex/librarian/scribe/importer/moved/folders.py @@ -225,6 +225,11 @@ def bulk_folders_moved(self, *, mark_in_progress=False) -> int: count += self._bulk_move_folders_and_create_parents(status) self.task.dirs_moved = {} + except Exception: + # Broad by intent, as in bulk_comics_moved. Converting + # dirs_moved to a bidict raises when two sources share a + # destination, which the watcher can emit. + self.log.exception(f"Moving {num_dirs_moved} folders") finally: self.status_controller.finish(status) if mark_in_progress: diff --git a/tests/importer/test_moved_dest_collision.py b/tests/importer/test_moved_dest_collision.py new file mode 100644 index 000000000..425dcfb4c --- /dev/null +++ b/tests/importer/test_moved_dest_collision.py @@ -0,0 +1,159 @@ +""" +A move onto a claimed path must not crash the import. + +The poller and watcher infer moves from inode matches without checking +that the destination is free in the database. A destination already held +by another row violated the ``(library, path)`` unique constraint inside +``bulk_update``, which aborted the whole import before the delete phase +could clear the stale row, so the same task crashed on every later scan. +Skipping the colliding move keeps the rest of the import running and lets +the next scan reconcile. +""" + +import shutil +from pathlib import Path +from threading import Event, Lock +from typing import override +from unittest.mock import patch + +from loguru import logger + +from codex.librarian.mp_queue import LIBRARIAN_QUEUE +from codex.librarian.scribe.importer.importer import ComicImporter +from codex.librarian.scribe.importer.tasks import ImportTask +from codex.models import ( + Comic, + Folder, + Imprint, + Library, + Publisher, + Series, + Volume, +) +from tests.importer.test_basic import ( + COMIC_PATH, + LIBRARY_PATH, + BaseTestImporter, +) + +_SRC_PATH = str(LIBRARY_PATH / "src.cbz") +_DEST_PATH = str(LIBRARY_PATH / "dest.cbz") +_OTHER_PATH = str(LIBRARY_PATH / "other.cbz") +_FREE_PATH = str(LIBRARY_PATH / "free.cbz") + + +class TestImporterMoveDestCollision(BaseTestImporter): + """Moves onto claimed destinations are skipped, not fatal.""" + + @override + def setUp(self) -> None: + super().setUp() + self.library = Library.objects.get(pk=self.task.library_id) + self.folder = Folder.objects.create( + library=self.library, + path=str(LIBRARY_PATH), + name=LIBRARY_PATH.name, + ) + pub = Publisher.objects.create(name="Collision Pub") + imp = Imprint.objects.create(name="Collision Imprint", publisher=pub) + ser = Series.objects.create(name="Collision Series", imprint=imp, publisher=pub) + self.tags = { + "publisher": pub, + "imprint": imp, + "series": ser, + "volume": Volume.objects.create( + name="1", series=ser, imprint=imp, publisher=pub + ), + } + self.issue_number = 0 + + def _create_comic(self, path: str) -> Comic: + """Create a comic with its file present, as presave stats disk.""" + shutil.copy(COMIC_PATH, path) + self.issue_number += 1 + return Comic.objects.create( + library=self.library, + path=path, + parent_folder=self.folder, + issue_number=self.issue_number, + name=Path(path).stem, + size=1, + page_count=1, + **self.tags, + ) + + def _move(self, files_moved: dict[str, str]) -> ComicImporter: + """Run the move phase with a fresh importer, as a scan would.""" + task = ImportTask(library_id=self.library.pk, files_moved=files_moved) + importer = ComicImporter(task, logger, LIBRARIAN_QUEUE, Lock(), Event()) + importer.move_and_modify_dirs() + return importer + + def test_move_onto_tracked_dest_skipped(self) -> None: + """A move onto a path another comic already holds is skipped.""" + src = self._create_comic(_SRC_PATH) + dest = self._create_comic(_DEST_PATH) + # The source file moved onto the destination on disk. + Path(_SRC_PATH).unlink() + + importer = self._move({_SRC_PATH: _DEST_PATH}) + + assert importer.counts.comics_moved == 0 + assert Comic.objects.get(pk=dest.pk).path == _DEST_PATH + assert Comic.objects.get(pk=src.pk).path == _SRC_PATH + + def test_duplicate_destinations_keep_lowest_src(self) -> None: + """Two sources claiming one destination keep only the first.""" + # "other" sorts before "src", so it wins the destination. + other = self._create_comic(_OTHER_PATH) + src = self._create_comic(_SRC_PATH) + shutil.copy(COMIC_PATH, _FREE_PATH) + Path(_OTHER_PATH).unlink() + Path(_SRC_PATH).unlink() + + importer = self._move({_SRC_PATH: _FREE_PATH, _OTHER_PATH: _FREE_PATH}) + + assert importer.counts.comics_moved == 1 + assert Comic.objects.get(pk=other.pk).path == _FREE_PATH + assert Comic.objects.get(pk=src.pk).path == _SRC_PATH + assert Comic.objects.filter(library=self.library, path=_FREE_PATH).count() == 1 + + def test_chained_move_converges_over_two_scans(self) -> None: + """A move onto a vacating path lands on the scan after it.""" + src = self._create_comic(_SRC_PATH) + dest = self._create_comic(_DEST_PATH) + # Both destination files are present, as they would be after a + # chained rename on disk, so only the claimed path blocks a move. + shutil.copy(COMIC_PATH, _FREE_PATH) + Path(_SRC_PATH).unlink() + + # Scan one: the tail move is free, the head move is blocked. + importer = self._move({_SRC_PATH: _DEST_PATH, _DEST_PATH: _FREE_PATH}) + + assert importer.counts.comics_moved == 1 + assert Comic.objects.get(pk=dest.pk).path == _FREE_PATH + assert Comic.objects.get(pk=src.pk).path == _SRC_PATH + + # Scan two: the destination is vacant now, so the head move lands. + importer = self._move({_SRC_PATH: _DEST_PATH}) + + assert importer.counts.comics_moved == 1 + assert Comic.objects.get(pk=src.pk).path == _DEST_PATH + + def test_move_phase_failure_does_not_abort(self) -> None: + """Any move failure is contained so later phases still run.""" + src = self._create_comic(_SRC_PATH) + shutil.copy(COMIC_PATH, _FREE_PATH) + Path(_SRC_PATH).unlink() + + task = ImportTask( + library_id=self.library.pk, files_moved={_SRC_PATH: _FREE_PATH} + ) + importer = ComicImporter(task, logger, LIBRARIAN_QUEUE, Lock(), Event()) + with patch.object( + importer, "_bulk_comics_move_prepare", side_effect=RuntimeError("boom") + ): + importer.move_and_modify_dirs() + + assert importer.counts.comics_moved == 0 + assert Comic.objects.get(pk=src.pk).path == _SRC_PATH From 7724e8c59b587118f89659c2cafbbabbb37f6df9 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 14 Aug 2026 15:36:16 -0700 Subject: [PATCH 81/82] bump version --- frontend/package.json | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 940e5b343..51e95f825 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "codex", - "version": "2.2.6", + "version": "2.2.7", "private": true, "description": "ui for codex api", "type": "module", diff --git a/pyproject.toml b/pyproject.toml index 6601d6804..addf52745 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ readme = "README.md" requires-python = ">=3.12" license = "GPL-3.0-only" name = "codex" -version = "2.2.6" +version = "2.2.7" [[project.authors]] name = "AJ Slater" email = "aj@slater.net" diff --git a/uv.lock b/uv.lock index 152a8d3f8..6bc90a8e2 100644 --- a/uv.lock +++ b/uv.lock @@ -637,7 +637,7 @@ wheels = [ [[package]] name = "codex" -version = "2.2.6" +version = "2.2.7" source = { editable = "." } dependencies = [ { name = "adrf" }, From 6e3ba898cfb5afa55dea1fd63e8976efda704054 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 14 Aug 2026 15:38:30 -0700 Subject: [PATCH 82/82] update deps --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index addf52745..4c0846971 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "adrf~=0.1.12", "bidict~=0.23", "channels~=4.2", - "comicbox[pdf]~=4.8.0", + "comicbox[pdf]~=4.8.1", "cryptography>=48.0.0", "dateparser~=1.2", "django-allauth[socialaccount]~=65.13", diff --git a/uv.lock b/uv.lock index 6bc90a8e2..f4c6bdd68 100644 --- a/uv.lock +++ b/uv.lock @@ -723,7 +723,7 @@ requires-dist = [ { name = "adrf", specifier = "~=0.1.12" }, { name = "bidict", specifier = "~=0.23" }, { name = "channels", specifier = "~=4.2" }, - { name = "comicbox", extras = ["pdf"], specifier = "~=4.8.0" }, + { name = "comicbox", extras = ["pdf"], specifier = "~=4.8.1" }, { name = "cryptography", specifier = ">=48.0.0" }, { name = "dateparser", specifier = "~=1.2" }, { name = "django", specifier = "~=6.0" }, @@ -809,7 +809,7 @@ wheels = [ [[package]] name = "comicbox" -version = "4.8.0" +version = "4.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, @@ -846,9 +846,9 @@ dependencies = [ { name = "xmltodict" }, { name = "zipremove" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/2b/38f5122b1aa64f72b36d851335b5ac532ea74f8c44b7d1c094d796f3177a/comicbox-4.8.0.tar.gz", hash = "sha256:28acf608904f2353e7c48d422d29d1f62f493ef9b36e40bf983577348cebb75a", size = 1019177, upload-time = "2026-08-02T00:57:00.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/72/0391e8b9ee70de43d37c8f1f3e2a2a64ea1fc60e356f6c09365c7412891e/comicbox-4.8.1.tar.gz", hash = "sha256:76ff43b2caa4382920c113854bfb6443437b584c3217491a72078f8d2b01a493", size = 1032727, upload-time = "2026-08-14T22:37:00.977Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/3c/e36e02402579645f818d12602a5d0c3336fd05cff773ca3c6c994c1885bc/comicbox-4.8.0-py3-none-any.whl", hash = "sha256:7f96ee493d9fb416846a55eb8a2bec61e6ec1460625821b6bf83cdbeac623798", size = 344052, upload-time = "2026-08-02T00:56:59.356Z" }, + { url = "https://files.pythonhosted.org/packages/fa/23/7674c01d0e90faa135581383fc514e1cbb2c50813b4fe55e4045db9b44ca/comicbox-4.8.1-py3-none-any.whl", hash = "sha256:557473e807b3fcfe8046025c107adb8292ee205c06a55b94f3943e739aa4ef25", size = 344019, upload-time = "2026-08-14T22:36:59.109Z" }, ] [package.optional-dependencies]