Friday, February 7, 2020

REST on Python Flask for API's - 3

We put together a simple REST API application. Although it feeds of hard-coded data from inside, it is an independent service. Something like in a real-world scenario. We can certainly attach a data store to perform DB operations, but that's for another post. To run this service as an independent application, we need to wrap it in a Docker container. Let's do that now.


To run the application within Docker, use the following Dockerfile

Head over to the root directory of application and create a new ".Dockerfile". Add the following contents to it

As you can see from the above file, we use "ubuntu". If that image is too big, you can certainly try something like "ArchLinux"

FROM ubuntu
LABEL MAINTANER="Krishnan Sriram"
RUN apt-get update -y && \
    apt-get install -y python-pip python-dev
# We copy just the requirements.txt first to leverage Docker cache
COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /app
ENTRYPOINT [ "python" ]

CMD [ "run.py" ]

To build this image, save it as ".Dockerfile" and run the following command

Change "flask-ubuntu" to name of your choice. I prefer to use a proper version, like below

docker build -t flask-ubuntu:1.0 .

After the build is successful, you can run it with the following command

docker run -d -p 500:5000 flask-ubuntu/1.0

If there are issues with your container instance, you can always go on to interactive mode to see what's happening with the instance

docker run -it -p 5000:5000 flask-ubuntu:1.0 /bin/bash

It's super handy to have a docker cheat-sheet around. If you can't find one, I'd say this is a good reference .



Our next objective will be to build and connect to a simple DB like Mongo or Postgres. We can try that on top of a simple, small but powerful ORM tool like SQLAlchemy.

REST on Python Flask for API's - 2

A simple REST API

Following up from our previous exercise on how to build a API with Flask, let's try to put together a simple REST API solution

We start by putting together a simple high-level structure
  • mkdir restapi && cd restapi
  • virtualenv venv
  • souce venv/bin/activate
  • mkdir api
  • touch app_namespace.py Dockerfile requirements.txt run.py
  • touch api/__init__.py api/player.py api/country.py api/health.py
  • pip install flask
  • pip install flask_restplus
Phew.....A lot of steps.....


open restapi directory in your favorite IDE, open app_namespace.py and create a Flask object

from flask import Flask

app = Flask(__name__)                  #  Create a Flask WSGI application

open api/__init__.py

from flask_restplus import Api
from app_namespace import app


api = Api(app)                         #  Create a Flask-RESTPlus API

What is __init__.py?

__init_.py is the first file to get executed from this directory. Think of it like a constructor to this file structure. Every directory can have a __init__.py. Before first execution of files in that directory init will get executed. In our case, we have 3 files in api directory - health, country and players. Before any invocation of api/health or api/players or api/country __init__.py will be called. All initialization common to files in this directory can/should be placed here.

open api/health.py

from flask_restplus import Resource
from api import api


ns = api.namespace('health', description='Operations related to players')


@ns.route('')                   #  Create a URL route to this resource
class Health(Resource):            #  Create a RESTful resource
    def get(self):                     #  Create GET endpoint
        return {'message': 'success'}

All that we have done is created a simple class that is derived from "Resource" and implement "get" method. If you are not familiar with Python classes, take a quick tour from here - https://realpython.com/python3-object-oriented-programming/. "get" method returns a simple JSON. If we need to return a complex JSON object, we could use something like "jsonify". We'll be using it down the line. If you are wondering why we don't have @ns.route('/health'). Reason we just have root ('/') here is because, "health namespace" is registered with "/health" path. Any route that comes from health namespace will have 'health' attached to it. That explains empty string on class Health
open api/player.py

from flask_restplus import Resource
from api import api
from flask import jsonify, request

ns = api.namespace('players', description='Operations related to players')


