Files
de1b773f55 fix: declare numpy and httpx so a clean install of hanzo-agent imports (#12)
* fix: declare what `import agents` needs, at floors 3.9 can install

hanzo-agent 0.1.0 on PyPI cannot be imported from a clean install. On
3.9, agents/memory/store.py imports numpy at module level, and the
wheel's METADATA does not declare it. On 3.10+, pip resolves openai
3.13.0, which replaced its httpx dependency with httpx2. agents imports
httpx directly, in tracing/processors.py and models/openai_provider.py,
and until now only got it through openai.

main declares numpy>=2.1.0, but numpy 2.1.0 dropped 3.9, whose newest
numpy is 2.0.2. requires-python still admits 3.9, so the pin cannot
resolve there, and `uv lock --check` fails.

- numpy: >=1.26 below 3.10, and >=2.1.0 from 3.10, which keeps the
  cp313t floor
- httpx: >=0.23.0,<1, the range openai 2.x required
- store.py imports numpy inside search_by_embedding, as retriever.py
  already does, so `import agents` does not need it
- uv.lock: relocked. No pinned version moves; the lock was already
  stale for the aiohttp and cryptography floors.

tests/test_packaging.py checks the declared numpy range on 3.9-3.13,
that `import agents` works with numpy blocked, and the in-memory
embedding search. A new CI job builds the wheel, installs it into a
clean venv on 3.9 and 3.12, and imports agents and numpy.

Co-Authored-By: Claude Opus 5 <[email protected]>

* fix: two more places 3.9 cannot evaluate a PEP 604 union

tests/test_zap.py annotates ZapAgentConfig.__init__ with `list | None`
and has no `from __future__ import annotations`, so it fails collection
on 3.9. That was the one error left in `pytest tests` there.

agents/extensions/web3/mpc_client.py declares a pydantic field as
`str | None`. Its future import does not help, because pydantic
evaluates the annotation when it builds the model, and on 3.9 it can
only do that with eval_type_backport installed. So `import
agents.extensions.web3` fails on 3.9 even with [web3] installed.

Declare the backport in the web3 extra, below 3.10, instead of rewriting
the field as Optional[str]. The repo's ruff UP rules would flag Optional
there, and their autofix would put the union back.

On 3.9, `pytest tests` now gives 369 passed, and the web3 extension's
own tests give 20 passed; before, they could not even be collected.
0.2.0, the declared floor, passes them too.

Co-Authored-By: Claude Opus 5 <[email protected]>

* fix: raise the web3 extra's floor to 6.20.2

On a clean 3.9 install of hanzo-agent[web3], the resolver picked web3
6.11.0 with eth-typing 5.2.1 and eth-account 0.13.7. web3 releases
before 6.20.2 declare no upper bound on eth-typing, and web3 6 cannot
import with eth-typing 5 ("cannot import name 'ContractName'"). 6.11
also imports pkg_resources, which a bare venv does not have. 6.20.2 is
the first 6.x that caps eth-typing below 5, and 6.17.1 was the first
to cap eth-account below 0.13.

uv.lock already pins web3 6.20.4, so no pinned version moves. A clean
[web3] install now resolves web3 6.20.4, eth-typing 4.4.0 and
eth-account 0.11.3 on both 3.9 and 3.12, with no setuptools, and the
extension's tests pass on both (20 each).

Co-Authored-By: Claude Opus 5 <[email protected]>

---------

Co-authored-by: Claude Opus 5 <[email protected]>
2026-09-10 21:29:11 -07:00

68 lines
2.7 KiB
Python

"""Packaging checks for hanzo-agent.
0.1.0 reached PyPI without numpy in its METADATA while ``agents/memory/store.py``
imported numpy at module level, so ``import agents`` raised ``ModuleNotFoundError``
in a clean environment. The pin that replaced it, ``numpy>=2.1.0``, cannot install
on 3.9, which requires-python still admits: numpy 2.1.0 dropped 3.9.
"""
from __future__ import annotations
import subprocess
import sys
from importlib.metadata import requires
import pytest
from packaging.requirements import Requirement
from packaging.specifiers import SpecifierSet
from agents.memory import InMemoryMemoryStore, MemoryType
# numpy 2.1.0 dropped Python 3.9 and 2.3.0 dropped 3.10, so these are the newest
# releases those interpreters can install, and they will not move.
LAST_NUMPY = {"3.9": "2.0.2", "3.10": "2.2.6"}
def _numpy_specifiers(python: str) -> list[SpecifierSet]:
env = {"python_version": python, "python_full_version": f"{python}.0", "extra": ""}
reqs = [Requirement(r) for r in requires("hanzo-agent") or []]
return [
r.specifier
for r in reqs
if r.name == "numpy" and (r.marker is None or r.marker.evaluate(env))
]
@pytest.mark.parametrize("python", ["3.9", "3.10", "3.11", "3.12", "3.13"])
def test_numpy_is_declared_on_every_supported_python(python: str) -> None:
assert _numpy_specifiers(python), f"no numpy requirement applies on Python {python}"
@pytest.mark.parametrize(("python", "version"), sorted(LAST_NUMPY.items()))
def test_numpy_floor_admits_the_last_numpy_for_old_pythons(python: str, version: str) -> None:
for spec in _numpy_specifiers(python):
assert spec.contains(version), (
f"numpy{spec} excludes {version}, the newest numpy for Python {python}"
)
def test_import_agents_does_not_need_numpy() -> None:
# A None entry in sys.modules makes `import numpy` raise ImportError.
code = "import sys; sys.modules['numpy'] = None; import agents"
result = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
async def test_search_by_embedding_ranks_by_cosine_similarity() -> None:
store = InMemoryMemoryStore()
same = await store.add("same", MemoryType.FACT, embedding=[1.0, 0.0])
near = await store.add("near", MemoryType.FACT, embedding=[1.0, 1.0])
await store.add("orthogonal", MemoryType.FACT, embedding=[0.0, 1.0])
await store.add("no embedding", MemoryType.FACT)
results = await store.search_by_embedding([2.0, 0.0], threshold=0.5)
assert [memory.id for memory, _ in results] == [same.id, near.id]
assert results[0][1] == pytest.approx(1.0)
assert results[1][1] == pytest.approx(2**-0.5)