Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/akotronis/booksapi

WINGS Assignment
https://github.com/akotronis/booksapi

asyncio docker flask flask-smorest jwt-authentication pytest python rest-api sqlalchemy

Last synced: 13 days ago
JSON representation

WINGS Assignment

Awesome Lists containing this project

README

        

# Flask BooksAPI

A CRUD rest api app for books fetched from [Openlibrary](https://openlibrary.org/developers/api)

# Implementation

## Models

Database models were created according to the logic indicated in the below diagram

[Database diagram](https://drawsql.app/teams/akotronis-team/diagrams/books)

(Folder **models**)

- `book`
- `author`
- `work`
- `book_author`
- `book_work`
- `user`

As indicated from openlibrary data,

- A **book** [may have](https://openlibrary.org/books/OL1017798M.json) multiple **authors** and belong to multiple **works**
- A **work** [may contain](https://openlibrary.org/works/OL45804W/Fantastic_Mr_Fox) multiple **books** and
- An **author** may have written multiple **books**

Implementation with [`flask-sqlalchemy`](https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/)

## Authentication

User authentication implemented with [`flask-jwt-extended`](https://flask-jwt-extended.readthedocs.io/en/stable/)

(Currently protected endpoint: `/books` (and `/v2/books`, see **versioning** below) → **GET**. Uncomment decorator `@jwt_required()` to protect others)

## Data transfer validation/(de)serialization

Implemented with [`marshmallow`](https://marshmallow.readthedocs.io/en/stable/)

(Folder **schemas**)

- `olib_book`
- `book`
- `author`
- `work`
- `book_author`
- `book_work`
- `user`

## Endpoints

(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:

### Books

- `/olib-books` → **GET**
- Fetch book codes defined in `book_codes.py` from open-library, extracts info, clean tables and populate database
- Accepts query `async=` based on which the get requests to open library are performed _sequentially_ (`async=False`) or _concurrently_ (`async=True`) using `requests` module in combination with `asyncio`. Second option is **significantly faster**.
- `/books/` → **GET, PUT, DELETE** a book with a specific `book_id`
- `/books` → **POST**
- Create a book with `{'code': , 'title': }`
- `/books` → **GET**
- Get list of all books in database. May filter results by adding query `contains=` (Filters by `` in `title`, case insensitive)
- `/books//authors/` → **POST**
- Link a book to an author. Add a row to `books_authors` table
- `/books//works/` → **POST**
- Link a book to a work. Add a row to `books_works` table

### Authors

- `/authors/` → **GET, PUT, DELETE** an author with a specific `author_id`
- `/authors` → **POST**
- Create an author with `{'code': }`
- `/authors` → **GET**
- Get list of all authors in database

### Works

- `/works/` → **GET, PUT, DELETE** a work with a specific `work_id`
- `/works` → **POST**
- Create a work with `{'code': }`
- `/works` → **GET**
- Get list of all works in database

### Users

- `/users/register` → **POST** register a user with `{'username': , 'password': }`
- `/users/login` → **POST** login a user with `{'username': , 'password': }`
- `/users/logout` → **POST** logout a user
- `/users/` → **GET, DELETE** a user by `user_id`

### Rules

- `/rules` → **GET** returns info about all available endpoints/methods and their versions

## Versioning

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

# Instructions (Run)

Make `.env` file as in `.env.example`

`WORK_ENV=prod/dev/test`. If empty, defaults to `dev`. See `config.py/run.py` for details.

## Run app outside Docker

`>>> python run.py`

## Build-Run the Dockerfile locally

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)

# Instructions (Test)

In root folder:

- `>>> pytest -s` to run all tests
- `>>> pytest --strict-markers -m -s` to run tests by marker, where `marker-name` s can be found in `pytest.ini` file

# TODO

- Implement versioning with Blueprints ✓
- Documentation with `flask-smorest/swagger`
- Use `flask-migrate` for migrations
- Use `gunicorn` as a server
- Unit testing ✓