@ns.route('/')
class PlayerCollection(Resource):
    players = [{
        "id": 1,
        "name": "KL Rahul",
        "age": 24
    },
        {
            "id": 2,
            "name": "Shikar Dhawan",
            "age": 28
        },
        {
            "id": 3,
            "name": "Rohit Sharma",
            "age": 32
        },
        {
            "id": 4,
            "name": "Virat Kohli",
            "age": 31
        }
    ]

    def get(self):
        """Returns list of players."""
        return jsonify(self.players)

    @api.response(201, 'Player successfully created.')
    def post(self):
        """Add new player to player's collection."""
        self.players.append(request.json)
        return None, 201


@ns.route('/')
@api.response(404, 'Player not found.')
class Player(Resource):            #  Create a RESTful resource
    def get(self, id):                     #  Create GET endpoint
        pl_collection = PlayerCollection()
        for player in pl_collection.players:
            if player["id"] == id:
                return jsonify(player)

"get" and "post" carry same endpoint. But when we look at capability to locate a player by ID or name, then /health alone does not suffice. we need additional parameters to URL. This could be ID or name, like https:///api/player/ or https:///api/player/

Finally, open api/country.py. You see the process is the same. Feel free to do something same like "player" endpoint. Create a simple data structure to store countries and capital. Create "get" and "post" for root endpoint and a new class to allow for search and updates.

from flask_restplus import Resource
from api import api
ns = api.namespace('country', description='Operations related to players')

@ns.route('')                   #  Create a URL route to this resource
class Country(Resource):            #  Create a RESTful resource
    def get(self):                     #  Create GET endpoint
        return {'hello': 'country'}

All this is fine. Unless we put the endpoints together we are not going to have a FLASK solution. This is what happens in run.py. We import all namespaces from respective api's, initialize application and run it.

Blueprint is a library as a part of flask that allows for Swagger like documentation. Every endpoint will get a mention in documentation. You'll notice it when you run the application. This allows for easier API consumption from other divisions in enterprise.  You can read more about it here - https://flask.palletsprojects.com/en/1.1.x/blueprints/

Note: Application by default runs on port 5000. If you wish to run it on any other port, override run method like below

app.run(debug=True, host='0.0.0.0', port=6000)

open /run.py
from flask import Blueprint
from app_namespace import app
from api import api
from api.player import ns as player_namespace
from api.country import ns as country_namespace
from api.health import ns as health_namespace



def initialize_app(flask_app):
    # configure_app(flask_app)
    blueprint = Blueprint('api', __name__, url_prefix='/api')
    api.init_app(blueprint)
    api.add_namespace(health_namespace)
    api.add_namespace(player_namespace)
    api.add_namespace(country_namespace)
    flask_app.register_blueprint(blueprint)

if __name__ == '__main__':
    initialize_app(app)
    app.run(debug=True, host='0.0.0.0')


Next step is to add docker capabilities and run this simple Flask solution in a container. Lets see that in NEXT part

REST on Python Flask for API's - 1


Python has been ranked as #1 language for many years. Both in terms of job opportunities and in terms of capabilities, Python has continuously proven it's worthy a language to invest on. Although I personally prefer a pollyglot solution for enterprise, I understand the problems a polyglot person like me brings to table - Top on the list techies, tools, security, libraries/frameworks, IDE and so forth.

If you are invested into Python, no fret. You can use Flask to build REST API's and then architect your microservice layer on top of it. You certainly don't need Golang, NodeJS or Java for that.


What is Flask?

Flask is a web framework. This means flask provides you with tools, libraries and technologies that allow you to build a web application. This web application can be some web pages, a blog, a wiki or go as big as a web-based calendar application or a commercial website.
Flask is part of the categories of the micro-framework. Micro-framework are normally framework with little to no dependencies to external libraries. This is what makes Flask a great solution for Micro-services

Python 3.x for us

We'll use 3.7 and above. The language is mostly the same as Python 2.7, but many details, especially how built-in objects like dictionaries and strings work, have changed considerably, and a lot of deprecated features have finally been removed. Also, the standard library has been reorganized in a few prominent places. Here are some major changes in Python3, we should be aware of
1. Views and Iterators Instead Of Lists
2. Introducing "Classes"
3. String formatting
4. Better exception handling
5. Finally, better overall performance


