Skip to content

Refresh the dependency lock, drop dependencies nothing needs, and move ruff to 0.16 - #1646

Merged
malatewang merged 6 commits into
MemMachine:mainfrom
edwinyyyu:chore/dependency-refresh
Sep 17, 2026
Merged

malatewang merged 6 commits into
MemMachine:mainfrom
edwinyyyu:chore/dependency-refresh

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Rebased onto main after #1645 merged (its relock commit is what makes this PR's uv lock --check meaningful at all). Five commits: refresh the lock, drop dependencies that are not requirements of these packages, then the three that move ruff to 0.16. This is #1596 applied to main, relocked against today's index rather than cherry-picked, since the two branches' locks had diverged.

Supersedes dependabot's #1583 and #1614, which bump boto3-stubs's exact pin and widen litellm's cap: after this PR boto3-stubs is gone and litellm has a floor instead of a cap.

1. Bring the lock up to date

30 of the 52 direct dependencies were behind their latest release. The refresh moves 112 locked packages, adds 6, removes 2, and downgrades none.

Nine major bumps: complexipy 6→8, cryptography 49→50, fastmcp 3→4 (and fastmcp-slim), mcp 1→2, sentence-transformers 5→6, setuptools 83→84, websockets 16→17, xxhash 3→4. Direct dependencies moving include fastapi 0.139.0→0.141.1, instructor 1.15.4→1.17.0, langchain-aws 1.6.2→1.7.6, litellm 1.85.7→1.101.0, neo4j 6.2.0→6.3.1, openai 2.45.0→2.54.0, qdrant-client 1.18.0→1.19.0, sqlalchemy 2.0.51→2.0.53, ty 0.0.59→0.0.81, uvicorn 0.51.0→0.53.0.

litellm's upper bound had to go. It was introduced as <1.85 by the PR that added the provider (#1386, whose description states the dependency that way but records no reason for the bound); dependabot later widened it to <1.86. Either way the range ends inside a run of releases — 1.83.8 through 1.92.x — that declare Requires-Python <3.14 (<3.14,>=3.9 from 1.83.8, <3.14,>=3.10 from 1.83.10). uv ignores that upper bound and installed 1.85.7 anyway; pip does not:

$ pip install --dry-run 'litellm>=1.63.0,<1.86'     # on Python 3.14
Would install litellm-1.83.7

So a pip user on 3.14 got 1.83.7 while the lockfile said 1.85.7. 1.93.0 is the first release supporting 3.14 (<3.15,>=3.10), so that is the floor now, and the lock moves to 1.101.0.

The floor also does work a cap would do badly. litellm requires openai<3, which is what holds the OpenAI SDK on the 2.x line in the lock — and left uncapped, the resolver satisfies openai<3 by walking litellm backwards to 1.83.0 (whose openai>=2.8.0 has no upper bound) rather than holding openai back. Stating the litellm requirement keeps the constraint where it belongs, and it lifts itself the day litellm supports openai 3.

