model-mirror-cli 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- model_mirror_cli-0.2.0/.github/workflows/ci.yml +45 -0
- model_mirror_cli-0.2.0/.github/workflows/release.yml +47 -0
- model_mirror_cli-0.2.0/.gitignore +59 -0
- model_mirror_cli-0.2.0/CONTRIBUTORS.md +87 -0
- model_mirror_cli-0.2.0/LICENSE +21 -0
- model_mirror_cli-0.2.0/PKG-INFO +550 -0
- model_mirror_cli-0.2.0/README.md +524 -0
- model_mirror_cli-0.2.0/RELEASING.md +61 -0
- model_mirror_cli-0.2.0/docs/torrent-backend-spike.md +70 -0
- model_mirror_cli-0.2.0/docs/torrent-distribution-requirements.md +432 -0
- model_mirror_cli-0.2.0/model_mirror/__init__.py +7 -0
- model_mirror_cli-0.2.0/model_mirror/audit.py +214 -0
- model_mirror_cli-0.2.0/model_mirror/checksums.py +371 -0
- model_mirror_cli-0.2.0/model_mirror/cli.py +2436 -0
- model_mirror_cli-0.2.0/model_mirror/config.py +300 -0
- model_mirror_cli-0.2.0/model_mirror/hub.py +901 -0
- model_mirror_cli-0.2.0/model_mirror/lock.py +119 -0
- model_mirror_cli-0.2.0/model_mirror/mirror.py +201 -0
- model_mirror_cli-0.2.0/model_mirror/progress.py +260 -0
- model_mirror_cli-0.2.0/model_mirror/removal.py +164 -0
- model_mirror_cli-0.2.0/model_mirror/repair.py +399 -0
- model_mirror_cli-0.2.0/model_mirror/state.py +150 -0
- model_mirror_cli-0.2.0/model_mirror/torrent.py +453 -0
- model_mirror_cli-0.2.0/model_mirror/torrent_coverage.py +400 -0
- model_mirror_cli-0.2.0/model_mirror/torrent_hashes.py +168 -0
- model_mirror_cli-0.2.0/model_mirror/torrent_import.py +620 -0
- model_mirror_cli-0.2.0/model_mirror/torrent_publication.py +582 -0
- model_mirror_cli-0.2.0/model_mirror/torrent_seed.py +228 -0
- model_mirror_cli-0.2.0/model_mirror/verify.py +153 -0
- model_mirror_cli-0.2.0/pyproject.toml +65 -0
- model_mirror_cli-0.2.0/scripts/check_release.py +24 -0
- model_mirror_cli-0.2.0/tests/smoke_test.py +28 -0
- model_mirror_cli-0.2.0/tests/test_audit.py +294 -0
- model_mirror_cli-0.2.0/tests/test_checksums.py +373 -0
- model_mirror_cli-0.2.0/tests/test_cli.py +2542 -0
- model_mirror_cli-0.2.0/tests/test_config.py +420 -0
- model_mirror_cli-0.2.0/tests/test_hub.py +1276 -0
- model_mirror_cli-0.2.0/tests/test_lock.py +66 -0
- model_mirror_cli-0.2.0/tests/test_mirror.py +376 -0
- model_mirror_cli-0.2.0/tests/test_progress.py +130 -0
- model_mirror_cli-0.2.0/tests/test_removal.py +395 -0
- model_mirror_cli-0.2.0/tests/test_repair.py +551 -0
- model_mirror_cli-0.2.0/tests/test_state.py +109 -0
- model_mirror_cli-0.2.0/tests/test_torrent.py +422 -0
- model_mirror_cli-0.2.0/tests/test_torrent_cli.py +337 -0
- model_mirror_cli-0.2.0/tests/test_torrent_coverage.py +221 -0
- model_mirror_cli-0.2.0/tests/test_torrent_fencing.py +215 -0
- model_mirror_cli-0.2.0/tests/test_torrent_hashes.py +116 -0
- model_mirror_cli-0.2.0/tests/test_torrent_hub_integration.py +67 -0
- model_mirror_cli-0.2.0/tests/test_torrent_import.py +595 -0
- model_mirror_cli-0.2.0/tests/test_torrent_publication.py +307 -0
- model_mirror_cli-0.2.0/tests/test_torrent_seed.py +348 -0
- model_mirror_cli-0.2.0/tests/test_verify.py +164 -0
- model_mirror_cli-0.2.0/uv.lock +512 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version:
|
|
16
|
+
- "3.11"
|
|
17
|
+
- "3.12"
|
|
18
|
+
- "3.13"
|
|
19
|
+
steps:
|
|
20
|
+
- name: Check out source
|
|
21
|
+
uses: actions/checkout@v7
|
|
22
|
+
- name: Install uv and Python
|
|
23
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
- name: Install locked dependencies
|
|
27
|
+
run: uv sync --locked --all-extras --dev
|
|
28
|
+
- name: Run test and coverage gate
|
|
29
|
+
run: |
|
|
30
|
+
uv run coverage run -m pytest -q
|
|
31
|
+
uv run coverage report -m
|
|
32
|
+
|
|
33
|
+
build:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- name: Check out source
|
|
37
|
+
uses: actions/checkout@v7
|
|
38
|
+
- name: Install uv
|
|
39
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
40
|
+
- name: Build wheel and source distribution
|
|
41
|
+
run: uv build --no-sources
|
|
42
|
+
- name: Smoke-test wheel
|
|
43
|
+
run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
|
|
44
|
+
- name: Smoke-test source distribution
|
|
45
|
+
run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment:
|
|
12
|
+
name: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
id-token: write
|
|
16
|
+
steps:
|
|
17
|
+
- name: Check out source
|
|
18
|
+
uses: actions/checkout@v7
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
23
|
+
- name: Validate release tag
|
|
24
|
+
run: |
|
|
25
|
+
python scripts/check_release.py "$GITHUB_REF_NAME"
|
|
26
|
+
if [ "$(git cat-file -t "$GITHUB_REF_NAME")" != "tag" ]; then
|
|
27
|
+
echo "::error::release tag must be annotated"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
31
|
+
if ! git merge-base --is-ancestor "$(git rev-list -n 1 "$GITHUB_REF_NAME")" origin/main; then
|
|
32
|
+
echo "::error::release tag must point to a commit on main"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
- name: Run test and coverage gate
|
|
36
|
+
run: |
|
|
37
|
+
uv sync --locked --all-extras --dev
|
|
38
|
+
uv run coverage run -m pytest -q
|
|
39
|
+
uv run coverage report -m
|
|
40
|
+
- name: Build wheel and source distribution
|
|
41
|
+
run: uv build --no-sources
|
|
42
|
+
- name: Smoke-test wheel
|
|
43
|
+
run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
|
|
44
|
+
- name: Smoke-test source distribution
|
|
45
|
+
run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py
|
|
46
|
+
- name: Publish to PyPI
|
|
47
|
+
run: uv publish
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Secrets and local configuration
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
*.pem
|
|
5
|
+
*.key
|
|
6
|
+
*.token
|
|
7
|
+
.model-mirror.yaml
|
|
8
|
+
model-mirror.yaml
|
|
9
|
+
.model-mirror/
|
|
10
|
+
.verification
|
|
11
|
+
.checksums
|
|
12
|
+
.manifest
|
|
13
|
+
|
|
14
|
+
# Model archives and large local state
|
|
15
|
+
models/
|
|
16
|
+
datasets/
|
|
17
|
+
spaces/
|
|
18
|
+
archives/
|
|
19
|
+
downloads/
|
|
20
|
+
.cache/
|
|
21
|
+
cache/
|
|
22
|
+
tmp/
|
|
23
|
+
logs/
|
|
24
|
+
*.safetensors
|
|
25
|
+
*.bin
|
|
26
|
+
*.pt
|
|
27
|
+
*.pth
|
|
28
|
+
*.ckpt
|
|
29
|
+
*.gguf
|
|
30
|
+
*.onnx
|
|
31
|
+
*.msgpack
|
|
32
|
+
*.h5
|
|
33
|
+
*.incomplete
|
|
34
|
+
SHA256SUMS
|
|
35
|
+
MANIFEST.jsonl
|
|
36
|
+
|
|
37
|
+
# Python
|
|
38
|
+
__pycache__/
|
|
39
|
+
*.py[cod]
|
|
40
|
+
*.egg-info/
|
|
41
|
+
.eggs/
|
|
42
|
+
dist/
|
|
43
|
+
build/
|
|
44
|
+
.venv/
|
|
45
|
+
venv/
|
|
46
|
+
|
|
47
|
+
# Test and tooling output
|
|
48
|
+
.pytest_cache/
|
|
49
|
+
.coverage
|
|
50
|
+
coverage.xml
|
|
51
|
+
htmlcov/
|
|
52
|
+
.mypy_cache/
|
|
53
|
+
.ruff_cache/
|
|
54
|
+
|
|
55
|
+
# OS/editor noise
|
|
56
|
+
.DS_Store
|
|
57
|
+
Thumbs.db
|
|
58
|
+
*~
|
|
59
|
+
*.swp
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Contributors
|
|
2
|
+
|
|
3
|
+
This document holds implementation notes and development details. User-facing
|
|
4
|
+
usage belongs in `README.md`.
|
|
5
|
+
|
|
6
|
+
## Development
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
uv sync
|
|
10
|
+
uv run coverage run -m pytest
|
|
11
|
+
uv run coverage report -m
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The test suite is expected to hold 100% statement and branch coverage. The
|
|
15
|
+
coverage gate is enforced in `pyproject.toml`.
|
|
16
|
+
|
|
17
|
+
## State Files
|
|
18
|
+
|
|
19
|
+
Each mirrored repo owns its state inside the repo directory:
|
|
20
|
+
|
|
21
|
+
- `.verification`: YAML status, timestamps, commit metadata, offline-only flag,
|
|
22
|
+
issues, repair paths
|
|
23
|
+
- `.manifest`: versioned JSONL records with local size, mtime, SHA-256, and Git blob SHA-1
|
|
24
|
+
- `.verification.lock`: advisory lock metadata while an operation is active
|
|
25
|
+
- `.model-mirror/snapshot.json`: authoritative commit-pinned expected file list
|
|
26
|
+
- `.model-mirror/torrent/coverage/`: atomic, resumable profile-specific piece
|
|
27
|
+
and Merkle hash coverage
|
|
28
|
+
- `.model-mirror/torrent/publications/`: torrent artifacts, recovery records,
|
|
29
|
+
and durable publication/desired-seed state
|
|
30
|
+
- `.model-mirror/torrent/fence.json`: the active commit update fence
|
|
31
|
+
|
|
32
|
+
Deleting a model directory deletes its verification state with it. There is no
|
|
33
|
+
global model state database.
|
|
34
|
+
|
|
35
|
+
## Commit Handling
|
|
36
|
+
|
|
37
|
+
Online operations resolve the requested revision to a concrete Hub commit before
|
|
38
|
+
downloading or verifying. A clean local mirror is trusted for its resolved
|
|
39
|
+
commit. If upstream moves, verification records `upstream_status: changed` but
|
|
40
|
+
does not mutate local files. `repair --update` is the explicit command for
|
|
41
|
+
moving to the new upstream commit recorded by verification.
|
|
42
|
+
|
|
43
|
+
If upstream is unavailable, verification exits non-zero and preserves the local
|
|
44
|
+
verification status when one already exists. `offline` sets `offline_only: true`
|
|
45
|
+
for that repo, clears the upstream-unavailable issue, and makes future
|
|
46
|
+
verification local-only until `online` clears the flag.
|
|
47
|
+
|
|
48
|
+
## Checksums
|
|
49
|
+
|
|
50
|
+
Manifest writes are incremental. After each file is hashed, `.manifest` is
|
|
51
|
+
atomically rewritten with a schema/version header and one record per payload
|
|
52
|
+
file. Each file is read once while both SHA-256 and Git blob SHA-1 are computed.
|
|
53
|
+
Later runs skip files whose size, mtime, and hash fields match the manifest
|
|
54
|
+
record. Downloads accumulate these hashes and any enabled torrent coverage
|
|
55
|
+
while streaming into the destination's `.incomplete` file. This makes an
|
|
56
|
+
interrupted download resumable without a mandatory second full-file hash pass.
|
|
57
|
+
|
|
58
|
+
## Locking
|
|
59
|
+
|
|
60
|
+
Mutating mirror, verification, repair, upgrade, and torrent control-plane
|
|
61
|
+
transitions take an advisory lock on `.verification.lock` for the target repo.
|
|
62
|
+
The first mirror operation writes `.verification` with `status: in_progress`
|
|
63
|
+
before downloading. The seeder acquires the lock only for short reconciliation
|
|
64
|
+
transitions, never for its lifetime. `list` does not block; it reports lock
|
|
65
|
+
metadata when a repo is busy.
|
|
66
|
+
|
|
67
|
+
## Hugging Face And Xet
|
|
68
|
+
|
|
69
|
+
`model-mirror` uses `huggingface_hub` to resolve pinned metadata and transport
|
|
70
|
+
URLs, then streams HTTP or Xet bytes into its own resumable hashing writer.
|
|
71
|
+
Environment setup is derived from model-mirror config and is intentionally
|
|
72
|
+
authoritative: if a config boolean is false, inherited Xet environment
|
|
73
|
+
variables for that feature are removed before importing or using the transport.
|
|
74
|
+
|
|
75
|
+
The conservative default is:
|
|
76
|
+
|
|
77
|
+
- high-performance Xet mode off
|
|
78
|
+
- range-get concurrency `1`
|
|
79
|
+
- optional sequential reconstruction writes for HDD-backed archives
|
|
80
|
+
|
|
81
|
+
This keeps the default path usable on lower-memory machines and lets power users
|
|
82
|
+
tune throughput explicitly.
|
|
83
|
+
|
|
84
|
+
## Design Documents
|
|
85
|
+
|
|
86
|
+
- [Torrent Distribution And Archive Upgrade Requirements](docs/torrent-distribution-requirements.md)
|
|
87
|
+
- [Torrent Backend Spike](docs/torrent-backend-spike.md)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xlr8harder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|