Replies: 3 comments 11 replies
-
|
You can generally inject a http client into the import httpx
from quart import Quart, current_app
app = Quart(__name__)
@app.before_serving
async def startup():
app.client = httpx.AsyncClient(base_url="https://httpbin.org", timeout=20)
@app.after_serving
async def shutdown():
await app.client.aclose()
@app.get("/sample")
async def sample():
response = await current_app.client.get("/json")
return response.json()I would add that if this is for production use/you'd prefer better performance, it would be worth looking into a better library than
among others. |
Beta Was this translation helpful? Give feedback.
-
|
Here is an example where I define a class that acts as a wrapper for the HTTPX async client. The # client.py
import httpx
class HTTPClient:
"""Async HTTP client."""
client: httpx.AsyncClient | None = None
def start(self, base_url, timeout: float = 20):
self.client = httpx.AsyncClient(base_url=base_url, timeout=timeout)
async def stop(self):
assert self.client is not None
await self.client.aclose()
def __call__(self) -> httpx.AsyncClient:
print("called client")
assert self.client is not None
return self.clientThis # main.py
from quart import Quart
from .client import HTTPClient
app = Quart(__name__)
http_client = HTTPClient()
@app.before_serving
async def startup():
http_client.start(base_url="https://httpbin.org", timeout=20)
@app.after_serving
async def shutdown():
await http_client.stop()
print("\nClosed HTTP client and shutdown server")
@app.get("/")
async def root():
return "hello there"
@app.get("/sample")
async def sample():
client = http_client()
response = await client.get("/json")
return response.json()This provides good type checking support and avoids attaching things to the @davidism and @servusdei2018 What do you think about this approach? |
Beta Was this translation helpful? Give feedback.
-
|
Here is another example which has the following file structure: Below is the main module content. I didn't think I could create the client at the module level because pytest complained about the client being closed before running the tests. But I got around that with some fixtures and asyncio support for pytest. # main.py
import httpx
from quart import Quart
app = Quart(__name__)
client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com")
@app.after_serving
async def shutdown():
"""Clean up resources after serving."""
await client.aclose()
print("\nClosed client and shutdown server")
@app.get("/")
async def root():
"""Return server status."""
return {"status": "ok", "message": "Server is running"}
@app.get("/users")
async def get_users():
"""Fetch and return user data from JSONPlaceholder API."""
response = await client.get("https://jsonplaceholder.typicode.com/users")
return response.json()
@app.get("/posts")
async def get_posts():
"""Fetch and return posts data from JSONPlaceholder API."""
response = await client.get("https://jsonplaceholder.typicode.com/posts")
return response.json()And here are the tests: # test_main.py
import pytest
import pytest_asyncio
import respx
from httpx import Response
from quart_httpx2.main import app
MOCK_USERS = [
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Doe", "email": "jane@example.com"},
]
MOCK_POSTS = [
{"id": 1, "title": "First Post", "body": "Hello world", "userId": 1},
{"id": 2, "title": "Second Post", "body": "Another post", "userId": 1},
]
@pytest_asyncio.fixture(scope="module")
async def client():
test_client = app.test_client()
yield test_client
@pytest.mark.asyncio
async def test_root(client):
response = await client.get("/")
assert response.status_code == 200
data = await response.get_json()
assert data == {"status": "ok", "message": "Server is running"}
@pytest.mark.asyncio
@respx.mock
async def test_get_users(client):
respx.get("https://jsonplaceholder.typicode.com/users").mock(
return_value=Response(200, json=MOCK_USERS)
)
response = await client.get("/users")
assert response.status_code == 200
data = await response.get_json()
assert isinstance(data, list)
assert len(data) == 2
assert data[0]["id"] == 1
assert data[0]["name"] == "John Doe"
assert data[0]["email"] == "john@example.com"
@pytest.mark.asyncio
@respx.mock
async def test_get_posts(client):
respx.get("https://jsonplaceholder.typicode.com/posts").mock(
return_value=Response(200, json=MOCK_POSTS)
)
response = await client.get("/posts")
assert response.status_code == 200
data = await response.get_json()
assert isinstance(data, list)
assert len(data) == 2
assert data[0]["id"] == 1
assert data[0]["title"] == "First Post"
assert data[0]["body"] == "Hello world"@davidism I think this example aligns with your earlier comments about defining the client globally. I had tried this before but had issues with writing tests but I seem to have fixed those issues. Anyway, what are your thoughts about this approach? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
The HTTPX docs suggest using a client instance for production code. To use the same client throughout my Quart project, I create a client instance at the module level as shown below.
In the main module I create the Quart app and routes as shown next. The async HTTP client is initialized by the
startup()method and the same client instance is used by all the routes. This example just returns the client response in the route. A more practical example would use the response data for some analysis then return JSON as a result of that analysis.I would like to get some feedback on whether this is a good approach for using a single HTTP client instance in a Quart project. Is there a better solution that doesn't use
global? Should I create a class that acts as a wrapper for thehttpx.AsyncClientinstead of the module approach I showed above? Is there some other design pattern that is more appropriate for a Quart project?Beta Was this translation helpful? Give feedback.
All reactions