What is Virtualenv?

virtualenv is a tool to create isolated Python environments. Since Python 3.3, a subset of it has been integrated into the standard library under the venv module. Note though, that the venv module does not offer all features of this library (e.g. cannot create bootstrap scripts, cannot create virtual environments for other python versions than the host python, not relocatable, etc.). Tools in general as such still may prefer using virtualenv for its ease of upgrading (via pip), unified handling of different Python versions and some more advanced features.

Why virtualenv?

The basic problem being addressed is one of dependencies and versions, and indirectly permissions. Imagine you have an application that needs version 1 of LibFoo, but another application requires version 2. How can you use both these applications? If you install everything into /usr/lib/python2.7/site-packages (or whatever your platform’s standard location is), it’s easy to end up in a situation where you unintentionally upgrade an application that shouldn’t be upgraded.

Or more generally, what if you want to install an application and leave it be? If an application works, any change in its libraries or the versions of those libraries can break the application.

Also, what if you can’t install packages into the global site-packages directory? For instance, on a shared host.

In all these cases, virtualenv can help you. It creates an environment that has its own installation directories, that doesn’t share libraries with other virtualenv environments (and optionally doesn’t access the globally installed libraries either).


PIP should be installed first. If you have not done that, check here

virtialenv Installation

pip install virtualenv

A simple Flask solution

Let's build a simple Flask application that will serve just one route
Open your terminal or command prompt and get started

  • mkdir flask-by-example && cd flask-by-example
  • virtualenv venv
  • source venv/bin/activate
  • pip install Flask
  • open VSCode/SublimeText/PyCharm/Atom or any editor of your choice and create the following files
    • app.py
    • requirements.txt
  • open app.py and stick the following code

from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello():
    return "Hello World!"

@app.route('/')
def hello_name(name):
 return "Hello {}!".format(name)

if __name__ == '__main__':
    app.run()

Run the application

python app.py

You have the application running. Open browser and check to see if all work’s fine for you (http://127.0.0.1:5000/)

To know all libraries used in this application, open terminal and type following command

pip freeze > requirements.txt
T
O       Open requirements.txt and check the list of dependencies your project has. Next time you start on flask project all that you need to do is

pip install -r requirements.txt

         All your dependencies are installed from a single line. Things can't be easier and cleaner than this, can it?

As we add more modules to our application, we need to get data and other configurations from environment. During initial phase of the project most developers start to hard code these into application. It becomes a challenge to identify and remove them at a later point. Easier route is to start by setting up configuration and dependencies the right way

      Create a simple file config.py and load it with the following content 

import os
basedir = os.path.abspath(os.path.dirname(__file__))


class Config(object):
    DEBUG = False
    TESTING = False
    CSRF_ENABLED = True
    SECRET_KEY = 'this-really-needs-to-be-changed'


class ProductionConfig(Config):
    DEBUG = False


class StagingConfig(Config):
    DEVELOPMENT = True
    DEBUG = True


class DevelopmentConfig(Config):
    DEVELOPMENT = True
    DEBUG = True


class TestingConfig(Config):
    TESTING = True

As you see from the code, we create constants in config that will later be consumed from application. This is by far a better choice to take and remove some simple errors that emerge out of application in later stage. Now the next question is how to update it as env variables and how to consume it from application?



Go back to terminal and install another package

pip install autoenv

sa    Create a .env file and add the following content

export APP_SETTINGS="config.DevelopmentConfig"

      From within app.py do the following

from flask import Flask

app = Flask(__name__)
my_env = app.config.from_object(os.environ['APP_SETTINGS'])

@app.route('/')
def hello():
    return "Hello World! in {0}".format(my_env)

.......
.......    


      Now, you know how to handle all those settings you need in the application like DB String, communication with other services, location of MQ's, log files and much more.




       Next up, let's build a a simple REST API solution