Download a Flask template ready to plug in your business logic | by Anirudh Lohia | Towards Data Science

Let’s assume after lots of hard work you have your machine learning model running the way it should. This model could be one which responds to a user’s request to classify a tweet sentiment or identify objects in an image or recommend a product or some other algorithm unique to your needs. You would now like to quickly deploy this model. The article below is an explanation of the template that I have created to get you up and running quickly.
Flask micro service is an easy way to deploy applications. However, one soon realises that you do not want to create a large monolithic single file program. The program should be modular to support future enhancements and it should be configurable with help of a configuration file. These configuration parameters are not only for Flask but also for your business application program (remember the tweet classification application you have created). Ideally the server should appropriately log everything. Most applications also need to connect to a database and finally, the inbound request parameters needs to be validated before invoking the model api (or return error).
Sounds quite a lot, right? It actually is quite a lot and one can easily spend quite some time building a robust framework providing these features. Here I provide a link to github that provides all the above mentioned features in a modular application.
You should be able to clone this Flask server template, and simply run it. Once you get this running you can start tweaking it to your specific requirements.
github.com
Here in this article, I shall only explain part of the where potentially changes are needed to customise this Flask template for your project. My underlying assumption is some level of familiarity with Flask, SQLAlchemy, Marshmallow. Just enough familiarity that if some part does not make sense it can be googled. Feel free to ask questions in the comments and I can answer/update the article to cover the points.
If you wish to install and run the server first, then skip this explanation section in middle and follow the sequence of commands below.
Let's start with the code organisation
Now the explanation of the code files
Below I shall explain the purpose of each file and the relevant portions of code which you may need to change (if any) as per your requirements.
base_dir/flask_server_template → The main template directory
flask_server_template/main.py
This is the entry point of the server. It reads the application configuration file (run_params.json), initialises logger, initialises flask server and runs the server.
### flask_server_template/main.py ###To use the template no changes are needed in this file.
flask_server_template/main_config.py
This module has does two things.
1. Read the application configuration file and creates a dictionary that can be used by another part of the application.
2. Creates a database connection string. This connection string utilised the parameters in the configuration file. This connection string varies as per the underlying database. SQLAlchemy documentation for creating your connection string is here.
### flask_server_template/main_config.py ###def get_db_con_str():    pwd = os.getenv('DB_PWD')  # get password from environment variable DB_PWD    db_con_str = f'postgresql://' \                 f'{run_conf_data["DB_USER"]}:' \                 f'{pwd}@' \                 f'{run_conf_data["DB_SERVER"]}:' \                 f'{run_conf_data["DB_PORT"]}/' \                 f'{run_conf_data["DB_NAME"]}'    os.environ['SQLALCHEMY_DATABASE_URI'] = db_con_str    return db_con_str
flask_server_template/config → Here are configuration files for the logger and the business logic application
config/logger.conf
There are two logging handlers. ‘handler_consoleHandler’ logs to screen while ‘handler_fileHandler’ into a file run_log.log. This file gets overwritten each time server is run. However, if you want to change this and create new file with timestamp you can uncomment the second line and comment the first one
### config/logger.conf ###......[handler_fileHandler]class=FileHandlerlevel=DEBUGformatter=simpleFormatterargs=('logs/run_log.log','w')#args=('../logs/' + time.strftime('%%Y%%m%%d_%%HH%%MM') +'.log','a')[handler_consoleHandler]class=StreamHandlerlevel=INFOformatter=simpleFormatterargs=(sys.stdout,)......
config/run_params.json
‘run_params’ has application level configuration containing information like database server, user name, mode of running flask (development/production)
### config/run_params.json ###{  "LOGGER_CONFIG": "config/logger.conf",  "FLASK_CONFIG": "flask_config.DevelopmentConfig",  "DB_SERVER": "localhost",  "DB_PORT": "5432",  "DB_NAME": "dvdrental",  "DB_USER": "dvdrental"}
flask_server_template/logs → The directory where run logs are created
To use the template no changes are needed in this directory.
flask_server_template/test → Your test scripts
test/flask_client.py
This template is a working template, ideally one should be able to simply download and run. The test client is to test these functionalities.
### test/flask_client.py ###To use the template no changes are needed in this file. New additional test cases can be written in this file or new test file created in this test folder.
flask_server_template/flask_server_template → The main flask directory containing flask initialisation code, configuration settings as well as redirection of routes of appropriate function.
flask_server_template/__init__.py
‘__init__.py’ contains the initialisation function of flask_server_template package. It has one main function ‘create_app’, which creates Flask object, sets Flask configuration, initialises the database and register the routes (URLs to their functions)
### flask_server_template/__init__.py ###To use the template no changes are needed in this file.
flask_server_template/flask_config.py
‘flask_config.py’ contains the configuration settings of flask. It provides general configurations for development as well as the production environment.
Most of the commonly used parameters have already been set. For more setting options have a look at the official documentation.
### flask_server_template/flask_config.py ###To use the template no changes are needed in this file.However, for quick reference, the default configurationsclass Config(object):    DEBUG = False    TESTING = False    CSRF_ENABLED = True    SECRET_KEY = 'this-really-needs-to-be-changed'    SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')    SQLALCHEMY_TRACK_MODIFICATIONS = False    SQLALCHEMY_ECHO = Falseclass ProductionConfig(Config):    DEBUG = Falseclass DevelopmentConfig(Config):    DEVELOPMENT = True    DEBUG = True
flask_server_template/flask_routes.py
‘flask_routes.py’ contains the mappings of the URLs to their corresponding implementation.
Note: There is a call that does the appropriate settings of the database. @routes.before_app_first_request. This is needed if we wish to use SQLAlchemy ORM feature. More details on this in the description of flask_server_template/db below.
For each REST API that needs to be catered to, there needs to be a mapping to a function. Sample of a single function mapping to URL is depicted here.
### flask_server_template/flask_routes.py ###......@routes.route('/module/v01/functions/film_info_orm', methods=['GET']) #TODO: change URLdef get_film_info_orm(): #TODO: change function name    try:        log.debug('inside get_film_info_orm') #TODO: change logging        film_df = bm.get_film_info_orm(request) #TODO: change business logic function here        response_code = 200        response_msg = film_df.to_json(orient='records') #TODO: change response    except RuntimeError as err:        response_code = 700         response_msg = jsonify(str(err))    return make_response(response_msg, response_code)......
flask_server_template/model → The business logic code resides here
model/business_model.py
In module ‘business_model.py’ adds the required business logic. The general flow would look like: validate the input parameters, get the data from database, perform required business logic application and return the results.
PS: Currently, I do not have any business logic. I am simply returning the data from database in pandas dataframe format.
### model/business_model.py ###......def get_film_info_orm(in_request): #TODO: appropriate function name    try:        # validate the URL parameters        title_schema_obj = TitleValidator() #TODO: appropriate validation object        title_schema_obj.load(in_request.args)        # verification passed, hence flask_server_template comes here else it would have gone to exception        title = in_request.args['title']        log.debug('title: ' + title)        # get your data in pandas data frame format        df = queries_orm.get_film_info(title) #TODO: appropriate function call here        # apply business logic (if any)        return df    except ValidationError as error:        log.error(f'get_all_film_df: {error}')        raise RuntimeError('Invalid Request Parameter: ' + str(error))......
model/request_validator.py
Module ‘business_model.py’ uses marshmallow to validate the URL parameters. This is a preferred way for validation as opposed to manually writing if then else statements to verify each component of the URL parameters. Verification includes data type checks (integer, string), range of values or from a specific list and/or some totally customised check.
### model/request_validator.py ###......def some_custom_check(data): #TODO: common checks    '''can be replaced with customised check'''    if not data:        raise ValidationError('some_custom_check for title failed')class TitleValidator(Schema): #TODO: appropriate class name# URL should have title of type string between length 1 and 50. After that call your custom check    title = fields.Str(required=True, validate=[validate.Length(min=1, max=50), some_custom_check])......
flask_server_template/db → The database queries and operations are in this folder
Before I go into details here, I have to clarify a few things:
I have used a postgresql tutorial database from postgresqltutorial
For demo purpose here, only three tables are relevant a. film b. actor c. film_actor
Since it is not possible to ship postgresql db without having the user to do additional installations, I have created these 3 tables in sqlite and shipped it along with this template (in directory flask_server_template/db/dvdrental.db).
Though, above I have highlighted postgresql connection, on Github there is also sqlite connection string and with this connection string the entire template works out of the box. (in short, if you install postgresql and the database from their website you can use postgresql else you can simply sqlite).
Since most data scientist work with tables, the queries return pandas data frames.
I have all the queries in exactly two format a. using SQLAlchemy Core i.e. raw SQL (select * from...) b. SQLAlchemy ORM to reflect the tables into python objects and perform queries in a pythonic way. This gives people the freedom to choose as per their preference.
Phew! Now I explain the code files
db/dvdrental.db
This is the sqlite database with three tables
film: stores films data such as title, release year, length, rating, etc.
actor: stores actors data including first name and last name.
film_actor: stores the relationships between films and actors.
### db/dvdrental.db ###To use the template no changes are needed in this file.
db/db_extensions.py
Module ‘db_extensions.py’ has database connection object, database meta data, object containing the reflection of the database (i.e. conversion of database tables into python objects) and the code to initialise the object relationship mapping.
### db/db_extensions.py ###To use the template no changes are needed in this file.def orm_init():...Base.prepare(db_obj.engine, reflect=True) #IMPORTANT for reflecting database into python objects
db/queries_orm.py
Module ‘queries_orm.py’ contains queries using SQLAlchemy ORM i.e. pythonic way of accessing the database.
### db/queries_orm.py ###def get_film_info(title):    film_orm = Base.classes.film #TODO: refer to correct table    qry = db_obj.session.query(film_orm).\         filter(film_orm.title == title) #TODO: your query    film_df = pd.read_sql(qry.statement, db_obj.session.bind)    return film_df#TODO: Another sample query below with multiple joins and filterdef get_film_actors(title):    film_orm = Base.classes.film    actor_orm = Base.classes.actor    film_actor_orm = Base.classes.film_actor    qry = db_obj.session.query(actor_orm).\            join(film_actor_orm,film_orm).\            filter(film_orm.title == title)    actor_df = pd.read_sql(qry.statement, db_obj.session.bind)    return actor_df
db/queries_rawsql.py
Module ‘queries_rawsql.py’ contains raw sql queries.
Disclaimer: In queries_rawsql.py there are sample parameterised queries (the where clause is with parameter). However, this format does not work for sqlite. It works fine on postgresql as well as oracle.
### db/queries_rawsql.py ###def get_all_films():    query = '''    SELECT * FROM film    '''  #TODO: your query    film_df = pd.read_sql(query, db_obj.session.bind)    return film_dfdef get_film_actors(title):    query = '''        SELECT a.first_name, a.last_name FROM actor a         INNER JOIN film_actor fa on fa.actor_id = a.actor_id         INNER JOIN film f on f.film_id = fa.film_id         WHERE f.title = %(title)s        ''' #TODO: your query    actor_df = pd.read_sql(sql=query, params={'title': title}, con=db_obj.session.bind)    return actor_df
I believe the best way to get up and running quickly is do the following steps
clone the repository
create a virtual environment
activate the virtual environment
install the libraries in requirements text
run the server
### Terminal ###$mkdir base$cd base$git clone https://github.com/alsm6169/flask_server_template.git$cd flask_server_template/$conda create --name ptest$conda activate ptest$conda install --file requirements.txt$python main.py
in another shell run the test file. Or alternatively,
open browser to run the requests
### Browser ###http://127.0.0.1:5000http://127.0.0.1:5000/module/v01/functions/film_list_ormhttp://127.0.0.1:5000/module/v01/functions/film_actors_orm?title=Alaska Phantom
This brings me to the end of this post. Hopefully, you find it easy to follow and use. Please let me know if you have some feedback/enhancement.