A CRUD rest api app for books fetched from Openlibrary
Database models were created according to the logic indicated in the below diagram
(Folder models)
book
author
work
book_author
book_work
user
As indicated from openlibrary data,
- A book may have multiple authors and belong to multiple works
- A work may contain multiple books and
- An author may have written multiple books
Implementation with flask-sqlalchemy
User authentication implemented with flask-jwt-extended
(Currently protected endpoint: /books
(and /v2/books
, see versioning below) → GET. Uncomment decorator @jwt_required()
to protect others)
Implemented with marshmallow
(Folder schemas)
olib_book
book
author
work
book_author
book_work
user
(Folder resources)
olib_book
book
author
work
user
In openlib there are books without registered authors, which suggests that authors should be handled by separate endpoints, decoupled from book endpoints implementation.
So the endpoints are the below:
/olib-books
→ GET- Fetch book codes defined in
book_codes.py
from open-library, extracts info, clean tables and populate database - Accepts query
async=<bool>
based on which the get requests to open library are performed sequentially (async=False
) or concurrently (async=True
) usingrequests
module in combination withasyncio
. Second option is significantly faster.
- Fetch book codes defined in
/books/<book_id>
→ GET, PUT, DELETE a book with a specificbook_id
/books
→ POST- Create a book with
{'code': <book_code_str>, 'title': <book_title_str>}
- Create a book with
/books
→ GET- Get list of all books in database. May filter results by adding query
contains=<string>
(Filters by<string>
intitle
, case insensitive)
- Get list of all books in database. May filter results by adding query
/books/<book_id>/authors/<author_id>
→ POST- Link a book to an author. Add a row to
books_authors
table
- Link a book to an author. Add a row to
/books/<book_id>/works/<work_id>
→ POST- Link a book to a work. Add a row to
books_works
table
- Link a book to a work. Add a row to
/authors/<author_id>
→ GET, PUT, DELETE an author with a specificauthor_id
/authors
→ POST- Create an author with
{'code': <author_code_str>}
- Create an author with
/authors
→ GET- Get list of all authors in database
/works/<work_id>
→ GET, PUT, DELETE a work with a specificwork_id
/works
→ POST- Create a work with
{'code': <work_code_str>}
- Create a work with
/works
→ GET- Get list of all works in database
/users/register
→ POST register a user with{'username': <username>, 'password': <password>}
/users/login
→ POST login a user with{'username': <username>, 'password': <password>}
/users/logout
→ POST logout a user/users/<user_id>
→ GET, DELETE a user byuser_id
/rules
→ GET returns info about all available endpoints/methods and their versions
Versioning with Blueprints is implemented in file versioning.py
- Endpoints with prefix
/v{i}
are exposed for all resources/methods where{i}
corresponds to the implemented resources/methods versions - Endpoints without
/v{i}
prefix correspond to the latest implemented versions - Implemented versions are:
v1, v2
for/books
→ GET (so we have/v1/books
→ GET | unprotected, and/v2/books
(=/books
) → GET | protected)v1
for all other resources/methods
/rules
has no versions
Make .env
file as in .env.example
WORK_ENV=prod/dev/test
. If empty, defaults to dev
. See config.py/run.py
for details.
>>> python run.py
With Docker (Desktop) installed, on the folder where Dockerfile
is:
>>> docker build -t books-api-image .
>>> docker run --rm -it --name books-api-container -p 5000:5000 -w /app -v ${PWD}:/app books-api-image
(Powershell) OR>>> docker run --rm -it --name books-api-container -p 5000:5000 -w /app -v "%cd%":/app books-api-image
(Terminal)
In root folder:
>>> pytest -s
to run all tests>>> pytest --strict-markers -m <marker-name> -s
to run tests by marker, wheremarker-name
s can be found inpytest.ini
file
- Implement versioning with Blueprints ✓
- Documentation with
flask-smorest/swagger
- Use
flask-migrate
for migrations - Use
gunicorn
as a server - Unit testing ✓