Merge pull request #461 from goodboy/tooling_skills_n_config_from_mtf_dev
Tooling skills n config from mtf devtrionics_start_or_cancel
commit
eb3e2b3574
|
|
@ -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/<pid>/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/<uid>/tractor/<session_uuid>/` — 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.
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(date *)",
|
|
||||||
"Bash(cp .claude/*)",
|
"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 diff *)",
|
||||||
"Bash(git log *)",
|
"Bash(git log *)",
|
||||||
"Bash(git status)",
|
"Bash(git status)",
|
||||||
|
|
@ -23,14 +31,12 @@
|
||||||
"Bash(UV_PROJECT_ENVIRONMENT=py* uv sync:*)",
|
"Bash(UV_PROJECT_ENVIRONMENT=py* uv sync:*)",
|
||||||
"Bash(UV_PROJECT_ENVIRONMENT=py* uv run:*)",
|
"Bash(UV_PROJECT_ENVIRONMENT=py* uv run:*)",
|
||||||
"Bash(echo EXIT:$?:*)",
|
"Bash(echo EXIT:$?:*)",
|
||||||
"Write(.claude/*commit_msg*)",
|
"Bash(echo \"EXIT=$?\")",
|
||||||
"Write(.claude/git_commit_msg_LATEST.md)",
|
"Read(/tmp/**)"
|
||||||
"Skill(run-tests)",
|
|
||||||
"Skill(close-wkt)",
|
|
||||||
"Skill(open-wkt)",
|
|
||||||
"Skill(prompt-io)"
|
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
}
|
},
|
||||||
|
"prefersReducedMotion": false,
|
||||||
|
"outputStyle": "default"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,3 +229,69 @@ Unlike asyncio, trio allows checkpoints in
|
||||||
that does `await` can itself be cancelled (e.g.
|
that does `await` can itself be cancelled (e.g.
|
||||||
by nursery shutdown). Watch for cleanup code that
|
by nursery shutdown). Watch for cleanup code that
|
||||||
assumes it will run to completion.
|
assumes it will run to completion.
|
||||||
|
|
||||||
|
### Unbounded waits in cleanup paths
|
||||||
|
|
||||||
|
Any `await <event>.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.**
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,26 @@ allowed-tools:
|
||||||
- Bash(python -m pytest *)
|
- Bash(python -m pytest *)
|
||||||
- Bash(python -c *)
|
- Bash(python -c *)
|
||||||
- Bash(python --version *)
|
- 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 sync *)
|
||||||
|
- Bash(UV_PROJECT_ENVIRONMENT=py* uv pip show *)
|
||||||
|
- Bash(git rev-parse *)
|
||||||
- Bash(ls *)
|
- Bash(ls *)
|
||||||
- Bash(cat *)
|
- 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
|
- Read
|
||||||
- Grep
|
- Grep
|
||||||
- Glob
|
- Glob
|
||||||
- Task
|
- Task
|
||||||
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
Run the `tractor` test suite using `pytest`. Follow this
|
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"
|
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)
|
||||||
|
|
||||||
### Worktree venv detection
|
**Always verify a `uv` venv is active before running
|
||||||
|
`python` or `pytest`.** This project uses
|
||||||
|
`UV_PROJECT_ENVIRONMENT=py<MINOR>` naming (e.g.
|
||||||
|
`py313`) — never `.venv`.
|
||||||
|
|
||||||
If running inside a git worktree (`git rev-parse
|
### Step 1: detect active venv
|
||||||
--git-common-dir` differs from `--git-dir`), verify
|
|
||||||
the Python being used is from the **worktree's own
|
Run this check first:
|
||||||
venv**, not the main repo's. Check:
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
**Case A — venv is active** (`VIRTUAL_ENV` is set
|
||||||
|
and points to a `py<MINOR>/` dir under the project
|
||||||
|
root or worktree):
|
||||||
|
|
||||||
|
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<MINOR> uv sync`,
|
||||||
|
> or would you prefer to activate your shell venv
|
||||||
|
> first?"
|
||||||
|
|
||||||
|
Options:
|
||||||
|
1. **"Create/sync venv"** — run
|
||||||
|
`UV_PROJECT_ENVIRONMENT=py<MINOR> uv sync` where
|
||||||
|
`<MINOR>` is detected from `python --version`
|
||||||
|
(e.g. `313` for 3.13). Then use
|
||||||
|
`py<MINOR>/bin/python` for all subsequent
|
||||||
|
commands in this session.
|
||||||
|
2. **"I'll activate it myself"** — stop and let the
|
||||||
|
user `source py<MINOR>/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
|
```sh
|
||||||
python -c "import tractor; print(tractor.__file__)"
|
python -c "import tractor; print(tractor.__file__)"
|
||||||
```
|
```
|
||||||
|
|
||||||
If the path points outside the worktree (e.g. to
|
If the path points outside the worktree, create a
|
||||||
the main repo), set up a local venv first:
|
worktree-local venv:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
UV_PROJECT_ENVIRONMENT=py<MINOR> uv sync
|
UV_PROJECT_ENVIRONMENT=py<MINOR> uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
where `<MINOR>` matches the active cpython minor
|
Then use `py<MINOR>/bin/python` for all commands.
|
||||||
version (detect via `python --version`, e.g.
|
|
||||||
`py313` for 3.13, `py314` for 3.14). Then use
|
|
||||||
`py<MINOR>/bin/python` for all subsequent commands.
|
|
||||||
|
|
||||||
**Why this matters**: without a worktree-local venv,
|
**Why this matters**: without the correct venv,
|
||||||
subprocesses spawned by tractor resolve modules from
|
subprocesses spawned by tractor resolve modules
|
||||||
the main repo's editable install, causing spurious
|
from the wrong editable install, causing spurious
|
||||||
`AttributeError` / `ModuleNotFoundError` for code
|
`AttributeError` / `ModuleNotFoundError`.
|
||||||
that only exists on the worktree's branch.
|
|
||||||
|
|
||||||
### Import + collection checks
|
### Fallback: `uv run`
|
||||||
|
|
||||||
Always run these, especially after refactors or
|
If the user can't or won't activate a venv, all
|
||||||
module moves — they catch import errors instantly:
|
`python` and `pytest` commands can be prefixed
|
||||||
|
with `UV_PROJECT_ENVIRONMENT=py<MINOR> 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
|
```sh
|
||||||
# 1. package import smoke check
|
# 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
|
If either fails, fix the import error before running
|
||||||
any actual tests.
|
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:(("<name>",pid=NNNN,...))`
|
||||||
|
# then (same SIGINT-first ladder):
|
||||||
|
# kill -INT <NNNN>; sleep 1; kill -9 <NNNN> 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
|
## 4. Run and report
|
||||||
|
|
||||||
- Run the constructed command.
|
- 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
|
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
|
```sh
|
||||||
python -m pytest --lf -x --tb=short --no-header
|
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`,
|
**Rule of thumb**: if a test fails with `TooSlowError`,
|
||||||
`trio.TooSlowError`, or `pexpect.TIMEOUT` and you didn't
|
`trio.TooSlowError`, or `pexpect.TIMEOUT` and you didn't
|
||||||
touch the relevant code path, it's flaky — skip it.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,12 @@ jobs:
|
||||||
run: uv build --sdist --python=3.13
|
run: uv build --sdist --python=3.13
|
||||||
|
|
||||||
- name: Install sdist from .tar.gz
|
- 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 ------
|
# ------ type-check ------
|
||||||
# mypy:
|
# mypy:
|
||||||
|
|
@ -148,9 +153,12 @@ jobs:
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: >
|
run: >
|
||||||
uv run
|
uv run
|
||||||
pytest tests/ -rsx
|
pytest
|
||||||
|
tests/
|
||||||
|
-rsx
|
||||||
--spawn-backend=${{ matrix.spawn_backend }}
|
--spawn-backend=${{ matrix.spawn_backend }}
|
||||||
--tpt-proto=${{ matrix.tpt_proto }}
|
--tpt-proto=${{ matrix.tpt_proto }}
|
||||||
|
--capture=fd
|
||||||
|
|
||||||
# XXX legacy NOTE XXX
|
# XXX legacy NOTE XXX
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -106,46 +106,55 @@ venv.bak/
|
||||||
# all files under
|
# all files under
|
||||||
.git/
|
.git/
|
||||||
|
|
||||||
# any commit-msg gen tmp files
|
# require very explicit staging for anything we **really**
|
||||||
.claude/skills/commit-msg/msgs/
|
# want put/kept in repo.
|
||||||
.claude/git_commit_msg_LATEST.md
|
notes_to_self/
|
||||||
.claude/*_commit_*.md
|
snippets/
|
||||||
.claude/*_commit*.toml
|
|
||||||
.claude/*_commit*.txt
|
|
||||||
.claude/skills/commit-msg/msgs/*
|
|
||||||
|
|
||||||
.claude/skills/pr-msg/msgs/*
|
# ------- AI shiz -------
|
||||||
# XXX, for rn, so i can telescope this file.
|
# `ai.skillz` symlinks,
|
||||||
!/.claude/skills/pr-msg/pr_msg_LATEST.md
|
# (machine-local, deploy via deploy-skill.sh)
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# ai.skillz symlinks (machine-local, deploy via deploy-skill.sh)
|
|
||||||
.claude/skills/py-codestyle
|
.claude/skills/py-codestyle
|
||||||
.claude/skills/code-review-changes
|
|
||||||
.claude/skills/close-wkt
|
.claude/skills/close-wkt
|
||||||
.claude/skills/open-wkt
|
|
||||||
.claude/skills/plan-io
|
.claude/skills/plan-io
|
||||||
.claude/skills/prompt-io
|
.claude/skills/prompt-io
|
||||||
.claude/skills/resolve-conflicts
|
.claude/skills/resolve-conflicts
|
||||||
.claude/skills/inter-skill-review
|
.claude/skills/inter-skill-review
|
||||||
.claude/skills/yt-url-lookup
|
|
||||||
|
|
||||||
# hybrid skills — symlinked SKILL.md + references
|
# /open-wkt specifics
|
||||||
.claude/skills/commit-msg/SKILL.md
|
.claude/skills/open-wkt
|
||||||
.claude/skills/pr-msg/SKILL.md
|
.claude/wkts/
|
||||||
.claude/skills/pr-msg/references
|
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
|
||||||
|
|
||||||
|
# /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
|
||||||
|
|
||||||
|
# /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
|
||||||
|
|
||||||
|
# use prompt-io instead?
|
||||||
|
.claude/plans
|
||||||
|
|
||||||
# nix develop --profile .nixdev
|
# nix develop --profile .nixdev
|
||||||
.nixdev*
|
.nixdev*
|
||||||
|
|
||||||
# :Obsession .
|
# :Obsession .
|
||||||
Session.vim
|
Session.vim
|
||||||
|
|
||||||
# `gish` local `.md`-files
|
# `gish` local `.md`-files
|
||||||
# TODO? better all around automation!
|
# TODO? better all around automation!
|
||||||
# -[ ] it'd be handy to also commit and sync with wtv git service?
|
# -[ ] it'd be handy to also commit and sync with wtv git service?
|
||||||
|
|
@ -159,7 +168,3 @@ gh/
|
||||||
|
|
||||||
# LLM conversations that should remain private
|
# LLM conversations that should remain private
|
||||||
docs/conversations/
|
docs/conversations/
|
||||||
|
|
||||||
# Claude worktrees
|
|
||||||
.claude/wkts/
|
|
||||||
claude_wkts
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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:<lvl>` 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`).
|
||||||
|
|
@ -9,7 +9,7 @@ name = "tractor"
|
||||||
version = "0.1.0a6dev0"
|
version = "0.1.0a6dev0"
|
||||||
description = 'structured concurrent `trio`-"actors"'
|
description = 'structured concurrent `trio`-"actors"'
|
||||||
authors = [{ name = "Tyler Goodlet", email = "goodboy_foss@protonmail.com" }]
|
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"
|
readme = "docs/README.rst"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
keywords = [
|
keywords = [
|
||||||
|
|
@ -29,8 +29,8 @@ classifiers = [
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.12",
|
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3.14",
|
||||||
"Topic :: System :: Distributed Computing",
|
"Topic :: System :: Distributed Computing",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
@ -43,11 +43,12 @@ dependencies = [
|
||||||
"tricycle>=0.4.1,<0.5",
|
"tricycle>=0.4.1,<0.5",
|
||||||
"wrapt>=1.16.0,<2",
|
"wrapt>=1.16.0,<2",
|
||||||
"colorlog>=6.8.2,<7",
|
"colorlog>=6.8.2,<7",
|
||||||
|
|
||||||
# built-in multi-actor `pdb` REPL
|
# built-in multi-actor `pdb` REPL
|
||||||
"pdbp>=1.8.2,<2", # windows only (from `pdbp`)
|
"pdbp>=1.8.2,<2", # windows only (from `pdbp`)
|
||||||
|
|
||||||
# typed IPC msging
|
# typed IPC msging
|
||||||
"msgspec>=0.21.0",
|
"msgspec>=0.20.0",
|
||||||
"cffi>=1.17.1",
|
|
||||||
"bidict>=0.23.1",
|
"bidict>=0.23.1",
|
||||||
"multiaddr>=0.2.0",
|
"multiaddr>=0.2.0",
|
||||||
"platformdirs>=4.4.0",
|
"platformdirs>=4.4.0",
|
||||||
|
|
@ -63,27 +64,44 @@ dev = [
|
||||||
]
|
]
|
||||||
devx = [
|
devx = [
|
||||||
# `tractor.devx` tooling
|
# `tractor.devx` tooling
|
||||||
"greenback>=1.2.1,<2",
|
|
||||||
"stackscope>=0.2.2,<0.3",
|
"stackscope>=0.2.2,<0.3",
|
||||||
# ^ requires this?
|
# ^ requires this?
|
||||||
"typing-extensions>=4.14.1",
|
"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 = [
|
testing = [
|
||||||
# test suite
|
# test suite
|
||||||
# TODO: maybe some of these layout choices?
|
# TODO: maybe some of these layout choices?
|
||||||
# https://docs.pytest.org/en/8.0.x/explanation/goodpractices.html#choosing-a-test-layout-import-rules
|
# 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",
|
"pexpect>=4.9.0,<5",
|
||||||
]
|
]
|
||||||
repl = [
|
repl = [
|
||||||
"pyperclip>=1.9.0",
|
"pyperclip>=1.9.0",
|
||||||
"prompt-toolkit>=3.0.50",
|
"prompt-toolkit>=3.0.50",
|
||||||
"xonsh>=0.22.2",
|
"xonsh>=0.23.0",
|
||||||
"psutil>=7.0.0",
|
"psutil>=7.0.0",
|
||||||
]
|
]
|
||||||
lint = [
|
lint = [
|
||||||
"ruff>=0.9.6"
|
"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
|
# TODO, add these with sane versions; were originally in
|
||||||
# `requirements-docs.txt`..
|
# `requirements-docs.txt`..
|
||||||
# docs = [
|
# docs = [
|
||||||
|
|
@ -92,10 +110,26 @@ lint = [
|
||||||
# ]
|
# ]
|
||||||
# ------ dependency-groups ------
|
# ------ 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]
|
[tool.uv.sources]
|
||||||
# XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)`
|
# XXX NOTE, only for @goodboy's hacking on `pprint(sort_dicts=False)`
|
||||||
# for the `pp` alias..
|
# 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]
|
# [tool.uv.sources.pdbp]
|
||||||
# XXX, in case we need to tmp patch again.
|
# XXX, in case we need to tmp patch again.
|
||||||
|
|
@ -164,6 +198,35 @@ all_bullets = true
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = '6.0'
|
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
|
# https://docs.pytest.org/en/stable/reference/reference.html#configuration-options
|
||||||
testpaths = [
|
testpaths = [
|
||||||
'tests'
|
'tests'
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ def test_pause_from_sync(
|
||||||
`examples/debugging/sync_bp.py`
|
`examples/debugging/sync_bp.py`
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
# XXX required for `breakpoint()` overload and
|
||||||
|
# thus`tractor.devx.pause_from_sync()`.
|
||||||
|
pytest.importorskip('greenback')
|
||||||
child = spawn('sync_bp')
|
child = spawn('sync_bp')
|
||||||
|
|
||||||
# first `sync_pause()` after nurseries open
|
# first `sync_pause()` after nurseries open
|
||||||
|
|
@ -260,6 +263,9 @@ def test_sync_pause_from_aio_task(
|
||||||
`examples/debugging/asycio_bp.py`
|
`examples/debugging/asycio_bp.py`
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
# XXX required for `breakpoint()` overload and
|
||||||
|
# thus`tractor.devx.pause_from_sync()`.
|
||||||
|
pytest.importorskip('greenback')
|
||||||
child = spawn('asyncio_bp')
|
child = spawn('asyncio_bp')
|
||||||
|
|
||||||
# RACE on whether trio/asyncio task bps first
|
# RACE on whether trio/asyncio task bps first
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,10 @@ def test_breakpoint_hook_restored(
|
||||||
calls used.
|
calls used.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
# XXX required for `breakpoint()` overload and
|
||||||
|
# thus`tractor.devx.pause_from_sync()`.
|
||||||
|
pytest.importorskip('greenback')
|
||||||
child = spawn('restore_builtin_breakpoint')
|
child = spawn('restore_builtin_breakpoint')
|
||||||
|
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
try:
|
try:
|
||||||
assert_before(
|
assert_before(
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,22 @@ import tractor
|
||||||
from tractor._testing import tractor_test
|
from tractor._testing import tractor_test
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.trio
|
def test_no_runtime():
|
||||||
async def test_no_runtime():
|
'''
|
||||||
"""A registrar must be established before any nurseries
|
A registrar must be established before any nurseries
|
||||||
can be created.
|
can be created.
|
||||||
|
|
||||||
(In other words ``tractor.open_root_actor()`` must be
|
(In other words ``tractor.open_root_actor()`` must be
|
||||||
engaged at some point?)
|
engaged at some point?)
|
||||||
"""
|
|
||||||
with pytest.raises(RuntimeError) :
|
'''
|
||||||
|
async def main():
|
||||||
async with tractor.find_actor('doggy'):
|
async with tractor.find_actor('doggy'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
with pytest.raises(tractor._exceptions.NoRuntime):
|
||||||
|
trio.run(main)
|
||||||
|
|
||||||
|
|
||||||
@tractor_test
|
@tractor_test
|
||||||
async def test_self_is_registered(reg_addr):
|
async def test_self_is_registered(reg_addr):
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ import trio
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
|
# XXX `cffi` dun build on py3.14 yet..
|
||||||
|
pytest.importorskip("cffi")
|
||||||
|
|
||||||
from tractor.ipc._ringbuf import (
|
from tractor.ipc._ringbuf import (
|
||||||
open_ringbuf,
|
open_ringbuf,
|
||||||
RBToken,
|
RBToken,
|
||||||
|
|
@ -14,7 +18,7 @@ from tractor._testing.samples import (
|
||||||
generate_sample_messages,
|
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
|
pytestmark = pytest.mark.skip
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
202
uv.lock
202
uv.lock
|
|
@ -1,6 +1,10 @@
|
||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
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]]
|
[[package]]
|
||||||
name = "async-generator"
|
name = "async-generator"
|
||||||
|
|
@ -44,18 +48,6 @@ version = "1.0.8"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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" }
|
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 = [
|
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/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/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" },
|
{ 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/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/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/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]]
|
[[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" }
|
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 = [
|
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/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/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" },
|
{ 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"
|
version = "1.2.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "greenlet" },
|
{ name = "greenlet", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "outcome" },
|
{ name = "outcome", marker = "python_full_version < '3.14'" },
|
||||||
{ name = "sniffio" },
|
{ 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" }
|
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 = [
|
wheels = [
|
||||||
|
|
@ -165,15 +170,6 @@ version = "3.1.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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" }
|
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 = [
|
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/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/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" },
|
{ 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" }
|
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" }
|
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 = [
|
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/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/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" },
|
{ 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/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/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/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]]
|
[[package]]
|
||||||
|
|
@ -281,14 +298,6 @@ version = "0.21.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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" }
|
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 = [
|
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/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/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" },
|
{ 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/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/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/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]]
|
[[package]]
|
||||||
|
|
@ -534,17 +559,18 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.5"
|
version = "9.1.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
{ name = "iniconfig" },
|
{ name = "iniconfig" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
{ name = "pluggy" },
|
{ 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 = [
|
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]]
|
[[package]]
|
||||||
|
|
@ -633,7 +659,6 @@ version = "0.1.0a6.dev0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "bidict" },
|
{ name = "bidict" },
|
||||||
{ name = "cffi" },
|
|
||||||
{ name = "colorlog" },
|
{ name = "colorlog" },
|
||||||
{ name = "msgspec" },
|
{ name = "msgspec" },
|
||||||
{ name = "multiaddr" },
|
{ name = "multiaddr" },
|
||||||
|
|
@ -646,7 +671,6 @@ dependencies = [
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "greenback" },
|
|
||||||
{ name = "pexpect" },
|
{ name = "pexpect" },
|
||||||
{ name = "prompt-toolkit" },
|
{ name = "prompt-toolkit" },
|
||||||
{ name = "psutil" },
|
{ name = "psutil" },
|
||||||
|
|
@ -657,10 +681,12 @@ dev = [
|
||||||
{ name = "xonsh" },
|
{ name = "xonsh" },
|
||||||
]
|
]
|
||||||
devx = [
|
devx = [
|
||||||
{ name = "greenback" },
|
|
||||||
{ name = "stackscope" },
|
{ name = "stackscope" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
|
eventfd = [
|
||||||
|
{ name = "cffi", marker = "python_full_version < '3.14'" },
|
||||||
|
]
|
||||||
lint = [
|
lint = [
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
]
|
]
|
||||||
|
|
@ -670,6 +696,12 @@ repl = [
|
||||||
{ name = "pyperclip" },
|
{ name = "pyperclip" },
|
||||||
{ name = "xonsh" },
|
{ name = "xonsh" },
|
||||||
]
|
]
|
||||||
|
subints = [
|
||||||
|
{ name = "msgspec", marker = "python_full_version >= '3.14'" },
|
||||||
|
]
|
||||||
|
sync-pause = [
|
||||||
|
{ name = "greenback", marker = "python_full_version < '3.14'" },
|
||||||
|
]
|
||||||
testing = [
|
testing = [
|
||||||
{ name = "pexpect" },
|
{ name = "pexpect" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
|
|
@ -678,9 +710,8 @@ testing = [
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "bidict", specifier = ">=0.23.1" },
|
{ name = "bidict", specifier = ">=0.23.1" },
|
||||||
{ name = "cffi", specifier = ">=1.17.1" },
|
|
||||||
{ name = "colorlog", specifier = ">=6.8.2,<7" },
|
{ 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 = "multiaddr", specifier = ">=0.2.0" },
|
||||||
{ name = "pdbp", specifier = ">=1.8.2,<2" },
|
{ name = "pdbp", specifier = ">=1.8.2,<2" },
|
||||||
{ name = "platformdirs", specifier = ">=4.4.0" },
|
{ name = "platformdirs", specifier = ">=4.4.0" },
|
||||||
|
|
@ -691,31 +722,32 @@ requires-dist = [
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "greenback", specifier = ">=1.2.1,<2" },
|
|
||||||
{ name = "pexpect", specifier = ">=4.9.0,<5" },
|
{ name = "pexpect", specifier = ">=4.9.0,<5" },
|
||||||
{ name = "prompt-toolkit", specifier = ">=3.0.50" },
|
{ name = "prompt-toolkit", specifier = ">=3.0.50" },
|
||||||
{ name = "psutil", specifier = ">=7.0.0" },
|
{ name = "psutil", specifier = ">=7.0.0" },
|
||||||
{ name = "pyperclip", specifier = ">=1.9.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 = "stackscope", specifier = ">=0.2.2,<0.3" },
|
||||||
{ name = "typing-extensions", specifier = ">=4.14.1" },
|
{ name = "typing-extensions", specifier = ">=4.14.1" },
|
||||||
{ name = "xonsh", specifier = ">=0.22.2" },
|
{ name = "xonsh", specifier = ">=0.23.0" },
|
||||||
]
|
]
|
||||||
devx = [
|
devx = [
|
||||||
{ name = "greenback", specifier = ">=1.2.1,<2" },
|
|
||||||
{ name = "stackscope", specifier = ">=0.2.2,<0.3" },
|
{ name = "stackscope", specifier = ">=0.2.2,<0.3" },
|
||||||
{ name = "typing-extensions", specifier = ">=4.14.1" },
|
{ 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" }]
|
lint = [{ name = "ruff", specifier = ">=0.9.6" }]
|
||||||
repl = [
|
repl = [
|
||||||
{ name = "prompt-toolkit", specifier = ">=3.0.50" },
|
{ name = "prompt-toolkit", specifier = ">=3.0.50" },
|
||||||
{ name = "psutil", specifier = ">=7.0.0" },
|
{ name = "psutil", specifier = ">=7.0.0" },
|
||||||
{ name = "pyperclip", specifier = ">=1.9.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 = [
|
testing = [
|
||||||
{ name = "pexpect", specifier = ">=4.9.0,<5" },
|
{ name = "pexpect", specifier = ">=4.9.0,<5" },
|
||||||
{ name = "pytest", specifier = ">=8.3.5" },
|
{ name = "pytest", specifier = ">=9.0.3" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -794,17 +826,6 @@ version = "1.17.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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" }
|
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 = [
|
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/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/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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "xonsh"
|
name = "xonsh"
|
||||||
version = "0.22.4"
|
version = "0.23.8"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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 = [
|
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/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/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/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/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/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]]
|
[[package]]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue