Froggy - The Python REST Framework
A keep it simple, stupid (KISS) framework for the development of REST-based microservices.
In order to use froggy:frog: on your projects, you can install it with pip3
:
git clone https://github.com/tiagomiguelcs/froggy.git
cd froggy
pip3 install .
sudo npm install -g apidoc
or
pip3 install git+https://github.com/tiagomiguelcs/froggy.git
sudo npm install -g apidoc
You can find the documentation at the following link.
A minimal restful API can be implemented using froggy's:frog: Framework
class.
# minimal.py
from flask import Flask, request
import froggy
from froggy.framework import Framework
app = Flask(__name__)
framework = Framework(app)
@framework.frogify('/hello_world', methods=['GET'])
def hello_world():
return 'Hello, World!'
export FLASK_APP=minimal.py
flask run
Several 'recipes' for the implementation of REST-based Flask Services using Froggy:frog: can be found below:
- Using Froggy's Exceptions Handling API
- Using Froggy's Response Wrapper
- Using Froggy's Database API
- Using Froggy's File Handling API
- Building an Authentication API
Froggy:frog: provides an exception handling API that auto returns a response object with details regarding an exception that was raisen.
# exception.py
import os
from flask import Flask, request
from froggy.framework import Framework
from froggy.exceptions import BadRequest
app = Flask(__name__)
# The value of key "docs" defines the path where the documentation
# should be stored by froggy's apiDoc wrapper.
settings = {"docs": os.getcwd()}
framework = Framework(app, settings)
@framework.frogify('/divide/<int:x>/<int:y>', methods=['GET'])
def divide(x, y):
try:
result = x / y
return framework.response(result)
except Exception as e:
raise BadRequest(path=request.path, error=str(e))
A response JSON Object can be returned by a service with a single line of code. Froggy:frog: response wrapper uses Flask's make_response
and jsonify
methods.
# response.py
import os
from flask import Flask, request
from froggy.framework import Framework
app = Flask(__name__)
# The value of key "docs" defines the path where the documentation
# should be stored by froggy's apiDoc wrapper.
settings = {"docs": os.getcwd()}
framework = Framework(app, settings)
@framework.frogify('/hello/<name>', methods=['GET'])
def hello(name):
return framework.response("Hello,"+name)
You can easly modify data and table structures using froggy's:frog: SQLite3 - MySQL is also supported - wrapper that takes care of exceptions, cursor handling, and results formating:
# sqlite3.py
import sqlite3, os
from flask import Flask, request
from froggy.framework import Framework
from froggy.database import Type
from froggy.database import Query
app = Flask(__name__)
framework = Framework(app)
@framework.frogify('/sqlite3/example', methods=['GET'])
# curl -d "email='kermit@muppets.com&psw=123" -X POST http://localhost:5000/auth/signup
def example():
conn = sqlite3.connect('example.db')
query = Query(conn, Type.Sqlite3)
query.execute("CREATE TABLE User (id INTEGER PRIMARY KEY AUTOINCREMENT, email text UNIQUE)")
query.execute("INSERT INTO User (email) VALUES (?)", ("kermit@muppets.com",))
""" A dictionary, or a list of dics, will be returned with the
resulting data of the SELECT statement for user 'kermit'.
"""
results = query.execute("SELECT * FROM User")
conn.close()
return(framework.response(results))
The returned JSON response object:
{
"froggy": "0.0.1",
"status": 200,
"data": {
"email": "kermit@muppets.com",
"id": 1
}
}
Froggy can also help you to build a file handling system by wrapping several Flask
and Python.os
methods to handle files.
In order to upload a file a public or static folder must exist on the root of the project. The service to upload a file can be implemented as follows:
# upload_file.py
UPLOAD_DIR = os.path.join(os.getcwd(),"public")
@framework.frogify('/cookbook/files/upload', methods=['POST'])
def upload():
file = froggy.files.File(request.files['file'], UPLOAD_DIR, {"txt", "md"})
if file.upload(): return(framework.response())
You can test the upload service using the following curl command:
curl -X POST --form file=@sample.txt http://localhost:5000/cookbook/files/upload
The following service can be implemented to retrieve a file from a public or static folder:
# get_file.py
UPLOAD_DIR = os.path.join(os.getcwd(),"public")
@framework.frogify('/cookbook/files/get/<path:filename>', methods=['GET'])
def get_file(filename):
return(froggy.files.File.get(UPLOAD_DIR, filename, True))
The following service can be implemented to retrieve the list of files available on a public or static folder:
# get_files.py
UPLOAD_DIR = os.path.join(os.getcwd(),"public")
@framework.frogify('/cookbook/files', methods=['GET'])
def get_files(filename):
return(framework.response(froggy.files.File.list(UPLOAD_DIR)))
The following service can be implemented to remove a file from a public or static folder:
# remove_file.py
UPLOAD_DIR = os.path.join(os.getcwd(),"public")
@framework.frogify('/cookbook/files/<string:filename>', methods=['DELETE'])
def remove_file(filename):
if (froggy.files.File.remove(UPLOAD_DIR, filename)): return(framework.response())
An authentication API should include services to signup users, perform server-side authentication and logout procedures. These services can be easly implemented using froggy's:frog: implementations of JWT and SQLite3:
# auth.py
import sqlite3, froggy
from flask import Flask, request
from froggy.framework import Framework
from froggy.authentication import JWTAuth
from froggy.database import Query
from froggy.exceptions import BadRequest
app = Flask(__name__)
settings = {"authentication": {"type": JWTAuth, "jwt_secret_token": "'Nobody-Calls-Me-Chicken'",
"jwt_expiration_seconds": 86400}}
framework = Framework(app, settings)
@framework.frogify('/auth/signup', methods=['POST'])
def signup():
# On a production env do NOT POST your password without SSL enabled.
email = request.form['email']
psw = request.form['psw']
# Hashing the password using the function available on the authentication module.
hashed_psw = framework.auth.hash_password(psw)
conn = sqlite3.connect('auth.db')
query = froggy.database.Query(conn, froggy.database.Type.Sqlite3)
try:
query.execute("CREATE TABLE User (id INTEGER PRIMARY KEY AUTOINCREMENT, email text UNIQUE, psw text)")
query.execute("INSERT INTO User (email, psw) VALUES (?, ?)", (email, hashed_psw))
except: pass
conn.close()
return(framework.response())
@framework.frogify('/auth/login', methods=['POST'])
def do_login():
# On a production env do NOT POST your password without SSL enabled.
email = request.form['email']
psw = request.form['psw']
conn = sqlite3.connect('auth.db')
query = froggy.database.Query(conn, froggy.database.Type.Sqlite3)
user = query.execute("SELECT id, email, psw FROM user WHERE email=?", (email,))
# Raise exception if the user is not found
if (not bool(user)):
raise BadRequest(path=request.path,message="Frog not Found", error="Unknown User", status=403)
database_stored_hashed_password = user['psw']
del user['psw']
# Perfom authentication using froggy's authentication API
framework.auth.authenticate(user, email, database_stored_hashed_password, psw)
# Return user info, including the JWT.
return(framework.response(user))
@framework.frogify('/auth/logout', methods=['GET'])
def do_logout():
# In order to properly logout when calling this service you need to
# define the authorization header with the user JWT.
return(framework.auth.hop_out(request))
To test your newly implemented services you can run the following curl commands:
curl -d "email='kermit@muppets.com&psw=123" -X POST http://localhost:5000/auth/signup
curl -d "email='kermit@muppets.com&psw=123" -X POST http://localhost:5000/auth/login
curl --request GET http://localhost:5000/auth/logout -H 'Authorization: <Replace-With-User-JWT>'
On a production environment, please, do not post your password without SSL enabled.
Froggy:frog: can also help you out to implement services that are only accessible by authorized users with a valid JWT (i.e., with an active login session):
# secure_user.py
# Only authenticated users can access this service.
from flask import Flask, request
import froggy
from froggy.framework import Framework
app = Flask(__name__)
framework = Framework(app)
@framework.frogify('/auth/fort_knox', authorization=True, methods=['GET'])
def fort_knox():
return(framework.response(data={"message": "Secure service only accessible by authorized users with a valid JWT."}))
This service can be tested using the following curl command:
curl -H "Authorization: <Replace-With-User-JWT>" http://localhost:5000/auth/fort_knox
Froggy:frog: can also help you out to implement services that are only accessible by authorized users with a valid JWT and beloging to a list of groups.
# secure_group.py
# Only users that belong to group 1 and/or 2 can access this service.
@framework.frogify('/auth/secure', groups=[1,2], authorization=True, methods=['GET'])
def fort_knox():
# The user JSON Object encoded in the JWT should contain a list of groups of the user.
return(framework.response(data={"message": "this is a secure service only accessible by authorized users."}))
A service can be easly documented using froggy's:frog: apiDoc wrapper.
- Create a file called
apidoc.json
on the root of the project as follows:
{
"name": "Documentation Demo",
"version": "1.0.0",
"description": "Description.",
"title": "Demo",
"sampleUrl": "http://localhost:5000"
}
- Create the main froggy application and add some API annotations to a service:
# main.py
import os
from flask import Flask, request
from froggy.framework import Framework
app = Flask(__name__)
# The value of key "docs" defines the path where the documentation
# should be stored by froggy's apiDoc wrapper.
settings = {"docs": os.getcwd()}
framework = Framework(app, settings)
@framework.frogify('/hello/<name>', methods=['GET'])
def hello(name):
"""
@api {get} /hello/:name Hello World
@apiName demo
@apiDescription Example of a simple 'Hello World' service.
@apiGroup Generic
@apiParam {String} name Name of the guest.
@apiSuccess {String} message The Hello World Message.
@apiExample {curl} Example usage:
curl -i http://localhost:5000/hello/Anna
"""
return framework.response("Hello,"+name)
- You're done:thumbsup:! You can now access the documentation created under the root of the project.
Be free to fork away to add or update Froggy's APIs. Below you can find some guidelines to start.
- Install Froggy and apiDoc system wide using
pip
andnpm
(alternatively, a virtual environment can also be used):
git clone https://github.com/tiagomiguelcs/froggy.git
cd froggy
pip3 install .
sudo npm install -g apidoc
After you have implemented your new awesome features, you can update the froggy package as follows:
pip3 install --upgrade .
It is highly recommended to use Sphinx docstring format to document newly implemented functions, methods, or classes. The documentation can then later be compiled using the following set of commands:
cd docs
make html
- Create a demo application to test the newly added features:
mkdir demo
cd demo
touch demo.py
python3 -m venv venv
source venv/bin/activate
export FLASK_APP=demo.py
flask run
Found a bug or want a new feature ? Please let us know by submitting an issue.