hugpy-platform 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.
- hugpy_platform-0.2.2/LICENSE +41 -0
- hugpy_platform-0.2.2/PKG-INFO +41 -0
- hugpy_platform-0.2.2/README.md +7 -0
- hugpy_platform-0.2.2/pyproject.toml +64 -0
- hugpy_platform-0.2.2/setup.cfg +4 -0
- hugpy_platform-0.2.2/src/hugpy_platform/__init__.py +105 -0
- hugpy_platform-0.2.2/src/hugpy_platform/app_dirs.py +305 -0
- hugpy_platform-0.2.2/src/hugpy_platform/async_runtime.py +226 -0
- hugpy_platform-0.2.2/src/hugpy_platform/atomic_json.py +28 -0
- hugpy_platform-0.2.2/src/hugpy_platform/binaries.py +49 -0
- hugpy_platform-0.2.2/src/hugpy_platform/buildinfo.py +356 -0
- hugpy_platform-0.2.2/src/hugpy_platform/central.py +34 -0
- hugpy_platform-0.2.2/src/hugpy_platform/choices.py +9 -0
- hugpy_platform-0.2.2/src/hugpy_platform/client_liveness.py +207 -0
- hugpy_platform-0.2.2/src/hugpy_platform/compat_pydantic.py +177 -0
- hugpy_platform-0.2.2/src/hugpy_platform/constants.py +331 -0
- hugpy_platform-0.2.2/src/hugpy_platform/env.py +18 -0
- hugpy_platform-0.2.2/src/hugpy_platform/environment_status.py +19 -0
- hugpy_platform-0.2.2/src/hugpy_platform/except_utils.py +62 -0
- hugpy_platform-0.2.2/src/hugpy_platform/filesystem.py +54 -0
- hugpy_platform-0.2.2/src/hugpy_platform/formatting.py +23 -0
- hugpy_platform-0.2.2/src/hugpy_platform/hardware.py +169 -0
- hugpy_platform-0.2.2/src/hugpy_platform/model_keys.py +22 -0
- hugpy_platform-0.2.2/src/hugpy_platform/module_imports.py +305 -0
- hugpy_platform-0.2.2/src/hugpy_platform/no_think.py +25 -0
- hugpy_platform-0.2.2/src/hugpy_platform/paths.py +12 -0
- hugpy_platform-0.2.2/src/hugpy_platform/platform_facade.py +101 -0
- hugpy_platform-0.2.2/src/hugpy_platform/procutil.py +146 -0
- hugpy_platform-0.2.2/src/hugpy_platform/py.typed +0 -0
- hugpy_platform-0.2.2/src/hugpy_platform/results.py +48 -0
- hugpy_platform-0.2.2/src/hugpy_platform/runner_config.py +10 -0
- hugpy_platform-0.2.2/src/hugpy_platform/test_isolation.py +265 -0
- hugpy_platform-0.2.2/src/hugpy_platform/trust.py +56 -0
- hugpy_platform-0.2.2/src/hugpy_platform/utils.py +448 -0
- hugpy_platform-0.2.2/src/hugpy_platform/verdict.py +32 -0
- hugpy_platform-0.2.2/src/hugpy_platform/versioning.py +13 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/PKG-INFO +41 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/SOURCES.txt +50 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/dependency_links.txt +1 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/requires.txt +12 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/scm_file_list.json +47 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/scm_version.json +8 -0
- hugpy_platform-0.2.2/src/hugpy_platform.egg-info/top_level.txt +1 -0
- hugpy_platform-0.2.2/tests/conftest.py +35 -0
- hugpy_platform-0.2.2/tests/test_buildinfo.py +338 -0
- hugpy_platform-0.2.2/tests/test_constants.py +88 -0
- hugpy_platform-0.2.2/tests/test_import_policy.py +107 -0
- hugpy_platform-0.2.2/tests/test_platform_env_value.py +94 -0
- hugpy_platform-0.2.2/tests/test_public_api.py +25 -0
- hugpy_platform-0.2.2/tests/test_small_modules.py +113 -0
- hugpy_platform-0.2.2/tests/test_storage_isolation.py +117 -0
- hugpy_platform-0.2.2/tests/test_utils.py +80 -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-platform
|
|
3
|
+
Version: 0.2.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
|
+
Project-URL: Homepage, https://hugpy.ai
|
|
8
|
+
Project-URL: Documentation, https://github.com/hugpy/hugpy/blob/main/py/foundation/hugpy_platform/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/hugpy/hugpy
|
|
10
|
+
Project-URL: Source, https://github.com/hugpy/hugpy/tree/main/py/foundation/hugpy_platform
|
|
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,platform,configuration
|
|
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: abstract_essentials
|
|
25
|
+
Requires-Dist: platformdirs
|
|
26
|
+
Provides-Extra: hardware
|
|
27
|
+
Requires-Dist: psutil; extra == "hardware"
|
|
28
|
+
Provides-Extra: hf
|
|
29
|
+
Requires-Dist: huggingface_hub; extra == "hf"
|
|
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-platform
|
|
36
|
+
|
|
37
|
+
`hugpy_platform` — 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: none.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# hugpy-platform
|
|
2
|
+
|
|
3
|
+
`hugpy_platform` — 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: none.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "setuptools-scm>=8"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hugpy-platform"
|
|
7
|
+
dynamic = ["version"]
|
|
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
|
+
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
|
+
"platform",
|
|
19
|
+
"configuration",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"abstract_essentials",
|
|
31
|
+
"platformdirs",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://hugpy.ai"
|
|
36
|
+
Documentation = "https://github.com/hugpy/hugpy/blob/main/py/foundation/hugpy_platform/README.md"
|
|
37
|
+
Repository = "https://github.com/hugpy/hugpy"
|
|
38
|
+
Source = "https://github.com/hugpy/hugpy/tree/main/py/foundation/hugpy_platform"
|
|
39
|
+
Issues = "https://github.com/hugpy/hugpy/issues"
|
|
40
|
+
Changelog = "https://github.com/hugpy/hugpy/releases"
|
|
41
|
+
Architecture = "https://github.com/hugpy/hugpy/blob/main/PARTITION.md"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
# hardware.free_ram_bytes() prefers psutil off-Linux; /proc/meminfo otherwise.
|
|
45
|
+
hardware = ["psutil"]
|
|
46
|
+
# constants.hfApi (lazy) — only callers that talk to the Hub need it.
|
|
47
|
+
hf = ["huggingface_hub"]
|
|
48
|
+
test = ["pytest>=8", "pytest-timeout"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
where = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.package-data]
|
|
54
|
+
hugpy_platform = ["py.typed", "**/*.json", "**/*.sh", "**/*.md", "**/*.txt"]
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Lockstep workspace version (2026-09-22): every in-tree hugpy-* distribution
|
|
58
|
+
# takes ONE version from the workspace git tag (vX.Y.Z at the repo root), so a
|
|
59
|
+
# release is a tag and a build always carries the commit it came from
|
|
60
|
+
# (X.Y.Z.devN+g<sha>[.dirty] between tags). A checkout without git metadata
|
|
61
|
+
# builds as 0.0.0+unknown, which central refuses to advertise to workers.
|
|
62
|
+
[tool.setuptools_scm]
|
|
63
|
+
root = "../../.."
|
|
64
|
+
fallback_version = "0.0.0+unknown"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""hugpy-platform: the stdlib-first foundation every Hugpy package stands on.
|
|
2
|
+
|
|
3
|
+
What lives here (and nothing above it — no engine, storage, fleet or server):
|
|
4
|
+
|
|
5
|
+
central canonical central base URL (``HUGPY_BASE_URL`` + aliases)
|
|
6
|
+
platform_facade OS facts (``IS_WINDOWS``/``IS_MACOS``/``IS_LINUX``) and the
|
|
7
|
+
sanitising ``env_value`` seam over ``abstract_essentials``
|
|
8
|
+
app_dirs per-OS data/config/cache/engine dirs and the ``~/.hugpy``
|
|
9
|
+
runtime-file accessors
|
|
10
|
+
constants storage roots, HF cache layout, fleet-wide defaults
|
|
11
|
+
(import has side effects: creates dirs — import it explicitly)
|
|
12
|
+
utils small filesystem / message / naming helpers
|
|
13
|
+
module_imports ``get_<package>()`` lazy accessors for heavy ML libraries
|
|
14
|
+
async_runtime one process-wide asyncio loop + sync bridges
|
|
15
|
+
client_liveness "is the HTTP client still there" probe (Flask optional)
|
|
16
|
+
binaries / procutil / hardware executables, process trees, RAM/GPU probes
|
|
17
|
+
compat_pydantic pure-Python pydantic stand-in for platforms without pydantic_core
|
|
18
|
+
except_utils ``caught`` / ``attempt`` / ``catching`` logging helpers
|
|
19
|
+
trust Hugging Face publisher trust tiers
|
|
20
|
+
buildinfo which build is this process: workspace version, sha/dirty,
|
|
21
|
+
editable source, lockstep check (``/health``, ``/build``, heartbeat)
|
|
22
|
+
|
|
23
|
+
This ``__init__`` re-exports only the light, side-effect-free names below; the
|
|
24
|
+
heavier modules are imported by dotted path. ``buildinfo`` is exported lazily
|
|
25
|
+
(``hugpy_platform.buildinfo`` resolves on first attribute access, PEP 562) so
|
|
26
|
+
importing the package never pays for it.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from hugpy_platform.app_dirs import (
|
|
31
|
+
cache_dir,
|
|
32
|
+
config_dir,
|
|
33
|
+
data_dir,
|
|
34
|
+
engine_dir,
|
|
35
|
+
ensure_hugpy_home,
|
|
36
|
+
hugpy_config_dir,
|
|
37
|
+
hugpy_home,
|
|
38
|
+
hugpy_logs_dir,
|
|
39
|
+
hugpy_run_dir,
|
|
40
|
+
hugpy_state_dir,
|
|
41
|
+
models_root,
|
|
42
|
+
)
|
|
43
|
+
from hugpy_platform.central import CENTRAL_ENV_VARS, DEFAULT_CENTRAL, central_base_url
|
|
44
|
+
from hugpy_platform.compat_pydantic import ensure_pydantic
|
|
45
|
+
from hugpy_platform.except_utils import FAILED, attempt, catching, caught, caught_block
|
|
46
|
+
from hugpy_platform.platform_facade import EXE_SUFFIX, IS_LINUX, IS_MACOS, IS_WINDOWS, env_value
|
|
47
|
+
from hugpy_platform.trust import trust_label, trust_tier
|
|
48
|
+
|
|
49
|
+
try: # the installed distribution's version: the workspace tag/commit, never a literal
|
|
50
|
+
from importlib.metadata import version as _dist_version
|
|
51
|
+
__version__ = _dist_version("hugpy-platform")
|
|
52
|
+
except Exception: # noqa: BLE001 — source tree without metadata
|
|
53
|
+
__version__ = "0.0.0+unknown"
|
|
54
|
+
|
|
55
|
+
_LAZY_SUBMODULES = ("buildinfo",)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def __getattr__(name: str):
|
|
59
|
+
"""Lazy submodule export: ``hugpy_platform.buildinfo`` without an import-time cost."""
|
|
60
|
+
if name in _LAZY_SUBMODULES:
|
|
61
|
+
import importlib
|
|
62
|
+
module = importlib.import_module(f"{__name__}.{name}")
|
|
63
|
+
globals()[name] = module
|
|
64
|
+
return module
|
|
65
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"__version__",
|
|
70
|
+
# central
|
|
71
|
+
"CENTRAL_ENV_VARS",
|
|
72
|
+
"DEFAULT_CENTRAL",
|
|
73
|
+
"central_base_url",
|
|
74
|
+
# platform facts + env seam
|
|
75
|
+
"EXE_SUFFIX",
|
|
76
|
+
"IS_LINUX",
|
|
77
|
+
"IS_MACOS",
|
|
78
|
+
"IS_WINDOWS",
|
|
79
|
+
"env_value",
|
|
80
|
+
# app dirs
|
|
81
|
+
"cache_dir",
|
|
82
|
+
"config_dir",
|
|
83
|
+
"data_dir",
|
|
84
|
+
"engine_dir",
|
|
85
|
+
"ensure_hugpy_home",
|
|
86
|
+
"hugpy_config_dir",
|
|
87
|
+
"hugpy_home",
|
|
88
|
+
"hugpy_logs_dir",
|
|
89
|
+
"hugpy_run_dir",
|
|
90
|
+
"hugpy_state_dir",
|
|
91
|
+
"models_root",
|
|
92
|
+
# pydantic shim
|
|
93
|
+
"ensure_pydantic",
|
|
94
|
+
# exception helpers
|
|
95
|
+
"FAILED",
|
|
96
|
+
"attempt",
|
|
97
|
+
"catching",
|
|
98
|
+
"caught",
|
|
99
|
+
"caught_block",
|
|
100
|
+
# trust tiers
|
|
101
|
+
"trust_label",
|
|
102
|
+
"trust_tier",
|
|
103
|
+
# build identity (lazy submodule)
|
|
104
|
+
"buildinfo",
|
|
105
|
+
]
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Per-OS application directories — one source of truth.
|
|
2
|
+
|
|
3
|
+
Replaces the scattered hardcoded ``/srv/abstractendeavors/...``,
|
|
4
|
+
``~/.local/share/hugpy``, ``/etc/llama-swap``, and ``/mnt/llm_storage`` literals.
|
|
5
|
+
Every path is overridable by the same env vars the code already honoured, so
|
|
6
|
+
existing Linux deployments are unaffected; only the *defaults* become per-OS:
|
|
7
|
+
|
|
8
|
+
data_dir() Linux ~/.local/share/hugpy macOS ~/Library/Application Support/hugpy Windows %LOCALAPPDATA%\\hugpy
|
|
9
|
+
config_dir() Linux ~/.config/hugpy macOS ~/Library/Application Support/hugpy Windows %LOCALAPPDATA%\\hugpy
|
|
10
|
+
cache_dir() Linux ~/.cache/hugpy macOS ~/Library/Caches/hugpy Windows %LOCALAPPDATA%\\hugpy\\Cache
|
|
11
|
+
engine_dir() data_dir()/engine — where the fetched llama.cpp binary lands
|
|
12
|
+
models_root() DEFAULT_ROOT or data_dir()/llm_storage
|
|
13
|
+
|
|
14
|
+
We use ``platformdirs`` when available (added to base deps) and fall back to a
|
|
15
|
+
hand-rolled per-OS layout so this module never hard-fails on import.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
|
|
21
|
+
from hugpy_platform.platform_facade import IS_MACOS, IS_WINDOWS, env_value
|
|
22
|
+
|
|
23
|
+
_APP = "hugpy"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _home(*parts: str) -> str:
|
|
27
|
+
return os.path.join(os.path.expanduser("~"), *parts)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _fallback_data() -> str:
|
|
31
|
+
if IS_WINDOWS:
|
|
32
|
+
base = os.environ.get("LOCALAPPDATA") or _home("AppData", "Local")
|
|
33
|
+
return os.path.join(base, _APP)
|
|
34
|
+
if IS_MACOS:
|
|
35
|
+
return _home("Library", "Application Support", _APP)
|
|
36
|
+
return os.path.join(os.environ.get("XDG_DATA_HOME") or _home(".local", "share"), _APP)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _fallback_config() -> str:
|
|
40
|
+
if IS_WINDOWS:
|
|
41
|
+
base = os.environ.get("LOCALAPPDATA") or _home("AppData", "Local")
|
|
42
|
+
return os.path.join(base, _APP)
|
|
43
|
+
if IS_MACOS:
|
|
44
|
+
return _home("Library", "Application Support", _APP)
|
|
45
|
+
return os.path.join(os.environ.get("XDG_CONFIG_HOME") or _home(".config"), _APP)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _fallback_cache() -> str:
|
|
49
|
+
if IS_WINDOWS:
|
|
50
|
+
base = os.environ.get("LOCALAPPDATA") or _home("AppData", "Local")
|
|
51
|
+
return os.path.join(base, _APP, "Cache")
|
|
52
|
+
if IS_MACOS:
|
|
53
|
+
return _home("Library", "Caches", _APP)
|
|
54
|
+
return os.path.join(os.environ.get("XDG_CACHE_HOME") or _home(".cache"), _APP)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _dirs():
|
|
58
|
+
try:
|
|
59
|
+
import platformdirs
|
|
60
|
+
|
|
61
|
+
return platformdirs.PlatformDirs(_APP, appauthor=False)
|
|
62
|
+
except Exception:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def data_dir() -> str:
|
|
67
|
+
override = env_value("HUGPY_DATA_DIR")
|
|
68
|
+
if override:
|
|
69
|
+
return _ensure(override)
|
|
70
|
+
d = _dirs()
|
|
71
|
+
return _ensure(d.user_data_dir if d else _fallback_data())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def config_dir() -> str:
|
|
75
|
+
override = env_value("HUGPY_CONFIG_DIR")
|
|
76
|
+
if override:
|
|
77
|
+
return _ensure(override)
|
|
78
|
+
d = _dirs()
|
|
79
|
+
return _ensure(d.user_config_dir if d else _fallback_config())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cache_dir() -> str:
|
|
83
|
+
override = env_value("HUGPY_CACHE_DIR")
|
|
84
|
+
if override:
|
|
85
|
+
return _ensure(override)
|
|
86
|
+
d = _dirs()
|
|
87
|
+
return _ensure(d.user_cache_dir if d else _fallback_cache())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def engine_dir() -> str:
|
|
91
|
+
"""Where ``hugpy install-engine`` unpacks the native llama.cpp binaries."""
|
|
92
|
+
override = env_value("HUGPY_ENGINE_DIR") or env_value("LLAMA_CPP_DIR")
|
|
93
|
+
if override:
|
|
94
|
+
return _ensure(override)
|
|
95
|
+
return _ensure(os.path.join(data_dir(), "engine"))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def models_root() -> str:
|
|
99
|
+
"""Model/upload/dataset storage root.
|
|
100
|
+
|
|
101
|
+
Honours the legacy ``DEFAULT_ROOT``/``MODELS_HOME`` env vars first — but only
|
|
102
|
+
if that path can actually be created and written. A stale/un-writable override
|
|
103
|
+
(e.g. ``DEFAULT_ROOT=/mnt/llm_storage`` carried in a server ``.env`` onto a
|
|
104
|
+
worker or a phone where ``/mnt`` is read-only) is ignored in favour of a
|
|
105
|
+
per-user dir under ``data_dir()``, so storage never lands on a dead path.
|
|
106
|
+
"""
|
|
107
|
+
override = env_value("DEFAULT_ROOT")
|
|
108
|
+
if override and _usable(override):
|
|
109
|
+
return override
|
|
110
|
+
# Preserve the historical Linux mount when it exists and is writable.
|
|
111
|
+
legacy = "/mnt/llm_storage"
|
|
112
|
+
try:
|
|
113
|
+
if os.path.isdir(legacy) and os.access(legacy, os.W_OK):
|
|
114
|
+
return legacy
|
|
115
|
+
except OSError:
|
|
116
|
+
pass
|
|
117
|
+
return _ensure(os.path.join(data_dir(), "llm_storage"))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def demo_media_base() -> str:
|
|
121
|
+
"""Base URL the video arm's canned demo loads its sample media from."""
|
|
122
|
+
return env_value("HUGPY_DEMO_MEDIA_BASE") or "https://hugpy.ai/demo-media"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def demo_media_dir() -> str:
|
|
126
|
+
"""Local demo-media tree to serve at ``/demo-media/`` (self-hosters).
|
|
127
|
+
|
|
128
|
+
Empty string means "not configured" — deliberately NO default and NO
|
|
129
|
+
directory creation; the ``/demo-media/`` route only exists when this is set.
|
|
130
|
+
"""
|
|
131
|
+
return env_value("HUGPY_DEMO_MEDIA_DIR") or ""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _usable(path: str) -> bool:
|
|
135
|
+
"""True only if *path* exists (or can be created) AND is writable."""
|
|
136
|
+
try:
|
|
137
|
+
os.makedirs(path, exist_ok=True)
|
|
138
|
+
return os.path.isdir(path) and os.access(path, os.W_OK)
|
|
139
|
+
except OSError:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _ensure(path: str) -> str:
|
|
144
|
+
try:
|
|
145
|
+
os.makedirs(path, exist_ok=True)
|
|
146
|
+
except OSError:
|
|
147
|
+
pass
|
|
148
|
+
return path
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# hugpy runtime "mechanic" files (HUGPY_HOME, default ~/.hugpy)
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# Historically these state/config/log files were written straight into $HOME
|
|
155
|
+
# (``~/todo.json``, ``~/steward-state.json``, ``~/.abstract_hugpy_worker.json``,
|
|
156
|
+
# ``~/model_metadata.db`` …), strewing the home directory. They now live under a
|
|
157
|
+
# single base dir — ``HUGPY_HOME`` (default ``~/.hugpy``) — split into::
|
|
158
|
+
#
|
|
159
|
+
# state/ todo.json, steward-state.json, model_metadata.db, flow.json, …
|
|
160
|
+
# config/ abstract_hugpy_worker.json (+ .settings.json/.update.json)
|
|
161
|
+
# logs/ bridge-mail.jsonl, bugreport.json
|
|
162
|
+
# run/ *.lock
|
|
163
|
+
#
|
|
164
|
+
# Each named accessor MIGRATES a legacy ``~/<name>`` file into its new home the
|
|
165
|
+
# first time it is resolved (atomic same-fs rename, cross-fs copy fallback), so
|
|
166
|
+
# upgrades are seamless and idempotent — no flag-day, no data loss. Every path
|
|
167
|
+
# stays overridable so existing deployments and tests can pin locations.
|
|
168
|
+
#
|
|
169
|
+
# NOTE: standalone station-stack scripts (keeper_relay.py, bugreport/scanner.py)
|
|
170
|
+
# run under the system interpreter and cannot import this package; they carry a
|
|
171
|
+
# byte-identical inline resolver. Keep the two in sync — this module is the spec.
|
|
172
|
+
|
|
173
|
+
def hugpy_home() -> str:
|
|
174
|
+
"""Single base directory for hugpy's runtime files. Default ``~/.hugpy``."""
|
|
175
|
+
return _ensure(env_value("HUGPY_HOME") or _home(".hugpy"))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def hugpy_state_dir() -> str:
|
|
179
|
+
return _ensure(os.path.join(hugpy_home(), "state"))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def hugpy_config_dir() -> str:
|
|
183
|
+
return _ensure(os.path.join(hugpy_home(), "config"))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def hugpy_logs_dir() -> str:
|
|
187
|
+
return _ensure(os.path.join(hugpy_home(), "logs"))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def hugpy_run_dir() -> str:
|
|
191
|
+
return _ensure(os.path.join(hugpy_home(), "run"))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _relocate(new_path: str, *legacy_names: str) -> str:
|
|
195
|
+
"""Return *new_path*, first migrating a legacy ``~/<name>`` into it once.
|
|
196
|
+
|
|
197
|
+
Idempotent and safe: if *new_path* already exists nothing moves. Uses an
|
|
198
|
+
atomic rename on the same filesystem and falls back to a copy+unlink across
|
|
199
|
+
filesystems. Any failure leaves the legacy file untouched and returns the
|
|
200
|
+
new path anyway (a fresh file is then created there).
|
|
201
|
+
"""
|
|
202
|
+
try:
|
|
203
|
+
if os.path.exists(new_path):
|
|
204
|
+
return new_path
|
|
205
|
+
for name in legacy_names:
|
|
206
|
+
old = _home(name)
|
|
207
|
+
if not os.path.exists(old):
|
|
208
|
+
continue
|
|
209
|
+
if os.path.abspath(old) == os.path.abspath(new_path):
|
|
210
|
+
continue
|
|
211
|
+
try:
|
|
212
|
+
os.replace(old, new_path) # atomic, same filesystem
|
|
213
|
+
except OSError:
|
|
214
|
+
import shutil
|
|
215
|
+
shutil.move(old, new_path) # cross-filesystem fallback
|
|
216
|
+
break
|
|
217
|
+
except OSError:
|
|
218
|
+
pass
|
|
219
|
+
return new_path
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# --- named accessors --------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
def worker_id_file() -> str:
|
|
225
|
+
"""``config/abstract_hugpy_worker.json`` — the worker identity file.
|
|
226
|
+
|
|
227
|
+
Honors an explicit ``WORKER_ID_FILE`` override (used by tests / multi-worker
|
|
228
|
+
hosts); otherwise migrates the legacy ``~/.abstract_hugpy_worker.json`` plus
|
|
229
|
+
its ``.settings.json`` / ``.update.json`` sidecars into ``config/``.
|
|
230
|
+
"""
|
|
231
|
+
override = env_value("WORKER_ID_FILE")
|
|
232
|
+
if override:
|
|
233
|
+
return override
|
|
234
|
+
new = os.path.join(hugpy_config_dir(), "abstract_hugpy_worker.json")
|
|
235
|
+
_relocate(new, ".abstract_hugpy_worker.json")
|
|
236
|
+
for suffix in (".settings.json", ".update.json"):
|
|
237
|
+
_relocate(new + suffix, ".abstract_hugpy_worker.json" + suffix)
|
|
238
|
+
return new
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def gguf_worker_id_file() -> str:
|
|
242
|
+
"""``config/gguf_worker.json`` — sibling worker identity (same pattern)."""
|
|
243
|
+
override = env_value("WORKER_ID_FILE")
|
|
244
|
+
if override:
|
|
245
|
+
return override
|
|
246
|
+
new = os.path.join(hugpy_config_dir(), "gguf_worker.json")
|
|
247
|
+
return _relocate(new, ".gguf_worker.json")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def todo_file() -> str:
|
|
251
|
+
return _relocate(os.path.join(hugpy_state_dir(), "todo.json"), "todo.json")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def todo_history_file() -> str:
|
|
255
|
+
return _relocate(
|
|
256
|
+
os.path.join(hugpy_state_dir(), "todo-history.jsonl"), "todo-history.jsonl"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def todo_lock() -> str:
|
|
261
|
+
return _relocate(os.path.join(hugpy_run_dir(), "todo.lock"), ".todo.lock")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def steward_config() -> str:
|
|
265
|
+
return _relocate(os.path.join(hugpy_config_dir(), "steward.json"), "steward.json")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def steward_state() -> str:
|
|
269
|
+
return _relocate(
|
|
270
|
+
os.path.join(hugpy_state_dir(), "steward-state.json"), "steward-state.json"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def model_metadata_db() -> str:
|
|
275
|
+
return _relocate(
|
|
276
|
+
os.path.join(hugpy_state_dir(), "model_metadata.db"), "model_metadata.db"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def model_physical_json() -> str:
|
|
281
|
+
# The ``.lock`` sidecar is derived by callers as ``path + ".lock"`` and rides
|
|
282
|
+
# along in state/ next to the file — intentionally not split into run/.
|
|
283
|
+
return _relocate(
|
|
284
|
+
os.path.join(hugpy_state_dir(), "model_physical.json"), "model_physical.json"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def flow_json() -> str:
|
|
289
|
+
return _relocate(os.path.join(hugpy_state_dir(), "flow.json"), "flow.json")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def bridge_mail() -> str:
|
|
293
|
+
return _relocate(
|
|
294
|
+
os.path.join(hugpy_logs_dir(), "bridge-mail.jsonl"), ".bridge-mail.jsonl"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def bugreport_json() -> str:
|
|
299
|
+
return _relocate(os.path.join(hugpy_logs_dir(), "bugreport.json"), "bugreport.json")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def ensure_hugpy_home() -> str:
|
|
303
|
+
"""Create the full ``HUGPY_HOME`` skeleton. Called by installers/postinst."""
|
|
304
|
+
hugpy_state_dir(); hugpy_config_dir(); hugpy_logs_dir(); hugpy_run_dir()
|
|
305
|
+
return hugpy_home()
|