From 66ca4f7b509a8e98102614678b3617d24eaf9e18 Mon Sep 17 00:00:00 2001 From: Valerio Sarasini Date: Tue, 7 Jan 2025 21:37:19 +0000 Subject: [PATCH 1/3] add list books route --- src/http_app/routes/api/books.py | 29 ++++++++++++++++++- .../http_app/routes/books/test_list_books.py | 13 +++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/http_app/routes/books/test_list_books.py diff --git a/src/http_app/routes/api/books.py b/src/http_app/routes/api/books.py index b99797ed..a2de2788 100644 --- a/src/http_app/routes/api/books.py +++ b/src/http_app/routes/api/books.py @@ -1,5 +1,5 @@ from fastapi import APIRouter, status -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, RootModel, ConfigDict from domains.books import BookService, dto @@ -20,6 +20,27 @@ class CreateBookResponse(BaseModel): ) +class ListBooksResponse(RootModel): + root: list[dto.Book] + model_config = ConfigDict( + json_schema_extra={ + "example": + [ + { + "title": "The Hitchhiker's Guide to the Galaxy", + "author_name": "Douglas Adams", + "book_id": 123, + }, + { + "title": "Clean Architecture: A Craftsman's Guide to Software Structure and Design", + "author_name": "Robert C. 'Uncle Bob' Martin", + "book_id": 321, + }, + ] + } + ) + + class CreateBookRequest(BaseModel): title: str author_name: str @@ -44,6 +65,12 @@ class CreateBookRequest(BaseModel): into the format needed for the proper HTTP Response """ +@router_v1.get("/", status_code=status.HTTP_200_OK) +async def list_books() -> ListBooksResponse: + book_service = BookService() + books = await book_service.list_books() + return ListBooksResponse(root=books) + @router_v1.post("/", status_code=status.HTTP_201_CREATED) async def create_book( diff --git a/tests/http_app/routes/books/test_list_books.py b/tests/http_app/routes/books/test_list_books.py new file mode 100644 index 00000000..7c00b8de --- /dev/null +++ b/tests/http_app/routes/books/test_list_books.py @@ -0,0 +1,13 @@ +from fastapi import status +from fastapi.testclient import TestClient + + +async def test_list_books(testapp): + ac = TestClient(app=testapp, base_url="http://test") + response = ac.get( + "/api/books/v1/" + ) + assert response.status_code == status.HTTP_200_OK + assert len(response.json()) == 1 + assert response.json()[0]['title'] == "The Shining" + assert response.json()[0]['author_name'] == "Stephen King" From 0098fb10922587df27458ad7e55042333bc2b591 Mon Sep 17 00:00:00 2001 From: Valerio Sarasini Date: Tue, 7 Jan 2025 22:22:30 +0000 Subject: [PATCH 2/3] return list of books under a key --- src/http_app/routes/api/books.py | 39 +++++++++++-------- .../http_app/routes/books/test_list_books.py | 12 +++--- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/http_app/routes/api/books.py b/src/http_app/routes/api/books.py index a2de2788..79821d42 100644 --- a/src/http_app/routes/api/books.py +++ b/src/http_app/routes/api/books.py @@ -1,5 +1,7 @@ +from typing import Iterable + from fastapi import APIRouter, status -from pydantic import BaseModel, RootModel, ConfigDict +from pydantic import BaseModel, ConfigDict from domains.books import BookService, dto @@ -20,23 +22,25 @@ class CreateBookResponse(BaseModel): ) -class ListBooksResponse(RootModel): - root: list[dto.Book] +class ListBooksResponse(BaseModel): + books: Iterable[dto.Book] model_config = ConfigDict( json_schema_extra={ - "example": - [ - { - "title": "The Hitchhiker's Guide to the Galaxy", - "author_name": "Douglas Adams", - "book_id": 123, - }, - { - "title": "Clean Architecture: A Craftsman's Guide to Software Structure and Design", - "author_name": "Robert C. 'Uncle Bob' Martin", - "book_id": 321, - }, - ] + "example": { + "books": [ + { + "title": "The Hitchhiker's Guide to the Galaxy", + "author_name": "Douglas Adams", + "book_id": 123, + }, + { + "title": "Clean Architecture: " + "A Craftsman's Guide to Software Structure and Design", + "author_name": "Robert C. 'Uncle Bob' Martin", + "book_id": 321, + }, + ] + } } ) @@ -65,11 +69,12 @@ class CreateBookRequest(BaseModel): into the format needed for the proper HTTP Response """ + @router_v1.get("/", status_code=status.HTTP_200_OK) async def list_books() -> ListBooksResponse: book_service = BookService() books = await book_service.list_books() - return ListBooksResponse(root=books) + return ListBooksResponse(books=books) @router_v1.post("/", status_code=status.HTTP_201_CREATED) diff --git a/tests/http_app/routes/books/test_list_books.py b/tests/http_app/routes/books/test_list_books.py index 7c00b8de..98ed07d8 100644 --- a/tests/http_app/routes/books/test_list_books.py +++ b/tests/http_app/routes/books/test_list_books.py @@ -4,10 +4,10 @@ async def test_list_books(testapp): ac = TestClient(app=testapp, base_url="http://test") - response = ac.get( - "/api/books/v1/" - ) + response = ac.get("/api/books/v1/") assert response.status_code == status.HTTP_200_OK - assert len(response.json()) == 1 - assert response.json()[0]['title'] == "The Shining" - assert response.json()[0]['author_name'] == "Stephen King" + body = response.json() + assert "books" in body + assert len(body["books"]) == 1 + assert body["books"][0]["title"] == "The Shining" + assert body["books"][0]["author_name"] == "Stephen King" From 158369f134875a6dd2a7cc7afa9218db75ff22d7 Mon Sep 17 00:00:00 2001 From: Valerio Sarasini Date: Tue, 7 Jan 2025 22:44:38 +0000 Subject: [PATCH 3/3] add uv installation step in readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 58d53843..5dadb4e1 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Create your GitHub repository using this template (The big green `Use this templ Optionally tweak name and authors in the `pyproject.toml` file, however the metadata are not used when building the application, nor are referenced anywhere in the code. +Before running any commands, install `uv`: + +- On Mac (using `brew`): `brew install uv` + Using Docker: * `make containers`: Build containers