Skip to content

An example online store REST API using flask, SQL ORM and OpenAPI Specifications (with WIP Vue.js frontend)

License

Notifications You must be signed in to change notification settings

Liam-Deacon/online-store-rest-api

Repository files navigation

Online Store Example

   GitHub pull-requests closed Python CI Frontend Docker CI/CD Backend Docker CI/CD Sphinx Documentation CI

Components 📲🧩

There is a Vue.js based front-end for displaying reactive web pages to the user and a Python Flask backend for serving the online store REST API.

Running The Node.JS Frontend 🏃‍♀️☕📜

The frontend can be started with the following command:

$ npm run frontend:start  # starts Vue.js app at http://localhost:8080

Note that the frontend can be found under online_store/frontend (WIP).

Running The Python Backend 🏃‍♂️🐍🔚

The backend can be started with the following command:

$ export FLASK_DEBUG=1  # for development with live reload
$ export FLASK_ENVIRONMENT=development  # this is the default
$ PYTHONPATH='.' python3 manage.py run

Background 📖

This repository was created to solve the Prezola Technical Challenge, which requires a solution capable of adding, removing and listing (added) gifts from a list. It also required a mechanism to purchase a gift from the list and generate a report (of purchased vs non-purchased) gifts.

This code project takes that concept and extends it to a generic (gift) store.

The challenge

"Write a program to the best of your knowledge which will allow the user to manage a single list of wedding gifts."

The user must be able to:

  • Add a gift to the list
  • Remove a gift from the list
  • List the already added gifts of the list
  • Purchase a gift from the list
  • Generate a report from the list which will print out gifts & their statuses.
    • The report must include two sections:
      • Purchased gifts: each purchased gift with their details.
      • Not purchased gifts: each available gift with their details.

Implementation Notes 📄

There are two concrete implementations for realising a gift list with the following classes from online_store/backend/gift_list.py:

  • BasicGiftList, a pure python implementation of a gift list (Well Tested).
  • SqlDatabaseGiftList, an SQL ORM based implementation of a gift list for use within a flask (or Django) REST API app. In this example, the ORM models are found in online_store/backend/models/ and the REST API is implemented in online_store/backend/routes/gifts.py.

Development Setup ⚙️

$ npm install  # needed for Swagger JSON to OAS YAML spec conversion
$ npm run frontend:install  # install packages needed for frontend app
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ pip install setuptools
(venv) $ pip install -r requirements-dev.txt -r requirements-test.txt
(venv) $ pip install -r requirements.txt

Testing 🧪

setup.cfg has configured pytest to collect coverage information and can be run as follows:

(venv) $ PYTHONPATH='.' py.test 

Basic Gift List Implementation 🎁

The following showcases a simple python implementation of the gift list:

# load store data
>>> import json
>>> store_items = json.load(open('products.json'))
# import implementation
>>> from online_store.backend.gift_list import BasicGiftList
# create a new gift list
>>> gift_list = BasicGiftList('Liam')
>>> gift_list  # show list representation in interpreter
Liam -> []
# add gift item
>>> gift_list.add_item(store_items[0])
>>> gift_list
Liam -> [{'id': 1, 'name': 'Tea pot', 'brand': 'Le Creuset', 'price': '47.00GBP', 'in_stock_quantity': 50}]
# remove gift item
>>> gift_list.add_item(store_items[1], quantity=3)
>>> gift_list.remove_item(store_items[0])
>>> gift_list
Liam -> [{'id': 2, 'name': 'Cast Iron Oval Casserole - 25cm; Volcanic', 'brand': 'Le Creuset', 'price': '210.00GBP', 'in_stock_quantity': 27}]
# purchase gift item
>>> gift_list.purchase_item(store_items[1], quantity=1)
# generate report
>>> gift_list.create_report()
Gift List Report for Liam:
==============================
Purchased items:
   - {'id': 2, 'name': 'Cast Iron Oval Casserole - 25cm; Volcanic', 'brand': 'Le Creuset', 'price': '210.00GBP', 'in_stock_quantity': 27} (quantity: 1)
------------------------------
Available items:
  - {'id': 2, 'name': 'Cast Iron Oval Casserole - 25cm; Volcanic', 'brand': 'Le Creuset', 'price': '210.00GBP', 'in_stock_quantity': 27} (quantity: 2)

Flask REST API + SQL ORM Implementation 🌍🕸️

Alternatively there is a REST API, which can be run with:

$ source venv/bin/activate
(venv) $ cd online_store
(venv) $ FLASK_DEBUG=1 flask run

