Deprecate httpx in favor of httpx2#2693
Conversation
Mirrors Kludex/starlette@508023b and pydantic/pydantic-ai#5664: prefer `httpx2` at import time and fall back to `httpx` with an `MCPDeprecationWarning` emitted lazily on first use of an HTTP-touching surface (`create_mcp_http_client`, `OAuthClientProvider`, `HttpResource`). The v2-cut PR will drop the fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se the lockfile pins `httpx` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
I think we don't need the emit warning function. Just the: # mcp.shared._httpx
try:
import httpx2 as httpx
except ImportError:
import httpx
# emit the warning here.In other modules: from mcp.shared._httpx import httpxIf I'm wrong, and it does emit multiple warnings, we can just have a boolean: # mcp.shared._httpx
_displayed_deprecation = False
try:
import httpx2 as httpx
except ImportError:
import httpx
if not _displayed_deprecation:
# emit the warning here. |
…zy helper Module-level `warnings.warn(...)` inside the `except ImportError` branch fires once per process via Python's module cache — no flag or helper function needed. `MCPDeprecationWarning` moves to `mcp.shared._warnings` so the class symbol exists independently of the shim, and the pytest `filterwarnings` entry matches on the message string only. Naming the category would force pytest's filter parser to import `mcp.shared._warnings`, which cascades through `mcp/__init__.py` and triggers the very warning we're filtering (the pydantic-ai pitfall). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
David's AICA here: Adopted in 3de2aa1 —
Net diff: −107 lines, +75. Full suite green (1177 passed, 100% branch coverage). I left a short comment on the |
There was a problem hiding this comment.
Don't we have an _exceptions.py module? If we do, we should put this there.
There was a problem hiding this comment.
yeahp that turned out to be the case
Drops the dedicated _warnings.py — the side-effect-free-module constraint that justified it is moot now that the filter matches by message only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| Subclasses `UserWarning` (not `DeprecationWarning`) so it is visible by default — | ||
| `DeprecationWarning` is silenced at the Python level for non-`__main__` callers. | ||
| """ |
There was a problem hiding this comment.
I think it should say the same thing as the starlette one, so we have the reference as why we have this.
There was a problem hiding this comment.
Check how we test integrations in logfire. It's a bit more cleaner than this.
David's AICA here:
Mirrors
Kludex/starlette@508023band the matchingpydantic/pydantic-ai#5664: preferhttpx2at import time, fall back tohttpxwith anMCPDeprecationWarningemitted on first use of an HTTP-touching surface. The v2-cut PR will drop thehttpxfallback. CC @Kludex.Summary
src/mcp/shared/_httpx.pyshim:MCPDeprecationWarning(UserWarning)—UserWarning(notDeprecationWarning) so it isn't silenced by the default Python filter.try: import httpx2 as httpx/except ImportError: import httpx, with an_HTTPX_IS_DEPRECATEDflag and anif TYPE_CHECKING: import httpx as httpxblock so pyright resolves types regardless of which package is installed at runtime.emit_httpx_deprecation_warning()fires the warning at most once per process, lazily — never at module import time (avoids tripping pytest'sparse_warning_filterduring collection, the same pitfall pydantic-ai hit).src/mcp/import httpx/from httpx import …sites now route through the shim (src/mcp/shared/_httpx_utils.py,src/mcp/client/session_group.py,src/mcp/client/sse.py,src/mcp/client/streamable_http.py,src/mcp/client/auth/oauth2.py,src/mcp/client/auth/utils.py,src/mcp/client/auth/extensions/client_credentials.py,src/mcp/server/mcpserver/resources/types.py).httpx_sseimports are intentionally left alone (out of scope).create_mcp_http_client()— covers SSE, streamable HTTP, session group, anything that goes through the standard factory.OAuthClientProvider.__init__— covers OAuth flows directly, plus theClientCredentialsOAuthProvider/PrivateKeyJWTOAuthProvider/RFC7523OAuthClientProvidersubclasses.HttpResource.read()— covers the server-side resource that fetches a URL directly.tests/shared/test_httpx_shim.py: 6 focused tests — emission, silence under simulatedhttpx2, once-per-process semantics, realcreate_mcp_http_clientintegration, and asys.modulesreload test that exercises the preferredimport httpx2 as httpxbranch (the lockfile pinshttpx, so without this test that branch would be uncovered in CI).pyproject.toml:[tool.pytest.ini_options].filterwarningsignore entry pinned to the exact message text +mcp.shared._httpx.MCPDeprecationWarningcategory, with a comment explaining when to remove it. Required because pytest is configured withfilterwarnings = ["error"]and every HTTP-touching test would otherwise trip the new warning under the current lockfile.httpx>=0.27.1,<1.0.0inpyproject.tomlis left as-is.httpx2exists on PyPI (v2.2.0), butmcpdoesn't declare it as a dependency and the lockfile only pinshttpx, so users who want the new package install it explicitly. The eventual v2-cut PR will bump the dependency tohttpx2and remove the shim entirely.Out of scope (intentional)
_httpx_utils.pyor renames in passing.httpx_sseimports — those are a separate upstream story.docs/migration.mdentry — this PR is non-breaking; the v2-cut PR that removes the fallback gets the migration note.Test plan
./scripts/testgreen (1177 passed, 100% branch coverage,strict-no-coverclean).uv run --frozen pyrightclean on all modified files.uv run --frozen ruff check/ruff formatclean.🤖 Generated with Claude Code