Catatan
Link ke repository code keseluruhan ada di bawah
Ide utama dari postingan ini adalah membuat objek async redis
pada event
startup
supaya kita bisa menggunakan sintaks async / await
.
Dengan pendekatan seperti pada postingan ini kita pastikan data kita di redis tidak terganggu dengan kode test
.
Sekarang kita menulis aplikasi web API dengan FastAPI dengan depedensi aioredis
untuk terhubung ke Redis dan library fakeredis untuk testing.
Persiapan
Kita siapkan environment kode kita di satu direktori.
mkdir fastapi-demo-aioredis
cd fastapi-demo-aioredis
virtualenv -p python3 venv
source venv/bin/activate
pip install aioredis fastapi uvicorn
Struktur direktory ini (fastapi-demo-aioredis
) kurang lebih seperti berikut.
app
├── app.py
├── deps.py
├── __init__.py
└── routes.py
tests
├── conftest.py
├── __init__.py
└── test_routes.py
main.py
Koding
app/app.py
import aioredis
from fastapi import Depends, FastAPI
from fastapi.requests import Request
from fastapi.responses import JSONResponse
from .routes import router
REDIS_URL = 'redis://localhost'
REDIS_DB = 0
REDIS_PASS = 'RedisPassword'
def create_app(redis=None):
"""
redis: Redis instance / coroutine
"""
app = FastAPI()
app.include_router(router)
@app.on_event('startup')
async def startup():
nonlocal redis
if redis is None:
# if redis is None, create the instance on start up
redis = await aioredis.from_url(
REDIS_URL, db=REDIS_DB, password=REDIS_PASS
)
assert await redis.ping()
@app.middleware('http')
async def http_middleware(request: Request, call_next):
nonlocal redis
# Initial response when exception raised on
# `call_next` function
err = {'error': True, 'message': "Internal server error"},
response = JSONResponse(err, status_code=500)
try:
request.state.redis = redis
response = await call_next(request)
finally:
return response
return app
app/deps.py
from fastapi.requests import Request
def get_redis(request: Request):
return request.state.redis
Sekarang mari kita buat router
-nya.
app/routes.py
from fastapi import APIRouter, Depends
from app.deps import get_redis
router = APIRouter()
@router.post('/post')
async def save_key_value(
key: str, value: str, redis=Depends(get_redis)
):
await redis.set(key, value)
return {'success': True, 'data': {key: value}}
@router.get('/get')
async def get_value(key: str, redis=Depends(get_redis)):
value = await redis.get(key)
return {
'success': True if value else False,
'data': {key: value}
}
Lalu jalankan.
uvicorn main:app --lifespan on # --port 8001
Jika sudah berjalan normar, kita bisa akses UI Swagger
-nya di http://127.0.0.1:8000/docs
Test
Persiapan
Install modul-modul yang diperlukan untuk test.
pip install pytest pytest-asyncio fakeredis httpx pytest
Test Fixtures
Karena kita mempersiapankan kode fungsi create_app
yang menerima argument instansi redis
, kita bisa gunakan modul aioredis
dari fakeredis
untuk testing.
Sekarang kita siapkan fixture untuk test kita.
Docs: pytest fixture
tests/conftest.py
import pytest
from fakeredis import aioredis
from httpx import AsyncClient
from app.app import create_app
@pytest.fixture
async def redis():
print(dir(aioredis))
return aioredis.FakeRedis(encoding='utf-8')
@pytest.fixture
def app(redis):
app = create_app(redis=redis)
return app
@pytest.fixture
def http_client(app):
return AsyncClient(app=app, base_url='http://test')
Kemudian kita tulis kode test.
tests/test_routes.py
import pytest
@pytest.mark.asyncio
class TestRoutes:
async def test_post_success(self, http_client):
async with http_client as http:
resp = await http.post('/post', params={
'key': 'test_key', 'value': 'test_value'
})
assert resp.status_code == 200
assert resp.json()['success'] == True
assert resp.json()['data'] \
== {'test_key': 'test_value'}
async def test_get_success(self, http_client, redis):
await redis.set('test_get_key', 'test_get_value')
async with http_client as http:
resp = await http.get('/get', params={
'key': 'test_get_key'
})
assert resp.status_code == 200
assert resp.json()['success'] == True
assert resp.json()['data'] \
== {'test_get_key': 'test_get_value'}
async def test_get_not_found(self, http_client):
async with http_client as http:
resp = await http.get('/get', params={
'key': 'test_not_avail'
})
assert resp.status_code == 200
assert resp.json()['success'] == False
Jalankan test.
pytest
Contoh keluaran command
-nya.
============= test session starts =============
platform linux -- Python 3.7.3, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /home/jockerz/fastapi-aioredis
plugins: asyncio-0.15.1, anyio-3.3.0
collected 3 items
tests/test_routes.py ... [100%]
============= 3 passed in 0.06s =============
Link repository: github.com/jockerz/fastapi-aioredis