This will start a flask development server running on http://localhost:5000 and provides a Swagger-UI at http://localhost:5000/apidocs/.

Examples

# register a new user
$ curl -X POST -H "Content-Type: application/json" -d '{"username": "me", "password": "test", "email": "me@test.com"}' 'http://localhost:5000/api/v1/auth/register'

{"code":200,"msg":"success","status":"ok"}

# login with user
$ curl -X POST -H "Content-Type: application/json" -d '{"username": "me", "password": "test"}'

{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MDIwMTIxMzQsIm5iZiI6MTYwMjAxMjEzNCwianRpIjoiOTA5NTRlZDAtZWIzMy00MTY2LThhODEtZDE5NDI3MjI5NTE0IiwiZXhwIjoxNjAyMDEzMDM0LCJpZGVudGl0eSI6Im1lIiwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.GYVTQK4Xw9JaiJJxa75vlKBS-mho0QjfcM94usPZtSI","code":200,"status":"ok"}

# note access token
$ export TOKEN='<JWT_FROM_LOGIN>'

# access gift list
$ curl -X GET -H  "accept: application/json" -H  "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/gifts/list

[]

# add one gift item with store id of 1 to list
$ curl -X POST -H  "accept: application/json" -H  "Authorization: Bearer $TOKEN" 'http://localhost:5000/api/v1/gifts/list/add?item_id=1&quantity=1'

{
  "code": 200,
  "msg": "Item added",
  "status": "ok"
}

# remove item with id 1 from gift list
$ curl -X DELETE -H  "accept: application/json" -H  "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/gifts/list/1

{
  "code": 200,
  "msg": "Item removed",
  "status": "ok"
}

# add then purchase item
$ curl -X POST -H  "accept: application/json" -H  "Authorization: Bearer $TOKEN" 'http://localhost:5000/api/v1/gifts/list/add?item_id=2&quantity=2'

{
  "code": 200,
  "msg": "Item added",
  "status": "ok"
}

$ curl -X POST -H  "accept: application/json" -H  "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/gifts/list/2/purchase?quantity=1

{
  "code": 200, 
  "msg": "gift purchased", 
  "status": "ok"
}

# produce report
$ curl -X GET -H  "accept: application/json" -H  "Authorization: Bearer $TOKEN" 'http://localhost:5000/api/v1/gifts/list/report'

{
  "available": [
    {
      "brand": "Le Creuset", 
      "currency": "GBP", 
      "id": 2, 
      "in_stock_quantity": 27, 
      "name": "Cast Iron Oval Casserole - 25cm; Volcanic", 
      "price": 210.0, 
      "quantity": 1
    }
  ], 
  "purchased": [
    {
      "brand": "Le Creuset", 
      "currency": "GBP", 
      "id": 2, 
      "in_stock_quantity": 27, 
      "name": "Cast Iron Oval Casserole - 25cm; Volcanic", 
      "price": 210.0, 
      "quantity": 1
    }
  ], 
  "user": 2
}

Bonus Features ✨

There are currently a number of extra features, which help

  • User-friendly backend application logging using loguru python package.
  • Simple containerisation using Docker - see DockerFile
  • Authentication using JSON web tokens via flask-jwt-extended middleware.
  • OpenAPI Specification (OAS) conformant client documentation generated using flasgger and viewable via SwaggerUI /apidocs endpoint when running the flask server.
  • Persistent data storage using SQL Database modelled using sqlalchemy ORM.

Developer Documentation 📗

The Sphinx documentation builder is currently used to extract python docstrings and the OpenAPI spec of the REST API.

To build the documentation:

$ cd docs/
$ make openapi_spec.yml
$ make html  # or another end documentation format e.g. epub

A live deployment to GitHub Pages can be found at https://liam-deacon.github.io/online-store-rest-api/

TODO 📝

  • Build script / CI using GitHub Actions
  • Sphinx documentation support
  • Deploy API documentation to GitHub Pages via Actions
  • Test SqlDatabaseGiftList
  • Automated tests for REST API
  • shields.io support for README badges
  • Add linting to CI
  • Build and deploy docker image(s) to dockerhub via CI/CD
  • Deploy flask app to Heroku for demo purposes (follow link)

Future Improvements 🔮

Given more time, the following improvements could be made:

  • Write frontend in React (or maybe Vue.js)
  • Create docker-compose.yml multi-container Docker compose script for orchestrating frontend, backend and database (e.g. React/NPM-based, Flask, Postgres).
  • Implement missing features in the code (i.e. wherever NotImplementError is raised)
  • Code tidy and refactor
  • Increase overall code coverage (aiming for nirvana at 100%)