diff --git a/.claude/notes/rt_vars_lift_plan.md b/.claude/notes/rt_vars_lift_plan.md new file mode 100644 index 000000000..afa54c1aa --- /dev/null +++ b/.claude/notes/rt_vars_lift_plan.md @@ -0,0 +1,125 @@ +# `RuntimeVars` env-var lift — design plan + +Status: **draft, awaiting user edits** + +## Goal + +Consolidate the sprawl of pytest CLI flags + ad-hoc env vars + +hardcoded fixture defaults into a *single* env-var-encoded +runtime-vars envelope, with a typed in-memory representation +(`tractor.runtime._state.RuntimeVars`) as the sole source of +truth. + +## Why now + +- `--tpt-proto`, `--spawn-backend`, `--diag-on-hang`, + `--diag-capture-delay` and (soon) `TRACTOR_REG_ADDR` etc. are + proliferating. Each adds a parsing seam. +- `tests/devx/test_debugger.py` invokes example scripts as + separate subprocesses; they currently can't see the + fixture-allocated `reg_addr` at all (root cause of why + parametrizing devx scripts on `reg_addr` is on your TODO). +- Concurrent pytest sessions on the same host collide on + shared defaults (the `registry@1616` race we just fixed is + one symptom; per-session unique addr is the structural + fix). +- `tractor.runtime._state.RuntimeVars: Struct` is already + defined and **unused** — its docstring even says it + "should be utilized as possible for future calls." + +## Design + +### Module: `tractor/_testing/_rtvars.py` + +Lifted from `modden.runtime.env`, ~50 LOC, no new deps. + +```python +_TRACTOR_RT_VARS_OSENV: str = '_TRACTOR_RT_VARS' + +def dump_rtvars(rtvars: RuntimeVars|dict) -> tuple[str, str]: + '''str-serialize via `str(dict)` — ast.literal_eval-able''' + +def load_rtvars(env: dict) -> RuntimeVars: + '''ast.literal_eval the env-var value, hydrate to struct''' + +def get_rtvars(proc: psutil.Process|None = None) -> RuntimeVars: + '''read the var from a target proc's env (or current)''' + +def update_rtvars( + rtvars: RuntimeVars|dict|None = None, + update_osenv: bool|dict = True, +) -> tuple[str, str]: + '''mutate + re-encode + (optionally) write to os.environ''' +``` + +### Encoding choice: `str(dict)` + `ast.literal_eval` + +Pros: +- stdlib only +- handles all the types tractor's tests need: `str`, `int`, + `float`, `bool`, `None`, `list`, `tuple`, `dict` +- human-readable in the env (greppable, inspectable via + `cat /proc//environ | tr '\0' '\n'`) + +Cons: +- non-stdlib types (msgspec Structs, `Path`, custom classes) + must be lowered first — fine for the test fixture set +- not stable across Python versions for esoteric repr cases + (we don't hit any) + +Alternatives considered: +- **msgpack**: adds a dep + binary form is ungreppable +- **json**: doesn't preserve tuples (becomes lists), which is + a common type for `reg_addr` +- **toml/yaml**: heavier deps, no real benefit + +### `RuntimeVars` becomes the single source of truth + +The legacy `_runtime_vars: dict[str, Any]` global in +`runtime/_state.py` becomes a *cached view* of a +`RuntimeVars` singleton instance: + +- `get_runtime_vars()` returns either the struct or a + `.to_dict()` view depending on caller's preference +- `set_runtime_vars(...)` validates against the struct schema +- spawn-time SpawnSpec sends the struct (already does + conceptually — just gets typed) +- `__setattr__` `breakpoint()` debug instrumentation gets + removed (unrelated cleanup, mentioned in conversation) + +### Migration path + +**Phase 0** *(prep)*: strip the stray `breakpoint()` from +`RuntimeVars.__setattr__`. + +**Phase 1**: land `_rtvars.py` as a leaf module, used only by +test infra. Subprocess-spawned scripts in `tests/devx/` +read `_TRACTOR_RT_VARS` on startup → reconstruct +`RuntimeVars` → call `tractor.open_root_actor(**rtvars.as_kwargs())`. +Concurrent runs become deterministic-isolated because each +session writes a unique `_registry_addrs` into the env. + +**Phase 2**: migrate runtime callers (`_state.get_runtime_vars`, +spawn `SpawnSpec`, `Actor.async_main`) to operate on the +struct directly, with the dict as a compat view that gets +deprecated. + +**Phase 3** *(structural)*: per-session bindspace subdir +`/run/user//tractor//` — encoded in the +rt-vars envelope, picked up by every subactor automatically. +Obsoletes the entire bindspace-leak warning class. + +## Open design questions (user input wanted) + +- (placeholder for your edits) +- (placeholder) +- (placeholder) + +## Out-of-scope for this lift + +- Anything in `modden.runtime.env` related to `Spawn`, + `WmCtl`, `Wks` — that's a workspace orchestration layer, + not an env-var helper. We only lift the four utility + functions + the var name constant. +- Switching to msgpack/json — explicitly chosen against + above. diff --git a/.claude/settings.local.json b/.claude/settings.local.json index abb6bc300..415594e99 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,8 +1,16 @@ { "permissions": { "allow": [ - "Bash(date *)", "Bash(cp .claude/*)", + "Read(.claude/**)", + "Read(.claude/skills/run-tests/**)", + "Write(.claude/**/*commit_msg*)", + "Write(.claude/git_commit_msg_LATEST.md)", + "Skill(run-tests)", + "Skill(close-wkt)", + "Skill(open-wkt)", + "Skill(prompt-io)", + "Bash(date *)", "Bash(git diff *)", "Bash(git log *)", "Bash(git status)", @@ -23,14 +31,12 @@ "Bash(UV_PROJECT_ENVIRONMENT=py* uv sync:*)", "Bash(UV_PROJECT_ENVIRONMENT=py* uv run:*)", "Bash(echo EXIT:$?:*)", - "Write(.claude/*commit_msg*)", - "Write(.claude/git_commit_msg_LATEST.md)", - "Skill(run-tests)", - "Skill(close-wkt)", - "Skill(open-wkt)", - "Skill(prompt-io)" + "Bash(echo \"EXIT=$?\")", + "Read(/tmp/**)" ], "deny": [], "ask": [] - } + }, + "prefersReducedMotion": false, + "outputStyle": "default" } diff --git a/.claude/skills/conc-anal/SKILL.md b/.claude/skills/conc-anal/SKILL.md index 4f498b7c3..fa121bb25 100644 --- a/.claude/skills/conc-anal/SKILL.md +++ b/.claude/skills/conc-anal/SKILL.md @@ -229,3 +229,69 @@ Unlike asyncio, trio allows checkpoints in that does `await` can itself be cancelled (e.g. by nursery shutdown). Watch for cleanup code that assumes it will run to completion. + +### Unbounded waits in cleanup paths + +Any `await .wait()` in a teardown path is +a latent deadlock unless the event's setter is +GUARANTEED to fire. If the setter depends on +external state (peer disconnects, child process +exit, subsequent task completion) that itself +depends on the current task's progress, you have +a mutual wait. + +Rule: **bound every `await X.wait()` in cleanup +paths with `trio.move_on_after()`** unless you +can prove the setter is unconditionally reachable +from the state at the await site. Concrete recent +example: `ipc_server.wait_for_no_more_peers()` in +`async_main`'s finally (see +`ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md` +"probe iteration 3") — it was unbounded, and when +one peer-handler was stuck the wait-for-no-more- +peers event never fired, deadlocking the whole +actor-tree teardown cascade. + +### The capture-pipe-fill hang pattern (grep this first) + +When investigating any hang in the test suite +**especially under fork-based backends**, first +check whether the hang reproduces under `pytest +-s` (`--capture=no`). If `-s` makes it go away +you're not looking at a trio concurrency bug — +you're looking at a Linux pipe-buffer fill. + +Mechanism: pytest replaces fds 1,2 with pipe +write-ends. Fork-child subactors inherit those +fds. High-volume error-log tracebacks (cancel +cascade spew) fill the 64KB pipe buffer. Child +`write()` blocks. Child can't exit. Parent's +`waitpid`/pidfd wait blocks. Deadlock cascades up +the tree. + +Pre-existing guards in `tests/conftest.py` encode +this knowledge — grep these BEFORE blaming +concurrency: + +```python +# tests/conftest.py:258 +if loglevel in ('trace', 'debug'): + # XXX: too much logging will lock up the subproc (smh) + loglevel: str = 'info' + +# tests/conftest.py:316 +# can lock up on the `_io.BufferedReader` and hang.. +stderr: str = proc.stderr.read().decode() +``` + +Full post-mortem + +`ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md` +for the canonical reproduction. Cost several +investigation sessions before catching it — +because the capture-pipe symptom was masked by +deeper cascade-deadlocks. Once the cascades were +fixed, the tree tore down enough to generate +pipe-filling log volume → capture-pipe finally +surfaced. Grep-note for future-self: **if a +multi-subproc tractor test hangs, `pytest -s` +first, conc-anal second.** diff --git a/.claude/skills/run-tests/SKILL.md b/.claude/skills/run-tests/SKILL.md index 1d2b284af..ea0d4ae63 100644 --- a/.claude/skills/run-tests/SKILL.md +++ b/.claude/skills/run-tests/SKILL.md @@ -8,14 +8,26 @@ allowed-tools: - Bash(python -m pytest *) - Bash(python -c *) - Bash(python --version *) - - Bash(git rev-parse *) + - Bash(UV_PROJECT_ENVIRONMENT=py* uv run python *) + - Bash(UV_PROJECT_ENVIRONMENT=py* uv run pytest *) - Bash(UV_PROJECT_ENVIRONMENT=py* uv sync *) + - Bash(UV_PROJECT_ENVIRONMENT=py* uv pip show *) + - Bash(git rev-parse *) - Bash(ls *) - Bash(cat *) + - Bash(jq * .pytest_cache/*) + # process inspection + SIGINT-first cleanup ladder (see + # the zombie-actor pre-flight / teardown steps below). + - Bash(ss *) + - Bash(pgrep *) + - Bash(pkill *) + - Bash(sleep *) + - Bash(rm -f /tmp/registry@*.sock) - Read - Grep - Glob - Task + - AskUserQuestion --- Run the `tractor` test suite using `pytest`. Follow this @@ -90,41 +102,104 @@ python -m pytest tests/ -x --tb=short --no-header --tpt-proto uds python -m pytest tests/ -x --tb=short --no-header -k "cancel and not slow" ``` -## 3. Pre-flight checks (before running tests) +## 3. Pre-flight: venv detection (MANDATORY) + +**Always verify a `uv` venv is active before running +`python` or `pytest`.** This project uses +`UV_PROJECT_ENVIRONMENT=py` naming (e.g. +`py313`) — never `.venv`. + +### Step 1: detect active venv + +Run this check first: + +```sh +python -c " +import sys, os +venv = os.environ.get('VIRTUAL_ENV', '') +prefix = sys.prefix +print(f'VIRTUAL_ENV={venv}') +print(f'sys.prefix={prefix}') +print(f'executable={sys.executable}') +" +``` + +### Step 2: interpret results -### Worktree venv detection +**Case A — venv is active** (`VIRTUAL_ENV` is set +and points to a `py/` dir under the project +root or worktree): -If running inside a git worktree (`git rev-parse ---git-common-dir` differs from `--git-dir`), verify -the Python being used is from the **worktree's own -venv**, not the main repo's. Check: +Use bare `python` / `python -m pytest` for all +commands. This is the normal, fast path. + +**Case B — no venv active** (`VIRTUAL_ENV` is empty +or `sys.prefix` points to a system Python): + +Use `AskUserQuestion` to ask the user: + +> "No uv venv is active. Should I activate one +> via `UV_PROJECT_ENVIRONMENT=py uv sync`, +> or would you prefer to activate your shell venv +> first?" + +Options: +1. **"Create/sync venv"** — run + `UV_PROJECT_ENVIRONMENT=py uv sync` where + `` is detected from `python --version` + (e.g. `313` for 3.13). Then use + `py/bin/python` for all subsequent + commands in this session. +2. **"I'll activate it myself"** — stop and let the + user `source py/bin/activate` or similar. + +**Case C — inside a git worktree** (`git rev-parse +--git-common-dir` differs from `--git-dir`): + +Verify Python resolves from the **worktree's own +venv**, not the main repo's: ```sh python -c "import tractor; print(tractor.__file__)" ``` -If the path points outside the worktree (e.g. to -the main repo), set up a local venv first: +If the path points outside the worktree, create a +worktree-local venv: ```sh UV_PROJECT_ENVIRONMENT=py uv sync ``` -where `` matches the active cpython minor -version (detect via `python --version`, e.g. -`py313` for 3.13, `py314` for 3.14). Then use -`py/bin/python` for all subsequent commands. +Then use `py/bin/python` for all commands. -**Why this matters**: without a worktree-local venv, -subprocesses spawned by tractor resolve modules from -the main repo's editable install, causing spurious -`AttributeError` / `ModuleNotFoundError` for code -that only exists on the worktree's branch. +**Why this matters**: without the correct venv, +subprocesses spawned by tractor resolve modules +from the wrong editable install, causing spurious +`AttributeError` / `ModuleNotFoundError`. -### Import + collection checks +### Fallback: `uv run` -Always run these, especially after refactors or -module moves — they catch import errors instantly: +If the user can't or won't activate a venv, all +`python` and `pytest` commands can be prefixed +with `UV_PROJECT_ENVIRONMENT=py uv run`: + +```sh +# instead of: python -m pytest tests/ -x +UV_PROJECT_ENVIRONMENT=py313 uv run pytest tests/ -x + +# instead of: python -c 'import tractor' +UV_PROJECT_ENVIRONMENT=py313 uv run python -c 'import tractor' +``` + +`uv run` auto-discovers the project and venv, +but is slower than a pre-activated venv due to +lock-file resolution on each invocation. Prefer +activating the venv when possible. + +### Step 3: import + collection checks + +After venv is confirmed, always run these +(especially after refactors or module moves): ```sh # 1. package import smoke check @@ -137,6 +212,101 @@ python -m pytest tests/ -x -q --co 2>&1 | tail -5 If either fails, fix the import error before running any actual tests. +### Step 4: zombie-actor / stale-registry check (MANDATORY) + +The tractor runtime's default registry address is +**`127.0.0.1:1616`** (TCP) / `/tmp/registry@1616.sock` +(UDS). Whenever any prior test run — especially one +using a fork-based backend like `subint_forkserver` — +leaks a child actor process, that zombie keeps the +registry port bound and **every subsequent test +session fails to bind**, often presenting as 50+ +unrelated failures ("all tests broken"!) across +backends. + +**This has to be checked before the first run AND +after any cancelled/SIGINT'd run** — signal failures +in the middle of a test can leave orphan children. + +```sh +# 1. TCP registry — any listener on :1616? (primary signal) +ss -tlnp 2>/dev/null | grep ':1616' || echo 'TCP :1616 free' + +# 2. leftover actor/forkserver procs — scoped to THIS +# repo's python path, so we don't false-flag legit +# long-running tractor-using apps (e.g. `piker`, +# downstream projects that embed tractor). +pgrep -af "$(pwd)/py[0-9]*/bin/python.*_actor_child_main|subint-forkserv" \ + | grep -v 'grep\|pgrep' \ + || echo 'no leaked actor procs from this repo' + +# 3. stale UDS registry sockets +ls -la /tmp/registry@*.sock 2>/dev/null \ + || echo 'no leaked UDS registry sockets' +``` + +**Interpretation:** + +- **TCP :1616 free AND no stale sockets** → clean, + proceed. The actor-procs probe is secondary — false + positives are common (piker, any other tractor- + embedding app); only cleanup if `:1616` is bound or + sockets linger. +- **TCP :1616 bound OR stale sockets present** → + surface PIDs + cmdlines to the user, offer cleanup: + + ```sh + # 1. GRACEFUL FIRST (tractor is structured concurrent — it + # catches SIGINT as an OS-cancel in `_trio_main` and + # cascades Portal.cancel_actor via IPC to every descendant. + # So always try SIGINT first with a bounded timeout; only + # escalate to SIGKILL if graceful cleanup doesn't complete). + pkill -INT -f "$(pwd)/py[0-9]*/bin/python.*_actor_child_main|subint-forkserv" + + # 2. bounded wait for graceful teardown (usually sub-second). + # Loop until the processes exit, or timeout. Keep the + # bound tight — hung/abrupt-killed descendants usually + # hang forever, so don't wait more than a few seconds. + for i in $(seq 1 10); do + pgrep -f "$(pwd)/py[0-9]*/bin/python.*_actor_child_main|subint-forkserv" >/dev/null || break + sleep 0.3 + done + + # 3. ESCALATE TO SIGKILL only if graceful didn't finish. + if pgrep -f "$(pwd)/py[0-9]*/bin/python.*_actor_child_main|subint-forkserv" >/dev/null; then + echo 'graceful teardown timed out — escalating to SIGKILL' + pkill -9 -f "$(pwd)/py[0-9]*/bin/python.*_actor_child_main|subint-forkserv" + fi + + # 4. if a test zombie holds :1616 specifically and doesn't + # match the above pattern, find its PID the hard way: + ss -tlnp 2>/dev/null | grep ':1616' # prints `users:(("",pid=NNNN,...))` + # then (same SIGINT-first ladder): + # kill -INT ; sleep 1; kill -9 2>/dev/null + + # 5. remove stale UDS sockets + rm -f /tmp/registry@*.sock + + # 6. re-verify + ss -tlnp 2>/dev/null | grep ':1616' || echo 'TCP :1616 now free' + ``` + +**Never ignore stale registry state.** If you see the +"all tests failing" pattern — especially +`trio.TooSlowError` / connection refused / address in +use on many unrelated tests — check registry **before** +spelunking into test code. The failure signature will +be identical across backends because they're all +fighting for the same port. + +**False-positive warning for step 2:** a plain +`pgrep -af '_actor_child_main'` will also match +legit long-running tractor-embedding apps (e.g. +`piker` at `~/repos/piker/py*/bin/python3 -m +tractor._child ...`). Always scope to the current +repo's python path, or only use step 1 (`:1616`) as +the authoritative signal. + ## 4. Run and report - Run the constructed command. @@ -217,7 +387,48 @@ python -c 'import tractor' && python -m pytest tests/ -x -q --co 2>&1 | tail -3 python -m pytest tests/test_local.py tests/test_rpc.py tests/test_spawning.py tests/discovery/test_registrar.py -x --tb=short --no-header ``` -### Re-run last failures only: +### Inspect last failures (without re-running): + +When the user asks "what failed?", "show failures", +or wants to check the last-failed set before +re-running — read the pytest cache directly. This +is instant and avoids test collection overhead. + +```sh +python -c " +import json, pathlib, sys +p = pathlib.Path('.pytest_cache/v/cache/lastfailed') +if not p.exists(): + print('No lastfailed cache found.'); sys.exit() +data = json.loads(p.read_text()) +# filter to real test node IDs (ignore junk +# entries that can accumulate from system paths) +tests = sorted(k for k in data if k.startswith('tests/')) +if not tests: + print('No failures recorded.') +else: + print(f'{len(tests)} last-failed test(s):') + for t in tests: + print(f' {t}') +" +``` + +**Why not `--cache-show` or `--co --lf`?** + +- `pytest --cache-show 'cache/lastfailed'` works + but dumps raw dict repr including junk entries + (stale system paths that leak into the cache). +- `pytest --co --lf` actually *collects* tests which + triggers import resolution and is slow (~0.5s+). + Worse, when cached node IDs don't exactly match + current parametrize IDs (e.g. param names changed + between runs), pytest falls back to collecting + the *entire file*, giving false positives. +- Reading the JSON directly is instant, filterable + to `tests/`-prefixed entries, and shows exactly + what pytest recorded — no interpretation. + +**After inspecting**, re-run the failures: ```sh python -m pytest --lf -x --tb=short --no-header ``` @@ -247,3 +458,73 @@ by your changes — note them and move on. **Rule of thumb**: if a test fails with `TooSlowError`, `trio.TooSlowError`, or `pexpect.TIMEOUT` and you didn't touch the relevant code path, it's flaky — skip it. + +## 9. The pytest-capture hang pattern (CHECK THIS FIRST) + +**Symptom:** a tractor test hangs indefinitely under +default `pytest` but passes instantly when you add +`-s` (`--capture=no`). + +**Cause:** tractor subactors (especially under fork- +based backends) inherit pytest's stdout/stderr +capture pipes via fds 1,2. Under high-volume error +logging (e.g. multi-level cancel cascade, nested +`run_in_actor` failures, anything triggering +`RemoteActorError` + `ExceptionGroup` traceback +spew), the **64KB Linux pipe buffer fills** faster +than pytest drains it. Subactor writes block → can't +finish exit → parent's `waitpid`/pidfd wait blocks → +deadlock cascades up the tree. + +**Pre-existing guards in the tractor harness** that +encode this same knowledge — grep these FIRST +before spelunking: + +- `tests/conftest.py:258-260` (in the `daemon` + fixture): `# XXX: too much logging will lock up + the subproc (smh)` — downgrades `trace`/`debug` + loglevel to `info` to prevent the hang. +- `tests/conftest.py:316`: `# can lock up on the + _io.BufferedReader and hang..` — noted on the + `proc.stderr.read()` post-SIGINT. + +**Debug recipe (in priority order):** + +1. **Try `-s` first.** If the hang disappears with + `pytest -s`, you've confirmed it's capture-pipe + fill. Skip spelunking. +2. **Lower the loglevel.** Default `--ll=error` on + this project; if you've bumped it to `debug` / + `info`, try dropping back. Each log level + multiplies pipe-pressure under fault cascades. +3. **If you MUST use default capture + high log + volume**, redirect subactor stdout/stderr in the + child prelude (e.g. + `tractor.spawn._subint_forkserver._child_target` + post-`_close_inherited_fds`) to `/dev/null` or a + file. + +**Signature tells you it's THIS bug (vs. a real +code hang):** + +- Multi-actor test under fork-based backend + (`subint_forkserver`, eventually `trio_proc` too + under enough log volume). +- Multiple `RemoteActorError` / `ExceptionGroup` + tracebacks in the error path. +- Test passes with `-s` in the 5-10s range, hangs + past pytest-timeout (usually 30+ s) without `-s`. +- Subactor processes visible via `pgrep -af + subint-forkserv` or similar after the hang — + they're alive but blocked on `write()` to an + inherited stdout fd. + +**Historical reference:** this deadlock cost a +multi-session investigation (4 genuine cascade +fixes landed along the way) that only surfaced the +capture-pipe issue AFTER the deeper fixes let the +tree actually tear down enough to produce pipe- +filling log volume. Full post-mortem in +`ai/conc-anal/subint_forkserver_test_cancellation_leak_issue.md`. +Lesson codified here so future-me grep-finds the +workaround before digging. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea5b98113..8bb1297cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,12 @@ jobs: run: uv build --sdist --python=3.13 - name: Install sdist from .tar.gz - run: python -m pip install dist/*.tar.gz + # XXX must install under py3.13 (matching the build's + # `--python=3.13`); the runner's default `python` is 3.12 + # which our `requires-python = ">=3.13"` now rejects. + run: | + uv venv --python 3.13 + uv pip install dist/*.tar.gz # ------ type-check ------ # mypy: @@ -148,9 +153,12 @@ jobs: - name: Run tests run: > uv run - pytest tests/ -rsx + pytest + tests/ + -rsx --spawn-backend=${{ matrix.spawn_backend }} --tpt-proto=${{ matrix.tpt_proto }} + --capture=fd # XXX legacy NOTE XXX # diff --git a/.gitignore b/.gitignore index 3537652e4..7c7a6fe59 100644 --- a/.gitignore +++ b/.gitignore @@ -106,46 +106,55 @@ venv.bak/ # all files under .git/ -# any commit-msg gen tmp files -.claude/skills/commit-msg/msgs/ -.claude/git_commit_msg_LATEST.md -.claude/*_commit_*.md -.claude/*_commit*.toml -.claude/*_commit*.txt -.claude/skills/commit-msg/msgs/* +# require very explicit staging for anything we **really** +# want put/kept in repo. +notes_to_self/ +snippets/ + +# ------- AI shiz ------- +# `ai.skillz` symlinks, +# (machine-local, deploy via deploy-skill.sh) +.claude/skills/py-codestyle +.claude/skills/close-wkt +.claude/skills/plan-io +.claude/skills/prompt-io +.claude/skills/resolve-conflicts +.claude/skills/inter-skill-review -.claude/skills/pr-msg/msgs/* -# XXX, for rn, so i can telescope this file. -!/.claude/skills/pr-msg/pr_msg_LATEST.md +# /open-wkt specifics +.claude/skills/open-wkt +.claude/wkts/ +claude_wkts +# /code-review-changes specifics +.claude/skills/code-review-changes # review-skill ephemeral ctx (per-PR, single-use) .claude/review_context.md .claude/review_regression.md -# per-skill session/conf (machine-local) -.claude/skills/*/conf.toml +# /pr-msg specifics +.claude/skills/pr-msg/* +# repo-specific +!.claude/skills/pr-msg/format-reference.md +# XXX, so u can nvim-telescope this file. +# !.claude/skills/pr-msg/pr_msg_LATEST.md -# ai.skillz symlinks (machine-local, deploy via deploy-skill.sh) -.claude/skills/py-codestyle -.claude/skills/code-review-changes -.claude/skills/close-wkt -.claude/skills/open-wkt -.claude/skills/plan-io -.claude/skills/prompt-io -.claude/skills/resolve-conflicts -.claude/skills/inter-skill-review -.claude/skills/yt-url-lookup +# /commit-msg specifics +# - any commit-msg gen tmp files +.claude/*_commit_*.md +.claude/*_commit*.txt +.claude/skills/commit-msg/* +!.claude/skills/commit-msg/style-duie-reference.md -# hybrid skills — symlinked SKILL.md + references -.claude/skills/commit-msg/SKILL.md -.claude/skills/pr-msg/SKILL.md -.claude/skills/pr-msg/references +# use prompt-io instead? +.claude/plans # nix develop --profile .nixdev .nixdev* # :Obsession . Session.vim + # `gish` local `.md`-files # TODO? better all around automation! # -[ ] it'd be handy to also commit and sync with wtv git service? @@ -159,7 +168,3 @@ gh/ # LLM conversations that should remain private docs/conversations/ - -# Claude worktrees -.claude/wkts/ -claude_wkts diff --git a/ai/conc-anal/fork_thread_semantics_execution_vs_memory.md b/ai/conc-anal/fork_thread_semantics_execution_vs_memory.md new file mode 100644 index 000000000..c07ad81d3 --- /dev/null +++ b/ai/conc-anal/fork_thread_semantics_execution_vs_memory.md @@ -0,0 +1,281 @@ +# `fork()` in a multi-threaded program — execution-side vs. memory-side of the same coin + +A reference doc for readers who've encountered one of two +opposite-sounding framings of POSIX `fork()` semantics in a +multi-threaded program and are confused by the other. + +This is a sibling to +`subint_fork_blocked_by_cpython_post_fork_issue.md` — that +doc covers a CPython-level refusal of fork-from-subint; +this one covers the more general POSIX layer, since +tractor's main-thread forkserver design rests on it. + +## TL;DR + +POSIX `fork()` only preserves the *calling* thread as a +runnable thread in the child — every other thread in the +parent simply never executes another instruction in the +child. trio's docs call this "leaked"; tractor's +`_main_thread_forkserver.py` docstring calls it "gone". +Both are correct: "gone" is the *execution* side (no +scheduler entry, no instructions retired), "leaked" is the +*memory* side (the dead threads' stacks and per-thread +heap structures still ride into the child's address space +as orphaned COW pages with no owner and no cleanup hook). +Same POSIX reality, two halves of the same coin. + +## The two framings + +[python-trio/trio#1614][trio-1614] (the canonical "trio + +fork" hazards thread) puts it this way: + +> If you use `fork()` in a process with multiple threads, +> all the other thread stacks are just leaked: there's +> nothing else you can reasonably do with them. + +`tractor.spawn._main_thread_forkserver`'s module docstring +(specifically the "What survives the fork? — POSIX +semantics" section) puts it this way: + +> POSIX `fork()` only preserves the *calling* thread as a +> runnable thread in the child. Every other thread in the +> parent — trio's runner thread, any `to_thread` cache +> threads, anything else — never executes another +> instruction post-fork. + +A reader bouncing between the two can be forgiven for +asking: well, *which* is it — leaked or gone? + +The answer is "yes". They're describing the same POSIX +behavior from two different angles: + +- trio is talking about the **bytes** the dead threads + leave behind — stacks, TLS slots, per-thread arena + metadata — and the fact that nothing in the child can + drive them forward, free them, or even safely walk + them. That's a memory leak in the strict sense: held + but unreachable. +- tractor is talking about the **execution** side + relevant to the forkserver design: which threads + retire instructions in the child? Exactly one — the + one that called `fork()`. Everything else, regardless + of the bytes left behind, is dead in a scheduler + sense. + +Neither framing is wrong; they're just answering +different questions. + +## POSIX `fork()` in a multi-threaded program — what actually happens + +Per POSIX (and concretely on Linux glibc), the contract +of `fork()` in a multi-threaded process is: + +1. The kernel creates a new process whose virtual + address space is a COW copy of the parent's. *All* + pages map across — code, heap, every thread's stack, + every malloc arena, every mmap region. +2. Of the parent's N threads, exactly **one** is + reified in the child as a runnable kernel task: the + thread that called `fork()`. The other N-1 threads + have *no* corresponding task in the child kernel. They + were never scheduled, never `clone()`d for the child, + never exist as runnable entities. +3. Their **memory artifacts** — pthread stacks, TLS, + `pthread_t` structures, glibc per-thread arena + bookkeeping — are still mapped in the child's address + space, because (1) duplicates *everything* page-wise. + They sit there as inert COW bytes. +4. The kernel does not clean those bytes up. There is no + "phantom-thread cleanup" pass post-fork. The kernel + doesn't know which mapped pages "belonged to" which + thread — at the kernel level mappings are + process-scoped, not thread-scoped. +5. The surviving thread (the caller of `fork()`) cannot + safely access those leaked bytes either. Any state + they encoded — held mutexes, in-flight syscalls, + half-updated invariants — is frozen at whatever + instant the parent's fork-syscall observed it. Some + of those mutexes may even still be locked from the + child's POV (the canonical "fork-in-multithreaded- + program-deadlocks" hazard; see `man pthread_atfork`). + +So: from the kernel's PoV, the child has one thread. +From the address-space's PoV, the child has all the +parent's bytes — including the corpses of the N-1 dead +threads' stacks. Both true simultaneously. + +## Why trio says "leaked" + +trio's framing makes sense from the parent's +PoV, looking at *what those threads were doing*. In a +running `trio.run()` process you typically have: + +- The trio runner thread itself — owns the `selectors` + epoll fd, the signal-wakeup-fd, the run-queue. +- Threadpool worker threads (`trio.to_thread`'s cache) + — blocked in `wait()` on the threadpool's work + condvar. +- Whatever other ad-hoc threads the application + started. + +Each of those threads owns *real work-state*: epoll +registrations, file descriptors held in +soon-to-be-completed reads, half-released locks, posted +but unconsumed wakeups. After fork, that state is still +encoded in the child's memory. None of it is invalid in +a well-formed-bytes sense. It's just that: + +- The thread that was driving it is gone. +- Nothing else in the child knows the layout well + enough to take over. +- Even if it did, the kernel objects backing the work + (epoll fd, signalfd) have separate post-fork + semantics that don't compose with userland trio + state. + +So the bytes are *held* (they're in the child's +address space, they count against RSS, they survive +until something clobbers them), and they're +*unreachable* in any meaningful sense — no thread can +safely drive them forward. That is the textbook +definition of a leak. + +trio's quote is reminding the user that `fork()` from a +multi-threaded process is a one-way memory hazard: +whatever those threads were doing, that work-state is +now garbage you happen to still be carrying. + +## Why tractor says "gone" + +tractor's `_main_thread_forkserver` framing is concerned +with a different question: *which thread executes in the +child, and is it safe?* + +The forkserver design rests on POSIX's "calling thread +is the sole survivor" guarantee. We pick that calling +thread very deliberately: a dedicated worker that has +provably never entered trio. So the thread that *does* +run in the child is one whose locals, TLS, and stack +contain nothing trio-related. Trio's runner thread — +the one that owned the epoll fd and the run-queue — is +*gone* from the child in the execution sense. It will +never run another instruction. The fact that its stack +bytes still exist in the child's address space (the +"leaked" view) is irrelevant to the forkserver, because +nothing in the child reads or writes those pages. + +So when the docstring says "Every other thread … is +gone the instant `fork()` returns in the child", it's +being precise about the surface that matters for the +backend: scheduler-level liveness. Nothing schedules +those threads ever again. Whether their bytes are +hanging around is a separate (and, for the design, +non-load-bearing) fact. + +## Cross-table + +The same tabular layout the `_main_thread_forkserver` +docstring uses, expanded with a fourth "what handles +it" column: + +| thread | parent | child (executing) | child (memory) | what handles it | +|---------------------|-----------|-------------------|------------------------------|-----------------------------| +| forkserver worker | continues | sole survivor | live stack | runs the child's bootstrap | +| `trio.run()` thread | continues | not running | leaked stack (zombie bytes) | overwritten by child's fresh `trio.run()` | +| any other thread | continues | not running | leaked stack (zombie bytes) | overwritten / GC'd / clobbered by `exec()` if used | + +The "child (executing)" column is the *execution* side +of the coin — what tractor cares about. The "child +(memory)" column is the *memory* side — what trio +cares about. + +The "what handles it" column is the deliberate punchline +of the design: nothing has to handle the leaked bytes +*explicitly*. They get clobbered by ordinary forward +progress in the child: + +- The fresh `trio.run()` the child boots up allocates + its own stack, scheduler, and run-queue, which over + time overlaps and overwrites the inherited zombie + pages. +- Python's GC walks live objects only; the dead-thread + Python frames aren't reachable from any + `PyThreadState`, so they get freed at the next + collection cycle. +- If the child eventually `exec()`s, the entire address + space is replaced and the leak vanishes. + +## What this means for the forkserver design + +The crucial point is that **the design doesn't and +*can't* prevent the leak**. There is no userland fix +for COW thread stacks. The kernel hands the child a +duplicated address space; that's what `fork()` *is*. No +amount of pre-fork hookery, `pthread_atfork()` +gymnastics, or post-fork cleanup can un-COW the dead +threads' pages without unmapping them, and unmapping +arbitrary regions of a duplicated address space is +neither portable nor safe. + +What the design *does* ensure is the orthogonal +property: the survivor thread is one that doesn't need +any of that leaked state to function. Concretely: + +- Survivor is the forkserver worker thread. +- That worker has provably never imported, called into, + or held any reference to `trio`. (Enforced by keeping + the worker's lifecycle entirely in + `_main_thread_forkserver.py` and never letting trio + task-state cross into it.) +- So the leaked pages — trio runner stack, threadpool + caches, etc. — are inert relative to the survivor. + No code path in the child references them. +- The child then boots its own fresh `trio.run()`, + which allocates new state in new pages. Over the + child's lifetime the COW'd zombie pages get + overwritten, GC'd, or (if the child eventually + `exec()`s) discarded wholesale. + +The "leak" is real but inert. It costs RSS until +clobbered; it doesn't cost correctness. That's exactly +the property the forkserver pattern is built on, and +it's also why the design needs the "calling thread is +trio-free" precondition to be airtight: if the survivor +were a trio thread, it *would* try to drive the leaked +trio state, and the leak would no longer be inert. + +## See also + +- `tractor/spawn/_main_thread_forkserver.py` — module + docstring's "What survives the fork? — POSIX + semantics" section is the in-tree, code-adjacent + prose this doc expands on. The cross-table here is a + fourth-column expansion of the table there. + +- [python-trio/trio#1614][trio-1614] — the trio issue + with the "leaked" framing, and the canonical thread + for trio + `fork()` hazards more broadly. + +- [`subint_fork_blocked_by_cpython_post_fork_issue.md`](./subint_fork_blocked_by_cpython_post_fork_issue.md) + — sibling analysis covering CPython's *post-fork* + hooks (`PyOS_AfterFork_Child`, + `_PyInterpreterState_DeleteExceptMain`) and why + fork-from-non-main-subint is a CPython-level hard + refusal. Complementary axis: this doc is about POSIX + semantics; that doc is about the CPython runtime + layer that runs *after* POSIX `fork()` returns in + the child. + +- `man pthread_atfork(3)` — canonical "fork in a + multithreaded process is dangerous" reference. + Especially the rationale section, which is the + closest thing to a normative statement of "the + surviving thread cannot safely use anything the dead + threads were touching." + +- `man fork(2)` (Linux) — "Other than [the calling + thread], … no other threads are replicated …" + paragraph is the kernel-side statement of the + execution-side framing this doc opens with. + +[trio-1614]: https://github.com/python-trio/trio/issues/1614 diff --git a/ai/conc-anal/test_register_duplicate_name_daemon_connect_race_issue.md b/ai/conc-anal/test_register_duplicate_name_daemon_connect_race_issue.md new file mode 100644 index 000000000..67d754710 --- /dev/null +++ b/ai/conc-anal/test_register_duplicate_name_daemon_connect_race_issue.md @@ -0,0 +1,273 @@ +# `test_register_duplicate_name` racy connect-failure on `daemon` fixture readiness + +## Symptom + +`tests/test_multi_program.py::test_register_duplicate_name` +fails intermittently under BOTH transports + ALL spawn +backends with connect-refused errors: + +``` +# under --tpt-proto=uds +FAILED tests/test_multi_program.py::test_register_duplicate_name +- ConnectionRefusedError: [Errno 111] Connection refused +( ^^^ this exc was collapsed from a group ^^^ ) + +# under --tpt-proto=tcp +FAILED tests/test_multi_program.py::test_register_duplicate_name +- OSError: all attempts to connect to 127.0.0.1:36003 failed +( ^^^ this exc was collapsed from a group ^^^ ) +``` + +Distinct from the cancel-cascade `TooSlowError` flake +class — see +`cancel_cascade_too_slow_under_main_thread_forkserver_issue.md`. +This is a **connect-time race** before the daemon is +fully ready to `accept()`, not a teardown-cascade +slowness. + +## Root cause: blind `time.sleep()` in `daemon` fixture + +`tests/conftest.py::daemon` boots a sub-py-process via +`subprocess.Popen([python, '-c', 'tractor.run_daemon(...)'])`, +then **blindly sleeps** a fixed delay before yielding +`proc` to the test: + +```python +# excerpt from tests/conftest.py::daemon +proc = subprocess.Popen([ + sys.executable, '-c', code, +]) + +bg_daemon_spawn_delay: float = _PROC_SPAWN_WAIT # 0.6 +if tpt_proto == 'uds': + bg_daemon_spawn_delay += 1.6 +if _non_linux and ci_env: + bg_daemon_spawn_delay += 1 + +# XXX, allow time for the sub-py-proc to boot up. +# !TODO, see ping-polling ideas above! +time.sleep(bg_daemon_spawn_delay) + +assert not proc.returncode +yield proc +``` + +Inherent fragility: the delay is "long enough on dev +boxes most of the time" but has no actual +synchronization with the daemon's `bind()` + `listen()` +completion. Under any of: + +- Loaded box (CI parallelism, big rebuild in + background, low-cpu-freq) +- Cold first-run (`importlib` cache miss, JIT warmup) +- Higher-than-expected `tractor` import cost +- Filesystem latency (UDS sockfile create, slow + tmpfs) + +...the sleep finishes BEFORE the daemon has bound its +listen socket → first test client call to +`tractor.find_actor()` / `wait_for_actor()` / +`open_nursery(registry_addrs=[reg_addr])`'s implicit +connect → `ConnectionRefusedError` (TCP) or +`FileNotFoundError`/`ConnectionRefusedError` (UDS). + +## Reproducer + +Easiest: run the suite under load. + +```bash +# create CPU pressure on another core in parallel +stress-ng --cpu 2 --timeout 600s & + +./py313/bin/python -m pytest \ + tests/test_multi_program.py::test_register_duplicate_name \ + --spawn-backend=main_thread_forkserver \ + --tpt-proto=tcp -v +``` + +Reproduces ~30-50% of the time on a dev laptop. On a +quiet idle box, may need 5-10 runs to hit. + +## Why the existing `_PROC_SPAWN_WAIT` tuning is +inadequate + +Recent `bg_daemon_spawn_delay` rename +(de-monotonic-grow fix) just-shipped removed the +*accumulation* bug where each invocation made the +NEXT test's wait longer too. Net effect: every +invocation now uses the SAME `0.6 + 1.6` (UDS) or +`0.6` (TCP) sleep, no growth. Good — but does +NOTHING for the underlying race. Each individual +test still relies on a blind sleep that may or may +not be sufficient. + +Bumping the constant higher pushes flake rate down +but never to zero AND adds dead time to every +non-flaking run. Not a fix, just a knob. + +## Side effects + +- **Inter-test cascade**: a single failure can cascade + via leaked subprocesses (the `daemon` fixture's + cleanup may not fully tear down a daemon that never + reached "ready"). The `_reap_orphaned_subactors` + session-end + `_track_orphaned_uds_per_test` + per-test fixtures handle most of this now, but the + affected test itself still fails. +- **Worsens under fork-spawn backends**: the daemon + has more init work + (`_main_thread_forkserver`-coordinator-thread + startup, etc.) so the sleep has to cover MORE. + +## Fix design — replace blind sleep with active poll + +The right primitive is **poll the daemon's bind +address until it accepts a connection or we time +out**, with the timeout being a hard ceiling rather +than a baseline. Two implementation paths: + +### Path A — TCP/UDS connect-poll loop + +Try `socket.connect(reg_addr)` in a tight loop with +short backoff (~50ms), succeed on the first non-error +return, fail-loud on a hard cap (e.g. 10s). Same +primitive works for both transports because both use +`socket.connect()` semantics. + +Rough shape: + +```python +def _wait_for_daemon_ready( + reg_addr, + tpt_proto: str, + timeout: float = 10.0, + poll_interval: float = 0.05, +) -> None: + deadline = time.monotonic() + timeout + while True: + if tpt_proto == 'tcp': + sock = socket.socket(socket.AF_INET) + target = reg_addr # (host, port) + else: # uds + sock = socket.socket(socket.AF_UNIX) + target = os.path.join(*reg_addr) + try: + sock.settimeout(poll_interval) + sock.connect(target) + except ( + ConnectionRefusedError, + FileNotFoundError, + socket.timeout, + ) as exc: + if time.monotonic() >= deadline: + raise TimeoutError( + f'Daemon never accepted on {target!r} ' + f'within {timeout}s' + ) from exc + time.sleep(poll_interval) + else: + sock.close() + return +``` + +Pros: trivial primitive, no tractor-runtime +dependency, works pre-yield in the fixture body, +fail-fast on truly-broken daemon. +Cons: doesn't actually do an IPC handshake, just +proves listen-side is up. A daemon that bound but +hasn't initialized its registrar table yet would +still race. + +### Path B — `tractor.find_actor()` poll + +Use the actual discovery API the test would call: + +```python +async def _wait_for_daemon_ready_via_discovery( + reg_addr, + timeout: float = 10.0, + poll_interval: float = 0.05, +): + deadline = trio.current_time() + timeout + async with tractor.open_root_actor( + registry_addrs=[reg_addr], + # ephemeral root just for the probe + ): + while True: + try: + async with tractor.find_actor( + 'registrar', # daemon's own name + registry_addrs=[reg_addr], + ) as portal: + if portal is not None: + return + except Exception: + pass + if trio.current_time() >= deadline: + raise TimeoutError(...) + await trio.sleep(poll_interval) +``` + +Pros: actually proves the discovery path works, +handles the "bound but not ready" case naturally. +Cons: requires booting an ephemeral root actor JUST +for the probe (overhead), more code, and runs in trio +which complicates the sync-fixture context. Need a +`trio.run()` wrapper. + +### Recommended: Path A with optional handshake check + +Path A is much simpler + handles 95% of the bug +class. If "bound-but-not-ready" turns out to still +race (it shouldn't — `tractor.run_daemon` doesn't +return from `bind()` until the registrar is +fully populated), escalate to Path B as a focused +follow-up. + +## Workarounds (until fix lands) + +1. **Bump `_PROC_SPAWN_WAIT`** higher (current: 0.6). + 2.0–3.0 hides most flakes at the cost of adding + dead time to every test. Not a fix but reduces + blast radius while the proper poll lands. +2. **`pytest-rerunfailures`** with `reruns=1` on the + `daemon` fixture's tests specifically. Hides the + flake but doesn't address it. +3. **Mark known-affected tests as `xfail(strict=False)`** + under `--ci`. Lets CI go green at the cost of + silently hiding regressions. + +(Recommend skipping all three — implement the active +poll instead.) + +## Investigation next steps + +1. Implement Path A as a `_wait_for_daemon_ready()` + helper in `tests/conftest.py`. Replace the + `time.sleep(bg_daemon_spawn_delay)` call with it. +2. Drop the `_PROC_SPAWN_WAIT` constant entirely + (active poll obsoletes blind sleep). +3. Run the suite 5-10 times to validate flake rate + drops to 0. +4. If flakes persist, profile whether the daemon + process exits with non-zero before the poll's + deadline hits — that'd be a different bug + (daemon startup crash) that the blind sleep was + masking. +5. Cross-check `tests/test_multi_program.py::test_*` + — multiple tests use the `daemon` fixture; all + should benefit from the same poll primitive. + +## Related + +- `tests/conftest.py::daemon` — the fixture under + fix +- `tests/conftest.py::_PROC_SPAWN_WAIT` — the + constant to drop +- `cancel_cascade_too_slow_under_main_thread_forkserver_issue.md` + — distinct flake class (cancel-cascade + `TooSlowError` at teardown, not connect-time race) +- `trio_wakeup_socketpair_busy_loop_under_fork_issue.md` + — different bug entirely; this race was masked + pre-WakeupSocketpair-patch by the busy-loop + hangs. diff --git a/ai/tooling-todos/logspec_leaf_module_granularity_route_b.md b/ai/tooling-todos/logspec_leaf_module_granularity_route_b.md new file mode 100644 index 000000000..11858ca36 --- /dev/null +++ b/ai/tooling-todos/logspec_leaf_module_granularity_route_b.md @@ -0,0 +1,159 @@ +# Logging-spec leaf-module granularity — "Route B" (decouple +# logger-*identity* from console-*display*) + +Follow-up notes recording the breaking-changes / costs of the +deeper fix that would give the `tractor.log` logging-spec (see +`LogSpec`/`apply_logspec()`) true **per-leaf-MODULE** level +control — deliberately *not* taken (for now) in favour of the +smaller sub-PACKAGE fix already landed. + +## Status / what already shipped + +The cheap, contained fix is **done**: `get_logger()`'s "strip +#2" (`log.py`, the `pkg_path = subpkg_path` collapse) no longer +eats a real sub-package component. It now strips the trailing +token *only* when it duplicates the caller's leaf-*module* +filename (which the header already shows via `{filename}`). + +Result: + +- `devx.debug` resolves to `tractor.devx.debug`, **distinct** + from a bare `devx` -> `tractor.devx` (its parent). So the + logging-spec can dial sub-package levels at any nesting depth + (`devx.debug:runtime` ≠ `devx:cancel`). +- The `get_logger(__name__)` cosmetic ("don't repeat the leaf + module in `{name}` since `{filename}` shows it") is preserved. + +What is **still NOT addressable** after that fix: + +- **Per-leaf-MODULE** levels. Every module in a (sub-)pkg shares + that pkg's logger, because `get_logger()` drops the leaf + module-name from the logger key by design. +- **Top-level lib modules** (eg. `tractor.to_asyncio`, + `__package__ == 'tractor'`) emit on the *root* `tractor` + logger, so a `to_asyncio:` spec entry hits a phantom + child -> no-op. + +## What "Route B" is + +Make the logger's *identity* the **full dotted module path** +(incl. the leaf module + top-level modules), eg. +`tractor.devx.debug._tty_lock` and `tractor.to_asyncio`, and +move the cosmetic leaf-trim out of logger-naming and into the +**formatter's `{name}` rendering**. + +Net effect: + +- Real per-module `Logger` nodes exist in the hierarchy -> + the spec can target ANY module; stdlib level-inheritance and + propagation "just work" top-down. +- Console headers stay clean because the formatter computes a + trimmed display string (drop the trailing token that equals + `{filename}`'s stem) instead of the logger doing it. + +## Why it's "broad" — breaking changes / costs + +The logger *name* is currently load-bearing well beyond +display; changing it ripples: + +1. **Every logger name changes.** + Today (post sub-pkg fix) names collapse to the sub-package; + Route B = full module path. This touches: + - handler attachment points + the `getChild()` hierarchy, + - any `logging.getLogger('tractor.X')` string lookups, + - any name-based filtering, + - the dedup / `_strict_debug` warning logic *inside* + `get_logger()` itself — the `pkg_name in name`, + `leaf_mod in pkg_path`, "duplicate pkg-name" branches all + key off the *name shape* and would need re-derivation. + +2. **Formatter rewrite.** + `LOG_FORMAT` uses `{name}` == `record.name` (the full logger + name). To keep headers clean we must compute a *display* + name and inject it as a record attr (eg. `record.pkg_ns`) + via a `logging.Filter` or a `colorlog.ColoredFormatter` + subclass overriding `.format()`, then point `LOG_FORMAT` at + that field. The `{filename}` vs `{name}` de-dup intent has + to be re-implemented per-record rather than per-logger. + +3. **Propagation / double-emit surface grows.** + Full-depth loggers mean more intermediate nodes + (`...debug._tty_lock` -> `.debug` -> `.devx` -> `tractor`). + If more than one level carries a handler (spec sub-handlers + + a root console), records double-emit. The + `propagate=False` trick we already use for filter-targeted + sub-loggers (`apply_logspec()`) must be applied carefully + across a deeper tree — more levels == more places to leak a + dup. + +4. **Level-inheritance semantics shift.** + Today setting a level on `tractor.devx` gates *all* devx + emits (they share that logger). Post-Route-B, + `tractor.devx.debug._tty_lock` is its own `NOTSET` logger + that *inherits* the effective level from ancestors — + functionally similar via inheritance, BUT any code that does + `log.setLevel(...)` / reads `log.level` on a (previously + collapsed) logger now only affects that exact node. All + `setLevel`/`.level =` call sites need an audit (eg. + `get_logger()`'s own `log.level = rlog.level` line). + +5. **Downstream contract churn.** + `modden` / `piker` call `get_logger()` / `get_console_log()` + and may depend on current names — including + `modden.runtime.daemon.setup_tractor_logging()` which + asserts `'tractor' not in name` on spec parts. The header + `{name}` field is user-visible in everyone's logs + CI + output. Changing the canonical names is a public-ish + behavior change -> needs a version note + downstream + coordination (or a formatter trim that keeps the *displayed* + string byte-identical to today). + +6. **`get_logger()` refactor risk.** + The fn tangles two concerns: compute logger *identity* and + compute the *display* string. Route B forces splitting them + inside a ~300-line fn with multiple `_strict_debug` + branches, dup-warnings, and the `name=__name__` convenience. + High chance of subtle regressions without an exhaustive + name-derivation test matrix. + +## Migration / test plan (if pursued) + +- Extract a pure helper + `_mk_logger_name(pkg_name, mod_name, mod_pkg) -> (logger_name, + display_name)` and cover it with an exhaustive unit matrix: + auto vs explicit vs `__name__`; package-`__init__` vs leaf + module; nested vs flat; `pkg_name in name` vs not; top-level + module (`__package__ == pkg_name`). +- Switch `get_logger()` to use it for *identity*; switch the + formatter to use `display_name` (via a record attr). +- Re-run the full suite + golden-diff a sample of rendered log + headers to confirm zero cosmetic churn. +- Coordinate the name change with `modden`/`piker`; bump + + CHANGES note. + +## Cheaper alternative — "Route A" (record-filter) + +If per-leaf control is wanted *before* committing to Route B: +keep names collapsed, add a `logging.Filter` on the configured +handler keyed on `record.module` / `record.pathname` that maps +each record's source module -> its spec level. Set the base +logger to the *minimum* level in the spec (so records aren't +pre-dropped by the logger), and let the filter discriminate +up/down within that floor. + +- Pros: no name churn, no formatter change, fully contained + next to `apply_logspec()`. +- Cons: a filter can only discriminate *within* what the logger + admits -> base must be permissive, so `at_least_level()` + expensive-work guards over-admit; matching dotted spec names + to a `pathname` is fiddly; doesn't clean up the hierarchy + itself. + +## Recommendation + +- Defer Route B unless true per-module loggers are wanted as a + first-class feature. +- If per-leaf control is needed soon, prefer **Route A** + (filter) — lower risk. +- The shipped sub-PACKAGE fix already covers the common ask + (`devx.debug` vs `devx`). diff --git a/pyproject.toml b/pyproject.toml index fee5547a3..0a23dce51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ name = "tractor" version = "0.1.0a6dev0" description = 'structured concurrent `trio`-"actors"' authors = [{ name = "Tyler Goodlet", email = "goodboy_foss@protonmail.com" }] -requires-python = ">=3.12, <3.14" +requires-python = ">=3.13, <3.15" readme = "docs/README.rst" license = "AGPL-3.0-or-later" keywords = [ @@ -29,8 +29,8 @@ classifiers = [ "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: System :: Distributed Computing", ] dependencies = [ @@ -43,11 +43,12 @@ dependencies = [ "tricycle>=0.4.1,<0.5", "wrapt>=1.16.0,<2", "colorlog>=6.8.2,<7", + # built-in multi-actor `pdb` REPL "pdbp>=1.8.2,<2", # windows only (from `pdbp`) + # typed IPC msging - "msgspec>=0.21.0", - "cffi>=1.17.1", + "msgspec>=0.20.0", "bidict>=0.23.1", "multiaddr>=0.2.0", "platformdirs>=4.4.0", @@ -63,27 +64,44 @@ dev = [ ] devx = [ # `tractor.devx` tooling - "greenback>=1.2.1,<2", "stackscope>=0.2.2,<0.3", # ^ requires this? "typing-extensions>=4.14.1", + # {include-group = 'sync_pause'}, # XXX, no 3.14 yet! +] +sync_pause = [ + "greenback>=1.2.1,<2", # TODO? 3.14 greenlet on nix? ] testing = [ # test suite # TODO: maybe some of these layout choices? # https://docs.pytest.org/en/8.0.x/explanation/goodpractices.html#choosing-a-test-layout-import-rules - "pytest>=8.3.5", + # bumped 8.3.5 → 9.0.3 per upstream security advisory + our + # local-only reliance on the post-9.0 capture-machinery shape + # (the `sys.__stderr__`-bypass print in + # `tractor._testing.trace._do_capture_snapshot` works on 8.x + # too, but standardizing on 9.x here ensures `--show-capture` + # interactions stay predictable across dev installs). + "pytest>=9.0.3", # CVE-2025-71176 (insecure tmpdir) patched in 9.0.3 "pexpect>=4.9.0,<5", ] repl = [ "pyperclip>=1.9.0", "prompt-toolkit>=3.0.50", - "xonsh>=0.22.2", + "xonsh>=0.23.0", "psutil>=7.0.0", ] lint = [ "ruff>=0.9.6" ] +# XXX, used for linux-only hi perf eventfd+shm channels +# now mostly moved over to `hotbaud`. +eventfd = [ + "cffi>=1.17.1", +] +subints = [ + "msgspec>=0.21.0", +] # TODO, add these with sane versions; were originally in # `requirements-docs.txt`.. # docs = [ @@ -92,10 +110,26 @@ lint = [ # ] # ------ dependency-groups ------ +[tool.uv.dependency-groups] +# for subints, we require 3.14+ due to 2 issues, +# - hanging behaviour for various multi-task teardown cases (see +# "Availability" section in the `tractor.spawn._subints` doc string). +# - `msgspec` support which is oustanding per PEP 684 upstream tracker: +# https://github.com/jcrist/msgspec/issues/563 +# +# https://docs.astral.sh/uv/concepts/projects/dependencies/#group-requires-python +subints = {requires-python = ">=3.14"} +eventfd = {requires-python = ">=3.13, <3.14"} +sync_pause = {requires-python = ">=3.13, <3.14"} [tool.uv.sources] # XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)` # for the `pp` alias.. +# ------ gh upstream ------ +# xonsh = { git = 'https://github.com/anki-code/xonsh.git', branch = 'prompt_next_suggestion' } +# ^ https://github.com/xonsh/xonsh/pull/6048 +# xonsh = { git = 'https://github.com/xonsh/xonsh.git', branch = 'main' } +# xonsh = { path = "../xonsh", editable = true } # [tool.uv.sources.pdbp] # XXX, in case we need to tmp patch again. @@ -164,6 +198,35 @@ all_bullets = true [tool.pytest.ini_options] minversion = '6.0' +# NOTE: `pytest-timeout`'s global per-test cap is intentionally +# NOT set — both of its enforcement methods break trio's +# runtime under our fork-based spawn backends: +# +# - `method='signal'` (the default; SIGALRM) raises `Failed` +# synchronously from the signal handler in trio's main +# thread, which leaves `GLOBAL_RUN_CONTEXT` half-installed +# ("Trio guest run got abandoned"). EVERY subsequent +# `trio.run()` in the same pytest session then bails with +# `RuntimeError: Attempted to call run() from inside a +# run()` — full-session poison: a single 200s hang +# cascades into 30+ false-positive failures across +# downstream test files. +# +# - `method='thread'` calls `_thread.interrupt_main()` which +# can let the resulting `KeyboardInterrupt` escape trio's +# `KIManager` under fork-cascade teardown races, killing +# the whole pytest session. +# +# For tests that legitimately need a wall-clock cap, use +# `with trio.fail_after(N):` INSIDE the test — trio's own +# Cancelled machinery handles the timeout cleanly through +# the actor nursery without disturbing global state. See +# `tests/test_advanced_streaming.py::test_dynamic_pub_sub`'s +# module-level NOTE for the canonical pattern. +# +# CI environments should rely on job-level wall-clock +# timeouts (e.g. GitHub Actions `timeout-minutes`) for an +# escape hatch on genuinely-stuck suites. # https://docs.pytest.org/en/stable/reference/reference.html#configuration-options testpaths = [ 'tests' diff --git a/tests/devx/test_pause_from_non_trio.py b/tests/devx/test_pause_from_non_trio.py index 4a03a1237..2288653f7 100644 --- a/tests/devx/test_pause_from_non_trio.py +++ b/tests/devx/test_pause_from_non_trio.py @@ -63,6 +63,9 @@ def test_pause_from_sync( `examples/debugging/sync_bp.py` ''' + # XXX required for `breakpoint()` overload and + # thus`tractor.devx.pause_from_sync()`. + pytest.importorskip('greenback') child = spawn('sync_bp') # first `sync_pause()` after nurseries open @@ -260,6 +263,9 @@ def test_sync_pause_from_aio_task( `examples/debugging/asycio_bp.py` ''' + # XXX required for `breakpoint()` overload and + # thus`tractor.devx.pause_from_sync()`. + pytest.importorskip('greenback') child = spawn('asyncio_bp') # RACE on whether trio/asyncio task bps first diff --git a/tests/devx/test_tooling.py b/tests/devx/test_tooling.py index c529bed2e..3678854ad 100644 --- a/tests/devx/test_tooling.py +++ b/tests/devx/test_tooling.py @@ -156,8 +156,10 @@ def test_breakpoint_hook_restored( calls used. ''' + # XXX required for `breakpoint()` overload and + # thus`tractor.devx.pause_from_sync()`. + pytest.importorskip('greenback') child = spawn('restore_builtin_breakpoint') - child.expect(PROMPT) try: assert_before( diff --git a/tests/test_local.py b/tests/test_local.py index 4e1e983bb..765ff7960 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -10,18 +10,22 @@ from tractor._testing import tractor_test -@pytest.mark.trio -async def test_no_runtime(): - """A registrar must be established before any nurseries +def test_no_runtime(): + ''' + A registrar must be established before any nurseries can be created. (In other words ``tractor.open_root_actor()`` must be engaged at some point?) - """ - with pytest.raises(RuntimeError) : + + ''' + async def main(): async with tractor.find_actor('doggy'): pass + with pytest.raises(tractor._exceptions.NoRuntime): + trio.run(main) + @tractor_test async def test_self_is_registered(reg_addr): diff --git a/tests/test_ringbuf.py b/tests/test_ringbuf.py index 0d3b420b3..56c4eae8b 100644 --- a/tests/test_ringbuf.py +++ b/tests/test_ringbuf.py @@ -4,6 +4,10 @@ import pytest import tractor + +# XXX `cffi` dun build on py3.14 yet.. +pytest.importorskip("cffi") + from tractor.ipc._ringbuf import ( open_ringbuf, RBToken, @@ -14,7 +18,7 @@ generate_sample_messages, ) -# in case you don't want to melt your cores, uncomment dis! +# XXX, in case you want to melt your cores, comment this skip line XD pytestmark = pytest.mark.skip diff --git a/uv.lock b/uv.lock index dd19b5ead..27511f7fa 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 -requires-python = ">=3.12, <3.14" +requires-python = ">=3.13, <3.15" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] [[package]] name = "async-generator" @@ -44,18 +48,6 @@ version = "1.0.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/75/aa/abcd75e9600987a0bc6cfe9b6b2ff3f0e2cb08c170addc6e76035b5c4cb3/blake3-1.0.8.tar.gz", hash = "sha256:513cc7f0f5a7c035812604c2c852a0c1468311345573de647e310aca4ab165ba", size = 117308, upload-time = "2025-10-14T06:47:48.83Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/a0/b7b6dff04012cfd6e665c09ee446f749bd8ea161b00f730fe1bdecd0f033/blake3-1.0.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8da4233984d51471bd4e4366feda1d90d781e712e0a504ea54b1f2b3577557b", size = 347983, upload-time = "2025-10-14T06:45:47.214Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a2/264091cac31d7ae913f1f296abc20b8da578b958ffb86100a7ce80e8bf5c/blake3-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1257be19f2d381c868a34cc822fc7f12f817ddc49681b6d1a2790bfbda1a9865", size = 325415, upload-time = "2025-10-14T06:45:48.482Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7d/85a4c0782f613de23d114a7a78fcce270f75b193b3ff3493a0de24ba104a/blake3-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:269f255b110840e52b6ce9db02217e39660ebad3e34ddd5bca8b8d378a77e4e1", size = 371296, upload-time = "2025-10-14T06:45:49.674Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/488475254976ed93fab57c67aa80d3b40df77f7d9db6528c9274bff53e08/blake3-1.0.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66ca28a673025c40db3eba21a9cac52f559f83637efa675b3f6bd8683f0415f3", size = 374516, upload-time = "2025-10-14T06:45:51.23Z" }, - { url = "https://files.pythonhosted.org/packages/7b/21/2a1c47fedb77fb396512677ec6d46caf42ac6e9a897db77edd0a2a46f7bb/blake3-1.0.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb04966537777af56c1f399b35525aa70a1225816e121ff95071c33c0f7abca", size = 447911, upload-time = "2025-10-14T06:45:52.637Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7d/db0626df16029713e7e61b67314c4835e85c296d82bd907c21c6ea271da2/blake3-1.0.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5b5da177d62cc4b7edf0cea08fe4dec960c9ac27f916131efa890a01f747b93", size = 505420, upload-time = "2025-10-14T06:45:54.445Z" }, - { url = "https://files.pythonhosted.org/packages/5b/55/6e737850c2d58a6d9de8a76dad2ae0f75b852a23eb4ecb07a0b165e6e436/blake3-1.0.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38209b10482c97e151681ea3e91cc7141f56adbbf4820a7d701a923124b41e6a", size = 394189, upload-time = "2025-10-14T06:45:55.719Z" }, - { url = "https://files.pythonhosted.org/packages/5b/94/eafaa5cdddadc0c9c603a6a6d8339433475e1a9f60c8bb9c2eed2d8736b6/blake3-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504d1399b7fb91dfe5c25722d2807990493185faa1917456455480c36867adb5", size = 388001, upload-time = "2025-10-14T06:45:57.067Z" }, - { url = "https://files.pythonhosted.org/packages/17/81/735fa00d13de7f68b25e1b9cb36ff08c6f165e688d85d8ec2cbfcdedccc5/blake3-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c84af132aa09abeadf9a0118c8fb26f4528f3f42c10ef8be0fcf31c478774ec4", size = 550302, upload-time = "2025-10-14T06:45:58.657Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c6/d1fe8bdea4a6088bd54b5a58bc40aed89a4e784cd796af7722a06f74bae7/blake3-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a25db3d36b55f5ed6a86470155cc749fc9c5b91c949b8d14f48658f9d960d9ec", size = 554211, upload-time = "2025-10-14T06:46:00.269Z" }, - { url = "https://files.pythonhosted.org/packages/55/d1/ca74aa450cbe10e396e061f26f7a043891ffa1485537d6b30d3757e20995/blake3-1.0.8-cp312-cp312-win32.whl", hash = "sha256:e0fee93d5adcd44378b008c147e84f181f23715307a64f7b3db432394bbfce8b", size = 228343, upload-time = "2025-10-14T06:46:01.533Z" }, - { url = "https://files.pythonhosted.org/packages/4d/42/bbd02647169e3fbed27558555653ac2578c6f17ccacf7d1956c58ef1d214/blake3-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:6a6eafc29e4f478d365a87d2f25782a521870c8514bb43734ac85ae9be71caf7", size = 215704, upload-time = "2025-10-14T06:46:02.79Z" }, { url = "https://files.pythonhosted.org/packages/55/b8/11de9528c257f7f1633f957ccaff253b706838d22c5d2908e4735798ec01/blake3-1.0.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:46dc20976bd6c235959ef0246ec73420d1063c3da2839a9c87ca395cf1fd7943", size = 347771, upload-time = "2025-10-14T06:46:04.248Z" }, { url = "https://files.pythonhosted.org/packages/50/26/f7668be55c909678b001ecacff11ad7016cd9b4e9c7cc87b5971d638c5a9/blake3-1.0.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d17eb6382634b3a5bc0c0e0454d5265b0becaeeadb6801ed25150b39a999d0cc", size = 325431, upload-time = "2025-10-14T06:46:06.136Z" }, { url = "https://files.pythonhosted.org/packages/77/57/e8a85fa261894bf7ce7af928ff3408aab60287ab8d58b55d13a3f700b619/blake3-1.0.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19fc6f2b7edab8acff6895fc6e38c19bd79f4c089e21153020c75dfc7397d52d", size = 370994, upload-time = "2025-10-14T06:46:07.398Z" }, @@ -80,6 +72,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/65/1859fddfabc1cc72548c2269d988819aad96d854e25eae00531517925901/blake3-1.0.8-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:511133bab85ff60ed143424ce484d08c60894ff7323f685d7a6095f43f0c85c3", size = 553805, upload-time = "2025-10-14T06:46:36.532Z" }, { url = "https://files.pythonhosted.org/packages/c1/c7/2969352017f62378e388bb07bb2191bc9a953f818dc1cd6b9dd5c24916e1/blake3-1.0.8-cp313-cp313t-win32.whl", hash = "sha256:9c9fbdacfdeb68f7ca53bb5a7a5a593ec996eaf21155ad5b08d35e6f97e60877", size = 228068, upload-time = "2025-10-14T06:46:37.826Z" }, { url = "https://files.pythonhosted.org/packages/d8/fc/923e25ac9cadfff1cd20038bcc0854d0f98061eb6bc78e42c43615f5982d/blake3-1.0.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3cec94ed5676821cf371e9c9d25a41b4f3ebdb5724719b31b2749653b7cc1dfa", size = 215369, upload-time = "2025-10-14T06:46:39.054Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2a/9f13ea01b03b1b4751a1cc2b6c1ef4b782e19433a59cf35b59cafb2a2696/blake3-1.0.8-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:2c33dac2c6112bc23f961a7ca305c7e34702c8177040eb98d0389d13a347b9e1", size = 347016, upload-time = "2025-10-14T06:46:40.318Z" }, + { url = "https://files.pythonhosted.org/packages/06/8e/8458c4285fbc5de76414f243e4e0fcab795d71a8b75324e14959aee699da/blake3-1.0.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c445eff665d21c3b3b44f864f849a2225b1164c08654beb23224a02f087b7ff1", size = 324496, upload-time = "2025-10-14T06:46:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/49/fa/b913eb9cc4af708c03e01e6b88a8bb3a74833ba4ae4b16b87e2829198e06/blake3-1.0.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47939f04b89c5c6ff1e51e883e5efab1ea1bf01a02f4d208d216dddd63d0dd8", size = 370654, upload-time = "2025-10-14T06:46:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/245e0800c33b99c8f2b570d9a7199b51803694913ee4897f339648502933/blake3-1.0.8-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73e0b4fa25f6e3078526a592fb38fca85ef204fd02eced6731e1cdd9396552d4", size = 374693, upload-time = "2025-10-14T06:46:45.186Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a6/8cb182c8e482071dbdfcc6ec0048271fd48bcb78782d346119ff54993700/blake3-1.0.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0543c57eb9d6dac9d4bced63e9f7f7b546886ac04cec8da3c3d9c8f30cbbb7", size = 447673, upload-time = "2025-10-14T06:46:46.358Z" }, + { url = "https://files.pythonhosted.org/packages/06/b7/1cbbb5574d2a9436d1b15e7eb5b9d82e178adcaca71a97b0fddaca4bfe3a/blake3-1.0.8-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed972ebd553c0c25363459e9fc71a38c045d8419e365b59acd8cd791eff13981", size = 507233, upload-time = "2025-10-14T06:46:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/9c/45/b55825d90af353b3e26c653bab278da9d6563afcf66736677f9397e465be/blake3-1.0.8-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bafdec95dfffa3f6571e529644744e280337df15ddd9728f224ba70c5779b23", size = 393852, upload-time = "2025-10-14T06:46:49.511Z" }, + { url = "https://files.pythonhosted.org/packages/34/73/9058a1a457dd20491d1b37de53d6876eff125e1520d9b2dd7d0acbc88de2/blake3-1.0.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d78f06f3fb838b34c330e2987090376145cbe5944d8608a0c4779c779618f7b", size = 386442, upload-time = "2025-10-14T06:46:51.205Z" }, + { url = "https://files.pythonhosted.org/packages/30/6d/561d537ffc17985e276e08bf4513f1c106f1fdbef571e782604dc4e44070/blake3-1.0.8-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:dd03ff08d1b6e4fdda1cd03826f971ae8966ef6f683a8c68aa27fb21904b5aa9", size = 549929, upload-time = "2025-10-14T06:46:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/03/2f/dbe20d2c57f1a67c63be4ba310bcebc707b945c902a0bde075d2a8f5cd5c/blake3-1.0.8-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4e02a3c499e35bf51fc15b2738aca1a76410804c877bcd914752cac4f71f052a", size = 553750, upload-time = "2025-10-14T06:46:54.194Z" }, + { url = "https://files.pythonhosted.org/packages/6b/da/c6cb712663c869b2814870c2798e57289c4268c5ac5fb12d467fce244860/blake3-1.0.8-cp314-cp314-win32.whl", hash = "sha256:a585357d5d8774aad9ffc12435de457f9e35cde55e0dc8bc43ab590a6929e59f", size = 228404, upload-time = "2025-10-14T06:46:56.807Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/c7dcd8bc3094bba1c4274e432f9e77a7df703532ca000eaa550bd066b870/blake3-1.0.8-cp314-cp314-win_amd64.whl", hash = "sha256:9ab5998e2abd9754819753bc2f1cf3edf82d95402bff46aeef45ed392a5468bf", size = 215460, upload-time = "2025-10-14T06:46:58.15Z" }, + { url = "https://files.pythonhosted.org/packages/75/3c/6c8afd856c353176836daa5cc33a7989e8f54569e9d53eb1c53fc8f80c34/blake3-1.0.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e2df12f295f95a804338bd300e8fad4a6f54fd49bd4d9c5893855a230b5188a8", size = 347482, upload-time = "2025-10-14T06:47:00.189Z" }, + { url = "https://files.pythonhosted.org/packages/6a/35/92cd5501ce8e1f5cabdc0c3ac62d69fdb13ff0b60b62abbb2b6d0a53a790/blake3-1.0.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:63379be58438878eeb76ebe4f0efbeaabf42b79f2cff23b6126b7991588ced67", size = 324376, upload-time = "2025-10-14T06:47:01.413Z" }, + { url = "https://files.pythonhosted.org/packages/11/33/503b37220a3e2e31917ef13722efd00055af51c5e88ae30974c733d7ece6/blake3-1.0.8-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88d527c247f9609dc1d45a08fd243e39f0d5300d54c57e048de24d4fa9240ebb", size = 370220, upload-time = "2025-10-14T06:47:02.573Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/fe817843adf59516c04d44387bd643b422a3b0400ea95c6ede6a49920737/blake3-1.0.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506a47897a11ebe8f3cdeb52f1365d6a2f83959e98ccb0c830f8f73277d4d358", size = 373454, upload-time = "2025-10-14T06:47:03.784Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/90a2a623575373dfc9b683f1bad1bf017feafa5a6d65d94fb09543050740/blake3-1.0.8-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5122a61b3b004bbbd979bdf83a3aaab432da3e2a842d7ddf1c273f2503b4884", size = 447102, upload-time = "2025-10-14T06:47:04.958Z" }, + { url = "https://files.pythonhosted.org/packages/93/ff/4e8ce314f60115c4c657b1fdbe9225b991da4f5bcc5d1c1f1d151e2f39d6/blake3-1.0.8-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0171e85d56dec1219abdae5f49a0ed12cb3f86a454c29160a64fd8a8166bba37", size = 506791, upload-time = "2025-10-14T06:47:06.82Z" }, + { url = "https://files.pythonhosted.org/packages/44/88/2963a1f18aab52bdcf35379b2b48c34bbc462320c37e76960636b8602c36/blake3-1.0.8-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:003f61e8c41dd9931edddf1cc6a1bb680fb2ac0ad15493ef4a1df9adc59ce9df", size = 393717, upload-time = "2025-10-14T06:47:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/45/d1/a848ed8e8d4e236b9b16381768c9ae99d92890c24886bb4505aa9c3d2033/blake3-1.0.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c3151955efb09ba58cd3e1263521e15e9e3866a40d6bd3556d86fc968e8f95", size = 386150, upload-time = "2025-10-14T06:47:10.363Z" }, + { url = "https://files.pythonhosted.org/packages/96/09/e3eb5d60f97c01de23d9f434e6e1fc117efb466eaa1f6ddbbbcb62580d6e/blake3-1.0.8-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:5eb25bca3cee2e0dd746a214784fb36be6a43640c01c55b6b4e26196e72d076c", size = 549120, upload-time = "2025-10-14T06:47:11.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/ad/3d9661c710febb8957dd685fdb3e5a861aa0ac918eda3031365ce45789e2/blake3-1.0.8-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ab4e1dea4fa857944944db78e8f20d99ee2e16b2dea5a14f514fb0607753ac83", size = 553264, upload-time = "2025-10-14T06:47:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/11/55/e332a5b49edf377d0690e95951cca21a00c568f6e37315f9749efee52617/blake3-1.0.8-cp314-cp314t-win32.whl", hash = "sha256:67f1bc11bf59464ef092488c707b13dd4e872db36e25c453dfb6e0c7498df9f1", size = 228116, upload-time = "2025-10-14T06:47:14.516Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5c/dbd00727a3dd165d7e0e8af40e630cd7e45d77b525a3218afaff8a87358e/blake3-1.0.8-cp314-cp314t-win_amd64.whl", hash = "sha256:421b99cdf1ff2d1bf703bc56c454f4b286fce68454dd8711abbcb5a0df90c19a", size = 215133, upload-time = "2025-10-14T06:47:16.069Z" }, ] [[package]] @@ -91,17 +107,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, @@ -150,9 +155,9 @@ name = "greenback" version = "1.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet" }, - { name = "outcome" }, - { name = "sniffio" }, + { name = "greenlet", marker = "python_full_version < '3.14'" }, + { name = "outcome", marker = "python_full_version < '3.14'" }, + { name = "sniffio", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/c1/ab3a42c0f3ed56df9cd33de1539b3198d98c6ccbaf88a73d6be0b72d85e0/greenback-1.2.1.tar.gz", hash = "sha256:de3ca656885c03b96dab36079f3de74bb5ba061da9bfe3bb69dccc866ef95ea3", size = 42597, upload-time = "2024-02-20T21:23:13.239Z" } wheels = [ @@ -165,15 +170,6 @@ version = "3.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload-time = "2024-09-20T18:21:04.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260, upload-time = "2024-09-20T17:08:07.301Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064, upload-time = "2024-09-20T17:36:47.628Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420, upload-time = "2024-09-20T17:39:21.258Z" }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035, upload-time = "2024-09-20T17:44:26.501Z" }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105, upload-time = "2024-09-20T17:08:42.048Z" }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077, upload-time = "2024-09-20T17:08:33.707Z" }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975, upload-time = "2024-09-20T17:44:15.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955, upload-time = "2024-09-20T17:09:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655, upload-time = "2024-09-20T17:21:22.427Z" }, { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload-time = "2024-09-20T17:08:26.312Z" }, { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload-time = "2024-09-20T17:36:48.983Z" }, { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload-time = "2024-09-20T17:39:22.705Z" }, @@ -228,22 +224,6 @@ version = "5.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, - { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, - { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, - { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, - { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, - { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, - { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, - { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, @@ -265,6 +245,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, + { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, + { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, + { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, + { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, + { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, + { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, + { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, + { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, + { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, + { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, + { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, + { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, + { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, ] [[package]] @@ -281,14 +298,6 @@ version = "0.21.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/60/f79b9b013a16fa3a58350c9295ddc6789f2e335f36ea61ed10a21b215364/msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c", size = 319193, upload-time = "2026-04-12T21:44:50.394Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/cf/317224852c00248c620a9bcf4b26e2e4ab8afd752f18d2a6ef73ebd423b6/msgspec-0.21.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4248cf0b6129b7d230eacd493c17cc2d4f3989f3bb7f633a928a85b7dcfa251", size = 196188, upload-time = "2026-04-12T21:44:07.181Z" }, - { url = "https://files.pythonhosted.org/packages/6d/81/074612945c0666078f7366f40000013de9f6ba687491d450df699bceebc9/msgspec-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5102c7e9b3acff82178449b85006d96310e690291bb1ea0142f1b24bcb8aabcb", size = 188473, upload-time = "2026-04-12T21:44:08.736Z" }, - { url = "https://files.pythonhosted.org/packages/8a/37/655101799590bcc5fddb2bd3fe0e6194e816c2d1da7c361725f5eb89a910/msgspec-0.21.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846758412e9518252b2ac9bffd6f0e54d9ff614f5f9488df7749f81ff5c80920", size = 218871, upload-time = "2026-04-12T21:44:09.917Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d1/d4cd9fe89c7d400d7a18f86ccc94daa3f0927f53558846fcb60791dce5d6/msgspec-0.21.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21995e74b5c598c2e004110ad66ec7f1b8c20bf2bcf3b2de8fd9a3094422d3ff", size = 225025, upload-time = "2026-04-12T21:44:11.191Z" }, - { url = "https://files.pythonhosted.org/packages/24/bf/e20549e602b9edccadeeff98760345a416f9cce846a657e8b18e3396b212/msgspec-0.21.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6129f0cca52992e898fd5344187f7c8127b63d810b2fd73e36fca73b4c6475ee", size = 222672, upload-time = "2026-04-12T21:44:12.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/68/04d7a8f0f786545cf9b8c280c57aa6befb5977af6e884b8b54191cbe44b3/msgspec-0.21.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef3ec2296248d1f8b9231acb051b6d471dfde8f21819e86c9adaaa9f42918521", size = 227303, upload-time = "2026-04-12T21:44:13.709Z" }, - { url = "https://files.pythonhosted.org/packages/cc/4d/619866af2840875be408047bf9e70ceafbae6ab50660de7134ed1b25eb86/msgspec-0.21.1-cp312-cp312-win_amd64.whl", hash = "sha256:d4ab834a054c6f0cbeef6df9e7e1b33d5f1bc7b86dea1d2fd7cad003873e783d", size = 190017, upload-time = "2026-04-12T21:44:14.977Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2e/a8f9eca8fd00e097d7a9e99ba8a4685db994494448e3d4f0b7f6e9a3c0f7/msgspec-0.21.1-cp312-cp312-win_arm64.whl", hash = "sha256:628aaa35c74950a8c59da330d7e98917e1c7188f983745782027748ee4ca573e", size = 175345, upload-time = "2026-04-12T21:44:16.431Z" }, { url = "https://files.pythonhosted.org/packages/7e/74/f11ede02839b19ff459f88e3145df5d711626ca84da4e23520cebf819367/msgspec-0.21.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:764173717a01743f007e9f74520ed281f24672c604514f7d76c1c3a10e8edb66", size = 196176, upload-time = "2026-04-12T21:44:17.613Z" }, { url = "https://files.pythonhosted.org/packages/bb/40/4476c1bd341418a046c4955aff632ec769315d1e3cb94e6acf86d461f9ed/msgspec-0.21.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:344c7cd0eaed1fb81d7959f99100ef71ec9b536881a376f11b9a6c4803365697", size = 188524, upload-time = "2026-04-12T21:44:18.815Z" }, { url = "https://files.pythonhosted.org/packages/ca/d9/9e9d7d7e5061b47540d03d640fab9b3965ba7ae49c1b2154861c8f007518/msgspec-0.21.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48943e278b3854c2f89f955ddc6f9f430d3f0784b16e47d10604ee0463cd21f5", size = 218880, upload-time = "2026-04-12T21:44:20.028Z" }, @@ -297,6 +306,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/27/0bba04b2b4ef05f3d068429410bc71d2cea925f1596a8f41152cccd5edb8/msgspec-0.21.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38fe93e86b61328fe544cb7fd871fad5a27c8734bfda90f65e5dbe288ae50f61", size = 227259, upload-time = "2026-04-12T21:44:24.11Z" }, { url = "https://files.pythonhosted.org/packages/b0/2d/09574b0eea02fed2c2c1383dbaae2c7f79dc16dcd6487a886000afb5d7c4/msgspec-0.21.1-cp313-cp313-win_amd64.whl", hash = "sha256:8bc666331c35fcce05a7cd2d6221adbe0f6058f8e750711413d22793c080ac6a", size = 189857, upload-time = "2026-04-12T21:44:25.359Z" }, { url = "https://files.pythonhosted.org/packages/46/34/105b1576ad182879914f0c821f17ee1d13abb165cb060448f96fe2aff078/msgspec-0.21.1-cp313-cp313-win_arm64.whl", hash = "sha256:42bb1241e0750c1a4346f2aa84db26c5ffd99a4eb3a954927d9f149ff2f42898", size = 175403, upload-time = "2026-04-12T21:44:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ad/86954e987d1d6a5c579e2c2e7832b65e0fff194179fdac4f581536086024/msgspec-0.21.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fab48eb45fdbfbdb2c0edfec00ffc53b6b6085beefc6b50b61e01659f9f8757f", size = 196261, upload-time = "2026-04-12T21:44:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a1/c5e46c3e42b866199365e35d11dddfd1fbd8bba4fdb3c52f965b1607ce94/msgspec-0.21.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3cb779ea0c35bc807ff941d415875c1f69ca0be91a2e907ab99a171811d86a9a", size = 188729, upload-time = "2026-04-12T21:44:28.99Z" }, + { url = "https://files.pythonhosted.org/packages/85/7d/1e29a319d678d6cb962ae5bdf32a6858ebdf38f73bc654c0e9c742a0c2c8/msgspec-0.21.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68604db36b3b4dd9bf160e436e12798a4738848144cea1aca1cb984011eb160f", size = 219866, upload-time = "2026-04-12T21:44:31.104Z" }, + { url = "https://files.pythonhosted.org/packages/25/1f/cca084ca2572810fff12ea9dbdcbe39eac048f40daf4a9077b49fcbe8cee/msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb", size = 224993, upload-time = "2026-04-12T21:44:32.649Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/d2120fc9d419a89a3a7c13e5b7078798c4b392a96a02a6e2b3ce43a8766c/msgspec-0.21.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52c5e21930942302394429c5a582ce7e6b62c7f983b3760834c2ce107e0dd6df", size = 223535, upload-time = "2026-04-12T21:44:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/75/17/42418b66a3ad972a89bab73dd78b79cc6282bb488a25e73c853cee7443b9/msgspec-0.21.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:abbb39d65681fa24ed394e01af3d59d869068324f900c61d06062b7fb9980f2f", size = 227222, upload-time = "2026-04-12T21:44:35.093Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/265c894268cca88ff67b144ca2b4c522fc8b9a6f1966a3640c70516e78e1/msgspec-0.21.1-cp314-cp314-win_amd64.whl", hash = "sha256:5666b1b560b97b6ec2eb3fca8a502298ebac56e13bbca1f88523538ce83d01ea", size = 193810, upload-time = "2026-04-12T21:44:36.612Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8f/a6d35f25bf1fc63c492fdd88fdce01ba0875ead48c2b91f90f33653b4131/msgspec-0.21.1-cp314-cp314-win_arm64.whl", hash = "sha256:d8b8578e4c83b14ceea4cef0d0b747e31d9330fe4b03b2b2ad4063866a178f93", size = 179125, upload-time = "2026-04-12T21:44:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/c6/39/74839641e64b99d87da55af0fc472854d42b46e2183b9e2a67fe1bb2a512/msgspec-0.21.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15f523d51c00ebad412213bfe9f06f0a50ec2b93e0c19e824a2d267cabb48ea2", size = 200171, upload-time = "2026-04-12T21:44:39.414Z" }, + { url = "https://files.pythonhosted.org/packages/70/9b/ce0cca6d2d87fcd4b6ff97600790494e64f26a2c55d61507cd2755c16193/msgspec-0.21.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e47390360583ba3d5c6cb44cf0a9f61b0a06a899d3c2c00627cedebb2e2884b", size = 192879, upload-time = "2026-04-12T21:44:40.882Z" }, + { url = "https://files.pythonhosted.org/packages/a7/08/673a7bb05e5702dc787ddd3011195b509f9867927970da59052211929987/msgspec-0.21.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60800e6299b798142dc40b0644da77ceac5ea0568be58228417eae14135c847", size = 226281, upload-time = "2026-04-12T21:44:42.181Z" }, + { url = "https://files.pythonhosted.org/packages/7d/45/86508cf57283e9070b3c447e3ab25b792a7a0855a3ea4e0c6d111ac34c97/msgspec-0.21.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f8e9dfcd98419cf7568808470c4317a3fb30bef0e3715b568730a2b272a20d7", size = 229863, upload-time = "2026-04-12T21:44:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/2c/62/e7c9367cd08d590559faacd711edbae36840342843e669440363f33c7d36/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92d89dfad13bd1ea640dc3e37e724ed380da1030b272bdf5ecafb983c3ad7c75", size = 230445, upload-time = "2026-04-12T21:44:44.806Z" }, + { url = "https://files.pythonhosted.org/packages/42/b4/c0f54632103846b658a10930025f4de41c8724b5e4805a5f3b395586cb7e/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d03867786e5d7ba25d666df4b11320c27170f4aeafcb8e3a8b0a50a4fb742ca", size = 231822, upload-time = "2026-04-12T21:44:46.343Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1d/0d85cc79d0ccf5508e9c846cc66552a6a16bf92abd1dbd8362617f7b35cd/msgspec-0.21.1-cp314-cp314t-win_amd64.whl", hash = "sha256:740fbf1c9d59992ca3537d6fbe9ebbf9eaf726a65fbf31448e0ecbc710697a63", size = 206650, upload-time = "2026-04-12T21:44:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/90/91/56c5d560f20e6c20e9e4f55bd0e458f7f162aa689ee350346c04c48eac0b/msgspec-0.21.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0d2cc73df6058d811a126ac3a8ad63a4dfa210c82f9cf5a004802eaf4712de90", size = 183149, upload-time = "2026-04-12T21:44:48.833Z" }, ] [[package]] @@ -534,17 +559,18 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.5" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, ] [[package]] @@ -633,7 +659,6 @@ version = "0.1.0a6.dev0" source = { editable = "." } dependencies = [ { name = "bidict" }, - { name = "cffi" }, { name = "colorlog" }, { name = "msgspec" }, { name = "multiaddr" }, @@ -646,7 +671,6 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "greenback" }, { name = "pexpect" }, { name = "prompt-toolkit" }, { name = "psutil" }, @@ -657,10 +681,12 @@ dev = [ { name = "xonsh" }, ] devx = [ - { name = "greenback" }, { name = "stackscope" }, { name = "typing-extensions" }, ] +eventfd = [ + { name = "cffi", marker = "python_full_version < '3.14'" }, +] lint = [ { name = "ruff" }, ] @@ -670,6 +696,12 @@ repl = [ { name = "pyperclip" }, { name = "xonsh" }, ] +subints = [ + { name = "msgspec", marker = "python_full_version >= '3.14'" }, +] +sync-pause = [ + { name = "greenback", marker = "python_full_version < '3.14'" }, +] testing = [ { name = "pexpect" }, { name = "pytest" }, @@ -678,9 +710,8 @@ testing = [ [package.metadata] requires-dist = [ { name = "bidict", specifier = ">=0.23.1" }, - { name = "cffi", specifier = ">=1.17.1" }, { name = "colorlog", specifier = ">=6.8.2,<7" }, - { name = "msgspec", specifier = ">=0.21.0" }, + { name = "msgspec", specifier = ">=0.20.0" }, { name = "multiaddr", specifier = ">=0.2.0" }, { name = "pdbp", specifier = ">=1.8.2,<2" }, { name = "platformdirs", specifier = ">=4.4.0" }, @@ -691,31 +722,32 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "greenback", specifier = ">=1.2.1,<2" }, { name = "pexpect", specifier = ">=4.9.0,<5" }, { name = "prompt-toolkit", specifier = ">=3.0.50" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pyperclip", specifier = ">=1.9.0" }, - { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest", specifier = ">=9.0.3" }, { name = "stackscope", specifier = ">=0.2.2,<0.3" }, { name = "typing-extensions", specifier = ">=4.14.1" }, - { name = "xonsh", specifier = ">=0.22.2" }, + { name = "xonsh", specifier = ">=0.23.0" }, ] devx = [ - { name = "greenback", specifier = ">=1.2.1,<2" }, { name = "stackscope", specifier = ">=0.2.2,<0.3" }, { name = "typing-extensions", specifier = ">=4.14.1" }, ] +eventfd = [{ name = "cffi", marker = "python_full_version == '3.13.*'", specifier = ">=1.17.1" }] lint = [{ name = "ruff", specifier = ">=0.9.6" }] repl = [ { name = "prompt-toolkit", specifier = ">=3.0.50" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pyperclip", specifier = ">=1.9.0" }, - { name = "xonsh", specifier = ">=0.22.2" }, + { name = "xonsh", specifier = ">=0.23.0" }, ] +subints = [{ name = "msgspec", marker = "python_full_version >= '3.14'", specifier = ">=0.21.0" }] +sync-pause = [{ name = "greenback", marker = "python_full_version == '3.13.*'", specifier = ">=1.2.1,<2" }] testing = [ { name = "pexpect", specifier = ">=4.9.0,<5" }, - { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest", specifier = ">=9.0.3" }, ] [[package]] @@ -794,17 +826,6 @@ version = "1.17.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, - { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, - { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" }, - { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" }, - { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" }, - { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" }, - { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" }, - { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" }, { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" }, { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" }, { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" }, @@ -832,13 +853,14 @@ wheels = [ [[package]] name = "xonsh" -version = "0.22.4" +version = "0.23.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/48/df/1fc9ed62b3d7c14612e1713e9eb7bd41d54f6ad1028a8fbb6b7cddebc345/xonsh-0.22.4.tar.gz", hash = "sha256:6be346563fec2db75778ba5d2caee155525e634e99d9cc8cc347626025c0b3fa", size = 826665, upload-time = "2026-02-17T07:53:39.424Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/77/0c4c39ad866d4ea1ef553f325d16e804d1bf1eeecc591f0e81b057aa37db/xonsh-0.23.8.tar.gz", hash = "sha256:541bb976c93a81571792644403bae8737145023da5f48d4c493909ab5c04ba0f", size = 1172271, upload-time = "2026-05-30T04:47:22.53Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/00/7cbc0c1fb64365a0a317c54ce3a151c9644eea5a509d9cbaae61c9fd1426/xonsh-0.22.4-py311-none-any.whl", hash = "sha256:38b29b29fa85aa756462d9d9bbcaa1d85478c2108da3de6cc590a69a4bcd1a01", size = 654375, upload-time = "2026-02-17T07:53:37.702Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c2/3dd498dc28d8f89cdd52e39950c5e591499ae423f61694c0bb4d03ed1d82/xonsh-0.22.4-py312-none-any.whl", hash = "sha256:4e538fac9f4c3d866ddbdeca068f0c0515469c997ed58d3bfee963878c6df5a5", size = 654300, upload-time = "2026-02-17T07:53:35.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/7d/1f9c7147518e9f03f6ce081b5bfc4f1aceb6ec5caba849024d005e41d3be/xonsh-0.22.4-py313-none-any.whl", hash = "sha256:cc5fabf0ad0c56a2a11bed1e6a43c4ec6416a5b30f24f126b8e768547c3793e2", size = 654818, upload-time = "2026-02-17T07:53:33.477Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4a/2aab8300ad218dfc7678c34d5f703f09df5681fecc6e66d48c951ef58049/xonsh-0.23.8-py311-none-any.whl", hash = "sha256:4bab3e405643df2cc78ec2cac13241471841796fe710386d2179666aae8a5f9c", size = 799846, upload-time = "2026-05-30T04:47:21.211Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/aa66ef6046f90769dd8fcb3ddca9d00282d12e3d73645abbf12f190f17cf/xonsh-0.23.8-py312-none-any.whl", hash = "sha256:c7d0f0fba0cafe0bd75bf202820aeffc74b52943fa27d98d3b4346793f6ba493", size = 799868, upload-time = "2026-05-30T04:47:19.158Z" }, + { url = "https://files.pythonhosted.org/packages/12/fe/2d757d82b57332f1c6cd3f8c168fbcf060a275895a763542255ae1c53d75/xonsh-0.23.8-py313-none-any.whl", hash = "sha256:1b7335522a6ecd63f0d84151977a7a9050874d3ecec00cf79919d0770bebb1b4", size = 800388, upload-time = "2026-05-30T04:47:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/80/96/567bb3131655ff73c821e8a030c53707ced6c8840330a859f67bbaefbd16/xonsh-0.23.8-py314-none-any.whl", hash = "sha256:2a411fc47958c6107b3e13372655d18c52be98368e2159a1910cfde77124b3b1", size = 800352, upload-time = "2026-05-30T04:47:14.812Z" }, ] [[package]]