mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-23 10:42:00 +00:00
fix(deps): declare packaging as a core dependency so it ships everywhere (#40522)
* fix(deps): declare packaging as a core dependency so it ships everywhere packaging is imported directly on three production paths but was never declared in [project.dependencies], so it only reached users transitively (pip/uv pull it for other tools). The slim official Docker image ships without it, where each try/except-ImportError fallback silently degrades: - plugins/memory/hindsight/__init__.py (_meets_minimum_version) returns False when packaging is absent, disabling update_mode='append' so every session leaks separate Hindsight documents (the reported #40503 symptom). - tools/lazy_deps.py (_is_satisfied) falls back to "installed counts as satisfied", defeating every version-constraint check on lazy extras. - hermes_cli/main.py drops to naive name==version requirement parsing. Promote it to a declared core dep pinned to packaging==26.0 — the exact version already resolved in uv.lock, so there is zero resolution churn (the lock change is two edge annotations marking it transitive->direct). It is a pure-Python py3-none-any wheel with no compiled extensions, safe to ship on every platform. Declaring it also wires it into the _verify_core_dependencies_installed() update-repair guard, which reinstalls missing [project.dependencies] on hermes update. Adds a hermetic tomllib-parse regression test that fails before the declaration and passes after. Fixes #40503 * test(deps): make packaging dep-name extraction PEP 508-robust Address Copilot review on #40522: the inline name-extraction only handled ==, >=, [ and ; and could mis-parse valid requirement strings using <=, ~=, !=, <, > or a direct reference (name @ url). Factor a _distribution_name helper that drops markers, direct-reference URLs and extras, then strips any version operator via regex, so a future dep declared with any PEP 508 specifier shape is matched correctly. --------- Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com>
This commit is contained in:
parent
d046169646
commit
b5421f4ba6
3 changed files with 50 additions and 0 deletions
|
|
@ -61,6 +61,16 @@ dependencies = [
|
|||
"prompt_toolkit==3.0.52",
|
||||
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
||||
"croniter==6.0.0",
|
||||
# ``packaging`` is imported directly on three production paths but was never
|
||||
# declared, so it only reached users transitively (pip/uv pull it for other
|
||||
# tools). The slim official Docker image ships without it, where the
|
||||
# try/except-ImportError fallbacks silently degrade: Hindsight's
|
||||
# ``_meets_minimum_version`` disables update_mode='append' (#40503),
|
||||
# tools/lazy_deps.py treats every version constraint as satisfied, and
|
||||
# hermes_cli/main.py drops to naive requirement parsing. Pure-Python
|
||||
# py3-none-any wheel, no compiled extensions — safe to ship everywhere.
|
||||
# Pinned to the version already resolved in uv.lock (no resolution churn).
|
||||
"packaging==26.0",
|
||||
# Markdown -> HTML conversion for rich message delivery (Matrix
|
||||
# `formatted_body`, and the `send_message` tool's HTML path). Now on the
|
||||
# DEFAULT delivery path, not matrix-specific: without it both
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from pathlib import Path
|
||||
import re
|
||||
import tomllib
|
||||
|
||||
import pytest
|
||||
|
|
@ -14,6 +15,22 @@ find_packages = pytest.importorskip("setuptools", exc_type=ImportError).find_pac
|
|||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _distribution_name(requirement: str) -> str:
|
||||
"""Extract the PEP 508 distribution name from a requirement string.
|
||||
|
||||
Robust to markers (``; python_version < '3.12'``), direct references
|
||||
(``name @ https://...``), extras (``name[extra]``) and every version
|
||||
operator (``==``, ``>=``, ``<=``, ``~=``, ``!=``, ``<``, ``>``), so a
|
||||
future dep declared with any valid specifier shape doesn't silently
|
||||
mis-parse here.
|
||||
"""
|
||||
spec = requirement.split(";", 1)[0] # drop environment markers
|
||||
spec = spec.split("@", 1)[0] # drop direct-reference URLs
|
||||
spec = spec.split("[", 1)[0] # drop extras
|
||||
spec = re.split(r"[=<>!~]", spec, maxsplit=1)[0] # drop any version operator
|
||||
return spec.strip().lower()
|
||||
|
||||
|
||||
def _packages_find_include():
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
return data["tool"]["setuptools"]["packages"]["find"]["include"]
|
||||
|
|
@ -61,6 +78,27 @@ def test_every_on_disk_subpackage_is_covered_by_packages_find():
|
|||
)
|
||||
|
||||
|
||||
def test_packaging_declared_as_core_dependency():
|
||||
"""Regression for #40503.
|
||||
|
||||
``packaging`` is imported directly on three production paths
|
||||
(plugins/memory/hindsight/__init__.py, tools/lazy_deps.py,
|
||||
hermes_cli/main.py) yet was undeclared, so it only reached users
|
||||
transitively. The slim Docker image shipped without it, silently
|
||||
disabling Hindsight append-mode and version-constraint checks. It must
|
||||
be a declared core dependency so it installs everywhere and the
|
||||
update-repair step (``_verify_core_dependencies_installed``) guards it.
|
||||
"""
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
core = data["project"]["dependencies"]
|
||||
names = {_distribution_name(dep) for dep in core}
|
||||
assert "packaging" in names, (
|
||||
"packaging is imported on production paths (hindsight version compare, "
|
||||
"lazy_deps version constraints, requirement parsing) and must be a "
|
||||
"declared core dependency, not a transitive — see #40503"
|
||||
)
|
||||
|
||||
|
||||
def test_faster_whisper_is_not_a_base_dependency():
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
deps = data["project"]["dependencies"]
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -1400,6 +1400,7 @@ dependencies = [
|
|||
{ name = "jinja2" },
|
||||
{ name = "markdown" },
|
||||
{ name = "openai" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "pillow" },
|
||||
{ name = "prompt-toolkit" },
|
||||
|
|
@ -1650,6 +1651,7 @@ requires-dist = [
|
|||
{ name = "nemo-relay", marker = "extra == 'nemo-relay'", specifier = "==0.3" },
|
||||
{ name = "numpy", marker = "extra == 'voice'", specifier = "==2.4.3" },
|
||||
{ name = "openai", specifier = "==2.24.0" },
|
||||
{ name = "packaging", specifier = "==26.0" },
|
||||
{ name = "parallel-web", marker = "extra == 'parallel-web'", specifier = "==0.4.2" },
|
||||
{ name = "pathspec", specifier = "==1.1.1" },
|
||||
{ name = "pillow", specifier = "==12.2.0" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue