diff --git a/pyproject.toml b/pyproject.toml index 13b86df..ea5a4ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,9 +23,9 @@ Source = "https://github.com/restatedev/sdk-python" [project.optional-dependencies] test = ["pytest", "hypercorn", "anyio"] lint = ["mypy>=1.11.2", "pyright>=1.1.390", "ruff>=0.6.9"] -harness = ["testcontainers", "hypercorn", "httpx"] +harness = ["testcontainers", "hypercorn", "httpx2[http2]>=2.12,<3"] serde = ["dacite", "pydantic", "msgspec"] -client = ["httpx[http2]"] +client = ["httpx2[http2]>=2.12,<3"] adk = ["google-adk>=1.20.0"] openai = ["openai-agents>=0.6.1"] pydantic_ai = ["pydantic-ai-slim>=2.0,<3"] diff --git a/python/restate/client.py b/python/restate/client.py index dc39213..f0d0fc0 100644 --- a/python/restate/client.py +++ b/python/restate/client.py @@ -13,7 +13,7 @@ """ from datetime import timedelta -import httpx +import httpx2 import typing from contextlib import asynccontextmanager @@ -32,7 +32,7 @@ class Client(RestateClient): A basic client for connecting to the Restate service. """ - def __init__(self, client: httpx.AsyncClient, headers: typing.Optional[dict] = None): + def __init__(self, client: httpx2.AsyncClient, headers: typing.Optional[dict] = None): self.headers = headers or {} self.client = client @@ -476,5 +476,5 @@ async def create_client( """ Create a new Restate client. """ - async with httpx.AsyncClient(base_url=ingress, headers=headers, http2=True) as http_client: + async with httpx2.AsyncClient(base_url=ingress, headers=headers, http2=True) as http_client: yield Client(http_client, headers) diff --git a/python/restate/harness.py b/python/restate/harness.py index 5861124..7dfa454 100644 --- a/python/restate/harness.py +++ b/python/restate/harness.py @@ -26,7 +26,7 @@ from testcontainers.core.container import DockerContainer # type: ignore from testcontainers.core.wait_strategies import CompositeWaitStrategy, HttpWaitStrategy -import httpx +import httpx2 def find_free_port(): @@ -169,12 +169,12 @@ def admin_url(self): return f"http://{self.get_container_host_ip()}:{self.get_exposed_port(9070)}" def get_admin_client(self): - """return an httpx client to access the admin interface""" - return httpx.Client(base_url=self.admin_url()) + """return an HTTPX2 client to access the admin interface""" + return httpx2.Client(base_url=self.admin_url()) def get_ingress_client(self): - """return an httpx client to access the ingress interface""" - return httpx.Client(base_url=self.ingress_url()) + """return an HTTPX2 client to access the ingress interface""" + return httpx2.Client(base_url=self.ingress_url()) def start(self, stream_logs=False): """start the container and wait for health checks to pass""" @@ -253,7 +253,7 @@ def stop(self): self.bind_address.cleanup() def ingress_client(self): - """return an httpx client to access the restate server's ingress""" + """return an HTTPX2 client to access the restate server's ingress""" if self.restate is None: raise AssertionError("The Restate server has not been started. Use .start()") return self.restate.get_ingress_client() diff --git a/tests/servercontext.py b/tests/servercontext.py index 4a30de4..2528828 100644 --- a/tests/servercontext.py +++ b/tests/servercontext.py @@ -90,7 +90,7 @@ async def test_retryable_exception(): invocation_retry_policy=InvocationRetryPolicy( max_attempts=3, # Something really long to trigger a test timeout. - # Default httpx client timeout is 5 seconds. + # Default HTTPX2 client timeout is 5 seconds. initial_interval=timedelta(hours=1), ), ) @@ -120,7 +120,7 @@ async def test_accidentally_wrapped_retryable_exception(): invocation_retry_policy=InvocationRetryPolicy( max_attempts=3, # Something really long to trigger a test timeout. - # Default httpx client timeout is 5 seconds. + # Default HTTPX2 client timeout is 5 seconds. initial_interval=timedelta(hours=1), ), ) diff --git a/uv.lock b/uv.lock index 948f461..bc93bd7 100644 --- a/uv.lock +++ b/uv.lock @@ -1228,15 +1228,15 @@ wheels = [ [[package]] name = "httpcore2" -version = "2.3.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h11" }, { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/34/18f1c596e677962f040284246f393b10a1f8ce440b3a7e69c637d0f1c7ad/httpcore2-2.3.0.tar.gz", hash = "sha256:07327e251560960eea8e969d92d4c6a325feb13cca39e25340731336c3baf924", size = 64300, upload-time = "2026-06-01T13:15:02.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/dd/3357218c69360d1cecc196c230c9a1d5c9afd5dba362056e23e60a5e64e5/httpcore2-2.3.0-py3-none-any.whl", hash = "sha256:477e9e334f74e5240dcac002e890580f36a57d40ff0fb14cc9655731d23b8415", size = 80024, upload-time = "2026-06-01T13:15:00.001Z" }, + { url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb" }, ] [[package]] @@ -1282,17 +1282,33 @@ wheels = [ [[package]] name = "httpx2" -version = "2.3.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "httpcore2" }, + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, { name = "idna" }, - { name = "truststore" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/9a/cca0b9145f13d8ae34b885ae28d403a1469a433abc78e0f94f4ce94e650b/httpx2-2.3.0.tar.gz", hash = "sha256:227e7c41d95a76d4077a52640564132777215fc3394e07b66a3116c33d668fa9", size = 81115, upload-time = "2026-06-01T13:15:04.324Z" } + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/ce/ae2911859847f9ba1d6b23027e53481cbeb50b93234f355a968d300ca2cb/httpx2-2.3.0-py3-none-any.whl", hash = "sha256:6f393663bdf6dbe7fe90118e3eb5b2bd024a675cae0390ac08cec9198812d8b7", size = 74538, upload-time = "2026-06-01T13:15:01.566Z" }, + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32" }, ] [[package]] @@ -1325,11 +1341,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4" }, ] [[package]] @@ -2898,10 +2914,10 @@ adk = [ { name = "google-adk" }, ] client = [ - { name = "httpx", extra = ["http2"] }, + { name = "httpx2", extra = ["http2"] }, ] harness = [ - { name = "httpx" }, + { name = "httpx2", extra = ["http2"] }, { name = "hypercorn" }, { name = "testcontainers" }, ] @@ -2944,8 +2960,8 @@ requires-dist = [ { name = "backports-zstd", marker = "python_full_version < '3.14' and extra == 'zstd'" }, { name = "dacite", marker = "extra == 'serde'" }, { name = "google-adk", marker = "extra == 'adk'", specifier = ">=1.20.0" }, - { name = "httpx", marker = "extra == 'harness'" }, - { name = "httpx", extras = ["http2"], marker = "extra == 'client'" }, + { name = "httpx2", extras = ["http2"], marker = "extra == 'client'", specifier = ">=2.12,<3" }, + { name = "httpx2", extras = ["http2"], marker = "extra == 'harness'", specifier = ">=2.12,<3" }, { name = "hypercorn", marker = "extra == 'harness'" }, { name = "hypercorn", marker = "extra == 'test'" }, { name = "langchain", marker = "extra == 'langchain'", specifier = ">=1.0.0" },