pydantic model generator for atproto lexicons

initial rust+pyo3 implementation

Pydantic model generator for ATProto lexicons using:
- atrium-lex for lexicon parsing
- pyo3 for python bindings
- maturin for build

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+73
.github/workflows/ci.yml
··· 1 + name: CI 2 + 3 + on: 4 + pull_request: 5 + paths: 6 + - "src/**" 7 + - "tests/**" 8 + - "pyproject.toml" 9 + - "uv.lock" 10 + - ".pre-commit-config.yaml" 11 + - ".github/workflows/ci.yml" 12 + push: 13 + branches: [main] 14 + paths: 15 + - "src/**" 16 + - "tests/**" 17 + - "pyproject.toml" 18 + - "uv.lock" 19 + - ".pre-commit-config.yaml" 20 + - ".github/workflows/ci.yml" 21 + 22 + permissions: 23 + contents: read 24 + 25 + jobs: 26 + pre-commit: 27 + timeout-minutes: 5 28 + runs-on: ubuntu-latest 29 + 30 + steps: 31 + - uses: actions/checkout@v5 32 + 33 + - name: install uv 34 + uses: astral-sh/setup-uv@v7 35 + with: 36 + enable-cache: true 37 + cache-dependency-glob: "uv.lock" 38 + 39 + - name: install dependencies 40 + run: uv sync 41 + 42 + - name: check lockfile is up to date 43 + run: | 44 + if ! uv lock --check; then 45 + echo "lockfile is out of date!" 46 + echo "to update the lockfile, run 'uv lock'." 47 + exit 1 48 + fi 49 + echo "lockfile is up to date" 50 + 51 + - name: run prek 52 + run: uv run prek run --all-files 53 + env: 54 + SKIP: no-commit-to-branch 55 + 56 + test: 57 + timeout-minutes: 5 58 + runs-on: ubuntu-latest 59 + 60 + steps: 61 + - uses: actions/checkout@v5 62 + 63 + - name: install uv 64 + uses: astral-sh/setup-uv@v7 65 + with: 66 + enable-cache: true 67 + cache-dependency-glob: "uv.lock" 68 + 69 + - name: install dependencies 70 + run: uv sync 71 + 72 + - name: run tests 73 + run: uv run pytest -v
+26
.github/workflows/publish.yml
··· 1 + name: Publish pmgfal to PyPI 2 + on: 3 + release: 4 + types: [published] 5 + workflow_dispatch: 6 + 7 + jobs: 8 + pypi-publish: 9 + name: Upload to PyPI 10 + runs-on: ubuntu-latest 11 + permissions: 12 + id-token: write 13 + steps: 14 + - name: Checkout 15 + uses: actions/checkout@v5 16 + with: 17 + fetch-depth: 0 18 + 19 + - name: "Install uv" 20 + uses: astral-sh/setup-uv@v7 21 + 22 + - name: Build 23 + run: uv build 24 + 25 + - name: Publish to PyPI 26 + run: uv publish -v dist/*
+34
.gitignore
··· 1 + __pycache__/ 2 + *.py[cod] 3 + *$py.class 4 + *.so 5 + .Python 6 + build/ 7 + develop-eggs/ 8 + dist/ 9 + downloads/ 10 + eggs/ 11 + .eggs/ 12 + lib/ 13 + lib64/ 14 + parts/ 15 + sdist/ 16 + var/ 17 + wheels/ 18 + *.egg-info/ 19 + .installed.cfg 20 + *.egg 21 + .venv/ 22 + venv/ 23 + ENV/ 24 + .env 25 + *.env 26 + .pytest_cache/ 27 + .coverage 28 + htmlcov/ 29 + .mypy_cache/ 30 + .ruff_cache/ 31 + .ty_cache/ 32 + 33 + # rust 34 + target/
+30
.pre-commit-config.yaml
··· 1 + fail_fast: false 2 + 3 + repos: 4 + - repo: https://github.com/abravalheri/validate-pyproject 5 + rev: v0.24.1 6 + hooks: 7 + - id: validate-pyproject 8 + 9 + - repo: https://github.com/astral-sh/ruff-pre-commit 10 + rev: v0.12.1 11 + hooks: 12 + - id: ruff-check 13 + args: [--fix, --exit-non-zero-on-fix] 14 + - id: ruff-format 15 + 16 + - repo: local 17 + hooks: 18 + - id: type-check 19 + name: type check 20 + entry: uv run ty check 21 + language: system 22 + types: [python] 23 + pass_filenames: false 24 + 25 + - repo: https://github.com/pre-commit/pre-commit-hooks 26 + rev: v6.0.0 27 + hooks: 28 + - id: no-commit-to-branch 29 + name: prevent commits to main 30 + args: [--branch, main]
+635
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "android_system_properties" 7 + version = "0.1.5" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 10 + dependencies = [ 11 + "libc", 12 + ] 13 + 14 + [[package]] 15 + name = "atrium-lex" 16 + version = "0.1.0" 17 + source = "git+https://github.com/atrium-rs/atrium?branch=main#6d59a772bc933743bee1dc877e02b692a9711b37" 18 + dependencies = [ 19 + "serde", 20 + "serde_repr", 21 + "serde_with", 22 + ] 23 + 24 + [[package]] 25 + name = "autocfg" 26 + version = "1.5.0" 27 + source = "registry+https://github.com/rust-lang/crates.io-index" 28 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 29 + 30 + [[package]] 31 + name = "base64" 32 + version = "0.13.1" 33 + source = "registry+https://github.com/rust-lang/crates.io-index" 34 + checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 35 + 36 + [[package]] 37 + name = "bumpalo" 38 + version = "3.19.0" 39 + source = "registry+https://github.com/rust-lang/crates.io-index" 40 + checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 41 + 42 + [[package]] 43 + name = "cc" 44 + version = "1.2.49" 45 + source = "registry+https://github.com/rust-lang/crates.io-index" 46 + checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" 47 + dependencies = [ 48 + "find-msvc-tools", 49 + "shlex", 50 + ] 51 + 52 + [[package]] 53 + name = "cfg-if" 54 + version = "1.0.4" 55 + source = "registry+https://github.com/rust-lang/crates.io-index" 56 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 57 + 58 + [[package]] 59 + name = "chrono" 60 + version = "0.4.42" 61 + source = "registry+https://github.com/rust-lang/crates.io-index" 62 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 63 + dependencies = [ 64 + "iana-time-zone", 65 + "num-traits", 66 + "serde", 67 + "windows-link", 68 + ] 69 + 70 + [[package]] 71 + name = "core-foundation-sys" 72 + version = "0.8.7" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 75 + 76 + [[package]] 77 + name = "darling" 78 + version = "0.20.11" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 81 + dependencies = [ 82 + "darling_core", 83 + "darling_macro", 84 + ] 85 + 86 + [[package]] 87 + name = "darling_core" 88 + version = "0.20.11" 89 + source = "registry+https://github.com/rust-lang/crates.io-index" 90 + checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 91 + dependencies = [ 92 + "fnv", 93 + "ident_case", 94 + "proc-macro2", 95 + "quote", 96 + "strsim", 97 + "syn", 98 + ] 99 + 100 + [[package]] 101 + name = "darling_macro" 102 + version = "0.20.11" 103 + source = "registry+https://github.com/rust-lang/crates.io-index" 104 + checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 105 + dependencies = [ 106 + "darling_core", 107 + "quote", 108 + "syn", 109 + ] 110 + 111 + [[package]] 112 + name = "deranged" 113 + version = "0.5.5" 114 + source = "registry+https://github.com/rust-lang/crates.io-index" 115 + checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 116 + dependencies = [ 117 + "powerfmt", 118 + "serde_core", 119 + ] 120 + 121 + [[package]] 122 + name = "find-msvc-tools" 123 + version = "0.1.5" 124 + source = "registry+https://github.com/rust-lang/crates.io-index" 125 + checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 126 + 127 + [[package]] 128 + name = "fnv" 129 + version = "1.0.7" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 132 + 133 + [[package]] 134 + name = "hashbrown" 135 + version = "0.12.3" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 138 + 139 + [[package]] 140 + name = "heck" 141 + version = "0.5.0" 142 + source = "registry+https://github.com/rust-lang/crates.io-index" 143 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 144 + 145 + [[package]] 146 + name = "hex" 147 + version = "0.4.3" 148 + source = "registry+https://github.com/rust-lang/crates.io-index" 149 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 150 + 151 + [[package]] 152 + name = "iana-time-zone" 153 + version = "0.1.64" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 156 + dependencies = [ 157 + "android_system_properties", 158 + "core-foundation-sys", 159 + "iana-time-zone-haiku", 160 + "js-sys", 161 + "log", 162 + "wasm-bindgen", 163 + "windows-core", 164 + ] 165 + 166 + [[package]] 167 + name = "iana-time-zone-haiku" 168 + version = "0.1.2" 169 + source = "registry+https://github.com/rust-lang/crates.io-index" 170 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 171 + dependencies = [ 172 + "cc", 173 + ] 174 + 175 + [[package]] 176 + name = "ident_case" 177 + version = "1.0.1" 178 + source = "registry+https://github.com/rust-lang/crates.io-index" 179 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 180 + 181 + [[package]] 182 + name = "indexmap" 183 + version = "1.9.3" 184 + source = "registry+https://github.com/rust-lang/crates.io-index" 185 + checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 186 + dependencies = [ 187 + "autocfg", 188 + "hashbrown", 189 + "serde", 190 + ] 191 + 192 + [[package]] 193 + name = "indoc" 194 + version = "2.0.7" 195 + source = "registry+https://github.com/rust-lang/crates.io-index" 196 + checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 197 + dependencies = [ 198 + "rustversion", 199 + ] 200 + 201 + [[package]] 202 + name = "itoa" 203 + version = "1.0.15" 204 + source = "registry+https://github.com/rust-lang/crates.io-index" 205 + checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 206 + 207 + [[package]] 208 + name = "js-sys" 209 + version = "0.3.83" 210 + source = "registry+https://github.com/rust-lang/crates.io-index" 211 + checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" 212 + dependencies = [ 213 + "once_cell", 214 + "wasm-bindgen", 215 + ] 216 + 217 + [[package]] 218 + name = "libc" 219 + version = "0.2.178" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 222 + 223 + [[package]] 224 + name = "log" 225 + version = "0.4.29" 226 + source = "registry+https://github.com/rust-lang/crates.io-index" 227 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 228 + 229 + [[package]] 230 + name = "memchr" 231 + version = "2.7.6" 232 + source = "registry+https://github.com/rust-lang/crates.io-index" 233 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 234 + 235 + [[package]] 236 + name = "memoffset" 237 + version = "0.9.1" 238 + source = "registry+https://github.com/rust-lang/crates.io-index" 239 + checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 240 + dependencies = [ 241 + "autocfg", 242 + ] 243 + 244 + [[package]] 245 + name = "num-conv" 246 + version = "0.1.0" 247 + source = "registry+https://github.com/rust-lang/crates.io-index" 248 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 249 + 250 + [[package]] 251 + name = "num-traits" 252 + version = "0.2.19" 253 + source = "registry+https://github.com/rust-lang/crates.io-index" 254 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 255 + dependencies = [ 256 + "autocfg", 257 + ] 258 + 259 + [[package]] 260 + name = "once_cell" 261 + version = "1.21.3" 262 + source = "registry+https://github.com/rust-lang/crates.io-index" 263 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 264 + 265 + [[package]] 266 + name = "pmgfal" 267 + version = "0.1.0" 268 + dependencies = [ 269 + "atrium-lex", 270 + "heck", 271 + "pyo3", 272 + "serde", 273 + "serde_json", 274 + ] 275 + 276 + [[package]] 277 + name = "portable-atomic" 278 + version = "1.11.1" 279 + source = "registry+https://github.com/rust-lang/crates.io-index" 280 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 281 + 282 + [[package]] 283 + name = "powerfmt" 284 + version = "0.2.0" 285 + source = "registry+https://github.com/rust-lang/crates.io-index" 286 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 287 + 288 + [[package]] 289 + name = "proc-macro2" 290 + version = "1.0.103" 291 + source = "registry+https://github.com/rust-lang/crates.io-index" 292 + checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 293 + dependencies = [ 294 + "unicode-ident", 295 + ] 296 + 297 + [[package]] 298 + name = "pyo3" 299 + version = "0.26.0" 300 + source = "registry+https://github.com/rust-lang/crates.io-index" 301 + checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383" 302 + dependencies = [ 303 + "indoc", 304 + "libc", 305 + "memoffset", 306 + "once_cell", 307 + "portable-atomic", 308 + "pyo3-build-config", 309 + "pyo3-ffi", 310 + "pyo3-macros", 311 + "unindent", 312 + ] 313 + 314 + [[package]] 315 + name = "pyo3-build-config" 316 + version = "0.26.0" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f" 319 + dependencies = [ 320 + "target-lexicon", 321 + ] 322 + 323 + [[package]] 324 + name = "pyo3-ffi" 325 + version = "0.26.0" 326 + source = "registry+https://github.com/rust-lang/crates.io-index" 327 + checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105" 328 + dependencies = [ 329 + "libc", 330 + "pyo3-build-config", 331 + ] 332 + 333 + [[package]] 334 + name = "pyo3-macros" 335 + version = "0.26.0" 336 + source = "registry+https://github.com/rust-lang/crates.io-index" 337 + checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded" 338 + dependencies = [ 339 + "proc-macro2", 340 + "pyo3-macros-backend", 341 + "quote", 342 + "syn", 343 + ] 344 + 345 + [[package]] 346 + name = "pyo3-macros-backend" 347 + version = "0.26.0" 348 + source = "registry+https://github.com/rust-lang/crates.io-index" 349 + checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf" 350 + dependencies = [ 351 + "heck", 352 + "proc-macro2", 353 + "pyo3-build-config", 354 + "quote", 355 + "syn", 356 + ] 357 + 358 + [[package]] 359 + name = "quote" 360 + version = "1.0.42" 361 + source = "registry+https://github.com/rust-lang/crates.io-index" 362 + checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 363 + dependencies = [ 364 + "proc-macro2", 365 + ] 366 + 367 + [[package]] 368 + name = "rustversion" 369 + version = "1.0.22" 370 + source = "registry+https://github.com/rust-lang/crates.io-index" 371 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 372 + 373 + [[package]] 374 + name = "ryu" 375 + version = "1.0.20" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 378 + 379 + [[package]] 380 + name = "serde" 381 + version = "1.0.228" 382 + source = "registry+https://github.com/rust-lang/crates.io-index" 383 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 384 + dependencies = [ 385 + "serde_core", 386 + "serde_derive", 387 + ] 388 + 389 + [[package]] 390 + name = "serde_core" 391 + version = "1.0.228" 392 + source = "registry+https://github.com/rust-lang/crates.io-index" 393 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 394 + dependencies = [ 395 + "serde_derive", 396 + ] 397 + 398 + [[package]] 399 + name = "serde_derive" 400 + version = "1.0.228" 401 + source = "registry+https://github.com/rust-lang/crates.io-index" 402 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 403 + dependencies = [ 404 + "proc-macro2", 405 + "quote", 406 + "syn", 407 + ] 408 + 409 + [[package]] 410 + name = "serde_json" 411 + version = "1.0.145" 412 + source = "registry+https://github.com/rust-lang/crates.io-index" 413 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 414 + dependencies = [ 415 + "itoa", 416 + "memchr", 417 + "ryu", 418 + "serde", 419 + "serde_core", 420 + ] 421 + 422 + [[package]] 423 + name = "serde_repr" 424 + version = "0.1.20" 425 + source = "registry+https://github.com/rust-lang/crates.io-index" 426 + checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 427 + dependencies = [ 428 + "proc-macro2", 429 + "quote", 430 + "syn", 431 + ] 432 + 433 + [[package]] 434 + name = "serde_with" 435 + version = "2.3.3" 436 + source = "registry+https://github.com/rust-lang/crates.io-index" 437 + checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" 438 + dependencies = [ 439 + "base64", 440 + "chrono", 441 + "hex", 442 + "indexmap", 443 + "serde", 444 + "serde_json", 445 + "serde_with_macros", 446 + "time", 447 + ] 448 + 449 + [[package]] 450 + name = "serde_with_macros" 451 + version = "2.3.3" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" 454 + dependencies = [ 455 + "darling", 456 + "proc-macro2", 457 + "quote", 458 + "syn", 459 + ] 460 + 461 + [[package]] 462 + name = "shlex" 463 + version = "1.3.0" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 466 + 467 + [[package]] 468 + name = "strsim" 469 + version = "0.11.1" 470 + source = "registry+https://github.com/rust-lang/crates.io-index" 471 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 472 + 473 + [[package]] 474 + name = "syn" 475 + version = "2.0.111" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 478 + dependencies = [ 479 + "proc-macro2", 480 + "quote", 481 + "unicode-ident", 482 + ] 483 + 484 + [[package]] 485 + name = "target-lexicon" 486 + version = "0.13.3" 487 + source = "registry+https://github.com/rust-lang/crates.io-index" 488 + checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" 489 + 490 + [[package]] 491 + name = "time" 492 + version = "0.3.44" 493 + source = "registry+https://github.com/rust-lang/crates.io-index" 494 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 495 + dependencies = [ 496 + "deranged", 497 + "itoa", 498 + "num-conv", 499 + "powerfmt", 500 + "serde", 501 + "time-core", 502 + "time-macros", 503 + ] 504 + 505 + [[package]] 506 + name = "time-core" 507 + version = "0.1.6" 508 + source = "registry+https://github.com/rust-lang/crates.io-index" 509 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 510 + 511 + [[package]] 512 + name = "time-macros" 513 + version = "0.2.24" 514 + source = "registry+https://github.com/rust-lang/crates.io-index" 515 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 516 + dependencies = [ 517 + "num-conv", 518 + "time-core", 519 + ] 520 + 521 + [[package]] 522 + name = "unicode-ident" 523 + version = "1.0.22" 524 + source = "registry+https://github.com/rust-lang/crates.io-index" 525 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 526 + 527 + [[package]] 528 + name = "unindent" 529 + version = "0.2.4" 530 + source = "registry+https://github.com/rust-lang/crates.io-index" 531 + checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" 532 + 533 + [[package]] 534 + name = "wasm-bindgen" 535 + version = "0.2.106" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" 538 + dependencies = [ 539 + "cfg-if", 540 + "once_cell", 541 + "rustversion", 542 + "wasm-bindgen-macro", 543 + "wasm-bindgen-shared", 544 + ] 545 + 546 + [[package]] 547 + name = "wasm-bindgen-macro" 548 + version = "0.2.106" 549 + source = "registry+https://github.com/rust-lang/crates.io-index" 550 + checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" 551 + dependencies = [ 552 + "quote", 553 + "wasm-bindgen-macro-support", 554 + ] 555 + 556 + [[package]] 557 + name = "wasm-bindgen-macro-support" 558 + version = "0.2.106" 559 + source = "registry+https://github.com/rust-lang/crates.io-index" 560 + checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" 561 + dependencies = [ 562 + "bumpalo", 563 + "proc-macro2", 564 + "quote", 565 + "syn", 566 + "wasm-bindgen-shared", 567 + ] 568 + 569 + [[package]] 570 + name = "wasm-bindgen-shared" 571 + version = "0.2.106" 572 + source = "registry+https://github.com/rust-lang/crates.io-index" 573 + checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 574 + dependencies = [ 575 + "unicode-ident", 576 + ] 577 + 578 + [[package]] 579 + name = "windows-core" 580 + version = "0.62.2" 581 + source = "registry+https://github.com/rust-lang/crates.io-index" 582 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 583 + dependencies = [ 584 + "windows-implement", 585 + "windows-interface", 586 + "windows-link", 587 + "windows-result", 588 + "windows-strings", 589 + ] 590 + 591 + [[package]] 592 + name = "windows-implement" 593 + version = "0.60.2" 594 + source = "registry+https://github.com/rust-lang/crates.io-index" 595 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 596 + dependencies = [ 597 + "proc-macro2", 598 + "quote", 599 + "syn", 600 + ] 601 + 602 + [[package]] 603 + name = "windows-interface" 604 + version = "0.59.3" 605 + source = "registry+https://github.com/rust-lang/crates.io-index" 606 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 607 + dependencies = [ 608 + "proc-macro2", 609 + "quote", 610 + "syn", 611 + ] 612 + 613 + [[package]] 614 + name = "windows-link" 615 + version = "0.2.1" 616 + source = "registry+https://github.com/rust-lang/crates.io-index" 617 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 618 + 619 + [[package]] 620 + name = "windows-result" 621 + version = "0.4.1" 622 + source = "registry+https://github.com/rust-lang/crates.io-index" 623 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 624 + dependencies = [ 625 + "windows-link", 626 + ] 627 + 628 + [[package]] 629 + name = "windows-strings" 630 + version = "0.5.1" 631 + source = "registry+https://github.com/rust-lang/crates.io-index" 632 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 633 + dependencies = [ 634 + "windows-link", 635 + ]
+22
Cargo.toml
··· 1 + [package] 2 + name = "pmgfal" 3 + version = "0.1.0" 4 + edition = "2021" 5 + license = "MIT" 6 + description = "pydantic model generator for atproto lexicons" 7 + 8 + [lib] 9 + name = "pmgfal" 10 + crate-type = ["cdylib", "rlib"] 11 + 12 + [dependencies] 13 + pyo3 = { version = "0.26", features = ["extension-module"] } 14 + atrium-lex = { git = "https://github.com/atrium-rs/atrium", branch = "main" } 15 + serde = { version = "1.0", features = ["derive"] } 16 + serde_json = "1.0" 17 + heck = "0.5" 18 + 19 + [profile.release] 20 + lto = true 21 + codegen-units = 1 22 + strip = true
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2024 zzstoatzz 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+49
README.md
··· 1 + # pmgfal 2 + 3 + pydantic model generator for atproto lexicons 4 + 5 + rust-powered lexicon parsing and python code generation. 6 + 7 + ## install 8 + 9 + ```bash 10 + uv add pmgfal 11 + ``` 12 + 13 + ## usage 14 + 15 + ```bash 16 + # auto-detect ./lexicons or current dir 17 + uvx pmgfal 18 + 19 + # explicit paths 20 + uvx pmgfal ./lexicons -o ./src/models 21 + 22 + # filter by namespace 23 + uvx pmgfal -p fm.plyr 24 + ``` 25 + 26 + ## output 27 + 28 + ```python 29 + # auto-generated by pmgfal - do not edit 30 + 31 + from __future__ import annotations 32 + 33 + from pydantic import BaseModel, Field 34 + 35 + 36 + class FmPlyrTrack(BaseModel): 37 + """fm.plyr.track record""" 38 + 39 + uri: str 40 + title: str 41 + artist: str 42 + duration_ms: int | None = Field(default=None, alias="durationMs") 43 + ``` 44 + 45 + ## how it works 46 + 47 + 1. parses lexicon json using [atrium-lex](https://github.com/atrium-rs/atrium) (rust) 48 + 2. generates pydantic v2 models 49 + 3. outputs standalone python - no atproto sdk dependency
+16
justfile
··· 1 + # build rust extension 2 + build: 3 + uv run maturin develop 4 + 5 + # run tests (requires build first) 6 + test: build 7 + uv run pytest 8 + 9 + # lint python 10 + lint: 11 + uv run ruff check 12 + uv run ruff format --check 13 + 14 + # format python 15 + fmt: 16 + uv run ruff format
+62
pyproject.toml
··· 1 + [project] 2 + name = "pmgfal" 3 + dynamic = ["version"] 4 + description = "pydantic model generator for atproto lexicons" 5 + readme = "README.md" 6 + authors = [{ name = "zzstoatzz", email = "thrast36@gmail.com" }] 7 + requires-python = ">=3.10" 8 + license = "MIT" 9 + 10 + keywords = ["atproto", "bluesky", "lexicon", "pydantic", "codegen"] 11 + 12 + classifiers = [ 13 + "Development Status :: 3 - Alpha", 14 + "Intended Audience :: Developers", 15 + "License :: OSI Approved :: MIT License", 16 + "Programming Language :: Python :: 3", 17 + "Programming Language :: Python :: 3.10", 18 + "Programming Language :: Python :: 3.11", 19 + "Programming Language :: Python :: 3.12", 20 + "Programming Language :: Python :: 3.13", 21 + "Programming Language :: Python :: 3.14", 22 + "Programming Language :: Rust", 23 + "Topic :: Software Development :: Code Generators", 24 + "Typing :: Typed", 25 + ] 26 + 27 + dependencies = [] 28 + 29 + [project.scripts] 30 + pmgfal = "pmgfal:main" 31 + 32 + [dependency-groups] 33 + dev = [ 34 + "maturin>=1.8.0", 35 + "pytest>=8.3.0", 36 + "pytest-sugar", 37 + "ruff>=0.12.0", 38 + "pydantic>=2.0.0", 39 + ] 40 + 41 + [build-system] 42 + requires = ["maturin>=1.8.0"] 43 + build-backend = "maturin" 44 + 45 + [tool.maturin] 46 + python-source = "python" 47 + module-name = "pmgfal._pmgfal" 48 + bindings = "pyo3" 49 + features = ["pyo3/extension-module"] 50 + 51 + [tool.pytest.ini_options] 52 + pythonpath = ["."] 53 + testpaths = ["tests"] 54 + 55 + [tool.ruff.lint] 56 + fixable = ["ALL"] 57 + ignore = ["COM812", "PLR0913", "SIM102", "SIM108"] 58 + extend-select = ["B", "C4", "I", "PIE", "RUF", "SIM", "UP"] 59 + 60 + [tool.ruff.lint.per-file-ignores] 61 + "__init__.py" = ["F401", "I001"] 62 + "tests/**/*.py" = ["S101"]
+78
python/pmgfal/__init__.py
··· 1 + """pydantic model generator for atproto lexicons.""" 2 + 3 + from __future__ import annotations 4 + 5 + import argparse 6 + import sys 7 + from pathlib import Path 8 + 9 + from pmgfal._pmgfal import __version__, generate 10 + 11 + __all__ = ["__version__", "generate", "main"] 12 + 13 + 14 + def main(args: list[str] | None = None) -> int: 15 + """cli entry point.""" 16 + parser = argparse.ArgumentParser( 17 + prog="pmgfal", 18 + description="pydantic model generator for atproto lexicons", 19 + ) 20 + parser.add_argument( 21 + "lexicon_dir", 22 + nargs="?", 23 + type=Path, 24 + help="directory containing lexicon json files (default: ./lexicons or .)", 25 + ) 26 + parser.add_argument( 27 + "-o", 28 + "--output", 29 + type=Path, 30 + default=Path("./generated"), 31 + help="output directory (default: ./generated)", 32 + ) 33 + parser.add_argument( 34 + "-p", 35 + "--prefix", 36 + type=str, 37 + default=None, 38 + help="namespace prefix filter (e.g. 'fm.plyr')", 39 + ) 40 + parser.add_argument( 41 + "-V", 42 + "--version", 43 + action="version", 44 + version=f"%(prog)s {__version__}", 45 + ) 46 + 47 + parsed = parser.parse_args(args) 48 + 49 + # auto-detect lexicon directory 50 + if parsed.lexicon_dir is None: 51 + if Path("./lexicons").is_dir(): 52 + lexicon_dir = Path("./lexicons") 53 + else: 54 + lexicon_dir = Path(".") 55 + else: 56 + lexicon_dir = parsed.lexicon_dir 57 + 58 + if not lexicon_dir.is_dir(): 59 + print(f"error: not a directory: {lexicon_dir}", file=sys.stderr) 60 + return 1 61 + 62 + try: 63 + files = generate( 64 + str(lexicon_dir), 65 + str(parsed.output), 66 + parsed.prefix, 67 + ) 68 + print(f"generated {len(files)} file(s):") 69 + for f in files: 70 + print(f" {f}") 71 + return 0 72 + except Exception as e: 73 + print(f"error: {e}", file=sys.stderr) 74 + return 1 75 + 76 + 77 + if __name__ == "__main__": 78 + sys.exit(main())
+19
python/pmgfal/_pmgfal.pyi
··· 1 + """type stubs for rust bindings.""" 2 + 3 + __version__: str 4 + 5 + def generate( 6 + lexicon_dir: str, 7 + output_dir: str, 8 + namespace_prefix: str | None = None, 9 + ) -> list[str]: 10 + """generate pydantic models from lexicon files. 11 + 12 + Args: 13 + lexicon_dir: directory containing lexicon json files 14 + output_dir: directory to write generated python files 15 + namespace_prefix: optional filter for specific nsid prefix 16 + 17 + Returns: 18 + list of generated file paths 19 + """
python/pmgfal/py.typed

