Skip to main content

Socketless HTTP testing over stdio IPC for ASGI/WSGI apps (FastAPI/Flask/Django) in sandboxed dev/AI environments.

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()

## 🔍 Enable debug logging
Pass `switch_to_ipc_connection(..., debug=True)` when you need to trace what the worker is doing. It prints to stderr: worker startup/handshake status, method/URL/headers count sent from the parent, what the worker received and the returned status/body length, reset_hook calls, and restart attempts with stderr from the worker.

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.
  • FastAPI apps run in-process via ASGITransport: define routes as async def and offload blocking work with anyio.to_thread.run_sync to avoid known hangs in some FastAPI/Starlette/httpx/anyio versions when using sync (def) endpoints.

See docs/spec.md for full design notes. README_ja.md provides the same info in Japanese.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

socketless_http-0.2.0.tar.gz (15.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

socketless_http-0.2.0-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file socketless_http-0.2.0.tar.gz.

File metadata

  • Download URL: socketless_http-0.2.0.tar.gz
  • Upload date:
  • Size: 15.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for socketless_http-0.2.0.tar.gz
Algorithm Hash digest
SHA256 3b2edde0e68561ca1478a7336b1835eae3a5f2e52403d2b4e6f9192952658c2e
MD5 703fb144708bf078d047b6ef14d8cb23
BLAKE2b-256 5e5b1302656832f29ede784f8ba1aa6de97803f294bc0cdae573db9ce9f83ab8

See more details on using hashes here.

File details

Details for the file socketless_http-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for socketless_http-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c2b4b67b25dbc33369bf8e71b97101df05f836fbd677d654446ee60b6c804a02
MD5 8a57bd6a61e08c0e4543a099390bd5aa
BLAKE2b-256 a7ef74e560cdc4607cc056cd39b89188aca23bc82c9ae4cedb335c306455da67

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page