Categories
Flask

Python Web Development with Flask — Configuration

Flask is a simple web framework written in Python.

In this article, we’ll look at how to develop simple Python web apps with Flask.

Update Configuration

We can set the configuration by setting some properties of the Flask object.

For example, we can write:

from flask import Flask
app = Flask(__name__)
app.testing = True

@app.route('/')
def hello_world():
    return 'Hello, World!'

We set the app.testing property to change the TESTING config.

Also, we can set more than one config settings:

from flask import Flask
app = Flask(__name__)
app.config.update(
    TESTING=True,
    SECRET_KEY=b'secret'
)

@app.route('/')
def hello_world():
    return 'Hello, World!'

The app.config.update method updates multiple configs.

Environment and Debug Features

We can set the FLASK_ENV environment variable to change the environment that the code runs in.

For example, we run:

$ export FLASK_ENV=development
$ flask run

in Linux and:

$ set FLASK_ENV=development
$ flask run

in Windows to run the code in development mode.

Configuring from Files

We can call app.config.from_objecr to load the config from a module.

For example, we can write:

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_object('settings')

@app.route('/')
def hello_world():
    return 'Hello, World!'

settings.py

DEBUG = False
SECRET_KEY = b'_5#y2L"F4Q8znxec]/'

We read the config from settings.py with the app.config.from_object method.

Also, we can use the app.config.from_envvar method to read the settings from the path to the file that we set from the environment variable.

For example, we can write:

app.py

from flask import Flask
app = Flask(__name__)
app.config.from_envvar('APP_SETTINGS')

@app.route('/')
def hello_world():
    print(app.config)
    return 'Hello, World!'

settings.cfg

DEBUG = False
SECRET_KEY = b'_5#y2L"F4Q8znxec]/'

Then in Windows, we run:

> set APP_SETTINGS=./settings.cfg
> python -m flask run

to run the app and read the settings from settings.cfg .

On Linux, we run:

$ export APP_SETTINGS=./settings.cfg
$ python -m flask run

Configuring from Environment Variables

We can read config from environment variables with the os.environ.get method.

For example, we can write:

from flask import Flask
import os

app = Flask(__name__)
SECRET_KEY = os.environ.get("SECRET_KEY")
print(SECRET_KEY)

@app.route('/')
def hello_world():
    return 'Hello, World!'

to read the SECRET_KEY environment variable from the OS’s environment variables.

Then we can set the environment variable and run it by running:

> set SECRET_KEY='5f352379324c22463451387a0aec5d2f'
> python -m flask run

on Windows.

On Linux, we run:

$ set SECRET_KEY='5f352379324c22463451387a0aec5d2f'
$ python -m flask run

Development / Production

We can use class inheritance to create a shared config class between different environments.

Then we can create child config classes for different environments.

For example, we can write:

app.py

from flask import Flask
import os

app = Flask(__name__)
app.config.from_object('settings.ProductionConfig')
print(app.config)

@app.route('/')
def hello_world():
    return 'Hello, World!'

settings.py

class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite:///:memory:'

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DEBUG = True

class TestingConfig(Config):
    TESTING = True

We call app.config.from_object with the 'settings.ProductionConfig' string to get the settings from the ProductionConfig subclass.

Conclusion

There are several ways to read configs for our app with Flask.

Categories
Flask

Python Web Development with Flask — Logging and Config

Flask is a simple web framework written in Python.

In this article, we’ll look at how to develop simple Python web apps with Flask.

Logging

We can add logging into our Flask app.

For example, we can write:

from flask import Flask, abort
from logging.config import dictConfig

dictConfig({
    'version': 1,
    'formatters': {'default': {
        'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
    }},
    'handlers': {'wsgi': {
        'class': 'logging.StreamHandler',
        'stream': 'ext://flask.logging.wsgi_errors_stream',
        'formatter': 'default'
    }},
    'root': {
        'level': 'INFO',
        'handlers': ['wsgi']
    }
})
app = Flask(__name__)

