Add your description here
Project description
socketless-http
Transparent HTTP testing over IPC for sandboxed AI/editor environments. Run your FastAPI/ASGI (and wrapped Flask/Django WSGI) tests without opening sockets or resolving localhost/testserver—keep your test code the same while the transport swaps underneath. Keep writing with the same httpx/TestClient ergonomics while dropping sockets entirely.
🎯 Why?
- AI editors / sandboxes often block sockets and DNS, so TestClient/httpx fail or hang.
- socketless-http swaps HTTP transport to stdio IPC, keeping your app/test code mostly unchanged. You keep the ergonomics of httpx/TestClient and gain reliable, socket-free execution.
🚀 Quickstart
uv add fastapi httpx
uv add --dev socketless-http pytest pytest-asyncio
In your tests (e.g., conftest.py):
from socketless_http import switch_to_ipc_connection, reset_ipc_state
# start IPC once per session
_cleanup = switch_to_ipc_connection(
"tests.sample_app:app", # ASGI app import path
reset_hook="tests.sample_app:reset_state", # optional per-test reset callable
base_url="http://testserver",
)
def teardown_module():
_cleanup()
# per-test reset (or use pytest fixtures from socketless_http.pytest_plugin)
def setup_function(_):
reset_ipc_state()
Use httpx or FastAPI TestClient as usual; requests go over IPC, not sockets:
import httpx
from fastapi.testclient import TestClient
def test_ping_with_httpx():
res = httpx.Client().get("/ping")
assert res.json() == {"status": "ok"}
def test_ping_with_testclient(app):
client = TestClient(app)
assert client.get("/ping").json() == {"status": "ok"}
📘 Tutorials
FastAPI (ASGI)
What you need: ASGI app import path, optional reset function, and a place to toggle IPC (e.g., conftest.py). After switching, keep using httpx/TestClient as usual.
Server (myapp/main.py):
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
async def hello(): return {"message": "fastapi"}
def reset_state(): pass
Client/test (FastAPI TestClient uses the same app defined above):
from socketless_http import switch_to_ipc_connection, reset_ipc_state
_cleanup = switch_to_ipc_connection("myapp.main:app", reset_hook="myapp.main:reset_state")
def teardown_module(): _cleanup()
def setup_function(_): reset_ipc_state()
def test_hello():
import httpx
assert httpx.Client().get("/hello").json() == {"message": "fastapi"}
def test_hello_with_testclient(app=app): # type: ignore[name-defined]
from fastapi.testclient import TestClient
client = TestClient(app)
assert client.get("/hello").json() == {"message": "fastapi"}
# conftest.py
from socketless_http import switch_to_ipc_connection, reset_ipc_state
_cleanup = switch_to_ipc_connection(
"myapp.main:app",
reset_hook="myapp.main:reset_state", # optional per-test cleanup
)
def teardown_module():
_cleanup()
def setup_function(_):
reset_ipc_state()
Flask (WSGI)
What you need: WSGI app import path, optional reset, and app_kind="wsgi" so it is wrapped via WsgiToAsgi before ASGITransport.
Server (myapp/wsgi.py):
from flask import Flask, jsonify
app = Flask(__name__)
@app.get("/hello")
def hello(): return jsonify(message="flask")
def reset_state(): pass
Client/test:
from socketless_http import switch_to_ipc_connection, reset_ipc_state
_cleanup = switch_to_ipc_connection("myapp.wsgi:app", reset_hook="myapp.wsgi:reset_state", app_kind="wsgi")
def teardown_module(): _cleanup()
def setup_function(_): reset_ipc_state()
def test_hello():
import httpx
assert httpx.Client().get("/hello").json() == {"message": "flask"}
from socketless_http import switch_to_ipc_connection, reset_ipc_state
_cleanup = switch_to_ipc_connection(
"myapp.wsgi:app",
reset_hook="myapp.wsgi:reset_state",
app_kind="wsgi",
)
def teardown_module():
_cleanup()
def setup_function(_):
reset_ipc_state()
Django (ASGI recommended)
What you need: set DJANGO_SETTINGS_MODULE, pass the ASGI app (myproject.asgi:application), and provide a DB reset hook if needed. Continue to use httpx/TestClient as normal.
Server (myproject/asgi.py + urls.py):
# asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = get_asgi_application()
# urls.py
from django.http import JsonResponse
from django.urls import path
urlpatterns = [path("hello/", lambda request: JsonResponse({"message": "django"}))]
def reset_db(): pass # e.g., flush test DB
Client/test:
import os
from socketless_http import switch_to_ipc_connection, reset_ipc_state
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
_cleanup = switch_to_ipc_connection("myproject.asgi:application", reset_hook="myproject.asgi:reset_db")
def teardown_module(): _cleanup()
def setup_function(_): reset_ipc_state()
def test_hello():
import httpx
assert httpx.Client().get("/hello/").json() == {"message": "django"}
If you must run Django in WSGI mode, set app_kind="wsgi" and pass myproject.wsgi:application, but ASGI is preferred.
pytest helpers
# conftest.py
from socketless_http.pytest_plugin import ipc_connection_fixture, reset_between_tests_fixture
ipc_connection = ipc_connection_fixture(
"tests.sample_app:app",
reset_hook="tests.sample_app:reset_state",
)
reset_between_tests = reset_between_tests_fixture()
What’s supported (MVP)
- Methods: GET/POST/PUT/PATCH/DELETE/OPTIONS/HEAD
- Bodies: bytes/text/JSON up to 5MB (no streaming yet)
- Headers/cookies round-trip; base_url override; follow_redirects client-side
- Reset hook per test; session-scoped worker reuse
- Worker auto-restart once if it dies; stderr is buffered and surfaced on errors
- WSGI apps supported via auto-detect or
app_kind="wsgi"(WsgiToAsgi wrapping)
Not yet
- WebSocket, SSE, HTTP/2, streaming/chunked bodies
- Parallel IPC requests (currently serialized)
- TLS options (
verify/cert) ignored/unsupported
Known constraints
- Transport is stdio IPC; no raw sockets are opened by clients or worker.
- Responses are buffered (no streaming); 5MB body limit per request/response.
- One worker process is reused; only one auto-restart attempt is made if it dies.
- TLS and HTTP/2 semantics are out of scope; keep to HTTP/1.1-style requests.
See docs/spec.md for full design notes. README_ja.md provides the same info in Japanese.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file socketless_http-0.1.0.tar.gz.
File metadata
- Download URL: socketless_http-0.1.0.tar.gz
- Upload date:
- Size: 12.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e4f33d5bc70c61ec4987f6a5e3b3142d4677c26d7068083a2714132ad732c07
|
|
| MD5 |
2bd16e6667282293d17dfc7b56f1f6db
|
|
| BLAKE2b-256 |
b34353f0fdeb22dc5464e5c274c7176a3f79d568e3d5ec448a8fc20bb1552ea0
|
File details
Details for the file socketless_http-0.1.0-py3-none-any.whl.
File metadata
- Download URL: socketless_http-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e5ed7eb62c728b07450ef901ec483c5919c1495eb9c1f46fe3da7861d4eaf4a
|
|
| MD5 |
a6e45aa22c74cc76ca69eeb1ee7ffef7
|
|
| BLAKE2b-256 |
52a44c3d33a56f92a5d645ce6ba5a5a5655221a2829346df38eefd8d67163918
|