hugpy-platform 0.3.0__tar.gz → 0.3.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 (41) hide show
  1. hugpy_platform-0.3.2/PKG-INFO +83 -0
  2. hugpy_platform-0.3.2/README.md +63 -0
  3. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/pyproject.toml +1 -1
  4. hugpy_platform-0.3.2/src/hugpy_platform/imports/atomic_json.py +29 -0
  5. hugpy_platform-0.3.2/src/hugpy_platform/imports/choices.py +10 -0
  6. hugpy_platform-0.3.2/src/hugpy_platform/imports/constants.py +332 -0
  7. hugpy_platform-0.3.2/src/hugpy_platform/imports/filesystem.py +55 -0
  8. hugpy_platform-0.3.2/src/hugpy_platform/imports/formatting.py +24 -0
  9. hugpy_platform-0.3.2/src/hugpy_platform/imports/hardware.py +73 -0
  10. hugpy_platform-0.3.2/src/hugpy_platform/module_imports.py +306 -0
  11. hugpy_platform-0.3.2/src/hugpy_platform/platform_facade.py +102 -0
  12. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/results.py +20 -26
  13. hugpy_platform-0.3.2/src/hugpy_platform/runner_config.py +11 -0
  14. hugpy_platform-0.3.2/src/hugpy_platform.egg-info/PKG-INFO +83 -0
  15. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform.egg-info/SOURCES.txt +10 -1
  16. hugpy_platform-0.3.0/PKG-INFO +0 -23
  17. hugpy_platform-0.3.0/README.md +0 -3
  18. hugpy_platform-0.3.0/src/hugpy_platform.egg-info/PKG-INFO +0 -23
  19. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/setup.cfg +0 -0
  20. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/__init__.py +0 -0
  21. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/app_dirs.py +0 -0
  22. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/async_runtime.py +0 -0
  23. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/atomic_json.py +0 -0
  24. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/binaries.py +0 -0
  25. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/choices.py +0 -0
  26. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/client_liveness.py +0 -0
  27. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/constants.py +0 -0
  28. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/filesystem.py +0 -0
  29. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/formatting.py +0 -0
  30. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/hardware.py +0 -0
  31. {hugpy_platform-0.3.0/src/hugpy_platform → hugpy_platform-0.3.2/src/hugpy_platform/imports}/module_imports.py +0 -0
  32. {hugpy_platform-0.3.0/src/hugpy_platform → hugpy_platform-0.3.2/src/hugpy_platform/imports}/platform_facade.py +0 -0
  33. {hugpy_platform-0.3.0/src/hugpy_platform → hugpy_platform-0.3.2/src/hugpy_platform/imports}/runner_config.py +0 -0
  34. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/procutil.py +0 -0
  35. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/py.typed +0 -0
  36. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/utils.py +0 -0
  37. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/verdict.py +0 -0
  38. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform/versioning.py +0 -0
  39. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform.egg-info/dependency_links.txt +0 -0
  40. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform.egg-info/requires.txt +0 -0
  41. {hugpy_platform-0.3.0 → hugpy_platform-0.3.2}/src/hugpy_platform.egg-info/top_level.txt +0 -0
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: hugpy-platform
3
+ Version: 0.3.2
4
+ Summary: Hugpy platform: stdlib-first foundation shared by every Hugpy package (env-backed configuration, app dirs, hardware and process probes, central URL, pydantic compatibility shim)
5
+ Author-email: putkoff <support@hugpy.ai>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Keywords: hugpy,llm,self-hosted,platform,configuration
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: abstract_essentials
17
+ Requires-Dist: platformdirs
18
+ Provides-Extra: hardware
19
+ Requires-Dist: psutil; extra == "hardware"
20
+
21
+ # hugpy-platform
22
+
23
+ The stdlib-first foundation that the hugpy arms build on: app directories,
24
+ storage-root constants, executable resolution, a process-wide async runtime,
25
+ hardware and process probes, and small formatting and result helpers.
26
+
27
+ Distribution `hugpy-platform` **0.3.0** ("lean"), import `hugpy_platform`.
28
+ Dependencies: `abstract_essentials`, `platformdirs`. Optional extra: `hardware`
29
+ (`psutil`).
30
+
31
+ **Status: LIVE.** It is a library only: no service, no console script, no
32
+ entry points. It is installed in `/opt/hugpy-media/venv` and
33
+ `/opt/hugpy-video/venv`, and imported by:
34
+
35
+ - `hugpy_media`
36
+ - `hugpy_video`
37
+ - `hugpy_engine`
38
+ - `hugpy_oracle`
39
+ - core's legacy `config/prompt.py`, which is not live
40
+
41
+ Core's live basic mode and the wrapper do not use it; the wrapper carries its
42
+ own lifted copies under `fitevict/lifted/`.
43
+
44
+ The lean 0.3.0 release ships only the modules the live arms import. The rest
45
+ of the pre-0.3 package is retired. The `pyproject.toml` description still mentions "central URL", but
46
+ `hugpy_platform.central` is not shipped. `hugpy_oracle/assessment/benchmark.py` imports
47
+ it inside a `try` and records no figure when it is missing.
48
+ `hugpy_engine/placement.py` falls back the same way for
49
+ `hugpy_platform.model_keys`.
50
+
51
+ ## Modules
52
+
53
+ `import hugpy_platform` re-exports only the light, side-effect-free accessors:
54
+
55
+ - `cache_dir`, `config_dir`, `data_dir`, `engine_dir`
56
+ - `hugpy_home`, `ensure_hugpy_home`, `hugpy_config_dir`, `hugpy_logs_dir`,
57
+ `hugpy_run_dir`, `hugpy_state_dir`
58
+ - `models_root`
59
+ - `EXE_SUFFIX`, `IS_LINUX`, `IS_MACOS`, `IS_WINDOWS`, `env_value`
60
+
61
+ Import everything else by dotted path.
62
+
63
+ | module | what it provides |
64
+ |---|---|
65
+ | `app_dirs` | per-OS application directories, the single source of truth for them |
66
+ | `platform_facade` | OS facts and the `env_value` environment seam |
67
+ | `constants` | storage roots resolved from the environment: `DEFAULT_ROOT`, `MODELS_HOME`, `UPLOADS_HOME`, `PROJECTS_HOME`, `IDENTITIES_HOME`, …, `HF_TOKEN`, `env_bool()` |
68
+ | `utils` | small filesystem, message and naming helpers (`get_messages`, `get_request_id`, `exists`, …) |
69
+ | `async_runtime` | one long-lived event loop in a daemon thread: `run`, `submit`, `await_sync`, `iter_sync` |
70
+ | `binaries` | portable executable resolution: `resolve_bin`, `with_exe` |
71
+ | `module_imports` | lazy heavy imports with actionable errors: `require`, `get_torch`, `get_transformers`, `get_whisper`, … |
72
+ | `runner_config` | `RunnerConfig`, the common constructor state for media runner adapters |
73
+ | `results` | result-object adapters: `result_text`, `vision_result_text`, `result_payload` |
74
+ | `filesystem` | `free_bytes`, `directory_bytes`, `is_within`, `write_text`, … |
75
+ | `hardware` | `total_vram_bytes` (torch or nvidia-smi) |
76
+ | `procutil` | `pid_alive`, `popen_detached`, `terminate_tree`, `reexec` |
77
+ | `atomic_json`, `formatting`, `choices`, `verdict`, `versioning`, `client_liveness` | atomic JSON writes, byte formatting, value selection, `VERDICT/SCORE/WHY` parsing, version lookup, client-disconnect detection |
78
+
79
+ This package has no `imports/` layer (see
80
+ [the convention](../../docs/packages.md#the-imports-layout-convention)).
81
+
82
+ See also [docs/packages.md](../../docs/packages.md) and
83
+ [docs/architecture.md](../../docs/architecture.md).
@@ -0,0 +1,63 @@
1
+ # hugpy-platform
2
+
3
+ The stdlib-first foundation that the hugpy arms build on: app directories,
4
+ storage-root constants, executable resolution, a process-wide async runtime,
5
+ hardware and process probes, and small formatting and result helpers.
6
+
7
+ Distribution `hugpy-platform` **0.3.0** ("lean"), import `hugpy_platform`.
8
+ Dependencies: `abstract_essentials`, `platformdirs`. Optional extra: `hardware`
9
+ (`psutil`).
10
+
11
+ **Status: LIVE.** It is a library only: no service, no console script, no
12
+ entry points. It is installed in `/opt/hugpy-media/venv` and
13
+ `/opt/hugpy-video/venv`, and imported by:
14
+
15
+ - `hugpy_media`
16
+ - `hugpy_video`
17
+ - `hugpy_engine`
18
+ - `hugpy_oracle`
19
+ - core's legacy `config/prompt.py`, which is not live
20
+
21
+ Core's live basic mode and the wrapper do not use it; the wrapper carries its
22
+ own lifted copies under `fitevict/lifted/`.
23
+
24
+ The lean 0.3.0 release ships only the modules the live arms import. The rest
25
+ of the pre-0.3 package is retired. The `pyproject.toml` description still mentions "central URL", but
26
+ `hugpy_platform.central` is not shipped. `hugpy_oracle/assessment/benchmark.py` imports
27
+ it inside a `try` and records no figure when it is missing.
28
+ `hugpy_engine/placement.py` falls back the same way for
29
+ `hugpy_platform.model_keys`.
30
+
31
+ ## Modules
32
+
33
+ `import hugpy_platform` re-exports only the light, side-effect-free accessors:
34
+
35
+ - `cache_dir`, `config_dir`, `data_dir`, `engine_dir`
36
+ - `hugpy_home`, `ensure_hugpy_home`, `hugpy_config_dir`, `hugpy_logs_dir`,
37
+ `hugpy_run_dir`, `hugpy_state_dir`
38
+ - `models_root`
39
+ - `EXE_SUFFIX`, `IS_LINUX`, `IS_MACOS`, `IS_WINDOWS`, `env_value`
40
+
41
+ Import everything else by dotted path.
42
+
43
+ | module | what it provides |
44
+ |---|---|
45
+ | `app_dirs` | per-OS application directories, the single source of truth for them |
46
+ | `platform_facade` | OS facts and the `env_value` environment seam |
47
+ | `constants` | storage roots resolved from the environment: `DEFAULT_ROOT`, `MODELS_HOME`, `UPLOADS_HOME`, `PROJECTS_HOME`, `IDENTITIES_HOME`, …, `HF_TOKEN`, `env_bool()` |
48
+ | `utils` | small filesystem, message and naming helpers (`get_messages`, `get_request_id`, `exists`, …) |
49
+ | `async_runtime` | one long-lived event loop in a daemon thread: `run`, `submit`, `await_sync`, `iter_sync` |
50
+ | `binaries` | portable executable resolution: `resolve_bin`, `with_exe` |
51
+ | `module_imports` | lazy heavy imports with actionable errors: `require`, `get_torch`, `get_transformers`, `get_whisper`, … |
52
+ | `runner_config` | `RunnerConfig`, the common constructor state for media runner adapters |
53
+ | `results` | result-object adapters: `result_text`, `vision_result_text`, `result_payload` |
54
+ | `filesystem` | `free_bytes`, `directory_bytes`, `is_within`, `write_text`, … |
55
+ | `hardware` | `total_vram_bytes` (torch or nvidia-smi) |
56
+ | `procutil` | `pid_alive`, `popen_detached`, `terminate_tree`, `reexec` |
57
+ | `atomic_json`, `formatting`, `choices`, `verdict`, `versioning`, `client_liveness` | atomic JSON writes, byte formatting, value selection, `VERDICT/SCORE/WHY` parsing, version lookup, client-disconnect detection |
58
+
59
+ This package has no `imports/` layer (see
60
+ [the convention](../../docs/packages.md#the-imports-layout-convention)).
61
+
62
+ See also [docs/packages.md](../../docs/packages.md) and
63
+ [docs/architecture.md](../../docs/architecture.md).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hugpy-platform"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Hugpy platform: stdlib-first foundation shared by every Hugpy package (env-backed configuration, app dirs, hardware and process probes, central URL, pydantic compatibility shim)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,29 @@
1
+ # LEAN 0.3.0 — lifted verbatim from /srv/hugpy_next/py/foundation/hugpy_platform/src/hugpy_platform/atomic_json.py; unused symbols removed.
2
+ """Atomic JSON writes for the small shared state files."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ import secrets
9
+
10
+
11
+ def read_json_dict(path: str) -> dict | None:
12
+ """Read a JSON object, returning None for absent or malformed files."""
13
+ try:
14
+ with open(path, "r", encoding="utf-8") as fh:
15
+ data = json.load(fh)
16
+ except Exception: # noqa: BLE001 — unreadable and malformed are both absent
17
+ return None
18
+ return data if isinstance(data, dict) else None
19
+
20
+
21
+ def save_json(path: str, data: dict) -> None:
22
+ """Write JSON with a unique temporary file and an atomic replacement."""
23
+ parent = os.path.dirname(path)
24
+ if parent:
25
+ os.makedirs(parent, exist_ok=True)
26
+ tmp = f"{path}.{os.getpid()}.{secrets.token_hex(4)}.tmp"
27
+ with open(tmp, "w", encoding="utf-8") as f:
28
+ json.dump(data, f, indent=2, sort_keys=True)
29
+ os.replace(tmp, path)
@@ -0,0 +1,10 @@
1
+ # LEAN 0.3.0 — lifted verbatim from /srv/hugpy_next/py/foundation/hugpy_platform/src/hugpy_platform/choices.py; unused symbols removed.
2
+ """Small value selection helpers."""
3
+
4
+
5
+ def first_not_none(explicit, from_preset, schema_default):
6
+ if explicit is not None:
7
+ return explicit
8
+ if from_preset is not None:
9
+ return from_preset
10
+ return schema_default
@@ -0,0 +1,332 @@
1
+ # LEAN 0.3.0 — lifted verbatim from /srv/hugpy_next/py/foundation/hugpy_platform/src/hugpy_platform/constants.py; unused symbols removed.
2
+ """Storage roots, Hugging Face cache layout and fleet-wide defaults.
3
+
4
+ Every value is env-overridable. Env reads go through
5
+ :func:`hugpy_platform.platform_facade.env_value` (process environment first,
6
+ then a ``.env`` file via ``abstract_essentials``), which also strips inline
7
+ ``# comments`` and stray quotes so a path can never be poisoned by a note in a
8
+ ``.env`` file.
9
+
10
+ Importing this module has deliberate side effects: the storage directories are
11
+ created (best-effort) and ``HF_HOME``/``HF_HUB_CACHE``/``TORCH_HOME``/
12
+ ``PIP_CACHE_DIR`` are seeded into the process environment. ``hfApi`` is
13
+ resolved lazily (PEP 562) so ``huggingface_hub`` is only imported by callers
14
+ that actually use it.
15
+ """
16
+ import os
17
+ import logging
18
+ import re
19
+ from typing import Literal, Optional
20
+
21
+ from abstract_essentials.json_utils import safe_dump_to_file
22
+ from abstract_essentials.types import make_list
23
+
24
+ from hugpy_platform.platform_facade import env_value as get_env_value
25
+
26
+ # Tokenizers set this as a sentinel for "no enforced limit". It's never a real window.
27
+
28
+ # ---------------------------------------------------------------------
29
+ # Model storage root
30
+ # ---------------------------------------------------------------------
31
+ HUGGINGFACE_DOMAIN = "https://huggingface.co"
32
+
33
+ # Don't phone home usage telemetry on the HF calls we do still make (the
34
+ # per-repo metadata cache in hugpy_storage.model_metadata minimizes those calls; this
35
+ # strips the tracking headers from the rest). setdefault ONLY — an
36
+ # operator-set value is never clobbered.
37
+ os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
38
+
39
+ HF_TOKEN = get_env_value("HF_TOKEN") or False
40
+
41
+
42
+ def env_bool(key: str, default: bool = False) -> bool:
43
+ """Env flag -> bool. `get_env_value(...) or default` can never yield False
44
+ (the string \"false\" is truthy, unset -> default), so flags parsed that way
45
+ are stuck at their default forever. This coerces properly.
46
+
47
+ Process environment wins over the .env file: get_env_value only reads the
48
+ .env file, but operational flags must also respond to a systemd
49
+ `Environment=` line or a `FLAG=false cmd` shell prefix."""
50
+ value = os.environ.get(key)
51
+ if value is None:
52
+ value = get_env_value(key)
53
+ if value is None:
54
+ return default
55
+ if isinstance(value, bool):
56
+ return value
57
+ return str(value).strip().lower() in ("1", "true", "yes", "on")
58
+
59
+ def _resolve_default_root():
60
+ # Single source of truth for the storage root, per-OS. Honours DEFAULT_ROOT,
61
+ # keeps the historical /mnt/llm_storage mount when present and writable, and
62
+ # otherwise lands in a per-user data dir (XDG / AppData / Library) so the API
63
+ # works out of the box on a fresh box, Windows, or macOS.
64
+ from hugpy_platform.app_dirs import models_root
65
+
66
+ return models_root()
67
+
68
+ DEFAULT_ROOT = _resolve_default_root()
69
+
70
+
71
+ def _root_is_usable(path: str) -> bool:
72
+ """True only if *path* can be created AND actually written to.
73
+
74
+ ``os.makedirs`` catches the PermissionError case (an unwritable parent like
75
+ ``/opt/flaskapps``); the create+unlink probe catches filesystems where
76
+ ``os.access`` lies (root-squash NFS, euid=0). Cheap, local, no network — this
77
+ runs once at import. Mirrors ``app_dirs._usable`` but with a real
78
+ write probe because an env-supplied root that can't be written is the exact
79
+ failure we're guarding against."""
80
+ try:
81
+ os.makedirs(path, exist_ok=True)
82
+ except OSError:
83
+ return False
84
+ probe = os.path.join(path, ".hugpy_write_test")
85
+ try:
86
+ with open(probe, "w"):
87
+ pass
88
+ os.unlink(probe)
89
+ return True
90
+ except OSError:
91
+ try:
92
+ os.unlink(probe)
93
+ except OSError:
94
+ pass
95
+ return False
96
+
97
+
98
+ def _env_root_or_default(env_key: str, default_path: str) -> str:
99
+ """Resolve a storage root from *env_key*, falling back to *default_path* when
100
+ the env value points somewhere we cannot actually write.
101
+
102
+ Root cause (worker boxes, 2026-07-05): a stale ``~/.env`` carried
103
+ ``MODELS_HOME`` / ``DEFAULT_ROOT`` etc. pointing at an unwritable legacy path
104
+ (``/opt/flaskapps``, a dead ``/mnt/llm_storage`` mount). Provisioning then
105
+ died with PermissionError forever and the models_local scan looked in the
106
+ wrong place. An env-supplied root that can't be created + written is a
107
+ misconfiguration, not a destination: warn LOUDLY naming the offending var and
108
+ value, and use the platform default (derived from the already-validated
109
+ DEFAULT_ROOT) instead of carrying the poisoned path forward.
110
+
111
+ Unset env -> default, no probe: the default is under DEFAULT_ROOT, which
112
+ ``_platform.paths.models_root()`` already validated. Env set + usable -> the
113
+ env value UNCHANGED, so central and every correctly-configured box behave
114
+ exactly as before (the reported /mnt/llm_storage on central IS writable and
115
+ passes)."""
116
+ value = get_env_value(env_key)
117
+ if not value:
118
+ return default_path
119
+ if _root_is_usable(value):
120
+ return value
121
+ logging.getLogger("abstract_hugpy").warning(
122
+ "%s=%r is not writable (cannot create/write there); IGNORING it and "
123
+ "falling back to %s. Fix or remove this env var — it is usually a stale "
124
+ "~/.env carrying a legacy/dead storage root onto this box.",
125
+ env_key, value, default_path,
126
+ )
127
+ return default_path
128
+
129
+
130
+ MODELS_HOME = MODELS_DIR = _env_root_or_default("MODELS_HOME", os.path.join(DEFAULT_ROOT,"models"))
131
+
132
+ UPLOADS_HOME = CHAT_UPLOAD_DIR = _env_root_or_default("UPLOADS_HOME", os.path.join(DEFAULT_ROOT,"uploads"))
133
+
134
+ PROJECTS_HOME = PROJECTS_DIR = _env_root_or_default("PROJECTS_HOME", os.path.join(DEFAULT_ROOT,"projects"))
135
+
136
+ # Identity profiles are PERSISTENT library items that OWN their reference images.
137
+ # IDENTITIES_HOME is a first-class storage root and a SIBLING of UPLOADS_HOME (both
138
+ # under DEFAULT_ROOT) — never a child of UPLOADS_HOME — so the session-scoped
139
+ # upload reaper (upload_routes._wipe_session, jailed to UPLOADS_HOME) structurally
140
+ # cannot reach an identity's copied reference images. Env-overridable exactly like
141
+ # its siblings so tests can repoint it before import.
142
+ IDENTITIES_HOME = IDENTITIES_DIR = _env_root_or_default("IDENTITIES_HOME", os.path.join(DEFAULT_ROOT,"identities"))
143
+
144
+ PROJECTS_PLACEMENT_PATH = get_env_value("PROJECTS_PLACEMENT_PATH") or os.path.join(PROJECTS_HOME,"placement.json")
145
+
146
+ DATASETS_HOME = DATASETS_DIR = _env_root_or_default("DATASETS_HOME", os.path.join(DEFAULT_ROOT,"datasets"))
147
+
148
+ MODELS_DISCOVERY_PATH = get_env_value("MODELS_DISCOVERY_PATH") or os.path.join(PROJECTS_HOME,"model_discovery.json")
149
+
150
+ MODELS_DICT_PATH = get_env_value("MODELS_DICT_PATH") or os.path.join(PROJECTS_HOME,"model_manifest.json")
151
+
152
+ # ── Console-managed Hugging Face token ──────────────────────────────────────
153
+ # The operator can save an HF token from the console so HF calls (search /
154
+ # metadata / downloads) are authenticated instead of anonymously rate-limited.
155
+ # It is persisted as a 0600 file OUTSIDE any git tree, next to the model
156
+ # manifest under PROJECTS_HOME — the same runtime state root api_keys.json uses.
157
+ # The storage-side store (hugpy_storage.hf_token) OWNS writing,
158
+ # validation, and the routes; it reads THIS path so there is one source of truth
159
+ # for where the file lives, with no flask->constants layering violation.
160
+ #
161
+ # Precedence: a STORED token wins; an env HF_TOKEN is the fallback (source:"env").
162
+ HF_TOKEN_PATH = get_env_value("HF_TOKEN_PATH") or os.path.join(PROJECTS_HOME, "hf_token")
163
+
164
+
165
+ def read_stored_hf_token():
166
+ """The console-saved HF token from HF_TOKEN_PATH, or False if none/unreadable.
167
+ Never raises; never logs the token."""
168
+ try:
169
+ with open(HF_TOKEN_PATH, "r", encoding="utf-8") as fh:
170
+ tok = fh.read().strip()
171
+ return tok or False
172
+ except OSError:
173
+ return False
174
+
175
+
176
+ # The GENUINE operator-supplied env token, captured ONCE before we pollute the
177
+ # process env with a stored token below. Source detection / fallback keys off
178
+ # THIS (never live os.environ, which apply_hf_token_to_env overwrites) so a
179
+ # stored token can never masquerade as an "env" token after it is cleared.
180
+ # Both the process env and the .env-backed get_env_value are consulted.
181
+ HF_TOKEN_ENV = (os.environ.get("HF_TOKEN") or get_env_value("HF_TOKEN") or "").strip() or False
182
+
183
+ # Seed the process token: a stored token overrides the env one. Setting
184
+ # os.environ["HF_TOKEN"] means every huggingface_hub call that does NOT force
185
+ # token=False (bare hf_hub_download / snapshot_download / HfApi()) picks it up
186
+ # automatically at call time; the lazy ``hfApi`` (module __getattr__ below) and
187
+ # the server's search routes honour this resolved value.
188
+ _stored_hf_token = read_stored_hf_token()
189
+ if _stored_hf_token:
190
+ HF_TOKEN = _stored_hf_token
191
+ if HF_TOKEN:
192
+ os.environ["HF_TOKEN"] = HF_TOKEN
193
+ os.environ["HUGGING_FACE_HUB_TOKEN"] = HF_TOKEN
194
+
195
+ HF_CACHE = _env_root_or_default("HF_CACHE", os.path.join(MODELS_HOME,"cache"))
196
+
197
+ HF_HOME = get_env_value("HF_HOME") or os.path.join(HF_CACHE,"huggingface")
198
+
199
+ HF_HUB_CACHE = get_env_value("HF_HUB_CACHE") or os.path.join(HF_HOME,"hub")
200
+
201
+ TORCH_HOME = get_env_value("TORCH_HOME") or os.path.join(HF_CACHE,"torch")
202
+
203
+ PIP_CACHE_DIR = get_env_value("PIP_CACHE_DIR") or os.path.join(HF_CACHE,"pip")
204
+
205
+ PATHS = [
206
+ MODELS_HOME,
207
+ UPLOADS_HOME,
208
+ PROJECTS_HOME,
209
+ IDENTITIES_HOME,
210
+ DATASETS_HOME,
211
+ HF_HUB_CACHE,
212
+ HF_CACHE,
213
+ HF_HUB_CACHE,
214
+ TORCH_HOME,
215
+ PIP_CACHE_DIR
216
+ ]
217
+
218
+
219
+
220
+ def _ensure_dirs(paths):
221
+ """Best-effort create the storage dirs.
222
+
223
+ Importing abstract_hugpy must never hard-crash just because a storage path
224
+ can't be made — e.g. on a worker box where DEFAULT_ROOT (/mnt/llm_storage)
225
+ is a broken/stale mount (OSError errno 5) or simply not present. Each dir is
226
+ created independently; failures are warned about, not fatal. Set
227
+ DEFAULT_ROOT to a local, writable path on such boxes.
228
+ """
229
+ import logging
230
+ failed = []
231
+ for path in paths:
232
+ try:
233
+ os.makedirs(path, exist_ok=True)
234
+ except OSError as exc:
235
+ failed.append((path, exc))
236
+ if failed:
237
+ logging.getLogger("abstract_hugpy").warning(
238
+ "could not create %d storage dir(s); continuing. "
239
+ "Set DEFAULT_ROOT to a writable path to silence this. Details: %s",
240
+ len(failed),
241
+ "; ".join(f"{p} ({e.__class__.__name__}: {e})" for p, e in failed),
242
+ )
243
+
244
+
245
+ _ensure_dirs(PATHS)
246
+ if not os.path.isfile(PROJECTS_PLACEMENT_PATH):
247
+ safe_dump_to_file(file_path=PROJECTS_PLACEMENT_PATH,data={})
248
+ os.environ.setdefault("HF_HOME", HF_HOME)
249
+ os.environ.setdefault("HF_HUB_CACHE", HF_HUB_CACHE)
250
+ os.environ.setdefault("TORCH_HOME", TORCH_HOME)
251
+ os.environ.setdefault("PIP_CACHE_DIR", PIP_CACHE_DIR)
252
+
253
+ HUGPY_MARKER= get_env_value("HUGPY_MARKER") or "hugpy.json"
254
+
255
+ LLAMA_HOST= get_env_value("LLAMA_HOST") or "http://127.0.0.1"
256
+ VISION_HOST= get_env_value("VISION_HOST") or "http://127.0.0.1"
257
+
258
+ # `legacy` is the operator's data-hoarding store — kept on disk but never
259
+ # walked, indexed, or counted by any metric/appendage in these systems.
260
+ EXCLUDE_DIR_NAMES = make_list(get_env_value("EXCLUDE_DIR_NAMES") or ".cache,.git,.locks,snapshots,blobs,refs,1_Pooling,2_Normalize,onnx,legacy,_archive,incoming,dedupes") # _archive/incoming/dedupes (2026-09-10): cold storage + staging are not live catalog rows
261
+ EXCLUDE_DIR_NAMES = frozenset(EXCLUDE_DIR_NAMES)
262
+
263
+ EXCLUDE_DIR_PREFIXES = make_list(get_env_value("EXCLUDE_DIR_PREFIXES") or "models--")
264
+ EXCLUDE_DIR_PREFIXES = tuple(EXCLUDE_DIR_PREFIXES) # HF cache root naming
265
+
266
+ TOKENIZER_SENTINEL_THRESHOLD = float(get_env_value("TOKENIZER_SENTINEL_THRESHOLD") or 10**9)
267
+ DEFAULT_TIMEOUT= float(get_env_value("DEFAULT_TIMEOUT") or 3600.0)
268
+ DEFAULT_MAX_TOKENS= int(get_env_value("DEFAULT_MAX_TOKENS") or 32768)
269
+ MIN_INPUT_WORDS_DEFAULT = get_env_value("MIN_INPUT_WORDS_DEFAULT") or 10
270
+ SOURCEKIND = make_list(get_env_value("SOURCEKIND") or "text,url,file,image")
271
+ SOURCEKIND =Literal[*SOURCEKIND]
272
+
273
+ JOBSTATUS = make_list(get_env_value("JOBSTATUS") or "queued,running,completed,failed,cancelled")
274
+ JOBSTATUS =Literal[*JOBSTATUS]
275
+
276
+ DEFAULT_TEMPERATURE = float(get_env_value("DEFAULT_TEMPERATURE") or 0.1)
277
+ DEFAULT_TOP_P = float(get_env_value("DEFAULT_TOP_P") or 1)
278
+
279
+ FINISH_REASONS = make_list(get_env_value("FINISH_REASONS") or "stop,max_tokens,cancelled,error")
280
+ FINISH_REASONS =Literal[*FINISH_REASONS]
281
+
282
+ ROLES = make_list(get_env_value("ROLES") or "system,user,assistant,tool")
283
+ ROLES = Literal[*ROLES]
284
+
285
+ # Stock defaults: one model per task, every one a curated staple in
286
+ # models_config.MODELS — efficiency-first picks (whole fleet ~17GB).
287
+ DEFAULT_CHAT_MODEL = get_env_value("DEFAULT_CHAT_MODEL") or "Qwen2.5-7B-Instruct-GGUF"
288
+ # The AGENT BRAIN — the model agent nodes/loops default to. A dedicated
289
+ # variable (operator ask, 2026-07-17) so the agents' default is never
290
+ # conflated with the generic chat default or the Discord bot's
291
+ # DEFAULT_MODEL_KEY. Brain switch 2026-07-17 (P3.4 scorecard, reliability
292
+ # over speed: coder 4/4 vs flux2-klein 3/4): Qwen3-Coder-Next. The
293
+ # hugpy_agent CLIENT package carries the same default with its own
294
+ # HUGPY_AGENT_BRAIN env knob; this is central's copy of the truth.
295
+ DEFAULT_AGENT_BRAIN = get_env_value("DEFAULT_AGENT_BRAIN") or "Qwen~Qwen3-Coder-Next-GGUF"
296
+ DEFAULT_VISION_MODEL = get_env_value("DEFAULT_VISION_MODEL") or "Qwen2.5-VL-3B-Instruct-GGUF"
297
+ DEFAULT_WHISPER_MODEL = get_env_value("DEFAULT_WHISPER_MODEL") or "whisper-large-v3-turbo"
298
+ DEFAULT_SUMMARIZE_MODEL = get_env_value("DEFAULT_SUMMARIZE_MODEL") or "flan-t5-large"
299
+ DEFAULT_EMBED_MODEL = get_env_value("DEFAULT_EMBED_MODEL") or "all-minilm-l6-v2"
300
+ DEFAULT_IMAGEGEN_MODEL = get_env_value("DEFAULT_IMAGEGEN_MODEL") or "sd-turbo"
301
+ DEFAULT_KEYWORDS_MODEL = get_env_value("DEFAULT_KEYWORDS_MODEL") or "all-minilm-l6-v2"
302
+ # Vision-analysis family (generic transformers-pipeline runner).
303
+ DEFAULT_DEPTH_MODEL = get_env_value("DEFAULT_DEPTH_MODEL") or "depth-anything-v2-small"
304
+ DEFAULT_DETECT_MODEL = get_env_value("DEFAULT_DETECT_MODEL") or "detr-resnet-50"
305
+ DEFAULT_IMG_CLASSIFY_MODEL = get_env_value("DEFAULT_IMG_CLASSIFY_MODEL") or "vit-base-patch16-224"
306
+ DEFAULT_SEGMENT_MODEL = get_env_value("DEFAULT_SEGMENT_MODEL") or "segformer-b0-ade"
307
+
308
+ DISK_AUTHORITATIVE = make_list(get_env_value("DISK_AUTHORITATIVE") or "name,folder,framework,filename")
309
+ OVERLAY_ALLOWED = set(make_list(get_env_value("OVERLAY_ALLOWED") or "port, host, timeout_s, include"))
310
+
311
+ GGUF_QUANT = re.compile(r"(Q\d+_[A-Z0-9_]+|F16|BF16|F32)", re.I)
312
+
313
+ DEFAULT_LOCAL_FILES_ONLY = env_bool("DEFAULT_LOCAL_FILES_ONLY", True)
314
+
315
+ # Kill switch for resolve()-time staple downloads. Default on: a fresh install
316
+ # pulls the curated MODELS fleet on first use. Set HUGPY_AUTO_DOWNLOAD=false on
317
+ # air-gapped boxes / workers that must never touch the network.
318
+ HUGPY_AUTO_DOWNLOAD = env_bool("HUGPY_AUTO_DOWNLOAD", True)
319
+
320
+
321
+ def __getattr__(name: str):
322
+ """Lazy ``hfApi`` (PEP 562): an authenticated ``huggingface_hub.HfApi`` bound
323
+ to the resolved ``HF_TOKEN``. Built on first access and cached in the module
324
+ namespace; ``hugpy_storage.hf_token`` rebinds it when the operator changes the
325
+ stored token. Keeps ``huggingface_hub`` out of the platform import path."""
326
+ if name == "hfApi":
327
+ from huggingface_hub import HfApi # optional extra: hugpy-platform[hf]
328
+
329
+ api = HfApi(token=HF_TOKEN)
330
+ globals()["hfApi"] = api
331
+ return api
332
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,55 @@
1
+ # LEAN 0.3.0 — lifted verbatim from /srv/hugpy_next/py/foundation/hugpy_platform/src/hugpy_platform/filesystem.py; unused symbols removed.
2
+ """Small filesystem probes shared across packages."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import shutil
8
+
9
+
10
+ def free_bytes(path: str) -> int | None:
11
+ """Free bytes on the target's filesystem, falling back to root."""
12
+ try:
13
+ probe = path if os.path.exists(path) else "/"
14
+ return shutil.disk_usage(probe).free
15
+ except OSError:
16
+ return None
17
+
18
+
19
+ def ensure_parent_best_effort(path: str) -> None:
20
+ parent = os.path.dirname(path)
21
+ if parent:
22
+ try:
23
+ os.makedirs(parent, exist_ok=True)
24
+ except OSError:
25
+ pass
26
+
27
+
28
+ def write_text(path: str, content: str) -> str:
29
+ """Write UTF-8 text, creating its parent, and return the path."""
30
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
31
+ with open(path, "w", encoding="utf-8") as fh:
32
+ fh.write(content)
33
+ return path
34
+
35
+
36
+ def is_within(path: str, root: str) -> bool:
37
+ """Whether a real path stays under a real root."""
38
+ resolved_path = os.path.realpath(path)
39
+ resolved_root = os.path.realpath(root)
40
+ try:
41
+ return os.path.commonpath([resolved_path, resolved_root]) == resolved_root
42
+ except ValueError:
43
+ return False
44
+
45
+
46
+ def directory_bytes(path: str) -> int:
47
+ """Count readable file bytes under a directory, tolerating vanished files."""
48
+ total = 0
49
+ for root, _, files in os.walk(path):
50
+ for name in files:
51
+ try:
52
+ total += os.path.getsize(os.path.join(root, name))
53
+ except OSError:
54
+ pass
55
+ return total
@@ -0,0 +1,24 @@
1
+ # LEAN 0.3.0 — lifted verbatim from /srv/hugpy_next/py/foundation/hugpy_platform/src/hugpy_platform/formatting.py; unused symbols removed.
2
+ """Pure formatting of byte counts for logs and operator messages."""
3
+
4
+ from __future__ import annotations
5
+
6
+
7
+ def ffmpeg_num(value) -> str:
8
+ """Use a compact numeric argument for ffmpeg."""
9
+ if isinstance(value, float) and value.is_integer():
10
+ return str(int(value))
11
+ return repr(value) if isinstance(value, float) else str(value)
12
+
13
+
14
+ def human_bytes(value, *, empty: str = "0 B") -> str:
15
+ """Format bytes in binary units, preserving each caller's empty label."""
16
+ if not value:
17
+ return empty
18
+ units = ("B", "KB", "MB", "GB", "TB")
19
+ number = float(value)
20
+ index = 0
21
+ while number >= 1024 and index < len(units) - 1:
22
+ number /= 1024
23
+ index += 1
24
+ return f"{number:.1f} {units[index]}"