@app.route('/')
def hello_world():
    app.logger.info('success')
    return 'hello'

to configure our logger and use it.

The dictConfig function lets us configure our logger.

version is an integer value representing the schema version.

formatters has the dictionary top construct a Formatter instance.

filters is a dict which each key is a filter id and each value is a dict describing how to configure the corresponding Filter instance.

handlers is a dict which each key is a handler ID and each value is a dict describing how to configure the corresponding Handler instance.

Injecting Request Information

We can create our own request formatter to add the data we want into our logging messages.

For example, we can write:

from flask import Flask, has_request_context, request
from flask.logging import default_handler
import logging

class RequestFormatter(logging.Formatter):
    def format(self, record):
        if has_request_context():
            record.url = request.url
            record.remote_addr = request.remote_addr
        else:
            record.url = None
            record.remote_addr = None
        return super().format(record)

formatter = RequestFormatter(
    '[%(asctime)s] %(remote_addr)s requested %(url)sn'
    '%(levelname)s in %(module)s: %(message)s'
)
default_handler.setFormatter(formatter)

app = Flask(__name__)

@app.route('/')
def hello_world():
    app.logger.error('error')
    return 'hello'

We create the RequestFormatter class that has the format method.

Inside it, we check if we’re making a request with the has_request_context function.

If it’s true , then we’re making a request.

Then we set the url of the request, and the remote_addr , which is the remote address of the request.

And then we return the formatted record.

Then we create a RequestFormatter instance to format the request data,.

And then we call setFormatter to use the formatter we created.

Since we logged an error in the hello_world function, we’ll see the error logged when we make a request to the route.

And we’ll see something like:

[2020-10-07 16:59:17,625] 127.0.0.1 requested http://localhost:5000/
ERROR in app: error

from the log.

Other Libraries

We can add our loggers with the addHandler method.

For example. we can write:

from flask import Flask, has_request_context, request
from flask.logging import default_handler
import logging

root = logging.getLogger()
root.addHandler(default_handler)
app = Flask(__name__)

@app.route('/')
def hello_world():
    app.logger.error('error')
    return 'hello'

to get the root logger by calling logging.getLogger() .

Then we call root.addHandler method to add the logger we want.

Configuration Basics

We can add our own configuration for our app.

For example, we can write:

from flask import Flask

app = Flask(__name__)
app.config['TESTING'] = True

@app.route('/')
def hello_world():
    return 'hello'

to add the TESTING config to our app.

Conclusion

We can add logging and configuration into our Flask app.

Categories
Flask

Python Web Development with Flask — Error Handling

Flask is a simple web framework written in Python.

In this article, we’ll look at how to develop simple Python web apps with Flask.

Application Errors

We can add our own error handlers into our Flask app.

For example, we can write:

from flask import Flask, abort
import werkzeug
app = Flask(__name__)

@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
    return 'bad request!', 400

@app.route('/')
def hello_world():
    return abort(400)

We add a handler for handling 400 errors.

The handler is registered with the app.errorhandler decorator.

The werkzeug.exceptions.BadRequest means we’re using it to handle 400 errors.

We return a 400 error in the hello_world route function, so we should see ‘bad request!’ returned with the response.

Equivalently, we can write:

from flask import Flask, abort
import werkzeug
app = Flask(__name__)

def handle_bad_request(e):
    return 'bad request!', 400

app.register_error_handler(400, handle_bad_request)

@app.route('/')
def hello_world():
    return abort(400)

to register the error handler.

We can also register error handlers for nonstandard status codes.

To do that, we just have to make a class that extends werkzeug.exceptions.HTTPException .

For instance, we can write:

from flask import Flask, abort
import werkzeug
app = Flask(__name__)

class InsufficientStorage(werkzeug.exceptions.HTTPException):
    code = 507
    description = 'Not enough storage space.'

def handle_507(e):
    return 'Not enough storage space.', 507

app.register_error_handler(InsufficientStorage, handle_507)

@app.route('/')
def hello_world():
    raise InsufficientStorage()

