hugpy-storage 0.2.2__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.
Files changed (68) hide show
  1. hugpy_storage-0.2.2/LICENSE +41 -0
  2. hugpy_storage-0.2.2/PKG-INFO +41 -0
  3. hugpy_storage-0.2.2/README.md +7 -0
  4. hugpy_storage-0.2.2/docs/REGISTRY-DB-INDEX.md +109 -0
  5. hugpy_storage-0.2.2/pyproject.toml +71 -0
  6. hugpy_storage-0.2.2/setup.cfg +4 -0
  7. hugpy_storage-0.2.2/src/hugpy_storage/__init__.py +56 -0
  8. hugpy_storage-0.2.2/src/hugpy_storage/admission.py +435 -0
  9. hugpy_storage-0.2.2/src/hugpy_storage/archive_mark.py +136 -0
  10. hugpy_storage-0.2.2/src/hugpy_storage/catalog_source.py +269 -0
  11. hugpy_storage-0.2.2/src/hugpy_storage/cli.py +98 -0
  12. hugpy_storage-0.2.2/src/hugpy_storage/console/__init__.py +0 -0
  13. hugpy_storage-0.2.2/src/hugpy_storage/console/cancelable_downloads.py +87 -0
  14. hugpy_storage-0.2.2/src/hugpy_storage/console/downloader.py +113 -0
  15. hugpy_storage-0.2.2/src/hugpy_storage/console/downloads.py +57 -0
  16. hugpy_storage-0.2.2/src/hugpy_storage/console/model_physical.py +557 -0
  17. hugpy_storage-0.2.2/src/hugpy_storage/deploy/hugpy-downloader-dev.service +58 -0
  18. hugpy_storage-0.2.2/src/hugpy_storage/download_models.py +751 -0
  19. hugpy_storage-0.2.2/src/hugpy_storage/downloader/__init__.py +34 -0
  20. hugpy_storage-0.2.2/src/hugpy_storage/downloader/__main__.py +8 -0
  21. hugpy_storage-0.2.2/src/hugpy_storage/downloader/daemon.py +333 -0
  22. hugpy_storage-0.2.2/src/hugpy_storage/downloader/engine.py +577 -0
  23. hugpy_storage-0.2.2/src/hugpy_storage/downloader/presence.py +73 -0
  24. hugpy_storage-0.2.2/src/hugpy_storage/downloader/queue.py +253 -0
  25. hugpy_storage-0.2.2/src/hugpy_storage/env.py +16 -0
  26. hugpy_storage-0.2.2/src/hugpy_storage/events.py +53 -0
  27. hugpy_storage-0.2.2/src/hugpy_storage/format_select.py +368 -0
  28. hugpy_storage-0.2.2/src/hugpy_storage/gguf_inspect.py +766 -0
  29. hugpy_storage-0.2.2/src/hugpy_storage/hf_metadata.py +29 -0
  30. hugpy_storage-0.2.2/src/hugpy_storage/hf_token.py +228 -0
  31. hugpy_storage-0.2.2/src/hugpy_storage/huggingface_api.py +240 -0
  32. hugpy_storage-0.2.2/src/hugpy_storage/hugpy_marker.py +811 -0
  33. hugpy_storage-0.2.2/src/hugpy_storage/model_config_shim.py +16 -0
  34. hugpy_storage-0.2.2/src/hugpy_storage/model_metadata.py +640 -0
  35. hugpy_storage-0.2.2/src/hugpy_storage/model_paths.py +439 -0
  36. hugpy_storage-0.2.2/src/hugpy_storage/model_physical.py +614 -0
  37. hugpy_storage-0.2.2/src/hugpy_storage/model_presence.py +321 -0
  38. hugpy_storage-0.2.2/src/hugpy_storage/model_status_cache.py +413 -0
  39. hugpy_storage-0.2.2/src/hugpy_storage/model_sync.py +162 -0
  40. hugpy_storage-0.2.2/src/hugpy_storage/providers.py +151 -0
  41. hugpy_storage-0.2.2/src/hugpy_storage/provision.py +2143 -0
  42. hugpy_storage-0.2.2/src/hugpy_storage/py.typed +0 -0
  43. hugpy_storage-0.2.2/src/hugpy_storage/schemas/__init__.py +0 -0
  44. hugpy_storage-0.2.2/src/hugpy_storage/schemas/download_schemas.py +43 -0
  45. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/PKG-INFO +41 -0
  46. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/SOURCES.txt +66 -0
  47. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/dependency_links.txt +1 -0
  48. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/entry_points.txt +3 -0
  49. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/requires.txt +10 -0
  50. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/scm_file_list.json +62 -0
  51. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/scm_version.json +8 -0
  52. hugpy_storage-0.2.2/src/hugpy_storage.egg-info/top_level.txt +1 -0
  53. hugpy_storage-0.2.2/tests/conftest.py +24 -0
  54. hugpy_storage-0.2.2/tests/test_admission_hook.py +178 -0
  55. hugpy_storage-0.2.2/tests/test_catalog_changed_event.py +90 -0
  56. hugpy_storage-0.2.2/tests/test_catalog_source.py +105 -0
  57. hugpy_storage-0.2.2/tests/test_cli_and_console.py +116 -0
  58. hugpy_storage-0.2.2/tests/test_download_queue_recovery.py +450 -0
  59. hugpy_storage-0.2.2/tests/test_downloader_queue.py +210 -0
  60. hugpy_storage-0.2.2/tests/test_import_policy.py +107 -0
  61. hugpy_storage-0.2.2/tests/test_install_manifest.py +171 -0
  62. hugpy_storage-0.2.2/tests/test_is_model_dir_owner_levels.py +68 -0
  63. hugpy_storage-0.2.2/tests/test_paths_archive_exclusion.py +82 -0
  64. hugpy_storage-0.2.2/tests/test_promote_finalize.py +115 -0
  65. hugpy_storage-0.2.2/tests/test_providers_and_presence.py +203 -0
  66. hugpy_storage-0.2.2/tests/test_transfer_rate.py +87 -0
  67. hugpy_storage-0.2.2/tests/test_worker_central_only.py +194 -0
  68. hugpy_storage-0.2.2/tests/test_worker_puller_auth.py +250 -0