One source change the new versions require: newer numpy typing catches list(vector.flat) not being list[float] (the SQLite vector store's replay path converts explicitly). The dev group also listed complexipy twice.

2. Drop dependencies nothing declares them for

  • boto3-stubs — a type-stub distribution in the runtime list. Nothing imports it, and ty check --project packages/server reports the same All checks passed with it uninstalled, so it bought neither runtime behavior nor type coverage. (Base boto3-stubs only types the boto3 entry points; per-service clients need the mypy-boto3-* extras. If stronger boto3 typing is wanted, that belongs in the dev group with those extras.) It was also pinned exactly at 1.43.13 while boto3 ran to 1.43.47 — boto3-stubs mirrors boto3 release for release, so an exact pin goes stale within days, which is why dependabot keeps opening PRs for it.
  • regex — belongs to memmachine-common, which declares it and imports it in api/spec.py. Nothing in the server package uses it.
  • dotenv → python-dotenv — the PyPI dotenv distribution ships no modules at all, only dist-info, and exists to depend on python-dotenv. importlib.metadata.packages_distributions()["dotenv"] is ["python-dotenv"], so from dotenv import load_dotenv has always been python-dotenv.
  • greenlet → sqlalchemy[asyncio] — greenlet is exactly what SQLAlchemy's asyncio extra installs, and this package uses create_async_engine. Asking SQLAlchemy for it also covers the platforms SQLAlchemy's own platform_machine marker on greenlet leaves out.

The lock loses boto3-stubs, botocore-stubs, types-s3transfer and dotenv. greenlet and python-dotenv stay, now for stated reasons.

reranker_manager also took runtime_checkable from typing_extensions, an undeclared dependency it reached through pydantic. It has been in typing since 3.8 and this package requires 3.12, so it now comes from typing, next to the Protocol the file already imports there. memmachine-common and memmachine-client keep their typing_extensions dependency — they support 3.10, and Self and Unpack are 3.11.

Kept deliberately, though nothing imports them, because each is named in the source or by the tests:

  • asyncpg and aiosqlite are the SQLAlchemy drivers this package builds URLs for. DatabaseBackend spells them out — POSTGRES = ("postgres", SqlAlchemyConf, "postgresql", "asyncpg") — and SqlAlchemyConf renders {dialect}+{driver}:// for create_async_engine.
  • tzdata on Windows. prompt_utilities builds zoneinfo.ZoneInfo(tz), and Windows ships no system tz database, so without it every lookup — including the UTC fallback in the except branch — raises ZoneInfoNotFoundError.
  • milvus-lite, which pymilvus does not declare. It serves file-backed Milvus URIs, both for the local path deployment mode and for test_milvus_vector_store, which opens MilvusClient(uri=tmp_path / "test_milvus.db") behind pytest.importorskip("milvus_lite").

3–5. Move ruff to 0.16.8

Originally left behind; folded in on request. 0.16 brings two things that touch committed content, each isolated in its own commit so the diff reads as what it is:

  • LOG004 (.exception() called outside an exception handler) fires four times on main. Two are genuine misuses: openai_embedder and amazon_bedrock_reranker call logger.exception(msg) and then raise ExternalServiceAPIError(msg) with nothing in flight, so the record gets NoneType: None appended. They become logger.error (commit 3). The other two are in the client's _handle_get_project_http_error, which is only ever called from inside an except block, so they were correct — but the helper ignored the error it is handed and leaned on the caller's active exception for its bare raise and logger.exception too. It now uses its parameter: logger.error(..., exc_info=error) and raise error (commit 4). Callers see the same exception object, the original traceback and the same 422 ValueError.__cause__; verified directly, and the client suite passes. No suppressions.
  • The formatter now formats Python code blocks in Markdown. Six docs change, by ruff format alone (commit 5): USAGE.md, examples/v1/README.md, integrations/aws_strands_agent_sdk/README.md, integrations/langgraph/README.md, maintainers/build-pip-packages.md, packages/client/README.md. Trailing commas, collapsed argument lists, column-aligned comments brought to two spaces; no words change. No .py file is reformatted.

The lint workflow's ruff-action reads the pin from pyproject.toml, so CI enforces 0.16.8 from commit 5 on. Each of the three commits is clean under its own pin (ruff check and ruff format --check at 0.15.14 for commits 3–4, at 0.16.8 for commit 5).

Deliberately left behind

  • nebula5-python — capped at <5.3 in Fix the ty static checks #1645; 5.3 has no public async client.
  • openai 3.x in the lock. The lock stays at 2.54.0 because a universal lock resolves the litellm extra too, and every litellm release through 1.103.0.dev1 caps openai<3. That is a property of the lock, not a requirement of this package, so the server declares no cap. 3.0's one breaking change is the HTTP layer (httpx to httpx2), which affects only code that hands the SDK a custom httpx client or transport; the server passes none. Verified: the server suite with every extra except litellm and openai forced to 3.3.0 — the version pip resolves for memmachine-server today, held there by instructor's jiter<0.15 (3.3.1 onward need jiter>=0.16) — is 1869 passed, 3 skipped, the same as on the lock. Installs without the litellm extra may resolve 3.x; that is intended.
  • npm packages (packages/ts-client, integrations/openclaw) — a separate ecosystem with its own dependabot PRs; untouched here.

Verification

Locked and checked with uv 0.12.15, the version setup-uv installs in CI. All six ty jobs, ruff check, ruff format --check and uv lock --check pass after each commit. Unit suite after commit 2: server 1869 passed, 3 skipped; client 255 passed; commits 3–5 change two log levels, one helper and six docs, and the client suite plus the touched server tests pass at the tip.

Related: #1596 (same change on speedkick, merged).

🤖 Generated with Claude Code

edwinyyyu and others added 2 commits September 15, 2026 17:53
The lock had drifted far behind: 30 of the 52 direct dependencies were
behind their latest release, some by a major version. Refreshing it moves
112 locked packages, adds 6 and removes 2, with no downgrades. Nine are
major bumps: complexipy 6 -> 8, cryptography 49 -> 50, fastmcp 3 -> 4 (and
fastmcp-slim), mcp 1 -> 2, sentence-transformers 5 -> 6, setuptools 83 ->
84, websockets 16 -> 17, xxhash 3 -> 4. Direct dependencies that moved
include fastapi 0.139.0 -> 0.141.1, instructor 1.15.4 -> 1.17.0, langchain-aws
1.6.2 -> 1.7.6, neo4j 6.2.0 -> 6.3.1, openai 2.45.0 -> 2.54.0, qdrant-client
1.18.0 -> 1.19.0, sqlalchemy 2.0.51 -> 2.0.53, ty 0.0.59 -> 0.0.81 and
uvicorn 0.51.0 -> 0.53.0.

Two specifiers had to change for the refresh to be honest:

- litellm was capped at <1.85 when the provider was added (MemMachine#1386, which
  records no reason for the bound), and dependabot later widened it to
  <1.86. That range ends inside a stretch of releases -- 1.83.8 through
  1.92.x -- that declare Requires-Python <3.14. uv ignores that upper
  bound and installed 1.85.7 anyway, but pip does not: on Python 3.14,
  `pip install memmachine-server[litellm]` resolves to 1.83.7, not the
  1.85.7 in the lock. 1.93.0 is the first release that supports 3.14,
  so that is the floor now, and the lock moves to 1.101.0. litellm also
  requires openai<3, which is what holds the OpenAI SDK on the 2.x line;
  without the floor the resolver satisfies that by walking litellm
  backwards to 1.83.0 instead.
- boto3-stubs was pinned exactly at 1.43.13 while boto3 resolved to
  1.43.47, so the stubs no longer described the installed SDK.

One source change the new versions require, and one cleanup:

- `list(vector.flat)` is a list of numpy scalars, not `list[float]`, which
  the newer numpy typing now reports at the assignment. The replay path
  converts explicitly.
- The dev group listed complexipy twice.

Left behind deliberately: nebula5-python (see the cap and its reason),
openai 3.x (litellm requires openai<3, and instructor's jiter<0.15 pin
holds openai below 3.11 regardless; 3.0 also swaps the transport to
httpx2), and ruff, still pinned at 0.15.14 -- 0.16 formats Markdown code
blocks, which would reformat six docs, and adds LOG004, which flags four
`logger.exception` calls, two of them genuine misuses and two in a helper
that is only ever called from an except block.

Same change as the first half of MemMachine#1596 on speedkick, applied to main.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Four entries in the server's dependency list are not requirements of this
package:

- boto3-stubs is a type-stub distribution sitting in the runtime list.
  Nothing imports it, and `ty check --project packages/server` reports
  the same "All checks passed" with the package uninstalled, so it was
  buying neither runtime behavior nor type coverage. (Base boto3-stubs
  only types the boto3 entry points; per-service clients need the
  mypy-boto3-* extras. If stronger boto3 typing is wanted later, that
  belongs in the dev group with those extras.)
- regex belongs to memmachine-common, which declares it and imports it in
  api/spec.py. Nothing in the server package uses it.
- The PyPI "dotenv" distribution ships no modules at all -- only
  dist-info -- and exists to depend on python-dotenv.
  `importlib.metadata.packages_distributions()["dotenv"]` is
  `["python-dotenv"]`, so `from dotenv import load_dotenv` has always
  been python-dotenv. Declare that instead.
- greenlet is what SQLAlchemy's asyncio extra installs, and this package
  uses create_async_engine, so ask for `sqlalchemy[asyncio]` and let it
  bring greenlet. That also covers the platforms SQLAlchemy's own
  platform_machine marker on greenlet leaves out.

The lock loses boto3-stubs, botocore-stubs, types-s3transfer and dotenv.

reranker_manager also took runtime_checkable from typing_extensions, an
undeclared dependency it reached through pydantic. It has been in
typing since 3.8 and this package requires 3.12, so it now comes from
typing, next to the Protocol the file already imports there.
memmachine-common and memmachine-client keep their typing_extensions
dependency: they support 3.10, and Self and Unpack are 3.11.

Same change as the second half of MemMachine#1596 on speedkick, applied to main.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
edwinyyyu and others added 3 commits September 16, 2026 11:35
openai_embedder's dimensionality check and amazon_bedrock_reranker's
score-count check each call logger.exception() and then raise their own
ExternalServiceAPIError. Nothing is being handled at that point:
logger.exception is error(..., exc_info=True), and with no active
exception it appends "NoneType: None" to the record. logger.error is
what both sites mean; the raise that follows carries the message.

ruff 0.16 reports both as LOG004 (`.exception()` call outside exception
handlers).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
_handle_get_project_http_error takes the caught HTTPError as a
parameter, but its bare `raise` statements and logger.exception() calls
ignored it and used the exception the *caller's* except block had
active. That works only while the helper is called from inside that
block, and ruff 0.16's LOG004 cannot see the call boundary, so it
reports the two logging calls as .exception() outside a handler.

The helper now uses what it is handed: logger.error(..., exc_info=error)
attaches the same traceback logger.exception would have, and
`raise error` re-raises the same object. Callers observe no difference:
the exception identity, its original traceback and the 422 ValueError's
__cause__ are unchanged (the helper's own frame is appended to the
traceback, as it is for any re-raise from a callee).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
ruff was held at 0.15.14 by the dependency refresh because 0.16 brings
two things that touch committed content, and both now have their own
commits ahead of this one:

- LOG004 (`.exception()` outside an exception handler), selected via
  the LOG group. Its four findings are resolved by the two preceding
  commits, so `ruff check` is clean at this pin with no suppressions.
- The formatter now formats Python code blocks inside Markdown. The six
  docs it touches are reformatted here, by `ruff format` and nothing
  else: USAGE.md, examples/v1/README.md,
  integrations/aws_strands_agent_sdk/README.md,
  integrations/langgraph/README.md, maintainers/build-pip-packages.md,
  packages/client/README.md. Trailing commas, collapsed argument
  lists, and column-aligned comments brought to two spaces; no words
  change.

The lint workflow's ruff-action reads the pin from pyproject.toml, so
CI enforces 0.16.8 from this commit on. No .py file is reformatted by
the new version.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@edwinyyyu edwinyyyu changed the title Refresh the dependency lock, and drop dependencies nothing needs Refresh the dependency lock, drop dependencies nothing needs, and move ruff to 0.16 Sep 16, 2026

@marvinyu-memverge marvinyu-memverge left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read this at 0f3cd48 - the lock changes, the drops, the four source hunks and the CI wiring all check out. One ask.

1. Cap openai<3 in the server's own dependency list. The openai<3 this PR leans on lives in the optional litellm extra, so a plain pip install memmachine-server never sees it, and instructor allows openai<4. The PR's own installation-test job shows what pip does with that: it installed openai 3.3.0 (with httpx2 2.13.0) while the lock says 2.54.0. 3.3.0 is the last 3.x that accepts instructor's jiter<0.15 (3.3.1 onward need jiter>=0.16), so pip walks back from 3.14.1 to there rather than to 2.x. It is the same lock-vs-pip split this PR fixes for litellm, in the other direction - pre-existing since openai 3.0.0 landed in August, but this is the PR that reasons about the mechanism. Since 3.x is its own PR per the description, openai>=2.21.0,<3 on the server package states that; the lock does not move.

Verified: ruff-action reads the pin from pyproject.toml via version-file; the lock drops boto3-stubs/botocore-stubs/types-s3transfer/dotenv and keeps greenlet + python-dotenv through sqlalchemy[asyncio] and the direct dependency; regex and typing_extensions have no imports left in the server package, dotenv has three; on PyPI, dotenv 0.9.9 requires only python-dotenv, litellm 1.85.7 is <3.14 and 1.93.0 is the first <3.15, litellm 1.101.0 requires openai<3.0.0, instructor 1.17.0 requires jiter<0.15 and openai<4; fastmcp.server.http.StarletteWithLifespan still resolves under fastmcp 4.0.3 with the MCP test suite green; raise error in the client helper is the active exception, so the object, traceback and 422 __cause__ are unchanged.

The litellm extra's comment also said litellm's openai<3 is what keeps
the resolved OpenAI SDK on the 2.x line. That describes the universal
lock, which resolves every extra, and not a requirement of this package:
the server hands the SDK no httpx client, which is the only thing
openai 3.0 changed, and its suite passes on openai 3.3.0 with the extra
absent (1869 passed, 3 skipped, the same as on the lock). Installs
without the extra may resolve openai 3.x. The comment now states only
the floor, which is this package's own decision.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@edwinyyyu

edwinyyyu commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Written by Claude:
Thanks for the read, and for the pip trace — you are right about the mechanism and about the boundary (3.3.1 is the first that needs jiter>=0.16; the description said 3.11, now fixed).

Not capping, though. The runtime list declares what this package's code needs: floors from the APIs it uses, caps only where its own code is incompatible. The openai<3 is litellm's requirement, so it belongs on the litellm extra, where the resolver applies it to installs that opt in and drops it the day litellm lifts it (no release has yet; every one through 1.103.0.dev1 caps it). Copying it onto the server would hold every install to 2.x for litellm's sake and leave a pin someone has to remember to remove — the same shape as the litellm cap this PR turns into a floor.

What makes that safe: 3.0's one breaking change is the HTTP layer (httpx to httpx2), and it only affects code that hands the SDK a custom httpx client or transport. The server passes none (grep -r 'httpx\|http_client' packages/server/src is empty). I ran the server suite from the lock with every extra except litellm and openai forced to 3.3.0 — jiter 0.14.0, httpx2 2.13.0, exactly what the test-server-package job resolved — and it is 1869 passed, 3 skipped, the same as on the lock; the three skips are the Neo4j and episode-type ones. So the pip user runs a tested major, and the lock's 2.54.0 is a property of a universal lock that resolves the litellm extra too, not a requirement of the package.

Changed at 83e3a95: the litellm extra's comment no longer says litellm's cap is what keeps openai on 2.x (it now explains only the floor), and the description's "openai 3.x" bullet says the above instead of "worth its own PR". Lock untouched, uv lock --check passes.

The gap that remains — the uv matrix only ever exercises the lock, so declared ranges get validated only when someone runs what I ran — is real, and the non-hacky fix is a CI job that installs from metadata and runs the suite instead of import checks. Separate issue, not this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants