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)
bookauthorworkbook_authorbook_workuser
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_bookbookauthorworkbook_authorbook_workuser
(Folder resources)
olib_bookbookauthorworkuser
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.pyfrom 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) usingrequestsmodule 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_authorstable
- 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_workstable
- 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, v2for/books→ GET (so we have/v1/books→ GET | unprotected, and/v2/books(=/books) → GET | protected)v1for all other resources/methods
/ruleshas 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 -sto run all tests>>> pytest --strict-markers -m <marker-name> -sto run tests by marker, wheremarker-names can be found inpytest.inifile
- Implement versioning with Blueprints ✓
- Documentation with
flask-smorest/swagger - Use
flask-migratefor migrations - Use
gunicornas a server - Unit testing ✓