Initial commit
This commit is contained in:
commit
6fdfde8dac
57 changed files with 18525 additions and 0 deletions
18
.env.example
Normal file
18
.env.example
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Dashboard configuration — copy to .env and adjust. All vars use the DASH_ prefix.
|
||||
|
||||
# Bind address and port. Keep 127.0.0.1 unless you know what you are doing:
|
||||
# the app has NO authentication.
|
||||
DASH_HOST=127.0.0.1
|
||||
DASH_PORT=8501
|
||||
|
||||
# Sampling and history (in-memory only, lost on restart).
|
||||
# retention_minutes * 60 / sample_interval = number of stored samples per series.
|
||||
DASH_SAMPLE_INTERVAL=2.0
|
||||
DASH_RETENTION_MINUTES=60
|
||||
|
||||
# llama.cpp plugin: point at your llama-server (router mode) instance.
|
||||
DASH_LLAMA_BASE_URL=http://127.0.0.1:8080
|
||||
# Only needed if llama-server runs with --api-key.
|
||||
DASH_LLAMA_API_KEY=
|
||||
# Timeout in seconds for /v1/models and load/unload requests.
|
||||
DASH_LLAMA_TIMEOUT=4.0
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.14
|
||||
85
AGENTS.md
Normal file
85
AGENTS.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# AGENTS.md
|
||||
|
||||
Guidance for AI coding agents working in this repository.
|
||||
|
||||
## What this is
|
||||
|
||||
A single-host computer dashboard and task manager: FastAPI (Python ≥ 3.12,
|
||||
managed with `uv`) backend serving htmx + Chart.js HTML fragments to one
|
||||
browser page. Binds to `127.0.0.1:8501`, no auth by design (see the README
|
||||
security section). Tabs: Overview, Disks, Processes, Journal, Services
|
||||
(systemd), Plugins (llama.cpp). Licensed under the Unlicense (see `LICENSE`).
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
uv sync # install dependencies
|
||||
uv run python main.py # run the server on http://127.0.0.1:8501
|
||||
```
|
||||
|
||||
There is no test suite. Verify changes with:
|
||||
|
||||
```sh
|
||||
uv run python -m compileall -q app
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8501/api/overview
|
||||
# ... other endpoints: /api/disks /api/processes /api/journal /api/services
|
||||
# /api/plugins /api/history
|
||||
grep -c Traceback /tmp/dash.log
|
||||
```
|
||||
|
||||
### Restarting the dev server
|
||||
|
||||
The server usually runs detached in the background. To restart it:
|
||||
|
||||
```sh
|
||||
PID=$(pgrep -f "python main\.py" | head -1)
|
||||
[ -n "$PID" ] && kill "$PID"
|
||||
setsid nohup uv run python main.py > /tmp/dash.log 2>&1 < /dev/null & disown
|
||||
```
|
||||
|
||||
Never use `pkill -f "uv run python main.py"` — the pattern also matches the
|
||||
agent's own shell command line and kills the session.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `app/collect/*` — collectors (cpu/mem/gpu/disks/procs/net/power) read
|
||||
psutil + sysfs; `app/sampling.py` runs them every `DASH_SAMPLE_INTERVAL`
|
||||
(default 2 s) into an in-memory ring buffer (`app/state.py`).
|
||||
- `app/routers/*` — each tab endpoint is an idempotent GET returning an htmx
|
||||
HTML fragment; templates live in `templates/` and self-poll via
|
||||
`hx-get` + `hx-trigger="every Ns"` + `hx-swap="outerHTML"`.
|
||||
- `templates/*.html` auto-reload on file change — no restart needed for
|
||||
template-only edits. Python changes require a restart.
|
||||
- `app/systemd/units.py` — systemd unit listing/detail/actions;
|
||||
`app/journal.py` — `journalctl -o export` parser with cursors.
|
||||
- `app/plugins/` — `base.Plugin` + llamacpp plugin (talks to a router-mode
|
||||
`llama-server` on port 8080).
|
||||
|
||||
## Conventions
|
||||
|
||||
- No code comments (the codebase has none).
|
||||
- basedpyright is configured as linter
|
||||
- Match surrounding style; keep functions small and typed where the codebase already is.
|
||||
- Keep polling endpoints cheap: collectors may cache lookups (unit names,
|
||||
enabled-state maps, SSID, temperature paths) with short TTLs.
|
||||
|
||||
## Hard-won pitfalls
|
||||
|
||||
- Jinja autoescape renders `↓` as literal text — use literal unicode
|
||||
(e.g. `↓`) in templates.
|
||||
- `journalctl -o export` output contains NUL bytes (grep treats it as
|
||||
binary); journalctl rejects negated matches (`!`/`!=`) — filter entries in
|
||||
Python instead.
|
||||
- psutil gotchas: there is no `psutil.AF_INET` (use `socket`);
|
||||
`net_if_addrs()` / `net_if_stats()` take no arguments;
|
||||
`sensors_battery().power_plugged` can be `None` — use
|
||||
`/sys/class/power_supply/*` (type/capacity/status/online) for battery + AC
|
||||
state.
|
||||
- `iw dev <if> link` prints `SSID: name` **unquoted**; the working regex is
|
||||
`SSID:\s+(\S.*)` (a `$` anchor fails without MULTILINE).
|
||||
- `/api/history` pads series with `null` for samples missing a key so all
|
||||
series stay aligned with the timestamps — keep that behaviour if you touch
|
||||
it.
|
||||
- AMD sysfs: GPU busy/VRAM/temp under
|
||||
`/sys/class/drm/card*/device` (+ `hwmon`), CPU temp from the `k10temp`
|
||||
hwmon (fallback `acpitz` thermal zone), both in millidegrees.
|
||||
24
LICENSE
Normal file
24
LICENSE
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
144
README.md
Normal file
144
README.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# dashboard
|
||||
|
||||
A single-host computer dashboard and combined task manager: live system
|
||||
metrics, disk IO, process explorer, journal tail, and a systemd unit manager —
|
||||
all in one browser page. Built with **FastAPI** (Python, managed by [uv]) and
|
||||
**htmx** + **Chart.js** on the front end (no build step, JS is vendored).
|
||||
|
||||
## Screenshots
|
||||
|
||||
| Overview | Disks |
|
||||
| --------------- | --------------- |
|
||||
|  |  |
|
||||
| Processes | Journal |
|
||||
|  |  |
|
||||
| Services | Plugins |
|
||||
|  |  |
|
||||
|
||||
## Authorship
|
||||
|
||||
This code was written by **Qwen3.8-27B** (an LLM) and corrected by
|
||||
Johannes Schriewer <hallo@dunkelstern.de>.
|
||||
|
||||
If you want to see the OpenCode transcripts see the `opencode` sub-directory.
|
||||
I will include all transcripts when changing the software there.
|
||||
|
||||
## ⚠️ Security
|
||||
|
||||
This app has **no authentication and no TLS**. It binds to `127.0.0.1` by
|
||||
default and is meant to be used from the machine itself. To use it remotely,
|
||||
tunnel it — e.g. `ssh -L 8501:127.0.0.1:8501 user@host` and open
|
||||
`http://127.0.0.1:8501` — or serve it through your own authenticated reverse
|
||||
proxy / VPN. Do **not** point `DASH_HOST` at a public interface without adding
|
||||
authentication yourself.
|
||||
|
||||
The Services tab can start/stop/enable/disable systemd units; any user who can
|
||||
reach the dashboard can do that (the app uses `sudo` for privileged actions,
|
||||
so the dashboard user needs sudo rights for `systemctl`).
|
||||
|
||||
## Quick start
|
||||
|
||||
Requirements: Python ≥ 3.12 and [uv]. On Linux (it is tested on Arch).
|
||||
|
||||
```sh
|
||||
uv sync
|
||||
uv run python main.py
|
||||
```
|
||||
|
||||
Then open <http://127.0.0.1:8501>.
|
||||
|
||||
## Configuration
|
||||
|
||||
All settings are environment variables with the `DASH_` prefix; a `.env` file
|
||||
in the project root is read automatically (see `.env.example`).
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
| ------------------------ | ----------------------- | ------------------------------------------------ |
|
||||
| `DASH_HOST` | `127.0.0.1` | Bind address |
|
||||
| `DASH_PORT` | `8501` | Port |
|
||||
| `DASH_SAMPLE_INTERVAL` | `2.0` | Seconds between samples |
|
||||
| `DASH_RETENTION_MINUTES` | `60` | In-memory history window (lost on restart) |
|
||||
| `DASH_LLAMA_BASE_URL` | `http://127.0.0.1:8080` | llama-server (router mode) base URL |
|
||||
| `DASH_LLAMA_API_KEY` | *(empty)* | Set if llama-server runs with `--api-key` |
|
||||
| `DASH_LLAMA_TIMEOUT` | `4.0` | Seconds for llama-server requests |
|
||||
|
||||
## Tabs
|
||||
|
||||
- **Overview** — CPU + GPU (utilisation, CPU temperature, VRAM, GPU temperature
|
||||
when exposed by sysfs/hwmon), RAM + Swap + VRAM, active Wi-Fi connection
|
||||
(SSID via `iw`) and IP addresses of all up interfaces. History charts
|
||||
(CPU/GPU, memory/VRAM, disk I/O) are in-memory, sampled every
|
||||
`DASH_SAMPLE_INTERVAL` for `DASH_RETENTION_MINUTES`.
|
||||
- **Disks** — partitions with size/use and per-device read/write rates.
|
||||
- **Processes** — live table, filterable and sortable by CPU, memory, RSS, GPU
|
||||
and IO columns.
|
||||
- **Journal** — streaming `journalctl` tail with level filter, unit filter,
|
||||
free-text search and a "hide sudo" toggle (on by default, since the
|
||||
dashboard's own `sudo systemctl`/`journalctl` calls otherwise log noise).
|
||||
Uses journal cursors, so filtering does not re-dump history.
|
||||
- **Services** — all systemd service units with state and enabled-ness,
|
||||
sortable by name, state and enabled-ness; click a
|
||||
name for details (main PID, start time, restarts, recent journal lines) and
|
||||
run `start` / `stop` / `restart` / `enable` / `disable` actions.
|
||||
- **Plugins** — currently **llama.cpp**: model status, load/unload buttons and
|
||||
a rescan for a `llama-server` running in router mode.
|
||||
|
||||
### llama.cpp router mode
|
||||
|
||||
The plugin talks to a `llama-server` started with a models directory (router
|
||||
mode), which exposes the native `/models`, `/models/load` and `/models/unload`
|
||||
endpoints:
|
||||
|
||||
```sh
|
||||
llama-server --models-dir /path/to/your/models --host 127.0.0.1 --port 8080
|
||||
```
|
||||
|
||||
The plugin polls `GET /health` and `GET /models`, posts `{"model": id}` to
|
||||
`/models/load` and `/models/unload`, and rescans with `GET /models?reload=1`.
|
||||
If the server is down the plugin shows *unreachable* and the rest of the
|
||||
dashboard keeps working.
|
||||
|
||||
## Running as a systemd service
|
||||
|
||||
A ready-made unit is in [`deploy/dashboard.service`](deploy/dashboard.service):
|
||||
|
||||
You have to edit that file to point to the checkout of the tool and change the user and group!
|
||||
|
||||
```sh
|
||||
cp deploy/dashboard.service /etc/systemd/system/dashboard.service
|
||||
# adjust User= and paths if needed
|
||||
uv sync # once, after changing dependencies
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now dashboard
|
||||
journalctl -u dashboard -f
|
||||
```
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
main.py # uvicorn entry point
|
||||
app/
|
||||
config.py # pydantic-settings (DASH_* env)
|
||||
main.py # app factory, lifespan sampler
|
||||
sampling.py # background sampler task
|
||||
state.py # in-memory ring buffers
|
||||
collect/ # cpu / mem / gpu / disks / procs / net collectors (psutil + sysfs)
|
||||
systemd/units.py # unit list / detail / whitelisted actions (sudo fallback)
|
||||
journal.py # journalctl -o export parser + cursors
|
||||
render.py # jinja env + filters
|
||||
routers/ # overview / disks / processes / journal / services / plugins
|
||||
plugins/ # base.Plugin + llamacpp plugin
|
||||
templates/ # htmx fragments
|
||||
static/ # css, js, vendored htmx + chart.js
|
||||
deploy/ # dashboard.service
|
||||
```
|
||||
|
||||
Adding a plugin: create a module in `app/plugins/` defining a `Plugin`
|
||||
instance (id, title, poll interval, fragment function) and register it in
|
||||
`app/plugins/__init__.py`.
|
||||
|
||||
## License
|
||||
|
||||
[The Unlicense](LICENSE) — public domain dedication, no conditions.
|
||||
|
||||
[uv]: https://docs.astral.sh/uv/
|
||||
1
app/__init__.py
Normal file
1
app/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from app import routers # noqa: F401
|
||||
3
app/collect/__init__.py
Normal file
3
app/collect/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from app.collect import cpu, disks, gpu, mem, power, procs
|
||||
|
||||
__all__ = ["cpu", "disks", "gpu", "mem", "power", "procs"]
|
||||
64
app/collect/cpu.py
Normal file
64
app/collect/cpu.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import glob
|
||||
|
||||
import psutil
|
||||
|
||||
_temp_path: str | None = None
|
||||
_temp_checked = False
|
||||
|
||||
|
||||
def _read(path: str) -> str | None:
|
||||
try:
|
||||
with open(path) as f:
|
||||
return f.read().strip()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _find_temp_path() -> str | None:
|
||||
for hwmon in sorted(glob.glob("/sys/class/hwmon/hwmon*")):
|
||||
name = (_read(f"{hwmon}/name") or "").lower()
|
||||
if name in ("k10temp", "coretemp", "cpu_thermal"):
|
||||
for t in sorted(glob.glob(f"{hwmon}/temp*_input")):
|
||||
return t
|
||||
return None
|
||||
for zone in sorted(glob.glob("/sys/class/thermal/thermal_zone*")):
|
||||
if (_read(f"{zone}/type") or "").lower() == "acpitz":
|
||||
return f"{zone}/temp"
|
||||
return None
|
||||
|
||||
|
||||
def temp() -> float | None:
|
||||
global _temp_path, _temp_checked
|
||||
if not _temp_checked:
|
||||
_temp_checked = True
|
||||
_temp_path = _find_temp_path()
|
||||
if _temp_path is None:
|
||||
return None
|
||||
v = _read(_temp_path)
|
||||
if not v:
|
||||
return None
|
||||
try:
|
||||
n = float(v)
|
||||
except ValueError:
|
||||
return None
|
||||
return round(n / 1000.0, 1)
|
||||
|
||||
|
||||
def prime() -> None:
|
||||
_ = psutil.cpu_percent(None)
|
||||
|
||||
|
||||
def core_count() -> int:
|
||||
return psutil.cpu_count(logical=True) or 1
|
||||
|
||||
|
||||
def sample() -> dict[str, float]:
|
||||
out: dict[str, float] = {"cpu": psutil.cpu_percent(None)}
|
||||
t = temp()
|
||||
if t is not None:
|
||||
out["cpu_temp"] = t
|
||||
l1, l5, l15 = psutil.getloadavg()
|
||||
out["load1"] = l1
|
||||
out["load5"] = l5
|
||||
out["load15"] = l15
|
||||
return out
|
||||
55
app/collect/disks.py
Normal file
55
app/collect/disks.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from typing import Any
|
||||
|
||||
import psutil
|
||||
from psutil._ntuples import sdiskio
|
||||
|
||||
|
||||
def counters() -> dict[str, sdiskio]:
|
||||
return psutil.disk_io_counters(perdisk=True) or {}
|
||||
|
||||
|
||||
def rates(prev: dict[str, sdiskio], dt: float) -> dict[str, float]:
|
||||
cur = counters()
|
||||
r = 0
|
||||
w = 0
|
||||
for name, c in cur.items():
|
||||
p = prev.get(name)
|
||||
if p is not None and dt > 0:
|
||||
r += max(0, int(c.read_bytes) - int(p.read_bytes))
|
||||
w += max(0, int(c.write_bytes) - int(p.write_bytes))
|
||||
return {"io_read": r / dt if dt > 0 else 0.0, "io_write": w / dt if dt > 0 else 0.0}
|
||||
|
||||
|
||||
def partitions() -> list[dict[str, Any]]:
|
||||
groups: dict[str, dict[str, Any]] = {}
|
||||
order: list[str] = []
|
||||
for p in psutil.disk_partitions(all=False):
|
||||
if p.device in groups:
|
||||
g = groups[p.device]
|
||||
if p.mountpoint not in g["mounts"]:
|
||||
g["mounts"].append(p.mountpoint)
|
||||
continue
|
||||
try:
|
||||
u = psutil.disk_usage(p.mountpoint)
|
||||
except (OSError, PermissionError):
|
||||
continue
|
||||
g = {
|
||||
"device": p.device,
|
||||
"fstype": p.fstype,
|
||||
"total": u.total,
|
||||
"used": u.used,
|
||||
"free": u.free,
|
||||
"pct": u.percent,
|
||||
"mounts": [p.mountpoint],
|
||||
}
|
||||
groups[p.device] = g
|
||||
order.append(p.device)
|
||||
out = [groups[d] for d in sorted(order)]
|
||||
for g in out:
|
||||
g["mounts"] = sorted(g["mounts"])
|
||||
mounts = g["mounts"]
|
||||
if len(mounts) > 3:
|
||||
g["mounts_disp"] = " · ".join(mounts[:3]) + f" +{len(mounts) - 3} more"
|
||||
else:
|
||||
g["mounts_disp"] = " · ".join(mounts)
|
||||
return out
|
||||
140
app/collect/gpu.py
Normal file
140
app/collect/gpu.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import glob
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
_name_cache: str | None = None
|
||||
|
||||
|
||||
def _read(path: str) -> str | None:
|
||||
try:
|
||||
with open(path) as f:
|
||||
return f.read().strip()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _shorten(name: str) -> str:
|
||||
name = re.sub(r"\s*\(rev.*\)$", "", name).strip()
|
||||
groups = re.findall(r"\[([^\]]+)\]", name)
|
||||
if len(groups) >= 2:
|
||||
brand = groups[0]
|
||||
series = groups[-1].split(" / ")[0]
|
||||
model = name.split("]", 1)[1].split("[", 1)[0].strip()
|
||||
return f"{brand} {model} ({series})".strip()
|
||||
return name[:50]
|
||||
|
||||
|
||||
def _gpu_name() -> str:
|
||||
global _name_cache
|
||||
if _name_cache is None:
|
||||
_name_cache = "GPU"
|
||||
if shutil.which("lspci"):
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["lspci"], capture_output=True, text=True, timeout=5, check=False
|
||||
).stdout
|
||||
for line in out.splitlines():
|
||||
if "VGA" in line or "3D controller" in line:
|
||||
_name_cache = _shorten(line.split(":", 2)[-1].strip())
|
||||
break
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
return _name_cache
|
||||
|
||||
|
||||
def _amd_sample() -> dict[str, Any] | None:
|
||||
devices = sorted(glob.glob("/sys/class/drm/card[0-9]*/device/gpu_busy_percent"))
|
||||
if not devices:
|
||||
return None
|
||||
busy_sum = 0
|
||||
count = 0
|
||||
vram_used = 0
|
||||
vram_total = 0
|
||||
temps: list[float] = []
|
||||
for busy_path in devices:
|
||||
dev = busy_path.rsplit("/", 1)[0]
|
||||
try:
|
||||
busy_sum += int(_read(busy_path) or 0)
|
||||
count += 1
|
||||
except ValueError:
|
||||
continue
|
||||
vram_used += int(_read(f"{dev}/mem_info_vram_used") or 0)
|
||||
vram_total += int(_read(f"{dev}/mem_info_vram_total") or 0)
|
||||
for hwmon in glob.glob(f"{dev}/hwmon/hwmon*"):
|
||||
t = _read(f"{hwmon}/temp1_input")
|
||||
if t:
|
||||
try:
|
||||
temps.append(int(t) / 1000.0)
|
||||
except ValueError:
|
||||
pass
|
||||
if count == 0:
|
||||
return None
|
||||
return {
|
||||
"gpu": round(busy_sum / count, 1),
|
||||
"vram_used": vram_used,
|
||||
"vram_total": vram_total,
|
||||
"vram_pct": round(vram_used / vram_total * 100, 1) if vram_total else None,
|
||||
"gpu_temp": max(temps) if temps else None,
|
||||
"gpu_name": _gpu_name(),
|
||||
}
|
||||
|
||||
|
||||
def _nvidia_sample() -> dict[str, Any] | None:
|
||||
if not shutil.which("nvidia-smi"):
|
||||
return None
|
||||
try:
|
||||
out = subprocess.run(
|
||||
[
|
||||
"nvidia-smi",
|
||||
"--query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu,name",
|
||||
"--format=csv,noheader,nounits",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
check=True,
|
||||
).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
lines = [l for l in out.splitlines() if l.strip()]
|
||||
if not lines:
|
||||
return None
|
||||
busy = used = total = 0
|
||||
temp = 0
|
||||
for line in lines:
|
||||
parts = [p.strip() for p in line.split(",")]
|
||||
try:
|
||||
busy += int(parts[0])
|
||||
used += int(parts[1])
|
||||
total += int(parts[2])
|
||||
temp = max(temp, int(parts[3]))
|
||||
except ValueError:
|
||||
continue
|
||||
name = lines[0].split(",")[-1].strip()
|
||||
vram_used = used * 1024 * 1024
|
||||
vram_total = total * 1024 * 1024
|
||||
return {
|
||||
"gpu": round(busy / len(lines), 1),
|
||||
"vram_used": vram_used,
|
||||
"vram_total": vram_total,
|
||||
"vram_pct": round(vram_used / vram_total * 100, 1) if vram_total else None,
|
||||
"gpu_temp": float(temp),
|
||||
"gpu_name": name,
|
||||
}
|
||||
|
||||
|
||||
def sample() -> dict[str, Any]:
|
||||
return (
|
||||
_amd_sample()
|
||||
or _nvidia_sample()
|
||||
or {
|
||||
"gpu": None,
|
||||
"vram_used": None,
|
||||
"vram_total": None,
|
||||
"vram_pct": None,
|
||||
"gpu_temp": None,
|
||||
"gpu_name": "no GPU detected",
|
||||
}
|
||||
)
|
||||
14
app/collect/mem.py
Normal file
14
app/collect/mem.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import psutil
|
||||
|
||||
|
||||
def sample() -> dict[str, int | float]:
|
||||
v = psutil.virtual_memory()
|
||||
s = psutil.swap_memory()
|
||||
return {
|
||||
"mem_used": v.used,
|
||||
"mem_total": v.total,
|
||||
"mem_pct": v.percent,
|
||||
"swap_used": s.used,
|
||||
"swap_total": s.total,
|
||||
"swap_pct": s.percent,
|
||||
}
|
||||
56
app/collect/net.py
Normal file
56
app/collect/net.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import glob
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import psutil
|
||||
|
||||
_wifi_cache: dict[str, tuple[float, str | None]] = {}
|
||||
_WIFI_TTL = 15.0
|
||||
_SSID_RE = re.compile(r"SSID:\s+(\S.*)")
|
||||
|
||||
|
||||
def _wifi_ifaces() -> set[str]:
|
||||
return {p.split("/")[-2] for p in glob.glob("/sys/class/net/*/wireless")}
|
||||
|
||||
|
||||
def _ssid(iface: str) -> str | None:
|
||||
hit = _wifi_cache.get(iface)
|
||||
now = time.monotonic()
|
||||
if hit is not None and now - hit[0] < _WIFI_TTL:
|
||||
return hit[1]
|
||||
ssid: str | None = None
|
||||
if shutil.which("iw"):
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["iw", "dev", iface, "link"], capture_output=True, text=True, timeout=3, check=False
|
||||
).stdout
|
||||
m = _SSID_RE.search(out)
|
||||
if m:
|
||||
ssid = m.group(1).strip().strip('"') or None
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
_wifi_cache[iface] = (now, ssid)
|
||||
return ssid
|
||||
|
||||
|
||||
def sample() -> dict[str, Any | None]:
|
||||
addrs = psutil.net_if_addrs()
|
||||
stats = psutil.net_if_stats()
|
||||
wifi_set = _wifi_ifaces()
|
||||
ifaces: list[dict[str, Any]] = []
|
||||
wifi: dict[str, Any] | None = None
|
||||
for name in sorted(addrs):
|
||||
if name == "lo":
|
||||
continue
|
||||
st = stats.get(name)
|
||||
if st is None or not bool(st.isup):
|
||||
continue
|
||||
ipv4 = [a.address for a in addrs[name] if a.family == socket.AF_INET]
|
||||
ifaces.append({"name": name, "ipv4": ipv4})
|
||||
if name in wifi_set and wifi is None:
|
||||
wifi = {"iface": name, "ssid": _ssid(name)}
|
||||
return {"net_ifaces": ifaces, "net_wifi": wifi}
|
||||
49
app/collect/power.py
Normal file
49
app/collect/power.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import glob
|
||||
from typing import Any
|
||||
|
||||
_PS = "/sys/class/power_supply"
|
||||
|
||||
|
||||
def _read(path: str) -> str | None:
|
||||
try:
|
||||
with open(path) as f:
|
||||
return f.read().strip()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _supplies() -> list[tuple[str, str]]:
|
||||
out: list[tuple[str, str]] = []
|
||||
for p in sorted(glob.glob(f"{_PS}/*")):
|
||||
t = _read(f"{p}/type")
|
||||
if t:
|
||||
out.append((t.lower(), p))
|
||||
return out
|
||||
|
||||
|
||||
def sample() -> dict[str, Any | None]:
|
||||
out: dict[str, Any | None] = {"battery": None, "battery_status": None, "ac_online": None}
|
||||
try:
|
||||
supplies = _supplies()
|
||||
for t, p in supplies:
|
||||
if t == "battery" and _read(f"{p}/present") == "1":
|
||||
cap = _read(f"{p}/capacity")
|
||||
if cap is not None:
|
||||
try:
|
||||
out["battery"] = int(cap)
|
||||
except ValueError:
|
||||
pass
|
||||
out["battery_status"] = _read(f"{p}/status")
|
||||
break
|
||||
for t, p in supplies:
|
||||
if t == "mains" and _read(f"{p}/online") == "1":
|
||||
out["ac_online"] = True
|
||||
break
|
||||
if out["ac_online"] is None:
|
||||
for t, p in supplies:
|
||||
if t == "usb" and _read(f"{p}/online") == "1":
|
||||
out["ac_online"] = True
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
return out
|
||||
90
app/collect/procs.py
Normal file
90
app/collect/procs.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import psutil
|
||||
|
||||
_prev_io: dict[int, tuple[float, float, float]] = {}
|
||||
_gpu_procs: dict[int, int] | None = None
|
||||
_gpu_probe_t = 0.0
|
||||
|
||||
|
||||
def _gpu_per_proc() -> dict[int, int]:
|
||||
global _gpu_procs, _gpu_probe_t
|
||||
if not shutil.which("nvidia-smi"):
|
||||
return {}
|
||||
if _gpu_procs is not None and time.monotonic() - _gpu_probe_t < 10:
|
||||
return _gpu_procs
|
||||
_gpu_probe_t = time.monotonic()
|
||||
_gpu_procs = {}
|
||||
try:
|
||||
out = subprocess.run(
|
||||
[
|
||||
"nvidia-smi",
|
||||
"--query-compute-apps=pid,used_memory",
|
||||
"--format=csv,noheader,nounits",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
check=False
|
||||
).stdout
|
||||
for line in out.splitlines():
|
||||
parts = [p.strip() for p in line.split(",")]
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
_gpu_procs[int(parts[0])] = int(parts[1])
|
||||
except ValueError:
|
||||
continue
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
return _gpu_procs
|
||||
|
||||
|
||||
def sample() -> list[dict[str, Any]]:
|
||||
now = time.monotonic()
|
||||
mem_total = psutil.virtual_memory().total
|
||||
gpu = _gpu_per_proc()
|
||||
out: list[dict[str, Any]] = []
|
||||
alive: set[int] = set()
|
||||
for p in psutil.process_iter():
|
||||
try:
|
||||
with p.oneshot():
|
||||
if p.ppid() in (0, 2):
|
||||
continue
|
||||
cpu = p.cpu_percent(None)
|
||||
mem = p.memory_info()
|
||||
name = p.name()
|
||||
user = p.username()
|
||||
try:
|
||||
io = p.io_counters()
|
||||
except (psutil.AccessDenied, psutil.NoSuchProcess, OSError):
|
||||
io = None
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
continue
|
||||
pid = p.pid
|
||||
alive.add(pid)
|
||||
entry = {
|
||||
"pid": pid,
|
||||
"name": name,
|
||||
"user": user,
|
||||
"cpu": cpu,
|
||||
"rss": mem.rss,
|
||||
"mem_pct": (mem.rss / mem_total * 100) if mem_total else 0.0,
|
||||
"io_read": 0.0,
|
||||
"io_write": 0.0,
|
||||
"gpu": gpu.get(pid),
|
||||
}
|
||||
if io is not None:
|
||||
prev = _prev_io.get(pid)
|
||||
if prev is not None and now > prev[0]:
|
||||
dt = now - prev[0]
|
||||
entry["io_read"] = max(0.0, (io.read_bytes - prev[1]) / dt)
|
||||
entry["io_write"] = max(0.0, (io.write_bytes - prev[2]) / dt)
|
||||
_prev_io[pid] = (now, io.read_bytes, io.write_bytes)
|
||||
out.append(entry)
|
||||
for pid in list(_prev_io):
|
||||
if pid not in alive:
|
||||
del _prev_io[pid]
|
||||
return out
|
||||
25
app/config.py
Normal file
25
app/config.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from functools import lru_cache
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="DASH_", env_file=".env", extra="ignore")
|
||||
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 8501
|
||||
sample_interval: float = 2.0
|
||||
retention_minutes: int = 60
|
||||
|
||||
llama_base_url: str = "http://127.0.0.1:8080"
|
||||
llama_api_key: str = ""
|
||||
llama_timeout: float = 4.0
|
||||
|
||||
@property
|
||||
def history_maxlen(self) -> int:
|
||||
return max(10, int(self.retention_minutes * 60 / self.sample_interval))
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
102
app/journal.py
Normal file
102
app/journal.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import asyncio
|
||||
import re
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
CURSOR_RE = re.compile(r"^[A-Za-z0-9;:=+./_-]+$")
|
||||
LEVELS = {"all": None, "warn": "warning", "err": "err"}
|
||||
FIELD_RE = re.compile(r"^([A-Z_][A-Z0-9_]*)=")
|
||||
|
||||
|
||||
def parse_export(text: str) -> list[dict[str, Any]]:
|
||||
entries: list[dict[str, Any]] = []
|
||||
cur: dict[str, Any] | None = None
|
||||
last_key: str | None = None
|
||||
for raw in text.splitlines():
|
||||
if raw == "":
|
||||
if cur is not None:
|
||||
entries.append(cur)
|
||||
cur, last_key = None, None
|
||||
continue
|
||||
m = FIELD_RE.match(raw)
|
||||
if m:
|
||||
if cur is None:
|
||||
cur = {}
|
||||
last_key = m.group(1)
|
||||
if last_key is not None:
|
||||
cur[last_key] = raw[m.end():]
|
||||
elif cur is not None and last_key is not None:
|
||||
cur[last_key] += "\n" + raw
|
||||
if cur is not None:
|
||||
entries.append(cur)
|
||||
return entries
|
||||
|
||||
|
||||
def format_entries(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
out: list[dict[str, Any]] = []
|
||||
for e in entries:
|
||||
ts = e.get("__REALTIME_TIMESTAMP")
|
||||
if ts is None:
|
||||
continue
|
||||
stamp = ""
|
||||
try:
|
||||
dt = datetime.fromtimestamp(int(ts) / 1e6, tz=UTC).astimezone()
|
||||
stamp = dt.strftime("%H:%M:%S")
|
||||
except (ValueError, OSError, TypeError):
|
||||
pass
|
||||
try:
|
||||
prio = int(e.get("PRIORITY", "6"))
|
||||
except ValueError:
|
||||
prio = 6
|
||||
msg = e.get("MESSAGE", "").rstrip("\n")
|
||||
out.append(
|
||||
{
|
||||
"stamp": stamp,
|
||||
"prio": prio,
|
||||
"ident": e.get("SYSLOG_IDENTIFIER") or e.get("_COMM") or e.get("_PID", "?"),
|
||||
"msg": msg,
|
||||
"cursor": e.get("__CURSOR", ""),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
async def _journalctl(argv: list[str]) -> str:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*argv,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
out, err = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(err.decode(errors="replace").strip() or "journalctl failed")
|
||||
return out.decode(errors="replace")
|
||||
|
||||
|
||||
async def tail(
|
||||
cursor: str | None,
|
||||
level: str,
|
||||
unit: str | None,
|
||||
search: str | None,
|
||||
lines: int,
|
||||
hide_sudo: bool = False,
|
||||
) -> tuple[list[dict[str, Any]], str | None]:
|
||||
fetch = lines * 2 if hide_sudo else lines
|
||||
args = ["--no-pager", "-o", "export", "-n", str(min(max(fetch, 1), 500))]
|
||||
lvl = LEVELS.get(level)
|
||||
if lvl:
|
||||
args += ["-p", lvl]
|
||||
if unit and re.match(r"^[A-Za-z0-9@:_.\-+]+\.\w+$", unit):
|
||||
args += ["-u", unit]
|
||||
if search:
|
||||
args += [search[:200]]
|
||||
if cursor and CURSOR_RE.match(cursor):
|
||||
args += ["--after-cursor", cursor]
|
||||
text = await _journalctl(["sudo", "journalctl"] + args)
|
||||
|
||||
entries = parse_export(text)
|
||||
if hide_sudo:
|
||||
entries = [e for e in entries if e.get("SYSLOG_IDENTIFIER") != "sudo"]
|
||||
entries = format_entries(entries)
|
||||
last_cursor = entries[-1]["cursor"] if entries else None
|
||||
return entries, last_cursor
|
||||
47
app/main.py
Normal file
47
app/main.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import asyncio
|
||||
import socket
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.config import get_settings
|
||||
from app.plugins import ROUTERS as PLUGIN_ROUTERS
|
||||
from app.render import BASE, render
|
||||
from app.routers import disks, overview, plugins, processes, services
|
||||
from app.routers import journal as journal_router
|
||||
from app.sampling import sampler_loop
|
||||
from app.state import HistoryStore
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
settings = get_settings()
|
||||
app.state.settings = settings
|
||||
app.state.store = HistoryStore(maxlen=settings.history_maxlen)
|
||||
task = asyncio.create_task(sampler_loop(app.state.store, settings.sample_interval))
|
||||
yield
|
||||
_ = task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
app = FastAPI(title="Dashboard", lifespan=lifespan)
|
||||
app.mount("/static", StaticFiles(directory=BASE / "static"), name="static")
|
||||
for r in (overview.router, disks.router, processes.router, journal_router.router, services.router, plugins.router):
|
||||
app.include_router(r)
|
||||
for r in PLUGIN_ROUTERS:
|
||||
app.include_router(r)
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
async def index():
|
||||
return HTMLResponse(render("index.html", hostname=socket.gethostname()))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
7
app/plugins/__init__.py
Normal file
7
app/plugins/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from app.plugins.base import Plugin
|
||||
from app.plugins.llamacpp import plugin as llamacpp_plugin, router as llamacpp_router
|
||||
|
||||
PLUGINS: list[Plugin] = [llamacpp_plugin]
|
||||
ROUTERS = [llamacpp_router]
|
||||
|
||||
__all__ = ["PLUGINS", "ROUTERS", "Plugin"]
|
||||
16
app/plugins/base.py
Normal file
16
app/plugins/base.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Plugin:
|
||||
id: str
|
||||
title: str
|
||||
description: str = ""
|
||||
poll_seconds: int = 5
|
||||
fragment_fn: Callable[[], Awaitable[str]] | None = field(default=None)
|
||||
|
||||
async def fragment(self) -> str:
|
||||
if self.fragment_fn is None:
|
||||
raise NotImplementedError
|
||||
return await self.fragment_fn()
|
||||
150
app/plugins/llamacpp.py
Normal file
150
app/plugins/llamacpp.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Form
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app.config import Settings, get_settings
|
||||
from app.plugins.base import Plugin
|
||||
from app.render import render
|
||||
|
||||
router = APIRouter(prefix="/api/plugins/llamacpp", tags=["plugins"])
|
||||
|
||||
|
||||
def _headers(settings: Settings) -> dict[str, str]:
|
||||
h = {}
|
||||
if settings.llama_api_key:
|
||||
h["Authorization"] = f"Bearer {settings.llama_api_key}"
|
||||
return h
|
||||
|
||||
|
||||
def _client() -> httpx.AsyncClient:
|
||||
settings = get_settings()
|
||||
return httpx.AsyncClient(
|
||||
base_url=settings.llama_base_url.rstrip("/"),
|
||||
timeout=settings.llama_timeout,
|
||||
headers=_headers(settings),
|
||||
)
|
||||
|
||||
|
||||
async def gather_status() -> dict[str, Any]:
|
||||
"""Query the llama-server router. Never raises; returns status dict."""
|
||||
settings = get_settings()
|
||||
status: dict[str, Any] = {
|
||||
"base_url": settings.llama_base_url,
|
||||
"reachable": False,
|
||||
"health": None,
|
||||
"models": [],
|
||||
"error": None,
|
||||
}
|
||||
try:
|
||||
async with _client() as client:
|
||||
try:
|
||||
r = await client.get("/health")
|
||||
status["health"] = r.json().get("status") if r.status_code == 200 else f"http {r.status_code}"
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
r = await client.get("/models")
|
||||
_ = r.raise_for_status()
|
||||
status["reachable"] = True
|
||||
data = r.json()
|
||||
for m in data.get("data", []):
|
||||
st: dict[str, Any] = m.get("status") or {}
|
||||
item: dict[str, str | bool | float] = {
|
||||
"id": str(m.get("id", "?")),
|
||||
"state": str(st.get("value", "unknown")),
|
||||
"failed": bool(st.get("failed")),
|
||||
"exit_code": str(st.get("exit_code")),
|
||||
"path": m.get("path", ""),
|
||||
}
|
||||
prog: dict[str, Any] = st.get("progress") or {}
|
||||
if prog:
|
||||
done = sum(p.get("done", 0) for p in prog.values())
|
||||
total = sum(p.get("total", 1) for p in prog.values())
|
||||
item["progress"] = round(done / total * 100, 1) if total else 0.0
|
||||
status["models"].append(item)
|
||||
status["models"].sort(key=lambda m: m["id"])
|
||||
except httpx.HTTPError as e:
|
||||
status["error"] = f"unreachable: {e.__class__.__name__}"
|
||||
except Exception as e: # noqa
|
||||
status["error"] = str(e)[:200]
|
||||
return status
|
||||
|
||||
|
||||
async def _action(endpoint: str, model: str) -> tuple[bool, str]:
|
||||
try:
|
||||
async with _client() as client:
|
||||
r = await client.post(endpoint, json={"model": model})
|
||||
if r.status_code < 300:
|
||||
return True, ""
|
||||
try:
|
||||
detail = r.json()
|
||||
msg = detail.get("error") or str(detail)
|
||||
except Exception: # noqa
|
||||
msg = r.text[:200]
|
||||
return False, f"http {r.status_code}: {msg}"
|
||||
except httpx.HTTPError as e:
|
||||
return False, f"unreachable: {e.__class__.__name__}"
|
||||
|
||||
|
||||
async def _fragment(message: str = "", error: str = "") -> str:
|
||||
status = await gather_status()
|
||||
status["message"] = message
|
||||
status["error_msg"] = error
|
||||
active = {m["id"] for m in status["models"] if m["state"] in ("loaded", "sleeping", "loading")}
|
||||
status["loaded"] = [m for m in status["models"] if m["id"] in active]
|
||||
status["available"] = [m for m in status["models"] if m["id"] not in active]
|
||||
status["loaded"].sort(key=lambda m: (m["state"] != "loaded", m["state"] != "sleeping", m["id"]))
|
||||
status["available"].sort(key=lambda m: m["id"])
|
||||
return render("plugins/llamacpp.html", **status)
|
||||
|
||||
|
||||
@router.get("/fragment")
|
||||
async def fragment():
|
||||
return HTMLResponse(await _fragment())
|
||||
|
||||
|
||||
@router.post("/load")
|
||||
async def load(model: str = Form(...)):
|
||||
ok, err = await _action("/models/load", model)
|
||||
return HTMLResponse(
|
||||
await _fragment(
|
||||
message=f"loading {model}" if ok else "",
|
||||
error="" if ok else err,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/unload")
|
||||
async def unload(model: str = Form(...)):
|
||||
ok, err = await _action("/models/unload", model)
|
||||
return HTMLResponse(
|
||||
await _fragment(
|
||||
message=f"unloading {model}" if ok else "",
|
||||
error="" if ok else err,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/rescan")
|
||||
async def rescan():
|
||||
try:
|
||||
async with _client() as client:
|
||||
r = await client.get("/models", params={"reload": "1"})
|
||||
if r.status_code < 300:
|
||||
msg = "model list refreshed"
|
||||
err = ""
|
||||
else:
|
||||
msg, err = "", f"http {r.status_code}"
|
||||
except httpx.HTTPError as e:
|
||||
msg, err = "", f"unreachable: {e.__class__.__name__}"
|
||||
return HTMLResponse(await _fragment(message=msg, error=err))
|
||||
|
||||
|
||||
plugin = Plugin(
|
||||
id="llamacpp",
|
||||
title="llama.cpp",
|
||||
description="Loaded model status for a llama-server in router mode, with load / unload controls.",
|
||||
poll_seconds=5,
|
||||
fragment_fn=_fragment,
|
||||
)
|
||||
69
app/render.py
Normal file
69
app/render.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
BASE = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def humanize(value: float | str | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
n = float(value)
|
||||
for unit in ("B", "KiB", "MiB", "GiB", "TiB"):
|
||||
if abs(n) < 1024 or unit == "TiB":
|
||||
if unit == "B":
|
||||
return f"{int(n)} B"
|
||||
return f"{n:.1f} {unit}"
|
||||
n /= 1024
|
||||
return f"{n:.1f} TiB"
|
||||
|
||||
|
||||
def rate(value: float | str | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
n = float(value)
|
||||
for unit in ("B/s", "KiB/s", "MiB/s", "GiB/s"):
|
||||
if abs(n) < 1024 or unit == "GiB/s":
|
||||
if unit == "B/s":
|
||||
return f"{int(n)} B/s"
|
||||
return f"{n:.1f} {unit}"
|
||||
n /= 1024
|
||||
return f"{n:.1f} GiB/s"
|
||||
|
||||
|
||||
def uptime_str(seconds: float | None) -> str:
|
||||
if seconds is None:
|
||||
return "—"
|
||||
td = timedelta(seconds=int(seconds))
|
||||
days, rem = divmod(td.seconds, 86400)
|
||||
hours, rem = divmod(rem, 3600)
|
||||
minutes = rem // 60
|
||||
parts: list[str] = []
|
||||
if days:
|
||||
parts.append(f"{days}d")
|
||||
if days or hours:
|
||||
parts.append(f"{hours}h")
|
||||
parts.append(f"{minutes}m")
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
def pct(value: float | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
return f"{value:.0f}%"
|
||||
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(BASE / "templates"),
|
||||
autoescape=select_autoescape(("html", "j2")),
|
||||
)
|
||||
env.filters["humanize"] = humanize
|
||||
env.filters["rate"] = rate
|
||||
env.filters["uptime"] = uptime_str
|
||||
env.filters["pct"] = pct
|
||||
|
||||
|
||||
def render(name: str, **kwargs: Any) -> str:
|
||||
return env.get_template(name).render(**kwargs)
|
||||
3
app/routers/__init__.py
Normal file
3
app/routers/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from app.routers import disks, journal, overview, plugins, processes, services
|
||||
|
||||
__all__ = ["overview", "disks", "processes", "journal", "services", "plugins"]
|
||||
40
app/routers/disks.py
Normal file
40
app/routers/disks.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import time
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app.collect import disks as disk_col
|
||||
from app.render import render
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["disks"])
|
||||
|
||||
_prev: dict[str, Any] | None = None
|
||||
_prev_t: float = 0.0
|
||||
|
||||
|
||||
@router.get("/disks")
|
||||
async def disks(_request: Request):
|
||||
global _prev, _prev_t
|
||||
now = time.monotonic()
|
||||
cur = disk_col.counters()
|
||||
per_disk: list[dict[str, Any]] = []
|
||||
dt = (now - _prev_t) if _prev is not None and _prev_t else 0.0
|
||||
for name, c in sorted(cur.items()):
|
||||
p = (_prev or {}).get(name)
|
||||
per_disk.append(
|
||||
{
|
||||
"device": name,
|
||||
"read_rate": (c.read_bytes - p.read_bytes) / dt if p and dt > 0 else 0.0,
|
||||
"write_rate": (c.write_bytes - p.write_bytes) / dt if p and dt > 0 else 0.0,
|
||||
"reads": c.read_count,
|
||||
"writes": c.write_count,
|
||||
"read_bytes": c.read_bytes,
|
||||
"write_bytes": c.write_bytes,
|
||||
}
|
||||
)
|
||||
_prev = cur
|
||||
_prev_t = now
|
||||
return HTMLResponse(
|
||||
render("disks.html", partitions=disk_col.partitions(), per_disk=per_disk)
|
||||
)
|
||||
50
app/routers/journal.py
Normal file
50
app/routers/journal.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app import journal
|
||||
from app.render import render
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["journal"])
|
||||
|
||||
|
||||
@router.get("/journal")
|
||||
async def journal_view(
|
||||
_request: Request,
|
||||
level: str = "all",
|
||||
unit: str = "",
|
||||
search: str = "",
|
||||
cursor: str = "",
|
||||
hide_sudo: str = "",
|
||||
):
|
||||
if level not in journal.LEVELS:
|
||||
level = "all"
|
||||
lines = 200 if cursor else 100
|
||||
error = None
|
||||
entries: list[dict[str, Any]] = []
|
||||
next_cursor = ""
|
||||
try:
|
||||
entries, nc = await journal.tail(
|
||||
cursor or None,
|
||||
level,
|
||||
unit or None,
|
||||
search or None,
|
||||
lines,
|
||||
hide_sudo=(hide_sudo == "on"),
|
||||
)
|
||||
next_cursor = nc or ""
|
||||
entries = entries[-400:]
|
||||
except (RuntimeError, OSError) as e:
|
||||
error = str(e)[:300]
|
||||
return HTMLResponse(
|
||||
render(
|
||||
"journal.html",
|
||||
entries=entries,
|
||||
next_cursor=next_cursor,
|
||||
level=level,
|
||||
unit=unit,
|
||||
search=search,
|
||||
error=error,
|
||||
)
|
||||
)
|
||||
72
app/routers/overview.py
Normal file
72
app/routers/overview.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import asyncio
|
||||
import socket
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import psutil
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
|
||||
from app.collect import net as net_col
|
||||
from app.render import render, uptime_str
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["overview"])
|
||||
|
||||
|
||||
@router.get("/overview")
|
||||
async def overview(request: Request):
|
||||
store = request.app.state.store
|
||||
s: dict[str, Any] = store.latest() or {}
|
||||
mem_total = s.get("mem_total") or 0
|
||||
vram_total = s.get("vram_total") or 0
|
||||
vram_used = s.get("vram_used")
|
||||
c = {
|
||||
"cpu": s.get("cpu"),
|
||||
"cpu_temp": s.get("cpu_temp"),
|
||||
"load1": s.get("load1"),
|
||||
"load5": s.get("load5"),
|
||||
"load15": s.get("load15"),
|
||||
"mem_used": s.get("mem_used"),
|
||||
"mem_total": mem_total,
|
||||
"mem_pct": s.get("mem_pct"),
|
||||
"swap_used": s.get("swap_used"),
|
||||
"swap_total": s.get("swap_total") or 0,
|
||||
"swap_pct": s.get("swap_pct"),
|
||||
"gpu": s.get("gpu"),
|
||||
"gpu_name": s.get("gpu_name"),
|
||||
"gpu_temp": s.get("gpu_temp"),
|
||||
"vram_used": vram_used,
|
||||
"vram_total": vram_total,
|
||||
"vram_pct": s.get("vram_pct")
|
||||
or ((vram_used / vram_total * 100) if (vram_total and vram_used is not None) else None),
|
||||
"battery": s.get("battery"),
|
||||
"battery_status": s.get("battery_status"),
|
||||
"ac_online": s.get("ac_online"),
|
||||
"uptime": uptime_str(time.time() - psutil.boot_time()),
|
||||
"hostname": socket.gethostname(),
|
||||
"cores": psutil.cpu_count(logical=True) or 1,
|
||||
**await asyncio.to_thread(net_col.sample),
|
||||
}
|
||||
return HTMLResponse(render("overview.html", c=c))
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
async def history(request: Request):
|
||||
snap = request.app.state.store.snapshot()
|
||||
ts = [round(t, 1) for t, _ in snap]
|
||||
keys: set[str] = set()
|
||||
for _, sample in snap:
|
||||
for k, v in sample.items():
|
||||
if isinstance(v, (int, float)) and not isinstance(v, bool):
|
||||
keys.add(k)
|
||||
series: dict[str, list[Any]] = {}
|
||||
for _, sample in snap:
|
||||
for k in keys:
|
||||
v = sample.get(k)
|
||||
if isinstance(v, (int, float)) and not isinstance(v, bool):
|
||||
if isinstance(v, float):
|
||||
v = round(v, 1)
|
||||
else:
|
||||
v = None
|
||||
series.setdefault(k, []).append(v)
|
||||
return JSONResponse({"ts": ts, "series": series})
|
||||
27
app/routers/plugins.py
Normal file
27
app/routers/plugins.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app.plugins import PLUGINS, Plugin
|
||||
from app.render import render
|
||||
|
||||
router = APIRouter(prefix="/api/plugins", tags=["plugins"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def plugins_index():
|
||||
items: list[dict[str, Plugin | str]] = []
|
||||
for p in PLUGINS:
|
||||
try:
|
||||
frag = await p.fragment()
|
||||
except Exception as e: # noqa
|
||||
frag = f"<div class='alert'>plugin error: {e}</div>"
|
||||
items.append({"p": p, "frag": frag})
|
||||
return HTMLResponse(render("plugins.html", items=items))
|
||||
|
||||
|
||||
@router.get("/{pid}/fragment")
|
||||
async def plugin_fragment(pid: str):
|
||||
plugin = next((p for p in PLUGINS if p.id == pid), None)
|
||||
if plugin is None:
|
||||
return HTMLResponse("unknown plugin", status_code=404)
|
||||
return HTMLResponse(await plugin.fragment())
|
||||
38
app/routers/processes.py
Normal file
38
app/routers/processes.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import asyncio
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app.collect import procs as proc_col
|
||||
from app.render import render
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["processes"])
|
||||
|
||||
SORT_KEYS = ("pid", "name", "cpu", "rss", "mem_pct", "io_read", "io_write", "gpu")
|
||||
|
||||
|
||||
@router.get("/processes")
|
||||
async def processes(q: str = "", sort: str = "cpu", order: str = "desc"):
|
||||
if sort not in SORT_KEYS:
|
||||
sort = "cpu"
|
||||
if order not in ("asc", "desc"):
|
||||
order = "desc"
|
||||
procs = await asyncio.to_thread(proc_col.sample)
|
||||
if q:
|
||||
ql = q.lower()
|
||||
procs = [p for p in procs if ql in p["name"].lower() or str(p["pid"]) == ql]
|
||||
reverse = order == "desc"
|
||||
try:
|
||||
procs.sort(key=lambda p: (p[sort] is None, p[sort]), reverse=reverse)
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
return HTMLResponse(
|
||||
render(
|
||||
"processes.html",
|
||||
procs=procs[:300],
|
||||
total=len(procs),
|
||||
q=q,
|
||||
sort=sort,
|
||||
order=order,
|
||||
)
|
||||
)
|
||||
110
app/routers/services.py
Normal file
110
app/routers/services.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Form
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from app import journal
|
||||
from app.render import render
|
||||
from app.systemd import units
|
||||
|
||||
router = APIRouter(prefix="/api/services", tags=["services"])
|
||||
|
||||
SORT_KEYS = ("name", "state", "enabled")
|
||||
_STATE_RANK = {
|
||||
"active": 0,
|
||||
"activating": 1,
|
||||
"deactivating": 1,
|
||||
"reloading": 1,
|
||||
"reactivating": 1,
|
||||
"failed": 2,
|
||||
"inactive": 3,
|
||||
"dead": 3,
|
||||
"maintenance": 3,
|
||||
}
|
||||
_ENABLED_RANK = {
|
||||
"enabled": 0,
|
||||
"indirect": 1,
|
||||
"static": 2,
|
||||
"disabled": 3,
|
||||
"alias": 4,
|
||||
"linked": 4,
|
||||
"linked-runtime": 4,
|
||||
"masked": 5,
|
||||
"": 6,
|
||||
}
|
||||
|
||||
|
||||
def _rank(u: dict[str, Any], key: str) -> int:
|
||||
if key == "state":
|
||||
return _STATE_RANK.get(u["active"], 9)
|
||||
if key == "enabled":
|
||||
return _ENABLED_RANK.get(u["enabled"], 9)
|
||||
return 0
|
||||
|
||||
|
||||
async def _list_fragment(q: str, sort: str = "name", order: str = "asc", error: str | None = None) -> str:
|
||||
if sort not in SORT_KEYS:
|
||||
sort = "name"
|
||||
if order not in ("asc", "desc"):
|
||||
order = "asc"
|
||||
unit_list = await units.unit_list()
|
||||
if q:
|
||||
ql = q.lower()
|
||||
unit_list = [
|
||||
u for u in unit_list if ql in u["name"].lower() or ql in u["desc"].lower()
|
||||
]
|
||||
reverse = order == "desc"
|
||||
if sort == "name":
|
||||
unit_list.sort(key=lambda u: u["name"], reverse=reverse)
|
||||
else:
|
||||
unit_list.sort(key=lambda u: (_rank(u, sort), u["name"]), reverse=reverse)
|
||||
state = await units.system_state()
|
||||
return render(
|
||||
"services.html",
|
||||
units=unit_list,
|
||||
state=state,
|
||||
q=q,
|
||||
sort=sort,
|
||||
order=order,
|
||||
error=error,
|
||||
)
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def services(q: str = "", sort: str = "name", order: str = "asc"):
|
||||
return HTMLResponse(await _list_fragment(q, sort, order))
|
||||
|
||||
|
||||
@router.get("/{unit}/detail")
|
||||
async def service_detail(unit: str):
|
||||
error = None
|
||||
props: dict[str, str] = {}
|
||||
log: list[dict[str, str]] = []
|
||||
try:
|
||||
props = await units.unit_detail(unit)
|
||||
except (ValueError, RuntimeError) as e:
|
||||
error = str(e)[:300]
|
||||
if not error:
|
||||
try:
|
||||
log, _ = await journal.tail(None, "all", unit, None, 15)
|
||||
except (RuntimeError, OSError):
|
||||
pass
|
||||
return HTMLResponse(render("service_detail.html", unit=unit, props=props, log=log, error=error))
|
||||
|
||||
|
||||
@router.post("/{unit}/action")
|
||||
async def service_action(
|
||||
unit: str,
|
||||
action: str = Form(...),
|
||||
q: str = Form(""),
|
||||
sort: str = Form("name"),
|
||||
order: str = Form("asc"),
|
||||
):
|
||||
error = None
|
||||
try:
|
||||
_ = await units.unit_action(unit, action)
|
||||
except ValueError as e:
|
||||
error = str(e)
|
||||
except RuntimeError as e:
|
||||
error = str(e)[:300]
|
||||
return HTMLResponse(await _list_fragment(q, sort, order, error=error))
|
||||
29
app/sampling.py
Normal file
29
app/sampling.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import asyncio
|
||||
import time
|
||||
|
||||
from app.collect import cpu, disks, gpu, mem, power
|
||||
from app.state import HistoryStore
|
||||
|
||||
|
||||
def _collect() -> dict[str, float | int | None]:
|
||||
sample: dict[str, float | int | None] = {}
|
||||
sample.update(cpu.sample())
|
||||
sample.update(mem.sample())
|
||||
sample.update(gpu.sample())
|
||||
sample.update(power.sample())
|
||||
return sample
|
||||
|
||||
|
||||
async def sampler_loop(store: HistoryStore, sample_interval: float) -> None:
|
||||
cpu.prime()
|
||||
prev_disk = disks.counters()
|
||||
prev_t = time.monotonic()
|
||||
while True:
|
||||
await asyncio.sleep(sample_interval)
|
||||
sample = await asyncio.to_thread(_collect)
|
||||
now = time.monotonic()
|
||||
dt = now - prev_t
|
||||
sample.update(disks.rates(prev_disk, dt))
|
||||
prev_disk = disks.counters()
|
||||
prev_t = now
|
||||
store.record(sample)
|
||||
19
app/state.py
Normal file
19
app/state.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import time
|
||||
from collections import deque
|
||||
|
||||
|
||||
class HistoryStore:
|
||||
def __init__(self, maxlen: int) -> None:
|
||||
self._buf: deque[tuple[float, dict[str, float | int | None]]] = deque(maxlen=maxlen)
|
||||
|
||||
def record(self, sample: dict[str, float | int | None]) -> None:
|
||||
self._buf.append((time.time(), sample))
|
||||
|
||||
def snapshot(self) -> list[tuple[float, dict[str, float | int | None]]]:
|
||||
return list(self._buf)
|
||||
|
||||
def latest(self) -> dict[str, float | int | None] | None:
|
||||
return self._buf[-1][1] if self._buf else None
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._buf)
|
||||
3
app/systemd/__init__.py
Normal file
3
app/systemd/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from app.systemd import units
|
||||
|
||||
__all__ = ["units"]
|
||||
117
app/systemd/units.py
Normal file
117
app/systemd/units.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import asyncio
|
||||
import re
|
||||
import time
|
||||
|
||||
UNIT_RE = re.compile(r"^[A-Za-z0-9@:_.\-+]+\.(service|socket|timer|target|path|slice)$")
|
||||
ACTIONS = ("start", "stop", "restart", "enable", "disable")
|
||||
|
||||
_enabled_cache: dict[str, str] | None = None
|
||||
_enabled_cache_at = 0.0
|
||||
_ENABLED_TTL = 30.0
|
||||
|
||||
_DETAIL_PROPS = (
|
||||
"ActiveState,SubState,LoadState,UnitFileState,Description,MainPID,"
|
||||
"ExecMainStartTimestamp,NRestarts,FragmentPath,Result"
|
||||
)
|
||||
|
||||
|
||||
async def _run(cmd: list[str]) -> tuple[int, str, str]:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
out, err = await proc.communicate()
|
||||
return proc.returncode or 0, out.decode(errors="replace"), err.decode(errors="replace")
|
||||
|
||||
|
||||
async def _systemctl(*args: str, privileged: bool = False) -> str:
|
||||
# Privileged verbs always go through sudo: plain attempts just get
|
||||
# rejected by systemd and spam the journal with auth failures.
|
||||
cmd = (["sudo", "systemctl", *args] if privileged else ["systemctl", *args])
|
||||
rc, out, err = await _run(cmd)
|
||||
if rc == 0:
|
||||
return out
|
||||
raise RuntimeError(err.strip() or f"systemctl {' '.join(args)} failed")
|
||||
|
||||
|
||||
async def _enabled_map(force: bool = False) -> dict[str, str]:
|
||||
global _enabled_cache, _enabled_cache_at
|
||||
now = time.monotonic()
|
||||
if not force and _enabled_cache is not None and now - _enabled_cache_at < _ENABLED_TTL:
|
||||
return _enabled_cache
|
||||
files = await _systemctl("list-unit-files", "--type=service", "--no-legend", "--plain")
|
||||
m: dict[str, str] = {}
|
||||
for line in files.splitlines():
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
m[parts[0]] = parts[1].strip()
|
||||
_enabled_cache = m
|
||||
_enabled_cache_at = now
|
||||
return m
|
||||
|
||||
|
||||
async def unit_list() -> list[dict[str, str]]:
|
||||
out = await _systemctl(
|
||||
"list-units", "--type=service", "--all", "--no-legend", "--plain"
|
||||
)
|
||||
enabled = await _enabled_map()
|
||||
units: dict[str, dict[str, str]] = {}
|
||||
for line in out.splitlines():
|
||||
parts = line.split(None, 4)
|
||||
if len(parts) < 4:
|
||||
continue
|
||||
name, load, active, sub = parts[0], parts[1], parts[2], parts[3]
|
||||
desc = parts[4] if len(parts) > 4 else ""
|
||||
units[name] = {
|
||||
"name": name,
|
||||
"load": load,
|
||||
"active": active,
|
||||
"sub": sub,
|
||||
"desc": desc,
|
||||
"enabled": enabled.get(name, ""),
|
||||
}
|
||||
for name, state in enabled.items():
|
||||
if name not in units:
|
||||
units[name] = {
|
||||
"name": name,
|
||||
"load": "",
|
||||
"active": "inactive",
|
||||
"sub": "dead",
|
||||
"desc": "",
|
||||
"enabled": state,
|
||||
}
|
||||
return sorted(units.values(), key=lambda u: u["name"])
|
||||
|
||||
|
||||
async def unit_detail(name: str) -> dict[str, str]:
|
||||
if not UNIT_RE.match(name):
|
||||
raise ValueError("invalid unit name")
|
||||
out = await _systemctl("show", name, f"-p{_DETAIL_PROPS}")
|
||||
props: dict[str, str] = {}
|
||||
for line in out.splitlines():
|
||||
if "=" in line:
|
||||
k, _, v = line.partition("=")
|
||||
props[k] = v
|
||||
return props
|
||||
|
||||
|
||||
async def unit_action(name: str, action: str) -> str:
|
||||
if not UNIT_RE.match(name):
|
||||
raise ValueError("invalid unit name")
|
||||
if action not in ACTIONS:
|
||||
raise ValueError("invalid action")
|
||||
out = await _systemctl(action, name, privileged=True)
|
||||
if action in ("enable", "disable"):
|
||||
global _enabled_cache, _enabled_cache_at
|
||||
_enabled_cache = None
|
||||
_enabled_cache_at = 0.0
|
||||
return out
|
||||
|
||||
|
||||
async def system_state() -> str:
|
||||
try:
|
||||
return (await _systemctl("is-system-running")).strip() or "unknown"
|
||||
except RuntimeError:
|
||||
return "unknown"
|
||||
18
deploy/dashboard.service
Normal file
18
deploy/dashboard.service
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[Unit]
|
||||
# Personal computer dashboard + task manager.
|
||||
# Adjust User= and the WorkingDirectory/ExecStart paths if you install elsewhere.
|
||||
Description=Computer dashboard and task manager
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=mac
|
||||
WorkingDirectory=/path/to/the/code
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
# Run `uv sync` first after changing dependencies.
|
||||
ExecStart=/path/to/the/code/.venv/bin/python main.py
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
7
main.py
Normal file
7
main.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import uvicorn
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = get_settings()
|
||||
uvicorn.run("app.main:app", host=s.host, port=s.port, log_level="warning")
|
||||
15153
opencode/opencode_session_2026-08-30.txt
Normal file
15153
opencode/opencode_session_2026-08-30.txt
Normal file
File diff suppressed because it is too large
Load diff
23
pyproject.toml
Normal file
23
pyproject.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[project]
|
||||
name = "dashboard"
|
||||
version = "0.1.0"
|
||||
description = "Computer dashboard and combined task manager (FastAPI + htmx)"
|
||||
license = "Unlicense"
|
||||
license-files = ["LICENSE"]
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"fastapi>=0.141.1",
|
||||
"httpx>=0.28.1",
|
||||
"jinja2>=3.1.6",
|
||||
"psutil>=7.2.2",
|
||||
"pydantic-settings>=2.15.0",
|
||||
"python-multipart>=0.0.32",
|
||||
"uvicorn[standard]>=0.52.4",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
|
||||
[tool.basedpyright]
|
||||
reportExplicitAny = false
|
||||
reportAny = false
|
||||
BIN
screenshots/Disks_20260830.png
Normal file
BIN
screenshots/Disks_20260830.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
screenshots/Journal_20260830.png
Normal file
BIN
screenshots/Journal_20260830.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 295 KiB |
BIN
screenshots/Overview_20260830.png
Normal file
BIN
screenshots/Overview_20260830.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
BIN
screenshots/Plugins_20260830.png
Normal file
BIN
screenshots/Plugins_20260830.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
screenshots/Processes_20260830.png
Normal file
BIN
screenshots/Processes_20260830.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
BIN
screenshots/Services_20260830.png
Normal file
BIN
screenshots/Services_20260830.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
263
static/css/style.css
Normal file
263
static/css/style.css
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
:root {
|
||||
--bg: #0e1116;
|
||||
--bg-panel: #161b23;
|
||||
--bg-card: #1c2330;
|
||||
--border: #2a3342;
|
||||
--text: #dbe2ea;
|
||||
--muted: #7d8a9c;
|
||||
--accent: #4fc3f7;
|
||||
--ok: #66bb6a;
|
||||
--warn: #ffb74d;
|
||||
--err: #ef5350;
|
||||
--busy: #fff176;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h1 { font-size: 18px; margin: 0; }
|
||||
h2 { font-size: 15px; margin: 18px 0 8px; }
|
||||
h3 { font-size: 14px; margin: 0 0 8px; }
|
||||
h4 { font-size: 13px; margin: 14px 0 6px; }
|
||||
.mono { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: 12.5px; }
|
||||
.muted { color: var(--muted); }
|
||||
.small { font-size: 12px; }
|
||||
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-panel);
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#tabs { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||
|
||||
.tab-btn {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 1px solid transparent;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13.5px;
|
||||
}
|
||||
.tab-btn:hover { color: var(--text); background: var(--bg-card); }
|
||||
.tab-btn.active {
|
||||
color: var(--text);
|
||||
background: var(--bg-card);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
main { padding: 14px 16px 40px; max-width: 1500px; margin: 0 auto; }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
.panel {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
min-height: 84px;
|
||||
}
|
||||
.card-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .05em; }
|
||||
.card-value { font-size: 20px; font-weight: 600; margin: 4px 0; }
|
||||
.card-sub { color: var(--muted); font-size: 12px; margin-top: 4px; }
|
||||
|
||||
.bar { height: 6px; background: #0b0e13; border-radius: 3px; overflow: hidden; }
|
||||
.bar.small-bar { width: 120px; margin-top: 4px; }
|
||||
.bar-fill { height: 100%; background: var(--accent); border-radius: 3px; transition: width .6s; }
|
||||
.bar-fill.gpu { background: #ba68c8; }
|
||||
.bar-fill.warm { background: var(--warn); }
|
||||
.bar-fill.hot { background: var(--err); }
|
||||
.bar-fill.low { background: var(--err); }
|
||||
.barrow { display: flex; align-items: center; gap: 6px; }
|
||||
.barrow + .barrow { margin-top: 4px; }
|
||||
.barrow-label { flex: 0 0 30px; font-size: 11px; color: var(--muted); }
|
||||
.barrow .bar { flex: 1 1 auto; }
|
||||
.memrow { margin-top: 6px; }
|
||||
.memrow .bar { margin-top: 3px; }
|
||||
.memrow-text { font-size: 12px; }
|
||||
|
||||
.charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 10px; }
|
||||
.chart-card {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
height: 240px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.chart-card h3 { flex: 0 0 auto; }
|
||||
.chart-card canvas { flex: 1 1 auto; min-height: 0; }
|
||||
|
||||
.inline-form { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
|
||||
input, select, button {
|
||||
background: var(--bg-card);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 6px 9px;
|
||||
font-size: 13px;
|
||||
}
|
||||
input::placeholder { color: var(--muted); }
|
||||
.chk { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: 6px; padding: 6px 9px; font-size: 13px; color: var(--muted); user-select: none; }
|
||||
.chk input { padding: 0; border: none; background: none; }
|
||||
button { cursor: pointer; }
|
||||
button:hover { border-color: var(--accent); }
|
||||
|
||||
.table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
.table th, .table td {
|
||||
text-align: left;
|
||||
padding: 5px 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.table th { color: var(--muted); font-size: 12px; font-weight: 500; }
|
||||
.table td.j-msg { white-space: normal; word-break: break-word; }
|
||||
.table tbody tr:hover { background: var(--bg-card); }
|
||||
.table-wrap { overflow-x: auto; }
|
||||
.pname { overflow: hidden; text-overflow: ellipsis; }
|
||||
.cell-clip { overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
.sortlink { color: inherit; text-decoration: none; cursor: pointer; }
|
||||
.sortlink.active { color: var(--accent); }
|
||||
|
||||
.alert {
|
||||
background: rgba(239, 83, 80, .12);
|
||||
border: 1px solid rgba(239, 83, 80, .5);
|
||||
color: #ffcdd2;
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.notice {
|
||||
background: rgba(79, 195, 247, .1);
|
||||
border: 1px solid rgba(79, 195, 247, .4);
|
||||
color: #b3e5fc;
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.part-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.part { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; }
|
||||
.part-head { display: flex; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; align-items: baseline; }
|
||||
|
||||
.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
|
||||
.dot-run { background: var(--ok); }
|
||||
.dot-dead { background: #555f6e; }
|
||||
.dot-failed { background: var(--err); }
|
||||
.dot-busy { background: var(--busy); }
|
||||
.dot-sleep { background: var(--accent); }
|
||||
|
||||
.svc-name { cursor: pointer; color: var(--accent); }
|
||||
.svc-name:hover { text-decoration: underline; }
|
||||
|
||||
.detail-pane { margin-top: 4px; }
|
||||
.detail-pane:empty { display: none; }
|
||||
.detail-inner {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 6px 18px; margin-bottom: 8px; }
|
||||
|
||||
.jbox, #journal-logwrap {
|
||||
background: #0b0e13;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
#journal-logwrap { max-height: 65vh; overflow-y: auto; padding: 6px 8px; }
|
||||
.jbox { max-height: 260px; overflow-y: auto; padding: 6px 8px; }
|
||||
.jline { display: flex; gap: 8px; padding: 1px 0; }
|
||||
.jline .j-time { color: var(--muted); flex: 0 0 auto; }
|
||||
.jline .j-ident { color: #90a4ae; flex: 0 0 200px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.jline .j-msg { white-space: pre-wrap; word-break: break-word; }
|
||||
.j-err { background: rgba(239, 83, 80, .13); }
|
||||
.j-err .j-msg { color: #ffcdd2; }
|
||||
.j-warn { background: rgba(255, 183, 77, .09); }
|
||||
.j-warn .j-msg { color: #ffe0b2; }
|
||||
|
||||
.actions { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||
.btn { padding: 3px 9px; font-size: 12px; }
|
||||
|
||||
.plugin-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
padding: 1px 9px;
|
||||
font-size: 11.5px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
}
|
||||
.badge-loaded { color: #c8e6c9; border-color: rgba(102, 187, 106, .6); background: rgba(102, 187, 106, .12); }
|
||||
.badge-loading { color: #fff9c4; border-color: rgba(255, 241, 118, .6); background: rgba(255, 241, 118, .1); }
|
||||
.badge-unloaded { color: var(--muted); }
|
||||
.badge-sleeping { color: #b3e5fc; border-color: rgba(79, 195, 247, .5); background: rgba(79, 195, 247, .1); }
|
||||
.badge-failed { color: #ffcdd2; border-color: rgba(239, 83, 80, .6); background: rgba(239, 83, 80, .12); }
|
||||
.badge-downloading { color: #b3e5fc; border-color: rgba(79, 195, 247, .5); background: rgba(79, 195, 247, .1); }
|
||||
.llama-id { overflow: hidden; text-overflow: ellipsis; }
|
||||
.llama-health {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.llama-health .dot { width: 12px; height: 12px; }
|
||||
.llama-health-text { font-weight: 600; font-size: 15px; }
|
||||
.llama-models { display: flex; flex-direction: column; gap: 6px; margin-bottom: 6px; }
|
||||
.llama-model {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 7px 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.llama-model .llama-id { flex: 1 1 auto; min-width: 80px; }
|
||||
.llama-model-actions { display: flex; gap: 6px; margin-left: auto; }
|
||||
.llama-load { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
|
||||
.llama-load select { max-width: 420px; }
|
||||
.part-mounts { max-width: 420px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.part-usage { margin-left: auto; }
|
||||
203
static/js/app.js
Normal file
203
static/js/app.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
// ---------- tabs ----------
|
||||
const tabBtns = document.querySelectorAll(".tab-btn");
|
||||
const sections = document.querySelectorAll(".tab");
|
||||
|
||||
function showTab(name) {
|
||||
tabBtns.forEach((b) => b.classList.toggle("active", b.dataset.tab === name));
|
||||
sections.forEach((s) => s.classList.toggle("hidden", s.id !== "tab-" + name));
|
||||
try {
|
||||
localStorage.setItem("dash.tab", name);
|
||||
} catch (e) {}
|
||||
}
|
||||
tabBtns.forEach((b) => b.addEventListener("click", () => showTab(b.dataset.tab)));
|
||||
try {
|
||||
const saved = localStorage.getItem("dash.tab");
|
||||
if (saved && document.getElementById("tab-" + saved)) showTab(saved);
|
||||
} catch (e) {}
|
||||
|
||||
// ---------- helpers ----------
|
||||
function fmtBytes(n, digits) {
|
||||
if (n == null || isNaN(n)) return "—";
|
||||
if (digits == null) digits = 1;
|
||||
const u = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||
let i = 0;
|
||||
while (Math.abs(n) >= 1024 && i < u.length - 1) {
|
||||
n /= 1024;
|
||||
i++;
|
||||
}
|
||||
return n.toFixed(digits) + " " + u[i];
|
||||
}
|
||||
|
||||
function fmtTime(ts) {
|
||||
const d = new Date(ts * 1000);
|
||||
return d.toLocaleTimeString([], { hour12: false });
|
||||
}
|
||||
|
||||
function downsampleIdx(len, max) {
|
||||
if (len <= max) return null;
|
||||
const step = Math.ceil(len / max);
|
||||
const idx = [];
|
||||
for (let i = 0; i < len; i += step) idx.push(i);
|
||||
if (idx[idx.length - 1] !== len - 1) idx.push(len - 1);
|
||||
return idx;
|
||||
}
|
||||
|
||||
// ---------- charts ----------
|
||||
const charts = {};
|
||||
|
||||
function baseOpts(extra) {
|
||||
const o = {
|
||||
animation: false,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: { mode: "index", intersect: false },
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { ticks: { maxTicksLimit: 7, maxRotation: 0, color: "#7d8a9c" }, grid: { display: false } },
|
||||
y: { beginAtZero: true, ticks: { color: "#7d8a9c" }, grid: { color: "rgba(42,51,66,.5)" } },
|
||||
},
|
||||
};
|
||||
if (extra) Object.assign(o.scales.y, extra);
|
||||
return o;
|
||||
}
|
||||
|
||||
function newDs(label, color, extra) {
|
||||
return Object.assign(
|
||||
{ label, data: [], borderColor: color, backgroundColor: color, borderWidth: 1.5, pointRadius: 0, tension: 0.25, fill: false },
|
||||
extra || {}
|
||||
);
|
||||
}
|
||||
|
||||
const LEGEND = { display: true, labels: { boxWidth: 10, color: "#7d8a9c" } };
|
||||
|
||||
function initCharts() {
|
||||
if (typeof Chart === "undefined") return;
|
||||
charts.cpu = new Chart(document.getElementById("chart-cpu"), {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [newDs("CPU", "#4fc3f7"), newDs("GPU", "#ba68c8")],
|
||||
},
|
||||
options: baseOpts({ max: 100 }),
|
||||
});
|
||||
charts.cpu.options.plugins.legend = LEGEND;
|
||||
charts.mem = new Chart(document.getElementById("chart-mem"), {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [newDs("Memory", "#81c784"), newDs("VRAM", "#ffb74d")],
|
||||
},
|
||||
options: baseOpts({ max: 100 }),
|
||||
});
|
||||
charts.mem.options.plugins.legend = LEGEND;
|
||||
charts.io = new Chart(document.getElementById("chart-io"), {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [newDs("read", "#e57373"), newDs("write", "#fff176")],
|
||||
},
|
||||
options: baseOpts({
|
||||
ticks: { color: "#7d8a9c", callback: (v) => fmtBytes(v, 0) },
|
||||
beginAtZero: true,
|
||||
}),
|
||||
});
|
||||
charts.io.options.plugins.legend = LEGEND;
|
||||
}
|
||||
|
||||
function updateCharts(hist) {
|
||||
if (!hist || !hist.ts || !hist.ts.length) return;
|
||||
const ts = hist.ts;
|
||||
const idx = downsampleIdx(ts.length, 400);
|
||||
const pick = (arr) => (arr && idx ? idx.map((i) => (i < arr.length ? arr[i] : null)) : arr);
|
||||
const labels = (idx ? idx.map((i) => ts[i]) : ts).map(fmtTime);
|
||||
|
||||
const set2 = (chart, keys) => {
|
||||
chart.data.labels = labels;
|
||||
keys.forEach((k, i) => {
|
||||
chart.data.datasets[i].data = pick(hist.series[k]) || [];
|
||||
});
|
||||
chart.update("none");
|
||||
};
|
||||
set2(charts.cpu, ["cpu", "gpu"]);
|
||||
set2(charts.mem, ["mem_pct", "vram_pct"]);
|
||||
|
||||
charts.io.data.labels = labels;
|
||||
charts.io.data.datasets[0].data = pick(hist.series.io_read) || [];
|
||||
charts.io.data.datasets[1].data = pick(hist.series.io_write) || [];
|
||||
charts.io.update("none");
|
||||
}
|
||||
|
||||
function pollHistory() {
|
||||
fetch("/api/history")
|
||||
.then((r) => r.json())
|
||||
.then(updateCharts)
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
initCharts();
|
||||
pollHistory();
|
||||
setInterval(pollHistory, 2000);
|
||||
|
||||
// ---------- journal ----------
|
||||
const journalLog = document.getElementById("journal-log");
|
||||
const journalCursor = document.getElementById("journal-cursor");
|
||||
const journalStatus = document.getElementById("journal-status");
|
||||
const MAX_LINES = 500;
|
||||
|
||||
document.body.addEventListener("htmx:afterSwap", (e) => {
|
||||
if (e.target.id !== "journal-log") return;
|
||||
// surface one-shot errors in the status bar instead of the log
|
||||
document.querySelectorAll("#journal-log .j-error-once").forEach((el) => {
|
||||
journalStatus.textContent = el.textContent.trim();
|
||||
el.remove();
|
||||
});
|
||||
// track cursor from the most recent line
|
||||
const lines = journalLog.querySelectorAll(".jline");
|
||||
const last = lines[lines.length - 1];
|
||||
if (last && last.dataset.cursor) journalCursor.value = last.dataset.cursor;
|
||||
// trim
|
||||
const extra = lines.length - MAX_LINES;
|
||||
if (extra > 0) {
|
||||
for (let i = 0; i < extra; i++) lines[i].remove();
|
||||
}
|
||||
// auto-scroll if user is at the bottom (within 40px)
|
||||
const wrap = document.getElementById("journal-logwrap");
|
||||
const atBottom = wrap.scrollHeight - wrap.scrollTop - wrap.clientHeight < 40;
|
||||
if (atBottom) wrap.scrollTop = wrap.scrollHeight;
|
||||
});
|
||||
|
||||
// reset cursor when the filter form is submitted
|
||||
const journalFilters = document.getElementById("journal-filters");
|
||||
journalFilters.addEventListener("submit", () => {
|
||||
journalCursor.value = "";
|
||||
journalStatus.textContent = "";
|
||||
journalLog.innerHTML = "";
|
||||
});
|
||||
|
||||
// ---------- table sorting (processes, services) ----------
|
||||
document.body.addEventListener("click", (e) => {
|
||||
const link = e.target.closest(".sortlink");
|
||||
if (!link) return;
|
||||
e.preventDefault();
|
||||
const formId = link.dataset.form || "proc-controls";
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return;
|
||||
const sortSel = form.querySelector("select[name=sort]");
|
||||
const orderSel = form.querySelector("select[name=order]");
|
||||
if (sortSel) sortSel.value = link.dataset.sort;
|
||||
if (orderSel) orderSel.value = link.dataset.order;
|
||||
htmx.trigger(form, "submit");
|
||||
});
|
||||
|
||||
// keyboard: Enter/Space on service names
|
||||
document.body.addEventListener("keydown", (e) => {
|
||||
const el = e.target.closest(".svc-name");
|
||||
if (el && (e.key === "Enter" || e.key === " ")) {
|
||||
e.preventDefault();
|
||||
htmx.trigger(el, "click");
|
||||
}
|
||||
});
|
||||
})();
|
||||
14
static/vendor/chart.umd.min.js
vendored
Normal file
14
static/vendor/chart.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/vendor/htmx.min.js
vendored
Normal file
1
static/vendor/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
50
templates/disks.html
Normal file
50
templates/disks.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<div id="disks" class="panel" hx-get="/api/disks" hx-trigger="every 2s" hx-swap="outerHTML">
|
||||
<h2>Filesystems</h2>
|
||||
<div class="part-list">
|
||||
{% for p in partitions %}
|
||||
<div class="part">
|
||||
<div class="part-head">
|
||||
<span class="mono">{{ p.device }}</span>
|
||||
<span class="muted">{{ p.fstype }}</span>
|
||||
<span class="muted part-mounts" title="{{ p.mounts | join("\n") }}">{{ p.mounts_disp }}</span>
|
||||
<span class="part-usage">{{ p.used | humanize }} / {{ p.total | humanize }} ({{ p.pct | round(0) }}%)</span>
|
||||
</div>
|
||||
<div class="bar"><div class="bar-fill{% if p.pct > 90 %} hot{% elif p.pct > 75 %} warm{% endif %}" style="width: {{ p.pct }}%"></div></div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted">no partitions found</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<h2>Device I/O</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
<colgroup>
|
||||
<col style="width: 110px">
|
||||
<col style="width: 100px">
|
||||
<col style="width: 100px">
|
||||
<col style="width: 100px">
|
||||
<col style="width: 100px">
|
||||
<col style="width: 110px">
|
||||
<col style="width: 110px">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr><th>Device</th><th class="num">Read</th><th class="num">Write</th><th class="num">Read rate</th><th class="num">Write rate</th><th class="num">Reads</th><th class="num">Writes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in per_disk %}
|
||||
<tr>
|
||||
<td class="mono cell-clip" title="{{ d.device }}">{{ d.device }}</td>
|
||||
<td class="num">{{ d.read_bytes | humanize }}</td>
|
||||
<td class="num">{{ d.write_bytes | humanize }}</td>
|
||||
<td class="num">{{ d.read_rate | rate }}</td>
|
||||
<td class="num">{{ d.write_rate | rate }}</td>
|
||||
<td class="num">{{ d.reads }}</td>
|
||||
<td class="num">{{ d.writes }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="muted">no block devices</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
101
templates/index.html
Normal file
101
templates/index.html
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ hostname }}</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<script src="/static/vendor/htmx.min.js"></script>
|
||||
<script src="/static/vendor/chart.umd.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<h1>{{ hostname }}</h1>
|
||||
<nav id="tabs">
|
||||
<button class="tab-btn active" data-tab="overview">Overview</button>
|
||||
<button class="tab-btn" data-tab="disks">Disks</button>
|
||||
<button class="tab-btn" data-tab="processes">Processes</button>
|
||||
<button class="tab-btn" data-tab="journal">Journal</button>
|
||||
<button class="tab-btn" data-tab="services">Services</button>
|
||||
<button class="tab-btn" data-tab="plugins">Plugins</button>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section id="tab-overview" class="tab">
|
||||
<div id="overview" class="panel" hx-get="/api/overview" hx-trigger="load, every 2s" hx-swap="outerHTML"></div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card"><h3>CPU / GPU %</h3><canvas id="chart-cpu"></canvas></div>
|
||||
<div class="chart-card"><h3>Memory / VRAM %</h3><canvas id="chart-mem"></canvas></div>
|
||||
<div class="chart-card"><h3>Disk I/O</h3><canvas id="chart-io"></canvas></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="tab-disks" class="tab hidden">
|
||||
<div id="disks" class="panel" hx-get="/api/disks" hx-trigger="load, every 2s" hx-swap="outerHTML"></div>
|
||||
</section>
|
||||
|
||||
<section id="tab-processes" class="tab hidden">
|
||||
<form id="proc-controls" class="inline-form" hx-get="/api/processes" hx-target="#processes-body" hx-swap="outerHTML">
|
||||
<input id="proc-q" name="q" placeholder="filter name / pid">
|
||||
<select name="sort" id="proc-sort">
|
||||
<option value="cpu">sort: CPU</option>
|
||||
<option value="rss">sort: MEM</option>
|
||||
<option value="mem_pct">sort: MEM%</option>
|
||||
<option value="io_read">sort: IO read</option>
|
||||
<option value="io_write">sort: IO write</option>
|
||||
<option value="pid">sort: PID</option>
|
||||
<option value="name">sort: Name</option>
|
||||
</select>
|
||||
<select name="order" id="proc-order">
|
||||
<option value="desc">desc</option>
|
||||
<option value="asc">asc</option>
|
||||
</select>
|
||||
<button type="submit">apply</button>
|
||||
</form>
|
||||
<div id="processes-body" class="panel" hx-get="/api/processes" hx-trigger="load, every 3s" hx-swap="outerHTML" hx-include="#proc-controls"></div>
|
||||
</section>
|
||||
|
||||
<section id="tab-journal" class="tab hidden">
|
||||
<div id="journal-status" class="muted small"></div>
|
||||
<form id="journal-filters" class="inline-form" hx-get="/api/journal" hx-target="#journal-log" hx-swap="innerHTML">
|
||||
<select name="level">
|
||||
<option value="all">all</option>
|
||||
<option value="warn">warn+</option>
|
||||
<option value="err">err+</option>
|
||||
</select>
|
||||
<input name="unit" placeholder="unit (ssh.service)">
|
||||
<input name="search" placeholder="search text">
|
||||
<label class="chk"><input type="checkbox" name="hide_sudo" checked> hide sudo</label>
|
||||
<button type="submit">filter</button>
|
||||
</form>
|
||||
<div id="journal-logwrap">
|
||||
<input type="hidden" id="journal-cursor" value="">
|
||||
<div id="journal-log" hx-get="/api/journal" hx-trigger="load, every 5s" hx-swap="beforeend" hx-include="#journal-filters, #journal-cursor"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="tab-services" class="tab hidden">
|
||||
<form id="svc-filters" class="inline-form" hx-get="/api/services" hx-target="#services" hx-swap="outerHTML">
|
||||
<input id="svc-q" name="q" placeholder="filter units">
|
||||
<select name="sort" id="svc-sort">
|
||||
<option value="name">sort: name</option>
|
||||
<option value="state">sort: state</option>
|
||||
<option value="enabled">sort: enabled</option>
|
||||
</select>
|
||||
<select name="order" id="svc-order">
|
||||
<option value="asc">asc</option>
|
||||
<option value="desc">desc</option>
|
||||
</select>
|
||||
<button type="submit">filter</button>
|
||||
</form>
|
||||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="load, every 5s" hx-swap="outerHTML" hx-include="#svc-filters"></div>
|
||||
<div id="service-detail" class="detail-pane"></div>
|
||||
</section>
|
||||
|
||||
<section id="tab-plugins" class="tab hidden">
|
||||
<div id="plugins" class="panel" hx-get="/api/plugins" hx-trigger="load" hx-swap="outerHTML"></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="/static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
templates/journal.html
Normal file
10
templates/journal.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{% if error %}
|
||||
<div class="jline j-error-once"><span class="muted">journalctl: {{ error }}</span></div>
|
||||
{% endif %}
|
||||
{% for e in entries %}
|
||||
<div class="jline{% if e.prio <= 3 %} j-err{% elif e.prio == 4 %} j-warn{% endif %}" data-cursor="{{ e.cursor }}">
|
||||
<span class="j-time">{{ e.stamp }}</span>
|
||||
<span class="j-ident">{{ e.ident }}</span>
|
||||
<span class="j-msg">{{ e.msg }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
71
templates/overview.html
Normal file
71
templates/overview.html
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<div id="overview" class="panel cards" hx-get="/api/overview" hx-trigger="every 2s" hx-swap="outerHTML">
|
||||
{% macro bar(pct) %}{% if pct is not none %}<div class="bar"><div class="bar-fill{% if pct > 85 %} hot{% elif pct > 65 %} warm{% endif %}" style="width: {{ pct }}%"></div></div>{% endif %}{% endmacro %}
|
||||
{% macro barrow(label, pct, cls) %}{% if pct is not none %}<div class="barrow"><span class="barrow-label">{{ label }}</span><div class="bar"><div class="bar-fill{% if cls %} {{ cls }}{% endif %}{% if pct > 85 %} hot{% elif pct > 65 %} warm{% endif %}" style="width: {{ pct }}%"></div></div></div>{% endif %}{% endmacro %}
|
||||
<div class="card">
|
||||
<div class="card-label">CPU / GPU</div>
|
||||
<div class="card-value">{{ (c.cpu | default(0, true)) | round(1) }}%</div>
|
||||
{{ barrow("CPU", c.cpu) }}
|
||||
{{ barrow("GPU", c.gpu, "gpu") }}
|
||||
{% if c.cpu_temp is not none or c.gpu_temp is not none %}
|
||||
<div class="card-sub">
|
||||
{% if c.cpu_temp is not none %}CPU {{ c.cpu_temp | round(0) | int }}°C{% endif %}
|
||||
{% if c.cpu_temp is not none and c.gpu_temp is not none %} ·{% endif %}
|
||||
{% if c.gpu_temp is not none %}GPU {{ c.gpu_temp | round(0) | int }}°C{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-sub">load {{ (c.load1 | default(0, true)) | round(2) }} · {{ c.cores }} cores</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-label">Memory</div>
|
||||
<div class="card-value">{{ c.mem_used | humanize }} <span class="muted">/ {{ c.mem_total | humanize }} ({{ (c.mem_pct | default(0, true)) | round(0) | int }}%)</span></div>
|
||||
{{ bar(c.mem_pct) }}
|
||||
<div class="memrow">
|
||||
<div class="memrow-text">Swap {% if c.swap_total %}{{ c.swap_used | humanize }} <span class="muted">/ {{ c.swap_total | humanize }} ({{ (c.swap_pct | default(0, true)) | round(0) | int }}%)</span>{% else %}<span class="muted">none</span>{% endif %}</div>
|
||||
{{ bar(c.swap_pct if c.swap_total else none) }}
|
||||
</div>
|
||||
<div class="memrow">
|
||||
<div class="memrow-text">VRAM {% if c.vram_total %}{{ c.vram_used | humanize }} <span class="muted">/ {{ c.vram_total | humanize }} ({{ (c.vram_pct | default(0, true)) | round(0) | int }}%)</span>{% else %}<span class="muted">—</span>{% endif %}</div>
|
||||
{{ bar(c.vram_pct if c.vram_total else none) }}
|
||||
</div>
|
||||
</div>
|
||||
{% if c.battery is not none %}
|
||||
<div class="card">
|
||||
<div class="card-label">Battery</div>
|
||||
<div class="card-value">{{ c.battery }}%</div>
|
||||
<div class="bar"><div class="bar-fill{% if c.battery < 20 %} low{% elif c.battery < 45 %} warm{% endif %}" style="width: {{ c.battery }}%"></div></div>
|
||||
{% if c.ac_online is not none %}
|
||||
<div class="card-sub">
|
||||
{% if c.ac_online %}on AC{% else %}on battery{% endif %}
|
||||
{% set st = (c.battery_status or "") | lower %}
|
||||
{% if st == "charging" %} · charging{% elif c.ac_online and st in ("full", "not charging") %} · full{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
<div class="card-label">Network</div>
|
||||
{% if c.net_wifi and c.net_wifi.ssid %}
|
||||
<div class="card-value">{{ c.net_wifi.ssid }}</div>
|
||||
<div class="card-sub">Wi-Fi via {{ c.net_wifi.iface }}</div>
|
||||
{% elif c.net_wifi %}
|
||||
<div class="card-value muted">Wi-Fi</div>
|
||||
<div class="card-sub">{{ c.net_wifi.iface }} · no connection</div>
|
||||
{% elif c.net_ifaces %}
|
||||
<div class="card-value muted">wired</div>
|
||||
{% else %}
|
||||
<div class="card-value muted">—</div>
|
||||
{% endif %}
|
||||
<div class="card-sub">
|
||||
{% for i in c.net_ifaces %}
|
||||
<div class="mono small">{{ i.name }}: {% if i.ipv4 %}{{ i.ipv4 | join(", ") }}{% else %}<span class="muted">no IPv4</span>{% endif %}</div>
|
||||
{% else %}
|
||||
<span class="muted">no active interfaces</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-label">Uptime</div>
|
||||
<div class="card-value">{{ c.uptime }}</div>
|
||||
<div class="card-sub">{{ c.hostname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
12
templates/plugins.html
Normal file
12
templates/plugins.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div id="plugins" class="panel">
|
||||
<div class="muted small">plugins load from <span class="mono">app/plugins/</span> — each module exposes a <span class="mono">Plugin</span> instance</div>
|
||||
{% for item in items %}
|
||||
<div class="plugin-card">
|
||||
<h3>{{ item.p.title }}</h3>
|
||||
<p class="muted small">{{ item.p.description }}</p>
|
||||
<div class="plugin-body" hx-get="/api/plugins/{{ item.p.id }}/fragment" hx-trigger="every {{ item.p.poll_seconds }}s" hx-swap="innerHTML">{{ item.frag | safe }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted">no plugins registered</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
51
templates/plugins/llamacpp.html
Normal file
51
templates/plugins/llamacpp.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<div class="llama">
|
||||
{% if not reachable %}
|
||||
<div class="alert">llama-server unreachable at <span class="mono">{{ base_url }}</span>{% if error %}: {{ error }}{% endif %}</div>
|
||||
<div class="muted small">start it in router mode: <span class="mono">llama-server --models-dir /path/to/models</span></div>
|
||||
{% else %}
|
||||
<div class="llama-health">
|
||||
<span class="dot {{ "dot-run" if health == "ok" else "dot-failed" }}"></span>
|
||||
<span class="llama-health-text">{{ "healthy" if health == "ok" else (health or "status unknown") }}</span>
|
||||
<span class="muted small mono">{{ base_url }}</span>
|
||||
</div>
|
||||
{% if message %}<div class="notice">{{ message }}</div>{% endif %}
|
||||
{% if error_msg %}<div class="alert">{{ error_msg }}</div>{% endif %}
|
||||
|
||||
<h4>Loaded models</h4>
|
||||
{% if loaded %}
|
||||
<div class="llama-models">
|
||||
{% for m in loaded %}
|
||||
<div class="llama-model">
|
||||
<span class="dot {{ "dot-run" if m.state == "loaded" else ("dot-sleep" if m.state == "sleeping" else ("dot-failed" if m.failed else "dot-busy")) }}"></span>
|
||||
<span class="mono llama-id" title="{{ m.path }}">{{ m.id }}</span>
|
||||
<span class="badge badge-{{ "failed" if m.failed else m.state }}">{{ m.state }}{% if m.failed %} (exit {{ m.exit_code }}){% endif %}</span>
|
||||
{% if m.progress is not none %}<div class="bar small-bar"><div class="bar-fill" style="width: {{ m.progress }}%"></div></div>{% endif %}
|
||||
<span class="llama-model-actions">
|
||||
<button class="btn" hx-post="/api/plugins/llamacpp/unload" hx-vals='{"model":"{{ m.id }}"}' hx-target="closest .plugin-body" hx-swap="innerHTML" hx-confirm="Unload {{ m.id }}?">unload</button>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted small">no models loaded</div>
|
||||
{% endif %}
|
||||
|
||||
<h4>Available models</h4>
|
||||
{% if available %}
|
||||
<div class="llama-load">
|
||||
<select id="llama-model-pick" name="model">
|
||||
{% for m in available %}
|
||||
<option value="{{ m.id }}">{{ m.id }}{% if m.failed %} (failed, exit {{ m.exit_code }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn" hx-post="/api/plugins/llamacpp/load" hx-include="#llama-model-pick" hx-target="closest .plugin-body" hx-swap="innerHTML">load</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted small">no unloaded models (is --models-dir set on the server?)</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn" hx-post="/api/plugins/llamacpp/rescan" hx-target="closest .plugin-body" hx-swap="innerHTML">rescan models</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
44
templates/processes.html
Normal file
44
templates/processes.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<div id="processes-body" class="panel" hx-get="/api/processes" hx-trigger="every 3s" hx-swap="outerHTML" hx-include="#proc-controls">
|
||||
<div class="muted small">showing {{ procs | length }} of {{ total }} processes</div>
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
<colgroup>
|
||||
<col style="width: 64px">
|
||||
<col>
|
||||
<col style="width: 100px">
|
||||
<col style="width: 72px">
|
||||
<col style="width: 72px">
|
||||
<col style="width: 92px">
|
||||
<col style="width: 92px">
|
||||
<col style="width: 92px">
|
||||
<col style="width: 92px">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for col, label in [("pid", "PID"), ("name", "Name"), ("user", "User"), ("cpu", "CPU%"), ("mem_pct", "MEM%"), ("rss", "RSS"), ("gpu", "GPU"), ("io_read", "IO read"), ("io_write", "IO write")] %}
|
||||
<th class="{% if col in ('cpu','mem_pct','rss','gpu','io_read','io_write') %}num{% endif %}">
|
||||
<a href="#" class="sortlink{% if sort == col %} active{% endif %}" data-sort="{{ col }}" data-order="{% if sort == col and order == 'desc' %}asc{% else %}desc{% endif %}">{{ label }}{% if sort == col %} {{ "↓" if order == "desc" else "↑" }}{% endif %}</a>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in procs %}
|
||||
<tr>
|
||||
<td class="mono">{{ p.pid }}</td>
|
||||
<td class="pname" title="{{ p.name }}">{{ p.name }}</td>
|
||||
<td class="muted">{{ p.user }}</td>
|
||||
<td class="num">{{ p.cpu | round(1) }}</td>
|
||||
<td class="num">{{ p.mem_pct | round(1) }}</td>
|
||||
<td class="num">{{ p.rss | humanize }}</td>
|
||||
<td class="num">{% if p.gpu is not none %}{{ p.gpu }} MB{% else %}<span class="muted">—</span>{% endif %}</td>
|
||||
<td class="num">{{ p.io_read | rate }}</td>
|
||||
<td class="num">{{ p.io_write | rate }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="9" class="muted">no processes match</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
24
templates/service_detail.html
Normal file
24
templates/service_detail.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<div class="detail-inner">
|
||||
<h3 class="mono">{{ unit }}</h3>
|
||||
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
||||
<div class="kv">
|
||||
<div><span class="muted">state:</span> {{ props.get("ActiveState", "?") }} ({{ props.get("SubState", "?") }})</div>
|
||||
<div><span class="muted">enabled:</span> {{ props.get("UnitFileState", "?") }}</div>
|
||||
<div><span class="muted">main pid:</span> {{ props.get("MainPID", "?") }}</div>
|
||||
<div><span class="muted">description:</span> {{ props.get("Description", "") }}</div>
|
||||
<div><span class="muted">restarts:</span> {{ props.get("NRestarts", "0") }}</div>
|
||||
<div><span class="muted">fragment:</span> <span class="mono">{{ props.get("FragmentPath", "") }}</span></div>
|
||||
</div>
|
||||
<h4>Recent log</h4>
|
||||
<div class="jbox">
|
||||
{% for e in log %}
|
||||
<div class="jline{% if e.prio <= 3 %} j-err{% elif e.prio == 4 %} j-warn{% endif %}">
|
||||
<span class="j-time">{{ e.stamp }}</span>
|
||||
<span class="j-ident">{{ e.ident }}</span>
|
||||
<span class="j-msg">{{ e.msg }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted small">no recent entries</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
52
templates/services.html
Normal file
52
templates/services.html
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="every 5s" hx-swap="outerHTML" hx-include="#svc-filters">
|
||||
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
||||
<div class="muted small">system: {{ state }} · {{ units | length }} units</div>
|
||||
<div class="table-wrap">
|
||||
<table class="table">
|
||||
<colgroup>
|
||||
<col style="width: 30px">
|
||||
<col style="width: 250px">
|
||||
<col>
|
||||
<col style="width: 130px">
|
||||
<col style="width: 90px">
|
||||
<col style="width: 190px">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><a href="#" class="sortlink{% if sort == "name" %} active{% endif %}" data-form="svc-filters" data-sort="name" data-order="{% if sort == "name" and order == "asc" %}desc{% else %}asc{% endif %}">Unit{% if sort == "name" %} {{ "↓" if order == "desc" else "↑" }}{% endif %}</a></th>
|
||||
<th class="muted">Description</th>
|
||||
<th><a href="#" class="sortlink{% if sort == "state" %} active{% endif %}" data-form="svc-filters" data-sort="state" data-order="{% if sort == "state" and order == "asc" %}desc{% else %}asc{% endif %}">State{% if sort == "state" %} {{ "↓" if order == "desc" else "↑" }}{% endif %}</a></th>
|
||||
<th><a href="#" class="sortlink{% if sort == "enabled" %} active{% endif %}" data-form="svc-filters" data-sort="enabled" data-order="{% if sort == "enabled" and order == "asc" %}desc{% else %}asc{% endif %}">Enabled{% if sort == "enabled" %} {{ "↓" if order == "desc" else "↑" }}{% endif %}</a></th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u in units %}
|
||||
{% set dot = "dot-dead" %}
|
||||
{% if u.sub in ("running", "exited") and u.active == "active" %}{% set dot = "dot-run" %}{% endif %}
|
||||
{% if u.active == "failed" %}{% set dot = "dot-failed" %}{% endif %}
|
||||
{% if u.sub in ("activating", "deactivating", "reloading") %}{% set dot = "dot-busy" %}{% endif %}
|
||||
<tr>
|
||||
<td><span class="dot {{ dot }}"></span></td>
|
||||
<td class="mono svc-name" hx-get="/api/services/{{ u.name }}/detail" hx-target="#service-detail" hx-swap="innerHTML" role="button" tabindex="0" title="click for details">{{ u.name }}</td>
|
||||
<td class="muted cell-clip" title="{{ u.desc }}">{{ u.desc }}</td>
|
||||
<td class="muted">{{ u.active }}{% if u.sub and u.sub not in ("running", "dead", "exited") %} <span class="small">({{ u.sub }})</span>{% endif %}</td>
|
||||
<td class="muted">{{ u.enabled or "—" }}</td>
|
||||
<td class="actions">
|
||||
{% if u.active != "active" %}
|
||||
<button class="btn" hx-post="/api/services/{{ u.name }}/action" hx-vals='{"action":"start"}' hx-include="#svc-filters" hx-target="#services" hx-swap="outerHTML">start</button>
|
||||
{% else %}
|
||||
<button class="btn" hx-post="/api/services/{{ u.name }}/action" hx-vals='{"action":"stop"}' hx-include="#svc-filters" hx-target="#services" hx-swap="outerHTML" hx-confirm="Stop {{ u.name }}?">stop</button>
|
||||
<button class="btn" hx-post="/api/services/{{ u.name }}/action" hx-vals='{"action":"restart"}' hx-include="#svc-filters" hx-target="#services" hx-swap="outerHTML" hx-confirm="Restart {{ u.name }}?">restart</button>
|
||||
{% endif %}
|
||||
<button class="btn" hx-post="/api/services/{{ u.name }}/action" hx-vals='{"action":"{% if u.enabled == "enabled" or u.enabled == "indirect" or u.enabled == "static" %}disable{% else %}enable{% endif %}"}' hx-include="#svc-filters" hx-target="#services" hx-swap="outerHTML">{% if u.enabled == "enabled" or u.enabled == "indirect" or u.enabled == "static" %}disable{% else %}enable{% endif %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="muted">no units match</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
750
uv.lock
Normal file
750
uv.lock
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.14.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.7.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashboard"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "psutil" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0.141.1" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
{ name = "psutil", specifier = ">=7.2.2" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.15.0" },
|
||||
{ name = "python-multipart", specifier = ">=0.0.32" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.52.4" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.141.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.32"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.52.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
standard = [
|
||||
{ name = "httptools" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
|
||||
{ name = "watchfiles" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.22.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/0d/098f23c4c858e5de9459ffc554fa07d5493fbcfca7f040b5800cf1cecc35/websockets-17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3", size = 217015, upload-time = "2026-08-26T14:55:45.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/86/bc1317b1a4d8c4688e2a7e564b5e004dab44c2534d7ca05de6ae9a863fca/websockets-17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98", size = 214692, upload-time = "2026-08-26T14:55:46.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/e7/df821761772beaa48c211ee0e234930b35c1473778470773823f56d3911b/websockets-17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab", size = 214959, upload-time = "2026-08-26T14:55:47.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/92/c3fb72f11764812fc648bf3838d224972427b348e8b3989d9e0a9df87da3/websockets-17.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74", size = 224278, upload-time = "2026-08-26T14:55:49.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/05/9f82d090c8d2d861604147ef6dfb938a90b039f9358d5193f1df62558593/websockets-17.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6", size = 224557, upload-time = "2026-08-26T14:55:50.348Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/50/5cbf677b865290fe36819ff00615826e7edc1df38786f770123ff39a933d/websockets-17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08", size = 225791, upload-time = "2026-08-26T14:55:51.75Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/1c/eb8a032285243381b09a221ae384c972d5000453ad136add4d1595cec798/websockets-17.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11", size = 228574, upload-time = "2026-08-26T14:55:52.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/85/413736251cb3ac04ce84cbd90e893d9a36a9698d4820b323aff3aa187e50/websockets-17.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4", size = 226428, upload-time = "2026-08-26T14:55:54.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/2b/a08bcc7fa1ca81a10f84ba32b6e6edd73a913f4b0c2640eed1fd626efacd/websockets-17.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8", size = 225184, upload-time = "2026-08-26T14:55:55.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/8a/3bd2d0cf6b148c8c866d5d9fdcde30c04bfd81fdfac86813e69377eb4448/websockets-17.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391", size = 222430, upload-time = "2026-08-26T14:55:57.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/c9/8e891ae342668735eabbbc669895e15195e4b45f24a4beeb58af76f414c7/websockets-17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a", size = 225227, upload-time = "2026-08-26T14:55:58.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/6f/c816f332dca11425e9bda7c07f7573eb5c5f8a735849d02b0d81e8ee20fa/websockets-17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837", size = 223831, upload-time = "2026-08-26T14:55:59.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/67/5e91d5308ce24fc1ec74f56536c12f4888bad45ff5ea50f3180f8c518c57/websockets-17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641", size = 224600, upload-time = "2026-08-26T14:56:00.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/96/faa298ecf2570d35b0eb37caddf4992178d907e108ed74bfffb6bc092c29/websockets-17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090", size = 225707, upload-time = "2026-08-26T14:56:02.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/12/5710d2482ca5061c1eec5eb46f6313837c760d4115b1795c85b6c08be4e3/websockets-17.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1", size = 223263, upload-time = "2026-08-26T14:56:03.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/47/0c30f4eebfd1d93fae779d268f678d48847fb98516f5200849574eee8820/websockets-17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067", size = 224244, upload-time = "2026-08-26T14:56:04.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/33/46c256195a1255079ae23d1b1267b2e1843dc5f46a67f973cdf2a3523dff/websockets-17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60", size = 224520, upload-time = "2026-08-26T14:56:05.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/9a/aef0792731df4352e5f417369b532b3325fe434765ca90c193f594ae1e67/websockets-17.1-cp312-cp312-win32.whl", hash = "sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb", size = 217485, upload-time = "2026-08-26T14:56:06.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/23/493ecfdaf32898e5ea24dc900e33e5e317f9662d5d9ab2d44b2e111b4e1c/websockets-17.1-cp312-cp312-win_amd64.whl", hash = "sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec", size = 217786, upload-time = "2026-08-26T14:56:08.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/3d/91954e2f7876f74ce1213e9b92c65a63b559cc4b942a931ebeb351cd9932/websockets-17.1-cp312-cp312-win_arm64.whl", hash = "sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729", size = 217711, upload-time = "2026-08-26T14:56:09.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/31/5f6450a7879f4f063ef08897cc385ea3ce3f1fe17f08b11e3fd959abdf27/websockets-17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e", size = 217006, upload-time = "2026-08-26T14:56:10.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/2a/c1b006fc861695d2aa4e35327b842015ce1d98cf8f99241829b3d6460bfc/websockets-17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc", size = 214690, upload-time = "2026-08-26T14:56:11.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/69/66e5b7d01445e0eeb1d4ab419c30315f2c90cf7a8a8cd4ecc47f894dba54/websockets-17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80", size = 214947, upload-time = "2026-08-26T14:56:12.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/ce/033cafe2d2538562efa876b9149a2c7a0f7787870a4b1bb6e28adc9ceb6b/websockets-17.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce", size = 224329, upload-time = "2026-08-26T14:56:14.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/c7/e1c2e8a67f6cc0aa43abe0046fb3b7a020980649e6a843751dc7ce9eb170/websockets-17.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51", size = 224611, upload-time = "2026-08-26T14:56:15.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/de/07c6d48eb3d2069709410c851e7de10ab83d752c4bd09862899627c2729b/websockets-17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c", size = 225848, upload-time = "2026-08-26T14:56:16.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/dd/3c68572d20509648cc2fb6f50ccf3deeb4b87270f2c8966e99476e278ea3/websockets-17.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3", size = 227290, upload-time = "2026-08-26T14:56:18.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4a/8f6651c8a22093539c9215af0c5bbf217b87b382c99d2112039b92d593c2/websockets-17.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435", size = 226476, upload-time = "2026-08-26T14:56:19.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/be/f6fc33cea86b1127fd1297b18c107e81580ab55a73a39f9a934441ef321f/websockets-17.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906", size = 225233, upload-time = "2026-08-26T14:56:20.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/83/65edaf05f7c9b1dea82f4d252fdc37706a84571646f06119a27b0a16fe19/websockets-17.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c", size = 222488, upload-time = "2026-08-26T14:56:22.208Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/42/d1169c2f7f1f0032b0d4b0c00f0711a070cd7c735de37bfeb876bc0f9606/websockets-17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780", size = 225295, upload-time = "2026-08-26T14:56:23.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/f4/64e2a386c3899b917c2933225c9b47887874229d159797f3bf1a11c20d51/websockets-17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c", size = 223891, upload-time = "2026-08-26T14:56:24.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/b3/dfb5c482f7e310a3432fdbb045ddfe6d34114680e89a233d4ff900a32961/websockets-17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad", size = 224661, upload-time = "2026-08-26T14:56:26.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/cf/94865130a336029f46412adc127c4fbe380f46172b90ce251369e35c4302/websockets-17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c", size = 225766, upload-time = "2026-08-26T14:56:27.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/34/eb8c658f86dfe562ed49a887a27424bfe9e618c26ea6f865b093d075d3a6/websockets-17.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a", size = 223323, upload-time = "2026-08-26T14:56:28.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/7e/2629609652ece5ca0c7ac235927dd4511b08131e3a5d53439b798fddf002/websockets-17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0", size = 224276, upload-time = "2026-08-26T14:56:29.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/6b/8525737fe840b38e5f40956c198fb586a4fac1e07144d41a5b949b989cf8/websockets-17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d", size = 224558, upload-time = "2026-08-26T14:56:31.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/ab/3a958c6cbcf74b118f601c20a80ac8bd5e8dfec0bcf7345116feaeefb121/websockets-17.1-cp313-cp313-win32.whl", hash = "sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608", size = 217475, upload-time = "2026-08-26T14:56:32.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/36/fb521f0f2994c25509651f169efe5582dddd8713d57a0757ba87859372ef/websockets-17.1-cp313-cp313-win_amd64.whl", hash = "sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f", size = 217784, upload-time = "2026-08-26T14:56:33.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/92/9b8419584681a12a7534b746dfb2737c466efe2455483e2fbf8b941a04ec/websockets-17.1-cp313-cp313-win_arm64.whl", hash = "sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2", size = 217715, upload-time = "2026-08-26T14:56:34.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/0d/500cf5daea09d4669dff3a7d67159094a0bd6c4ef130381404f6edd3eb5f/websockets-17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e", size = 217048, upload-time = "2026-08-26T14:56:36.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/12/5b12c6168aa269cffbfd24d177cd492b130120403a418c7e89462e27b4ac/websockets-17.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642", size = 214737, upload-time = "2026-08-26T14:56:37.43Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/36/e453e5106e4e2416f008ac222837c2f1637a063b08008afcd1088889b631/websockets-17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6", size = 214955, upload-time = "2026-08-26T14:56:38.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/30/0204bb86176db02cdfc678ce65ed808a66fab87d250ce61a8790800a60b0/websockets-17.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19", size = 224331, upload-time = "2026-08-26T14:56:39.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/c8/d8372256e00c4e3cab1115c45075d1eeedb642a3f2b42bd70c4deae03f06/websockets-17.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b", size = 224685, upload-time = "2026-08-26T14:56:41.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/7d/650355b8f67f908ff99603351d4458d1a0b787d627950a47c38db7e25308/websockets-17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce", size = 225927, upload-time = "2026-08-26T14:56:42.359Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/6c/a9ffa5b903579eed76017870f055d75ecc73988d9d0c9b65a92ba0bf2a27/websockets-17.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5", size = 227300, upload-time = "2026-08-26T14:56:43.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/5d/4551c2269066af7481ee44605a0813770961615b5b5da3e87a8f5cb859ea/websockets-17.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c", size = 226533, upload-time = "2026-08-26T14:56:44.669Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/43/237a99233e5c445759a613831b3a92e91905afc064dc3bd0ad33c35fd1e2/websockets-17.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555", size = 225280, upload-time = "2026-08-26T14:56:45.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/b5/e9407a91613d1d1cd932414143a1012096b26674a782fc55a0bd23217ee4/websockets-17.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f", size = 222540, upload-time = "2026-08-26T14:56:47.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d2/db76628db0577b783205d9779f64d8e373416b04c62d1546be4b75dc8540/websockets-17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a", size = 225354, upload-time = "2026-08-26T14:56:48.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/4c/2174181c067b89a74ae18e2650c2ac29959f4b796afe876ab3f4d30d642c/websockets-17.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78", size = 223867, upload-time = "2026-08-26T14:56:49.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/75/274decb9a8253561b5be3261e02a6676fc8ecdf31e95b722e53d5bfb8fd2/websockets-17.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6", size = 224652, upload-time = "2026-08-26T14:56:50.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/e6/49824f1fb4db7656d2f7492b1d8be16147b759d909490e32f4776843ee64/websockets-17.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb", size = 225822, upload-time = "2026-08-26T14:56:52.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/6a/5dc43838c0b02a95f42c47a0de33c5ddd7767a9feeb4d0d8777ac1cfefe4/websockets-17.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d", size = 223379, upload-time = "2026-08-26T14:56:53.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/62/585637cf06d6b321232f79c55dc14d65518d12cf87c94c44f5864068810e/websockets-17.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81", size = 224330, upload-time = "2026-08-26T14:56:55.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/68/c3b234a6a1366b6ab5bbfaa4434a1b946e1dc4e8ddd6824bfd93a8835b7f/websockets-17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da", size = 224622, upload-time = "2026-08-26T14:56:56.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/d4/84cf3d1376f5d8207f55f43c1c818babd6b89447f5dcd01f18a6d5526796/websockets-17.1-cp314-cp314-win32.whl", hash = "sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a", size = 217036, upload-time = "2026-08-26T14:56:57.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/0f/9e7ac63c5d7cb642952200814f584318e65146df008b7d375d5d9c6b2c97/websockets-17.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5", size = 217382, upload-time = "2026-08-26T14:56:59.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/bb/1ae6b91f7f3ac05f5c9f14a72dc2181c115ff370bcd8a7f10f02c174adfd/websockets-17.1-cp314-cp314-win_arm64.whl", hash = "sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82", size = 217268, upload-time = "2026-08-26T14:57:00.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/f0/f65644d0e0b2b90918a8c41503841cc4072a58f2bf76c09bc36e751fc0dd/websockets-17.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603", size = 217379, upload-time = "2026-08-26T14:57:02.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/35/4c46d1f620ac1a30f92b6eae78ee40a772a93f568647ca7ccdc5ea283cf8/websockets-17.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f", size = 214911, upload-time = "2026-08-26T14:57:03.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/6e/4587e8406d7c1188e97b9cf466c081e93399380d447f885bfce81626cd37/websockets-17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5", size = 215115, upload-time = "2026-08-26T14:57:04.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/06/1381c8fff525041025909eb80ace32489194a00ba22a0a8d428030afcc84/websockets-17.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2", size = 224696, upload-time = "2026-08-26T14:57:05.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/9d/9034e867dc85340be058619751742b895f722326e83100d110063461ca07/websockets-17.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b", size = 224975, upload-time = "2026-08-26T14:57:07.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/eb/ed03aa3cae748ebf6397e5d44028f433f746bad09dc568ff754fda3a3c9b/websockets-17.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441", size = 226151, upload-time = "2026-08-26T14:57:08.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/c9/cc1964a096d16f3b73cb1ee5f14f277f5a3bcac07c6e8f9a1dcded99f4c8/websockets-17.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db", size = 228292, upload-time = "2026-08-26T14:57:09.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/26/46da6dd0363c2db2e4876fd59a40fd40c1943a82d7018d0a33afbce47d52/websockets-17.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd", size = 226722, upload-time = "2026-08-26T14:57:11.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/98/ecd8f5e1c5d0e54c08ebc5c66852271112166db68107cb0e17ca1bf25009/websockets-17.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c", size = 225451, upload-time = "2026-08-26T14:57:12.601Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/4d/da8d2760db53e17aae763738b6ba834b1fcf16813d3632f3edb6951e1ec8/websockets-17.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256", size = 223003, upload-time = "2026-08-26T14:57:13.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/40/ea401c141a79c5b1d0021a0dab9d0df2051c108f1620fbb39a6e7c714c3b/websockets-17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257", size = 225704, upload-time = "2026-08-26T14:57:15.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/8e/07ab3f44215d89840d5385fdcaaab1fed8caeffa67c6899e15062957c12c/websockets-17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d", size = 224192, upload-time = "2026-08-26T14:57:16.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/93/ccf1af0a23e5748d4e22292a377d78d15cf294d7e707bbb11a8990ae6bd5/websockets-17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922", size = 225082, upload-time = "2026-08-26T14:57:17.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/db/e32200f99ce282e728d2929f2c429db353cf3282db7d0eba99eb32c9fec1/websockets-17.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7", size = 226101, upload-time = "2026-08-26T14:57:18.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/3d/e7a6e9777b29433620167c98f3caaff0d6b08b1239a273ef7f7fd1393349/websockets-17.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a", size = 223794, upload-time = "2026-08-26T14:57:20.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/05/ac569090726dedd6656f3ee28b0c02dfb1ba76e898dceaccc2987a237cef/websockets-17.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2", size = 224567, upload-time = "2026-08-26T14:57:21.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/50/4ef62941111db6b31193f4fabbb65f845a5177579040cb8fe0d774d25034/websockets-17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566", size = 224993, upload-time = "2026-08-26T14:57:22.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/42/2b95ada4ea19bf3a2072b68669ce4f4afb212690b727d31640576287fd68/websockets-17.1-cp314-cp314t-win32.whl", hash = "sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68", size = 217168, upload-time = "2026-08-26T14:57:24.466Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/0a/67d5ee08dd8060a37d612fd40a625b5376ad19ae48fe1c8ad428c278b817/websockets-17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51", size = 217508, upload-time = "2026-08-26T14:57:25.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/a3/822005d0c674451d2411027b878cdc128a2b7ea5a30d337d9e279da22eba/websockets-17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03", size = 217425, upload-time = "2026-08-26T14:57:27.324Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/d5/99a6c6a1eb5d5ae9f45f59a3c97f4e3b21f310eb404a547fb3e7d2fc054c/websockets-17.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f", size = 216970, upload-time = "2026-08-26T14:57:28.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/0e/1e7f6e833728193958d3ed3d67b5d57c3c7cfa948abf94d4bc553257c954/websockets-17.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2", size = 214699, upload-time = "2026-08-26T14:57:29.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/00/95d39549f86e34425a0412bcbe61708dd1fc46af654e2134a6c4389102ad/websockets-17.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e", size = 214927, upload-time = "2026-08-26T14:57:31.148Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/ff/b442415fc4f7f9943b0fc8e8eebaa13923ca73361e167c439ba634eecbd9/websockets-17.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8", size = 224373, upload-time = "2026-08-26T14:57:32.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/dd/b83537aae4cf61615b9d8b2dbb235c0030ba85457a6d934798273814600f/websockets-17.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42", size = 224801, upload-time = "2026-08-26T14:57:34.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/83/5ab0abed58454909e8dbab45086ac68ee4556d7a8ada26735addc909b903/websockets-17.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b", size = 225967, upload-time = "2026-08-26T14:57:35.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/26/e2412f2b998a8c1dfc00c0709ff6ee0c634dd0b0b4f92bdfe9667876b71c/websockets-17.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d", size = 227664, upload-time = "2026-08-26T14:57:36.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/25/0dd4495df3c0e02f6db705312ba85ab9b2dd42257dc23eb0da10066e4844/websockets-17.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb", size = 226447, upload-time = "2026-08-26T14:57:37.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/67/6df3f63ffc48f08126ed0cd2fd2a41092967c3e364f8ec100deae90b6d77/websockets-17.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e", size = 225343, upload-time = "2026-08-26T14:57:39.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/8d/a8479bbb09ff054907d141123d8f52fb6ae5ac39c6dbe39e6a02a8408309/websockets-17.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80", size = 222748, upload-time = "2026-08-26T14:57:40.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/fb/4c3d2a3269cde3f3087916de9c3d9fc5d7196b46846d8c3a9ae59ad0a884/websockets-17.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335", size = 225453, upload-time = "2026-08-26T14:57:41.859Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/1c/6467b401d19408f34e1c7389c222c2c7e1dfdf08c551190269b5eabc726c/websockets-17.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9", size = 224112, upload-time = "2026-08-26T14:57:43.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/5f/744e032ac80e11039a7447657ebabb46e9b5c2dbcec83be571335212932f/websockets-17.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774", size = 224646, upload-time = "2026-08-26T14:57:44.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/47/bcb9128d9afc4d0934d9192e2a24897ca2f7a63df2654904915349c6c46d/websockets-17.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6", size = 225797, upload-time = "2026-08-26T14:57:46.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/e0/b058047b7cf565e1105b10ef6b6b24a6ebe3575678c7dc75a645334705a7/websockets-17.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394", size = 223605, upload-time = "2026-08-26T14:57:48.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/69/fc1555bff884de363f1bf9eebf2836dbeb29fa7e4f957debb7bbcf43abba/websockets-17.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a", size = 224508, upload-time = "2026-08-26T14:57:49.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/f9/648d4e68621688b19093b06f7b497d520952e68cdea1c1b54371fe9491de/websockets-17.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4", size = 224767, upload-time = "2026-08-26T14:57:50.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/93/f8342b55864f71df13eb8e9ef7dce691b87a87f04f75bb8a1385b3336e7c/websockets-17.1-cp315-cp315-win32.whl", hash = "sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927", size = 217003, upload-time = "2026-08-26T14:57:52.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/f0/7b5fdb774c245e0b6217009e2a24d2105c1a64923949f33be41aa7959302/websockets-17.1-cp315-cp315-win_amd64.whl", hash = "sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc", size = 217300, upload-time = "2026-08-26T14:57:53.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/33/1fe6ed1b5087516115ca451b2c240314b010647071f8fc3bd78a21e4dddb/websockets-17.1-cp315-cp315-win_arm64.whl", hash = "sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d", size = 217214, upload-time = "2026-08-26T14:57:54.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/ca/ed02e75996a266d76c5fcb5dd9b930db4cf2b388ca5fa3d2a72086f81568/websockets-17.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13", size = 217282, upload-time = "2026-08-26T14:57:56.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/7d/d536f5bc89ea5b52fd1c1727c59fabafee6bc41f5ce92c3bd2f83047908c/websockets-17.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51", size = 214863, upload-time = "2026-08-26T14:57:57.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/37/944cf17bad668e9be1247e6314f88a48b9faf7c250e383410db8b38af0b9/websockets-17.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e", size = 215073, upload-time = "2026-08-26T14:57:58.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/bf/3267966cc1bbc2b8fa62fd329651b0af502df1f5d1c0eed027ff339d6aa8/websockets-17.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800", size = 225229, upload-time = "2026-08-26T14:58:00.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/d8/85ea722f483510abb39fc71aafb4465d17cf9051a275ab036874ff3c300c/websockets-17.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab", size = 225500, upload-time = "2026-08-26T14:58:01.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ce/64c7d00005bd0d15ecb5c5fcb7fb2597b6b92ddd16c4fa6bbc3d2835ad63/websockets-17.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7", size = 226829, upload-time = "2026-08-26T14:58:03.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/dc/096c67940fb957e667ca3c542818150434eb0388c6fdc90b3a502f3c3e96/websockets-17.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24", size = 228457, upload-time = "2026-08-26T14:58:04.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/fe/f2331b6b7ccc67589891da354fa46a5cb79e95f83b9fd0e734d77f1f2140/websockets-17.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6", size = 227265, upload-time = "2026-08-26T14:58:06.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/a5/fb1642302f8ec77ca922203074f155a9831a5128ad75e725059a476d1227/websockets-17.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623", size = 226143, upload-time = "2026-08-26T14:58:07.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/41/7133fcfb63f5562750b269d6a845c689dde6a2c6407286da395beea19ddd/websockets-17.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151", size = 223501, upload-time = "2026-08-26T14:58:08.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/b1/82b36bfabc79ff2d383a1fc043cee6a13f794ef4f6bf1b4810ad6988cf6f/websockets-17.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8", size = 226330, upload-time = "2026-08-26T14:58:09.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/7d/5b511b9bf6e9ad331e6ff902fcbcc71c3794d10ef3b5efe80ccb8f0a7861/websockets-17.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a", size = 224980, upload-time = "2026-08-26T14:58:11.303Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/50/aed08f25301f8eef23be903ff9319fcf35630ca2bdec9d226f7d804dd5b3/websockets-17.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2", size = 225478, upload-time = "2026-08-26T14:58:12.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/47/0d63d4168536b4682c9d19b7399443b1176f25dbb68878374fa716670230/websockets-17.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3", size = 226588, upload-time = "2026-08-26T14:58:14.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/dd/844bd0b6386fc81ed6a55f4b6dd26f01c6987eda205afa10175ea12b2164/websockets-17.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe", size = 224336, upload-time = "2026-08-26T14:58:15.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/18/03709c84bc88ec4dcea68d4be4ccd07d611073dec111203a5bf45af8809d/websockets-17.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d", size = 225197, upload-time = "2026-08-26T14:58:17.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/cf/0d1c694b6466c89e875b85b32b51312c472cf6708eee91914866f5087dde/websockets-17.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc", size = 225493, upload-time = "2026-08-26T14:58:18.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/f5/99857c3dd9676749f33e3668665a34ad6099505fb8d75eb084f49f7807a9/websockets-17.1-cp315-cp315t-win32.whl", hash = "sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3", size = 217130, upload-time = "2026-08-26T14:58:20.037Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/84/77599922ab441bfe61508f97dab2c71f8e114d31793993ea54011db16199/websockets-17.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110", size = 217448, upload-time = "2026-08-26T14:58:21.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/3c/8b9a225b523f06a9389be81f1b0ab07c49bec6014742e6aa359c1f920f1f/websockets-17.1-cp315-cp315t-win_arm64.whl", hash = "sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81", size = 217372, upload-time = "2026-08-26T14:58:22.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" },
|
||||
]
|
||||
Loading…
Reference in a new issue