Skip to content

Commit faf7f1f

Browse files
committed
Implement API versioning at resource level
1 parent d2db5d8 commit faf7f1f

File tree

6 files changed

+24
-51
lines changed

6 files changed

+24
-51
lines changed

http_app/__init__.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from typing import Union
22

33
from fastapi import FastAPI, Request
4-
from fastapi_versionizer import versionize
54
from starlette.responses import JSONResponse
65
from starlette_prometheus import PrometheusMiddleware, metrics
76
from structlog import get_logger
87

98
from config import AppConfig, init_logger
109
from domains import init_domains
11-
from http_app.routes import init_unversioned_routes, init_versioned_routes
10+
from http_app.routes import init_routes
1211
from storage import init_storage
1312

1413

@@ -23,20 +22,9 @@ def create_app(
2322

2423
init_storage()
2524

26-
app.add_middleware(PrometheusMiddleware)
25+
init_routes(app)
2726

28-
init_versioned_routes(app)
29-
versionize(
30-
app=app,
31-
prefix_format="/api/v{major}",
32-
docs_url="/docs",
33-
redoc_url="/redoc",
34-
)
35-
36-
"""
37-
Routes initalised after `versionize` are not versioned
38-
"""
39-
init_unversioned_routes(app)
27+
app.add_middleware(PrometheusMiddleware)
4028
app.add_route("/metrics/", metrics)
4129

4230
return app

http_app/routes/__init__.py

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,10 @@
11
from fastapi import FastAPI
2-
from fastapi.openapi.docs import get_swagger_ui_html
3-
from starlette.responses import HTMLResponse
42

5-
from http_app.routes.api.books import router as api_books_router
6-
from http_app.routes.graphql import graphql_app
7-
from http_app.routes.hello import router as hello_router
8-
from http_app.routes.ping import router as ping_router
3+
from http_app.routes import api, graphql, hello, ping
94

105

11-
def init_versioned_routes(app: FastAPI) -> None:
12-
app.include_router(api_books_router)
13-
14-
15-
def init_unversioned_routes(app: FastAPI) -> None:
16-
app.include_router(ping_router)
17-
app.include_router(hello_router)
18-
app.include_router(graphql_app, prefix="/graphql")
19-
20-
@app.get("/docs", response_class=HTMLResponse, include_in_schema=False)
21-
async def get_api_versions() -> HTMLResponse:
22-
"""
23-
Swagger page for non-versioned routes.
24-
"""
25-
26-
return get_swagger_ui_html( # pragma: no cover
27-
openapi_url=f"{app.openapi_url}",
28-
title=f"{app.title}",
29-
swagger_ui_parameters={"defaultModelsExpandDepth": -1},
30-
)
6+
def init_routes(app: FastAPI) -> None:
7+
app.include_router(api.router)
8+
app.include_router(ping.router)
9+
app.include_router(hello.router)
10+
app.include_router(graphql.router, prefix="/graphql")

http_app/routes/api/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from fastapi import APIRouter
2+
3+
from . import books
4+
5+
router = APIRouter(prefix="/api")
6+
7+
router.include_router(books.router_v1)
8+
router.include_router(books.router_v2)

http_app/routes/api/books.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from fastapi import APIRouter, status
2-
from fastapi_versionizer import api_version
32
from pydantic import BaseModel
43

54
from domains.books import Book, BookData, BookService
65

7-
router = APIRouter(prefix="/books")
6+
router_v1 = APIRouter(prefix="/books/v1")
7+
router_v2 = APIRouter(prefix="/books/v2")
88

99

1010
class CreateBookResponse(BaseModel):
@@ -44,8 +44,7 @@ class Config:
4444
"""
4545

4646

47-
@api_version(1)
48-
@router.post("/", status_code=status.HTTP_201_CREATED)
47+
@router_v1.post("/", status_code=status.HTTP_201_CREATED)
4948
async def create_book(
5049
data: CreateBookRequest,
5150
) -> CreateBookResponse:
@@ -54,9 +53,7 @@ async def create_book(
5453
return CreateBookResponse(book=created_book)
5554

5655

57-
# Example v2 API with added parameter
58-
@api_version(2)
59-
@router.post("/", status_code=status.HTTP_201_CREATED)
56+
@router_v2.post("/", status_code=status.HTTP_201_CREATED)
6057
async def create_book_v2(
6158
data: CreateBookRequest,
6259
some_optional_query_param: bool = False,

http_app/routes/graphql/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
schema = Schema(query=Query)
77

8-
graphql_app: GraphQLRouter = GraphQLRouter(schema)
8+
router: GraphQLRouter = GraphQLRouter(schema)

tests/http_app/routes/books/test_create_book.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async def test_create_book(testapp):
99
)
1010
async with AsyncClient(app=testapp, base_url="http://test") as ac:
1111
response = await ac.post(
12-
"/api/v1/books/",
12+
"/api/books/v1/",
1313
json=new_book_data,
1414
)
1515
assert response.status_code == status.HTTP_201_CREATED
@@ -27,7 +27,7 @@ async def test_create_book_v2(testapp):
2727
)
2828
async with AsyncClient(app=testapp, base_url="http://test") as ac:
2929
response = await ac.post(
30-
"/api/v2/books/",
30+
"/api/books/v2/",
3131
json=new_book_data,
3232
)
3333
assert response.status_code == status.HTTP_201_CREATED

0 commit comments

Comments
 (0)