This is a binary file and will not be displayed.

+241
src/lib.rs
··· 1 + use std::fs; 2 + use std::path::Path; 3 + 4 + use atrium_lex::lexicon::{ 5 + LexObject, LexObjectProperty, LexRecord, LexUserType, 6 + }; 7 + use atrium_lex::LexiconDoc; 8 + use heck::{ToPascalCase, ToSnakeCase}; 9 + use pyo3::prelude::*; 10 + 11 + const HEADER: &str = r#"# auto-generated by pmgfal - do not edit 12 + 13 + from __future__ import annotations 14 + 15 + from typing import Any 16 + 17 + from pydantic import BaseModel, Field 18 + "#; 19 + 20 + /// convert lexicon type to python type annotation 21 + fn type_to_python(prop: &LexObjectProperty) -> String { 22 + match prop { 23 + LexObjectProperty::Boolean(_) => "bool".to_string(), 24 + LexObjectProperty::Integer(_) => "int".to_string(), 25 + LexObjectProperty::String(_) => "str".to_string(), 26 + LexObjectProperty::Bytes(_) => "bytes".to_string(), 27 + LexObjectProperty::CidLink(_) => "str".to_string(), 28 + LexObjectProperty::Blob(_) => "dict[str, Any]".to_string(), 29 + LexObjectProperty::Array(arr) => { 30 + let item_type = match &arr.items { 31 + atrium_lex::lexicon::LexArrayItem::Boolean(_) => "bool", 32 + atrium_lex::lexicon::LexArrayItem::Integer(_) => "int", 33 + atrium_lex::lexicon::LexArrayItem::String(_) => "str", 34 + atrium_lex::lexicon::LexArrayItem::Bytes(_) => "bytes", 35 + atrium_lex::lexicon::LexArrayItem::CidLink(_) => "str", 36 + atrium_lex::lexicon::LexArrayItem::Blob(_) => "dict[str, Any]", 37 + atrium_lex::lexicon::LexArrayItem::Ref(_) => "dict[str, Any]", 38 + atrium_lex::lexicon::LexArrayItem::Union(_) => "dict[str, Any]", 39 + atrium_lex::lexicon::LexArrayItem::Unknown(_) => "Any", 40 + }; 41 + format!("list[{}]", item_type) 42 + } 43 + LexObjectProperty::Ref(_) => "dict[str, Any]".to_string(), 44 + LexObjectProperty::Union(_) => "dict[str, Any]".to_string(), 45 + LexObjectProperty::Unknown(_) => "Any".to_string(), 46 + } 47 + } 48 + 49 + /// generate class name from nsid and def name 50 + fn to_class_name(nsid: &str, def_name: &str) -> String { 51 + let mut parts: Vec<&str> = nsid.split('.').collect(); 52 + if def_name != "main" { 53 + parts.push(def_name); 54 + } 55 + parts.iter().map(|p| p.to_pascal_case()).collect() 56 + } 57 + 58 + /// generate python field name 59 + fn to_field_name(name: &str) -> String { 60 + let snake = name.to_snake_case(); 61 + // handle python keywords 62 + match snake.as_str() { 63 + "type" | "class" | "import" | "from" | "global" | "lambda" => format!("{}_", snake), 64 + _ => snake, 65 + } 66 + } 67 + 68 + /// generate a pydantic model class from an object definition 69 + fn generate_object_class( 70 + class_name: &str, 71 + obj: &LexObject, 72 + description: Option<&str>, 73 + ) -> String { 74 + let mut lines = vec![format!("class {}(BaseModel):", class_name)]; 75 + 76 + if let Some(desc) = description { 77 + lines.push(format!(" \"\"\"{}\"\"\"", desc)); 78 + } 79 + 80 + if obj.properties.is_empty() { 81 + lines.push(" pass".to_string()); 82 + return lines.join("\n"); 83 + } 84 + 85 + let required: std::collections::HashSet<_> = obj 86 + .required 87 + .as_ref() 88 + .map(|r| r.iter().collect()) 89 + .unwrap_or_default(); 90 + 91 + for (name, prop) in &obj.properties { 92 + let field_name = to_field_name(name); 93 + let mut py_type = type_to_python(prop); 94 + 95 + let is_required = required.contains(name); 96 + if !is_required { 97 + py_type = format!("{} | None", py_type); 98 + } 99 + 100 + let needs_alias = field_name != *name; 101 + let needs_default = !is_required; 102 + 103 + if needs_alias || needs_default { 104 + let mut field_args = vec![]; 105 + if needs_default { 106 + field_args.push("default=None".to_string()); 107 + } 108 + if needs_alias { 109 + field_args.push(format!("alias=\"{}\"", name)); 110 + } 111 + lines.push(format!( 112 + " {}: {} = Field({})", 113 + field_name, 114 + py_type, 115 + field_args.join(", ") 116 + )); 117 + } else { 118 + lines.push(format!(" {}: {}", field_name, py_type)); 119 + } 120 + } 121 + 122 + lines.join("\n") 123 + } 124 + 125 + /// parse lexicon files from a directory 126 + fn parse_lexicons(dir: &Path) -> Result<Vec<LexiconDoc>, String> { 127 + let mut docs = vec![]; 128 + 129 + fn visit_dir(dir: &Path, docs: &mut Vec<LexiconDoc>) -> Result<(), String> { 130 + if !dir.is_dir() { 131 + return Err(format!("not a directory: {}", dir.display())); 132 + } 133 + 134 + for entry in fs::read_dir(dir).map_err(|e| e.to_string())? { 135 + let entry = entry.map_err(|e| e.to_string())?; 136 + let path = entry.path(); 137 + 138 + if path.is_dir() { 139 + visit_dir(&path, docs)?; 140 + } else if path.extension().map(|e| e == "json").unwrap_or(false) { 141 + let content = fs::read_to_string(&path).map_err(|e| e.to_string())?; 142 + match serde_json::from_str::<LexiconDoc>(&content) { 143 + Ok(doc) => docs.push(doc), 144 + Err(_) => continue, // skip non-lexicon json 145 + } 146 + } 147 + } 148 + Ok(()) 149 + } 150 + 151 + visit_dir(dir, &mut docs)?; 152 + docs.sort_by(|a, b| a.id.cmp(&b.id)); 153 + Ok(docs) 154 + } 155 + 156 + /// generate pydantic models from lexicon files 157 + #[pyfunction] 158 + #[pyo3(signature = (lexicon_dir, output_dir, namespace_prefix=None))] 159 + fn generate( 160 + lexicon_dir: &str, 161 + output_dir: &str, 162 + namespace_prefix: Option<&str>, 163 + ) -> PyResult<Vec<String>> { 164 + let lexicon_path = Path::new(lexicon_dir); 165 + let output_path = Path::new(output_dir); 166 + 167 + let docs = parse_lexicons(lexicon_path) 168 + .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?; 169 + 170 + let filtered: Vec<_> = docs 171 + .iter() 172 + .filter(|doc| { 173 + namespace_prefix 174 + .map(|p| doc.id.starts_with(p)) 175 + .unwrap_or(true) 176 + }) 177 + .collect(); 178 + 179 + if filtered.is_empty() { 180 + return Ok(vec![]); 181 + } 182 + 183 + fs::create_dir_all(output_path) 184 + .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?; 185 + 186 + let mut output = String::from(HEADER); 187 + output.push('\n'); 188 + 189 + for doc in &filtered { 190 + output.push_str(&format!("\n# {}\n", doc.id)); 191 + 192 + for (def_name, def) in &doc.defs { 193 + let class_name = to_class_name(&doc.id, def_name); 194 + 195 + match def { 196 + LexUserType::Record(LexRecord { record, description, .. }) => { 197 + let atrium_lex::lexicon::LexRecordRecord::Object(obj) = record; 198 + let desc = description.as_deref().unwrap_or(&doc.id); 199 + output.push_str(&generate_object_class(&class_name, obj, Some(desc))); 200 + output.push_str("\n\n"); 201 + } 202 + LexUserType::Object(obj) => { 203 + output.push_str(&generate_object_class( 204 + &class_name, 205 + obj, 206 + obj.description.as_deref(), 207 + )); 208 + output.push_str("\n\n"); 209 + } 210 + LexUserType::Token(_) => { 211 + output.push_str(&format!( 212 + "# token: {}\n{} = \"{}#{}\"\n\n", 213 + class_name, 214 + class_name.to_uppercase(), 215 + doc.id, 216 + def_name 217 + )); 218 + } 219 + _ => {} 220 + } 221 + } 222 + } 223 + 224 + let output_file = if let Some(prefix) = namespace_prefix { 225 + output_path.join(format!("{}.py", prefix.replace('.', "_"))) 226 + } else { 227 + output_path.join("models.py") 228 + }; 229 + 230 + fs::write(&output_file, &output) 231 + .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?; 232 + 233 + Ok(vec![output_file.to_string_lossy().to_string()]) 234 + } 235 + 236 + #[pymodule] 237 + fn _pmgfal(m: &Bound<'_, PyModule>) -> PyResult<()> { 238 + m.add_function(wrap_pyfunction!(generate, m)?)?; 239 + m.add("__version__", env!("CARGO_PKG_VERSION"))?; 240 + Ok(()) 241 + }
+1
tests/__init__.py
··· 1 + """black-box tests for pmgfal."""
+161
tests/test_generate.py
··· 1 + """black-box tests for model generation.""" 2 + 3 + import json 4 + import tempfile 5 + from pathlib import Path 6 + 7 + 8 + class TestGenerate: 9 + """test the generate function end-to-end.""" 10 + 11 + def test_generate_simple_record(self): 12 + """generate a pydantic model from a simple record lexicon.""" 13 + from pmgfal import generate 14 + 15 + lexicon = { 16 + "lexicon": 1, 17 + "id": "fm.plyr.track", 18 + "description": "a music track", 19 + "defs": { 20 + "main": { 21 + "type": "record", 22 + "description": "track record", 23 + "record": { 24 + "type": "object", 25 + "properties": { 26 + "uri": {"type": "string"}, 27 + "title": {"type": "string"}, 28 + "artist": {"type": "string"}, 29 + "durationMs": {"type": "integer"}, 30 + }, 31 + "required": ["uri", "title", "artist"], 32 + }, 33 + } 34 + }, 35 + } 36 + 37 + with tempfile.TemporaryDirectory() as tmpdir: 38 + lexicon_dir = Path(tmpdir) / "lexicons" 39 + lexicon_dir.mkdir() 40 + (lexicon_dir / "track.json").write_text(json.dumps(lexicon)) 41 + 42 + output_dir = Path(tmpdir) / "generated" 43 + files = generate(str(lexicon_dir), str(output_dir)) 44 + 45 + assert len(files) == 1 46 + content = Path(files[0]).read_text() 47 + 48 + # verify header 49 + assert "auto-generated by pmgfal" in content 50 + 51 + # verify class exists 52 + assert "class FmPlyrTrack(BaseModel):" in content 53 + 54 + # verify required fields 55 + assert "uri: str" in content 56 + assert "title: str" in content 57 + assert "artist: str" in content 58 + 59 + # verify optional field with alias 60 + assert "duration_ms" in content 61 + assert "durationMs" in content 62 + 63 + def test_generate_with_namespace_filter(self): 64 + """filter by namespace prefix.""" 65 + from pmgfal import generate 66 + 67 + lexicons = [ 68 + { 69 + "lexicon": 1, 70 + "id": "fm.plyr.track", 71 + "defs": { 72 + "main": { 73 + "type": "record", 74 + "record": {"type": "object", "properties": {}}, 75 + } 76 + }, 77 + }, 78 + { 79 + "lexicon": 1, 80 + "id": "com.other.thing", 81 + "defs": { 82 + "main": { 83 + "type": "record", 84 + "record": {"type": "object", "properties": {}}, 85 + } 86 + }, 87 + }, 88 + ] 89 + 90 + with tempfile.TemporaryDirectory() as tmpdir: 91 + lexicon_dir = Path(tmpdir) / "lexicons" 92 + lexicon_dir.mkdir() 93 + 94 + for lex in lexicons: 95 + name = lex["id"].replace(".", "_") 96 + (lexicon_dir / f"{name}.json").write_text(json.dumps(lex)) 97 + 98 + output_dir = Path(tmpdir) / "generated" 99 + files = generate(str(lexicon_dir), str(output_dir), "fm.plyr") 100 + 101 + assert len(files) == 1 102 + content = Path(files[0]).read_text() 103 + assert "FmPlyrTrack" in content 104 + assert "ComOtherThing" not in content 105 + 106 + def test_generate_empty_dir(self): 107 + """empty directory returns empty list.""" 108 + from pmgfal import generate 109 + 110 + with tempfile.TemporaryDirectory() as tmpdir: 111 + lexicon_dir = Path(tmpdir) / "lexicons" 112 + lexicon_dir.mkdir() 113 + output_dir = Path(tmpdir) / "generated" 114 + 115 + files = generate(str(lexicon_dir), str(output_dir)) 116 + assert files == [] 117 + 118 + def test_generated_models_are_valid_pydantic(self): 119 + """generated models should be importable and work with pydantic.""" 120 + from pmgfal import generate 121 + 122 + lexicon = { 123 + "lexicon": 1, 124 + "id": "test.example", 125 + "defs": { 126 + "main": { 127 + "type": "record", 128 + "record": { 129 + "type": "object", 130 + "properties": { 131 + "name": {"type": "string"}, 132 + "count": {"type": "integer"}, 133 + }, 134 + "required": ["name"], 135 + }, 136 + } 137 + }, 138 + } 139 + 140 + with tempfile.TemporaryDirectory() as tmpdir: 141 + lexicon_dir = Path(tmpdir) / "lexicons" 142 + lexicon_dir.mkdir() 143 + (lexicon_dir / "example.json").write_text(json.dumps(lexicon)) 144 + 145 + output_dir = Path(tmpdir) / "generated" 146 + files = generate(str(lexicon_dir), str(output_dir)) 147 + 148 + # exec the generated code 149 + content = Path(files[0]).read_text() 150 + namespace = {} 151 + exec(content, namespace) 152 + 153 + # verify class exists and works 154 + TestExample = namespace["TestExample"] 155 + instance = TestExample(name="test", count=42) 156 + assert instance.name == "test" 157 + assert instance.count == 42 158 + 159 + # verify optional field default 160 + instance2 = TestExample(name="test") 161 + assert instance2.count is None
+386
uv.lock
··· 1 + version = 1 2 + revision = 3 3 + requires-python = ">=3.10" 4 + 5 + [[package]] 6 + name = "annotated-types" 7 + version = "0.7.0" 8 + source = { registry = "https://pypi.org/simple" } 9 + sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } 10 + wheels = [ 11 + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, 12 + ] 13 + 14 + [[package]] 15 + name = "colorama" 16 + version = "0.4.6" 17 + source = { registry = "https://pypi.org/simple" } 18 + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 19 + wheels = [ 20 + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 21 + ] 22 + 23 + [[package]] 24 + name = "exceptiongroup" 25 + version = "1.3.1" 26 + source = { registry = "https://pypi.org/simple" } 27 + dependencies = [ 28 + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 29 + ] 30 + sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } 31 + wheels = [ 32 + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, 33 + ] 34 + 35 + [[package]] 36 + name = "iniconfig" 37 + version = "2.3.0" 38 + source = { registry = "https://pypi.org/simple" } 39 + sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } 40 + wheels = [ 41 + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, 42 + ] 43 + 44 + [[package]] 45 + name = "maturin" 46 + version = "1.10.2" 47 + source = { registry = "https://pypi.org/simple" } 48 + dependencies = [ 49 + { name = "tomli", marker = "python_full_version < '3.11'" }, 50 + ] 51 + sdist = { url = "https://files.pythonhosted.org/packages/02/44/c593afce7d418ae6016b955c978055232359ad28c707a9ac6643fc60512d/maturin-1.10.2.tar.gz", hash = "sha256:259292563da89850bf8f7d37aa4ddba22905214c1e180b1c8f55505dfd8c0e81", size = 217835, upload-time = "2025-11-19T11:53:17.348Z" } 52 + wheels = [ 53 + { url = "https://files.pythonhosted.org/packages/15/74/7f7e93019bb71aa072a7cdf951cbe4c9a8d5870dd86c66ec67002153487f/maturin-1.10.2-py3-none-linux_armv6l.whl", hash = "sha256:11c73815f21a755d2129c410e6cb19dbfacbc0155bfc46c706b69930c2eb794b", size = 8763201, upload-time = "2025-11-19T11:52:42.98Z" }, 54 + { url = "https://files.pythonhosted.org/packages/4a/85/1d1b64dbb6518ee633bfde8787e251ae59428818fea7a6bdacb8008a09bd/maturin-1.10.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7fbd997c5347649ee7987bd05a92bd5b8b07efa4ac3f8bcbf6196e07eb573d89", size = 17072583, upload-time = "2025-11-19T11:52:45.636Z" }, 55 + { url = "https://files.pythonhosted.org/packages/7c/45/2418f0d6e1cbdf890205d1dc73ebea6778bb9ce80f92e866576c701ded72/maturin-1.10.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3ce9b2ad4fb9c341f450a6d32dc3edb409a2d582a81bc46ba55f6e3b6196b22", size = 8827021, upload-time = "2025-11-19T11:52:48.143Z" }, 56 + { url = "https://files.pythonhosted.org/packages/7f/83/14c96ddc93b38745d8c3b85126f7d78a94f809a49dc9644bb22b0dc7b78c/maturin-1.10.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:f0d1b7b5f73c8d30a7e71cd2a2189a7f0126a3a3cd8b3d6843e7e1d4db50f759", size = 8751780, upload-time = "2025-11-19T11:52:51.613Z" }, 57 + { url = "https://files.pythonhosted.org/packages/46/8d/753148c0d0472acd31a297f6d11c3263cd2668d38278ed29d523625f7290/maturin-1.10.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:efcd496a3202ffe0d0489df1f83d08b91399782fb2dd545d5a1e7bf6fd81af39", size = 9241884, upload-time = "2025-11-19T11:52:53.946Z" }, 58 + { url = "https://files.pythonhosted.org/packages/b9/f9/f5ca9fe8cad70cac6f3b6008598cc708f8a74dd619baced99784a6253f23/maturin-1.10.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a41ec70d99e27c05377be90f8e3c3def2a7bae4d0d9d5ea874aaf2d1da625d5c", size = 8671736, upload-time = "2025-11-19T11:52:57.133Z" }, 59 + { url = "https://files.pythonhosted.org/packages/0a/76/f59cbcfcabef0259c3971f8b5754c85276a272028d8363386b03ec4e9947/maturin-1.10.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:07a82864352feeaf2167247c8206937ef6c6ae9533025d416b7004ade0ea601d", size = 8633475, upload-time = "2025-11-19T11:53:00.389Z" }, 60 + { url = "https://files.pythonhosted.org/packages/53/40/96cd959ad1dda6c12301860a74afece200a3209d84b393beedd5d7d915c0/maturin-1.10.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:04df81ee295dcda37828bd025a4ac688ea856e3946e4cb300a8f44a448de0069", size = 11177118, upload-time = "2025-11-19T11:53:03.014Z" }, 61 + { url = "https://files.pythonhosted.org/packages/e5/b6/144f180f36314be183f5237011528f0e39fe5fd2e74e65c3b44a5795971e/maturin-1.10.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96e1d391e4c1fa87edf2a37e4d53d5f2e5f39dd880b9d8306ac9f8eb212d23f8", size = 9320218, upload-time = "2025-11-19T11:53:05.39Z" }, 62 + { url = "https://files.pythonhosted.org/packages/eb/2d/2c483c1b3118e2e10fd8219d5291843f5f7c12284113251bf506144a3ac1/maturin-1.10.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a217aa7c42aa332fb8e8377eb07314e1f02cf0fe036f614aca4575121952addd", size = 8985266, upload-time = "2025-11-19T11:53:07.618Z" }, 63 + { url = "https://files.pythonhosted.org/packages/1d/98/1d0222521e112cd058b56e8d96c72cf9615f799e3b557adb4b16004f42aa/maturin-1.10.2-py3-none-win32.whl", hash = "sha256:da031771d9fb6ddb1d373638ec2556feee29e4507365cd5749a2d354bcadd818", size = 7667897, upload-time = "2025-11-19T11:53:10.14Z" }, 64 + { url = "https://files.pythonhosted.org/packages/a0/ec/c6c973b1def0d04533620b439d5d7aebb257657ba66710885394514c8045/maturin-1.10.2-py3-none-win_amd64.whl", hash = "sha256:da777766fd584440dc9fecd30059a94f85e4983f58b09e438ae38ee4b494024c", size = 8908416, upload-time = "2025-11-19T11:53:12.862Z" }, 65 + { url = "https://files.pythonhosted.org/packages/1b/01/7da60c9f7d5dc92dfa5e8888239fd0fb2613ee19e44e6db5c2ed5595fab3/maturin-1.10.2-py3-none-win_arm64.whl", hash = "sha256:a4c29a770ea2c76082e0afc6d4efd8ee94405588bfae00d10828f72e206c739b", size = 7506680, upload-time = "2025-11-19T11:53:15.403Z" }, 66 + ] 67 + 68 + [[package]] 69 + name = "packaging" 70 + version = "25.0" 71 + source = { registry = "https://pypi.org/simple" } 72 + sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 73 + wheels = [ 74 + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 75 + ] 76 + 77 + [[package]] 78 + name = "pluggy" 79 + version = "1.6.0" 80 + source = { registry = "https://pypi.org/simple" } 81 + sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } 82 + wheels = [ 83 + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, 84 + ] 85 + 86 + [[package]] 87 + name = "pmgfal" 88 + source = { editable = "." } 89 + 90 + [package.dev-dependencies] 91 + dev = [ 92 + { name = "maturin" }, 93 + { name = "pydantic" }, 94 + { name = "pytest" }, 95 + { name = "pytest-sugar" }, 96 + { name = "ruff" }, 97 + ] 98 + 99 + [package.metadata] 100 + 101 + [package.metadata.requires-dev] 102 + dev = [ 103 + { name = "maturin", specifier = ">=1.8.0" }, 104 + { name = "pydantic", specifier = ">=2.0.0" }, 105 + { name = "pytest", specifier = ">=8.3.0" }, 106 + { name = "pytest-sugar" }, 107 + { name = "ruff", specifier = ">=0.12.0" }, 108 + ] 109 + 110 + [[package]] 111 + name = "pydantic" 112 + version = "2.12.5" 113 + source = { registry = "https://pypi.org/simple" } 114 + dependencies = [ 115 + { name = "annotated-types" }, 116 + { name = "pydantic-core" }, 117 + { name = "typing-extensions" }, 118 + { name = "typing-inspection" }, 119 + ] 120 + sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } 121 + wheels = [ 122 + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, 123 + ] 124 + 125 + [[package]] 126 + name = "pydantic-core" 127 + version = "2.41.5" 128 + source = { registry = "https://pypi.org/simple" } 129 + dependencies = [ 130 + { name = "typing-extensions" }, 131 + ] 132 + sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } 133 + wheels = [ 134 + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, 135 + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, 136 + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, 137 + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, 138 + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, 139 + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, 140 + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, 141 + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, 142 + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, 143 + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, 144 + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, 145 + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, 146 + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, 147 + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, 148 + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, 149 + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, 150 + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, 151 + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, 152 + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, 153 + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, 154 + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, 155 + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, 156 + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, 157 + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, 158 + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, 159 + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, 160 + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, 161 + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, 162 + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, 163 + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, 164 + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, 165 + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, 166 + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, 167 + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, 168 + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, 169 + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, 170 + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, 171 + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, 172 + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, 173 + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, 174 + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, 175 + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, 176 + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, 177 + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, 178 + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, 179 + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, 180 + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, 181 + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, 182 + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, 183 + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, 184 + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, 185 + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, 186 + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, 187 + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, 188 + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, 189 + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, 190 + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, 191 + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, 192 + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, 193 + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, 194 + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, 195 + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, 196 + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, 197 + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, 198 + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, 199 + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, 200 + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, 201 + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, 202 + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, 203 + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, 204 + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, 205 + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, 206 + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, 207 + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, 208 + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, 209 + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, 210 + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, 211 + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, 212 + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, 213 + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, 214 + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, 215 + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, 216 + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, 217 + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, 218 + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, 219 + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, 220 + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, 221 + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, 222 + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, 223 + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, 224 + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, 225 + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, 226 + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, 227 + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, 228 + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, 229 + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, 230 + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, 231 + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, 232 + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, 233 + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, 234 + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, 235 + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, 236 + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, 237 + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, 238 + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, 239 + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, 240 + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, 241 + ] 242 + 243 + [[package]] 244 + name = "pygments" 245 + version = "2.19.2" 246 + source = { registry = "https://pypi.org/simple" } 247 + sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } 248 + wheels = [ 249 + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, 250 + ] 251 + 252 + [[package]] 253 + name = "pytest" 254 + version = "9.0.2" 255 + source = { registry = "https://pypi.org/simple" } 256 + dependencies = [ 257 + { name = "colorama", marker = "sys_platform == 'win32'" }, 258 + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 259 + { name = "iniconfig" }, 260 + { name = "packaging" }, 261 + { name = "pluggy" }, 262 + { name = "pygments" }, 263 + { name = "tomli", marker = "python_full_version < '3.11'" }, 264 + ] 265 + sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } 266 + wheels = [ 267 + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, 268 + ] 269 + 270 + [[package]] 271 + name = "pytest-sugar" 272 + version = "1.1.1" 273 + source = { registry = "https://pypi.org/simple" } 274 + dependencies = [ 275 + { name = "pytest" }, 276 + { name = "termcolor" }, 277 + ] 278 + sdist = { url = "https://files.pythonhosted.org/packages/0b/4e/60fed105549297ba1a700e1ea7b828044842ea27d72c898990510b79b0e2/pytest-sugar-1.1.1.tar.gz", hash = "sha256:73b8b65163ebf10f9f671efab9eed3d56f20d2ca68bda83fa64740a92c08f65d", size = 16533, upload-time = "2025-08-23T12:19:35.737Z" } 279 + wheels = [ 280 + { url = "https://files.pythonhosted.org/packages/87/d5/81d38a91c1fdafb6711f053f5a9b92ff788013b19821257c2c38c1e132df/pytest_sugar-1.1.1-py3-none-any.whl", hash = "sha256:2f8319b907548d5b9d03a171515c1d43d2e38e32bd8182a1781eb20b43344cc8", size = 11440, upload-time = "2025-08-23T12:19:34.894Z" }, 281 + ] 282 + 283 + [[package]] 284 + name = "ruff" 285 + version = "0.14.8" 286 + source = { registry = "https://pypi.org/simple" } 287 + sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } 288 + wheels = [ 289 + { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, 290 + { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, 291 + { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, 292 + { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, 293 + { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, 294 + { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, 295 + { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, 296 + { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, 297 + { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, 298 + { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, 299 + { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, 300 + { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, 301 + { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, 302 + { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, 303 + { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, 304 + { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, 305 + { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, 306 + { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, 307 + ] 308 + 309 + [[package]] 310 + name = "termcolor" 311 + version = "3.2.0" 312 + source = { registry = "https://pypi.org/simple" } 313 + sdist = { url = "https://files.pythonhosted.org/packages/87/56/ab275c2b56a5e2342568838f0d5e3e66a32354adcc159b495e374cda43f5/termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58", size = 14423, upload-time = "2025-10-25T19:11:42.586Z" } 314 + wheels = [ 315 + { url = "https://files.pythonhosted.org/packages/f9/d5/141f53d7c1eb2a80e6d3e9a390228c3222c27705cbe7f048d3623053f3ca/termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6", size = 7698, upload-time = "2025-10-25T19:11:41.536Z" }, 316 + ] 317 + 318 + [[package]] 319 + name = "tomli" 320 + version = "2.3.0" 321 + source = { registry = "https://pypi.org/simple" } 322 + sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } 323 + wheels = [ 324 + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, 325 + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, 326 + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, 327 + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, 328 + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, 329 + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, 330 + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, 331 + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, 332 + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, 333 + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, 334 + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, 335 + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, 336 + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, 337 + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, 338 + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, 339 + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, 340 + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, 341 + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, 342 + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, 343 + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, 344 + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, 345 + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, 346 + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, 347 + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, 348 + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, 349 + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, 350 + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, 351 + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, 352 + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, 353 + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, 354 + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, 355 + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, 356 + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, 357 + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, 358 + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, 359 + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, 360 + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, 361 + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, 362 + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, 363 + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, 364 + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, 365 + ] 366 + 367 + [[package]] 368 + name = "typing-extensions" 369 + version = "4.15.0" 370 + source = { registry = "https://pypi.org/simple" } 371 + 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" } 372 + wheels = [ 373 + { 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" }, 374 + ] 375 + 376 + [[package]] 377 + name = "typing-inspection" 378 + version = "0.4.2" 379 + source = { registry = "https://pypi.org/simple" } 380 + dependencies = [ 381 + { name = "typing-extensions" }, 382 + ] 383 + 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" } 384 + wheels = [ 385 + { 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" }, 386 + ]