to create the InsufficientStorage exception class.

We set the code and description instance variables with the status code and description respectively.

Then we call app.register_error_handler to register the error handler.

Generic Exception Handlers

We can create generic exception handlers.

For instance, we can write:

from werkzeug.exceptions import HTTPException
from flask import json
from flask import Flask, abort
app = Flask(__name__)

@app.errorhandler(HTTPException)
def handle_exception(e):
    response = e.get_response()
    response.data = json.dumps({
        "code": e.code,
        "name": e.name,
        "description": e.description,
    })
    response.content_type = "application/json"
    return response

@app.route('/')
def hello_world():
    return abort(400)

We add the handle_exception function with the app.errorhandler decorator to register the exception handler.

In the function, we create the response object from the e.get_response method.

Then we set the response.data property to set the response data.

We use json.dumps to convert the dictionary to a string.

Unhandled Exceptions

Unhandled exceptions are returned as 500 errors.

We can add a 500 error handler to catch handled and unhandled exceptions.

For example, we can write:

from werkzeug.exceptions import InternalServerError
from flask import json
from flask import Flask, abort
app = Flask(__name__)

@app.errorhandler(InternalServerError)
def handle_500(e):
    original = getattr(e, "original_exception", None)
    if original is None:
        return 'handled error'
    return 'unhandled error', 500

@app.route('/')
def hello_world():
    raise Exception()
    return 'hello'

We add the handle_500 function to handle both handled and unhandled errors.

The original variable being None means that it’s a handled error.

Otherwise, it’s an unhandled error.

Since we raised a generic exception in the hello_world function, we should see 'unhandled error' in the response.

Conclusion

We can catch and handle errors raised in Flask apps.

Categories
Flask

Python Web Development with Flask — Template Functions and Filters

Flask is a simple web framework written in Python.

In this article, we’ll look at how to develop simple Python web apps with Flask.

Templates

Flask comes with the Jinja template engine.

We can use it to render HTML.

It can access the config , request , session , g ,url_for , and get_flashed_messages contexts.

config is the current configuration object.

request is the current request object.

session is the current session object.

g us the request bound object for global variables.

url_for is the url_for function.

And get_flashed_messages is the get_flashes_messages function.

Standard Filters

We can use filters that are provided by Flask and Jinja.

For example, we can write:

app.py

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def hello_world():
    user = {
        'first_name': 'james',
        'last_name': 'smith'
    }
    return render_template('index.html', user=user)

templates/index.html

<script type=text/javascript>
    const user = {{ user|tojson }};
</script>

to add the user dictionary.

Then we pass the user object into the template and use the tojson filter with it to convert the dictionary to JSON.

So we get:

<script type="text/javascript">
    const user = {"first_name": "james", "last_name": "smith"};
</script>

rendered in the template.

Autoescaping

We can disable autoescaping with the autoescape false block.

For example, we can write:

app.py

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def hello_world():
    return render_template(
        'index.html',
        will_not_be_escaped='<b>will_not_be_escaped</b>'
    )

templates/index.html

{% autoescape false %}
<p>{{ will_not_be_escaped }}</p>
{% endautoescape %}

The will_not_be_escaped isn’t escaped, so the raw HTML string is rendered.

So we’ll see the <b> tag rendered as bold text.

Registering Filters

We can register our own filters.

For example, we can write:

app.py

from flask import Flask, render_template
app = Flask(__name__)

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

@app.route('/')
def hello_world():
    return render_template(
        'index.html',
        arr=[1, 2, 3]
    )

templates/index.html

{% for a in arr | reverse %}
    <p>{{a}}</p>
{% endfor %}

We register the reverse filter with the app.template_filter decorator.

In the reverse_filter function, we return the reversed array s .

Then in the template, we use the reverse filter and render it.

We can also write:

app.py

from flask import Flask, render_template
app = Flask(__name__)

def reverse_filter(s):
    return s[::-1]

app.jinja_env.filters['reverse'] = reverse_filter