@@ -0,0 +1,41 @@
1
+ hugpy — Source-Available License
2
+
3
+ Copyright (c) 2026 putkoff (hugpy.ai). All rights reserved.
4
+
5
+ Permission is granted, free of charge, to use this software ("hugpy") for
6
+ personal and non-commercial purposes, and for time-limited commercial
7
+ evaluation, subject to the following conditions:
8
+
9
+ 1. Non-commercial use means use by an individual for personal purposes, or
10
+ use by a non-profit or educational institution for its own internal
11
+ purposes. Any use by, for, or on behalf of a for-profit business or in
12
+ connection with revenue-generating activity is commercial use — including
13
+ internal business use, use in producing goods or services, and use on
14
+ paid engagements.
15
+
16
+ 2. Commercial use requires a commercial license from the copyright holder.
17
+ Exception: a business may evaluate the software internally for up to
18
+ thirty (30) days free of charge; continued use after that requires a
19
+ commercial license.
20
+
21
+ 3. Redistribution of this software, in source or binary form, modified or
22
+ unmodified, is not permitted without prior written permission from the
23
+ copyright holder. Downloading the software from an official distribution
24
+ channel (PyPI, npm, hugpy.ai) is not redistribution.
25
+
26
+ 4. Modification for personal use or internal evaluation is permitted;
27
+ distribution of modified versions is not.
28
+
29
+ 5. This notice must be retained in all copies or substantial portions of
30
+ the software.
31
+
32
+ 6. Any use outside these terms automatically terminates this license.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING
38
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
39
+ IN THE SOFTWARE.
40
+
41
+ For commercial licensing or redistribution permission: https://hugpy.ai
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: hugpy-storage
3
+ Version: 0.2.2
4
+ Summary: Hugpy storage: model download queue and daemon, Hugging Face transport and token, resumable transfers, model sync, physical inventory and on-disk model layout
5
+ Author-email: putkoff <support@hugpy.ai>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://hugpy.ai
8
+ Project-URL: Documentation, https://github.com/hugpy/hugpy/blob/main/py/storage/hugpy_storage/README.md
9
+ Project-URL: Repository, https://github.com/hugpy/hugpy
10
+ Project-URL: Source, https://github.com/hugpy/hugpy/tree/main/py/storage/hugpy_storage
11
+ Project-URL: Issues, https://github.com/hugpy/hugpy/issues
12
+ Project-URL: Changelog, https://github.com/hugpy/hugpy/releases
13
+ Project-URL: Architecture, https://github.com/hugpy/hugpy/blob/main/PARTITION.md
14
+ Keywords: hugpy,llm,self-hosted,huggingface,downloads,model-storage,gguf
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: hugpy-platform
25
+ Requires-Dist: hugpy-control
26
+ Requires-Dist: abstract_essentials
27
+ Requires-Dist: huggingface_hub>=0.20
28
+ Requires-Dist: requests>=2.28
29
+ Requires-Dist: pydantic>=2
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest>=8; extra == "test"
32
+ Requires-Dist: pytest-timeout; extra == "test"
33
+ Dynamic: license-file
34
+
35
+ # hugpy-storage
36
+
37
+ `hugpy_storage` — extracted from `abstract_hugpy_dev` as part of the Hugpy
38
+ partition. Ownership and allowed dependencies are declared in
39
+ `py/partition.toml`; see `PARTITION.md` at the workspace root.
40
+
41
+ Allowed Python dependencies inside the ecosystem: hugpy_platform, hugpy_control.
@@ -0,0 +1,7 @@
1
+ # hugpy-storage
2
+
3
+ `hugpy_storage` — extracted from `abstract_hugpy_dev` as part of the Hugpy
4
+ partition. Ownership and allowed dependencies are declared in
5
+ `py/partition.toml`; see `PARTITION.md` at the workspace root.
6
+
7
+ Allowed Python dependencies inside the ecosystem: hugpy_platform, hugpy_control.
@@ -0,0 +1,109 @@
1
+ # Registry → rebuildable DB index (operator decision, 2026-09-10)
2
+
3
+ **Decision (operator-approved):** keep `hugpy.json` markers as the per-model
4
+ source of truth co-located with the weights; replace the aggregate JSON
5
+ registries (`projects/model_discovery.json`, `projects/model_manifest.json`)
6
+ with a **database index that is rebuilt from markers** — droppable and
7
+ regenerable at any time, never a second truth.
8
+
9
+ ## Why
10
+
11
+ The 2026-09-10 incident chain showed the JSON registries' failure modes:
12
+
13
+ - **Phantom persistence.** The walk registered org dirs (`gguf/<org>`),
14
+ downloader staging (`<repo>.tmp-<pid>`), `_archive/` and `incoming/` rows;
15
+ the merge ("re-adopt prior location — don't throw it away") and the shrink
16
+ guard then kept them alive after the walk bugs were fixed. Rows had to be
17
+ removed by hand-editing JSON.
18
+ - **Count wobble.** The console model total moved with every download
19
+ start/finish because staging dirs entered and left the walk.
20
+ - **Swallowed models.** `gguf/ponpoke` registered as a model and pruned the
21
+ walk, hiding `flux2-klein-9b-uncensored-text-encoder` from the catalog
22
+ entirely (fixed in `paths.py::is_model_dir` — org-level guard).
23
+ - **Whole-file rewrites** race between central, the worker agent and the
24
+ downloader; there is no row-level write.
25
+
26
+ ## Shape
27
+
28
+ - **Markers (`hugpy.json`)** — identity, capability flags, and now the
29
+ **`quants` manifest** (`[{file, quant, bytes, shards}]`, mmproj excluded,
30
+ shard sets collapsed), stamped at `write_hugpy_marker` time and kept true by
31
+ `sync_marker_quants` on every discovery walk (2026-09-10). Markers travel
32
+ with the files, survive central loss, and make the store self-describing.
33
+ - **DB index** — SQLite beside the comms mirror
34
+ (`llm_storage/comms/`, same pattern the download queue already uses) or the
35
+ toolserver Postgres. Tables ≈ models(hub_id, name, framework, tasks,
36
+ folder, bytes, status, marker_json, walked_at), quants(model, file, quant,
37
+ bytes, shards). `rebuild()` = walk markers → truncate + reinsert. All
38
+ readers (`get_models_dict`, /api/models, quant choices, storage proposals)
39
+ move to the index; the JSONs become export artifacts or die.
40
+ - **No shrink guard needed**: a degraded-mount walk is detected the same way,
41
+ but the remedy is "don't rebuild now", not "never delete rows".
42
+
43
+ ## Prerequisite already done
44
+
45
+ Discovery hardening shipped 2026-09-10 (src + both installed wheels):
46
+ org-level guard in `is_model_dir`, `.tmp-` exclusion, `_archive`/`incoming`/
47
+ `dedupes` excluded from the walk, quant manifest on markers. A future
48
+ `backfill_markers` sweep (exists in `hugpy_marker.py`) will stamp the last
49
+ unstamped dirs so the index can be built from markers alone.
50
+
51
+ ## Phase 1 — SHIPPED 2026-09-10
52
+
53
+ - Database `hugpy` (owner: user `hugpy`) on the toolserver's Postgres server —
54
+ same infra, separate database, toolserver's tables untouched.
55
+ - `imports/src/model_index.py`: env-gated (`HUGPY_REGISTRY_DB=pg` +
56
+ `HUGPY_REGISTRY_PG_DSN`, URI form — a space-separated DSN breaks shell
57
+ sourcing of the env file), autocommit + `conn.transaction()` (psycopg3's
58
+ `with conn:` CLOSES the connection — do not reintroduce it).
59
+ - Dual-write: `discover_models` saves the report to the DB after the shrink
60
+ guard (a refused degraded walk never reaches the DB; an empty report never
61
+ truncates). JSON remains the fallback/export.
62
+ - DB-first read: `models_config._load_discovery_report` — DB when enabled and
63
+ populated, else the JSON path byte-identical to before. Workers (no flag,
64
+ no psycopg) are untouched.
65
+ - `model_quants` fills from marker `quants` riding the report rows; it grows
66
+ as markers get stamped/synced (backfill_markers sweep = the remaining step
67
+ to full coverage).
68
+
69
+ ## Next phases (operator: "most everything for hugpy should be converted to db")
70
+
71
+ 1. `model_manifest.json` (download registry) → table, same dual-write pattern.
72
+ 2. Marker backfill sweep so model_quants covers the whole store.
73
+ 3. Runtime settings / overrides / priority groups stores → tables.
74
+ 4. Retire the JSON files once each reader is DB-first and burned in.
75
+
76
+ ## Metrics card — SHIPPED 2026-09-10 (same session)
77
+
78
+ Modeled on the operator's `llm_storage/assets/metrics.xlsx_0.ods`:
79
+ - `model_metrics` — rollup card, one row per (model × quant × alloc_mode ×
80
+ worker): cold/hot load s (fed later by load probes/battery), last +
81
+ running-average tok/s, n_samples, upload_time_s, media_bytes, task.
82
+ - `model_calls` — append-only per-call ledger (every serve: tok/s,
83
+ prompt/completion tokens, elapsed, request_id, config-state snapshot).
84
+ - Recorder hook: `workers.record_serve_metrics` (the one tok/s writer) —
85
+ captures worker/alloc/moe/4-bit inside the store lock, writes outside it.
86
+ - Read: `GET /api/llm/models/<key>/metrics` → {metrics, worker_averages,
87
+ calls}; alias-tolerant (`Jackrong~X` ≡ `X`).
88
+ - Seeded from the fleet's live model_tok_stats (31 rows).
89
+ - Gotcha fixed: PID fork-guard on the PG connection (gunicorn forks after a
90
+ boot-time connect; a shared socket = "SSL bad record mac").
91
+
92
+ ## DB code layout — STANDARD (operator ruling, 2026-09-10)
93
+
94
+ All hugpy DB creation and calls follow the explicit repository pattern of
95
+ solcatcher's `src/db/repositories/repos` — implemented for the model index at
96
+ `imports/src/model_index/` and REQUIRED for every future table:
97
+
98
+ query_registry.py EVERY SQL statement, named constants grouped per
99
+ repository (CREATE_TABLE + CREATE_INDEXES first,
100
+ then the named operations). Nothing inlines SQL.
101
+ client.py DatabaseClient — DSN resolution, PID fork-guard,
102
+ psycopg3 autocommit + transaction(), fail-open
103
+ mark_unavailable(). The only file that imports psycopg.
104
+ repositories.py One class per table, one explicit method per query.
105
+ Thin: parameters in, rows out.
106
+ service.py The business layer — enabled() gate, locking,
107
+ transactions, alias forms, guards, degradation.
108
+ __init__.py Explicit exports + a module-level singleton exposing
109
+ the plain-function API call sites use.
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "setuptools-scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hugpy-storage"
7
+ dynamic = ["version"]
8
+ description = "Hugpy storage: model download queue and daemon, Hugging Face transport and token, resumable transfers, model sync, physical inventory and on-disk model layout"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "LicenseRef-Proprietary"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "putkoff", email = "support@hugpy.ai" }]
14
+ keywords = [
15
+ "hugpy",
16
+ "llm",
17
+ "self-hosted",
18
+ "huggingface",
19
+ "downloads",
20
+ "model-storage",
21
+ "gguf",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
30
+ ]
31
+ dependencies = [
32
+ "hugpy-platform",
33
+ "hugpy-control",
34
+ "abstract_essentials",
35
+ "huggingface_hub>=0.20",
36
+ "requests>=2.28",
37
+ "pydantic>=2",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://hugpy.ai"
42
+ Documentation = "https://github.com/hugpy/hugpy/blob/main/py/storage/hugpy_storage/README.md"
43
+ Repository = "https://github.com/hugpy/hugpy"
44
+ Source = "https://github.com/hugpy/hugpy/tree/main/py/storage/hugpy_storage"
45
+ Issues = "https://github.com/hugpy/hugpy/issues"
46
+ Changelog = "https://github.com/hugpy/hugpy/releases"
47
+ Architecture = "https://github.com/hugpy/hugpy/blob/main/PARTITION.md"
48
+
49
+ [project.optional-dependencies]
50
+ test = ["pytest>=8", "pytest-timeout"]
51
+
52
+ [project.scripts]
53
+ hugpy-storage = "hugpy_storage.cli:main"
54
+ # Transitional alias for the systemd unit / operators (PARTITION.md).
55
+ hugpy-downloader = "hugpy_storage.downloader.daemon:main"
56
+
57
+ [tool.setuptools.packages.find]
58
+ where = ["src"]
59
+
60
+ [tool.setuptools.package-data]
61
+ hugpy_storage = ["py.typed", "**/*.json", "**/*.sh", "**/*.md", "**/*.txt", "deploy/*.service"]
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Lockstep workspace version (2026-09-22): every in-tree hugpy-* distribution
65
+ # takes ONE version from the workspace git tag (vX.Y.Z at the repo root), so a
66
+ # release is a tag and a build always carries the commit it came from
67
+ # (X.Y.Z.devN+g<sha>[.dirty] between tags). A checkout without git metadata
68
+ # builds as 0.0.0+unknown, which central refuses to advertise to workers.
69
+ [tool.setuptools_scm]
70
+ root = "../../.."
71
+ fallback_version = "0.0.0+unknown"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,56 @@
1
+ """hugpy_storage — bytes at rest and in transit.
2
+
3
+ The download queue and daemon (``downloader``), resumable central/HF transfer
4
+ (``provision``, ``model_sync``, ``download_models``), Hugging Face transport
5
+ and token (``huggingface_api``, ``hf_token``, ``model_metadata``), physical
6
+ inventory and its caches (``model_physical``, ``model_status_cache``,
7
+ ``model_presence``), the on-disk model layout (``model_paths``,
8
+ ``hugpy_marker``, ``gguf_inspect``) and the console-side helpers.
9
+
10
+ Seams to the layers above (installed by the composition root):
11
+
12
+ * :mod:`hugpy_storage.catalog_source` — the model registry Protocol
13
+ (``set_catalog_source``); null default knows no models.
14
+ * :mod:`hugpy_storage.providers` — budget gate, transfer telemetry, executor
15
+ registrar, serve-path hook, footprint selector.
16
+ * :mod:`hugpy_storage.events` — ``catalog.changed`` on the control bus after
17
+ the inventory moves.
18
+
19
+ This ``__init__`` is deliberately light: nothing heavy (huggingface_hub,
20
+ requests, pydantic, sqlite stores) is imported here.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from hugpy_storage.catalog_source import (
25
+ CatalogSource,
26
+ DictCatalogSource,
27
+ NullCatalogSource,
28
+ get_catalog_source,
29
+ set_catalog_source,
30
+ )
31
+ from hugpy_storage.events import publish_catalog_changed
32
+ from hugpy_storage.providers import (
33
+ set_budget_gate,
34
+ set_executor_registrar,
35
+ set_footprint_selector,
36
+ set_serve_path_hook,
37
+ set_transfer_telemetry,
38
+ )
39
+
40
+ try: # the installed distribution's version: the workspace tag/commit, never a literal
41
+ from importlib.metadata import version as _dist_version
42
+ __version__ = _dist_version("hugpy-storage")
43
+ except Exception: # noqa: BLE001 — source tree without metadata
44
+ __version__ = "0.0.0+unknown"
45
+
46
+ __all__ = [
47
+ "__version__",
48
+ # catalog seam
49
+ "CatalogSource", "NullCatalogSource", "DictCatalogSource",
50
+ "set_catalog_source", "get_catalog_source",
51
+ # upper-layer hooks
52
+ "set_budget_gate", "set_transfer_telemetry", "set_executor_registrar",
53
+ "set_serve_path_hook", "set_footprint_selector",
54
+ # events
55
+ "publish_catalog_changed",
56
+ ]