@app.route('/')
def hello_world():
    return render_template(
        'index.html',
        arr=[1, 2, 3]
    )

to do the same thing.

We put the filter in the app.jinja_env.filters dictionary.

Context Processors

We can add functions that we can run in templates.

To do that, we use the @app.context_processor decorator.

For example, we can write:

app.py

from flask import Flask, render_template
app = Flask(__name__)

@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}'.format(amount, currency)
    return dict(format_price=format_price)

@app.route('/')
def hello_world():
    return render_template('index.html')

templates/index.html

{{ format_price(1.22) }}

We add th utility_processor function and applied the @app.context_processor to it.

Then inside it, we created the format_price function that returns a formatted currency string.

And then we return a dictionary with the function as the value of the format_price key.

Now we can call it in index.html to render a currency string.

Conclusion

We can call functions and use filters in templates with Flask.

Categories
Flask

Python Web Development with Flask — Logging, App Commands, and Blueprints

Flask is a simple web framework written in Python.

In this article, we’ll look at how to develop simple Python web apps with Flask.

Logging

Flask comes with a logger.

We can use it with the app.logger object.

For example, we can write:

from flask import Flask
from markupsafe import escape

app = Flask(__name__)

@app.route('/')
def index():
    app.logger.debug('A value for debugging')
    app.logger.warning('A warning occurred (%d apples)', 42)
    app.logger.error('An error occurred')
    return 'hello'

to call methods to do various levels of logging.

Add App Commands

We can add app commands with Flask.

This way, we can automate tasks that are otherwise manual.

For example, we can create a task to initialize a database by writing:

app.py

from flask import Flask, current_app, g
import os
import sqlite3
import click
from flask.cli import with_appcontext

def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect(
            'db.sqlite',
            detect_types=sqlite3.PARSE_DECLTYPES
        )
        g.db.row_factory = sqlite3.Row
    return g.db

def close_db(e=None):
    db = g.pop('db', None)
    if db is not None:
        db.close()

def init_db():
    db = get_db()
    with current_app.open_resource('schema.sql') as f:
        db.executescript(f.read().decode('utf8'))

@click.command('init-db')
@with_appcontext
def init_db_command():
    init_db()
    click.echo('Initialized the database.')

app = Flask(__name__)
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)

@app.route('/')
def index():
    return 'hello'

schema.sql

DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS post;

CREATE TABLE user (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT UNIQUE NOT NULL,
  password TEXT NOT NULL
);

CREATE TABLE post (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  author_id INTEGER NOT NULL,
  created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  title TEXT NOT NULL,
  body TEXT NOT NULL,
  FOREIGN KEY (author_id) REFERENCES user (id)
);

The following code:

@click.command('init-db')
@with_appcontext
def init_db_command():
    init_db()
    click.echo('Initialized the database.')

creates our command.

We pass in the command name into the @click.command decorator.

init-db is the name of the command we registered.

The with_appcontext decorator is also required to use the app context.

In the init_db function, we call get_db to get the database connection.

Then we call current_app.open_resource to get the SQL script with the and run it with db.executescript .

We call:

app.teardown_appcontext(close_db)

to close the database connection.

Now we run:

flask init-db

to run the init-db command to run our schema.sql script to create the tables.

Blueprints

To separate our apps into smaller modules, we can use blueprints.

For example, we can write:

app.py

from flask import Flask
from auth import bp
app = Flask(__name__)
app.register_blueprint(bp)

auth.py

from flask import Blueprint

bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/register', methods=('GET', 'POST'))
def register():
    return 'register'

to create the auth blueprint.

We use the Blueprint class to create the blueprint.

The first argument is the name.

__name__ is the location that the blueprint is defined in.

The 3rd argument is the URL prefix.

To register a route in the blueprint, we call the @bp.route decorator like we do with the app.route decorator.

Then to register the blueprint, we write:

app.register_blueprint(bp)

to register the blueprint.

Conclusion

We can add logging, app commands, and blueprints with Flask.