Categories
Flask

Python Web Development with Flask — Methods Views Decorators and Context

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.

Decorating Views

We can add decorators to view classes with the decorators property.

For example, we can write:

from flask import Flask, request, session, abort
from flask.views import MethodView

app = Flask(__name__)
app.secret_key = b'secret'

def user_required(f):
    def decorator(*args, **kwargs):
        if 'username' not in session:
            abort(401)
        return f(*args, **kwargs)
    return decorator

class UserAPI(MethodView):
    decorators = [user_required]

    def get(self):
        return 'get'

    def post(self):
        return 'post'

class LoginAPI(MethodView):
    def get(self):
        session['username'] = 'username'
        return 'logged in'

class LogoutAPI(MethodView):
    def get(self):
        session.pop('username', None)
        return 'logged out'

app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))
app.add_url_rule('/login/', view_func=LoginAPI.as_view('login'))
app.add_url_rule('/logout/', view_func=LogoutAPI.as_view('logout'))

We add the LoginAPI and LogoutAPI to and add the get methods to them to add the username session key and remove it respectively.

In UserAPI , we user the user_required decorator to check whether the 'username' key in the session object.

If it’s not, we return 401.

Otherwise, we call the method that we’re requesting in the class.

Method Views for APIs

We can get URL parameters in our route methods.

For example, we can write:

from flask import Flask, request, session, abort
from flask.views import MethodView

app = Flask(__name__)

class UserAPI(MethodView):
    def get(self, user_id):
        print(user_id)
        if user_id is None:
            return 'users'
        else:
            return str(user_id)

    def post(self):
        return 'post'

    def delete(self, user_id):
        return str(user_id)

    def put(self, user_id):
        return 'put'

user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users/', defaults={'user_id': None},
                 view_func=user_view, methods=['GET', ])
app.add_url_rule('/users/', view_func=user_view, methods=['POST', ])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
                 methods=['GET', 'PUT', 'DELETE'])

We have the UserAPI class that has the get , post , delete and put methods.

The user_id parameter is the URL parameter.

The defaults parameter is set on the GET route to set the user_id with a default value.

We also accept the user_id URL parameter in the PUT and DELETE routes.

Application Context

We can use the application context to keep track of the app0level data during a request, CLI command, or other activity.

We can access the global context with the g variable.

For example, we can write:

from flask import Flask, g
from flask.views import MethodView

app = Flask(__name__)

def connect_to_database():
    pass

def get_db():
    if 'db' not in g:
        g.db = connect_to_database()
    return g.db

@app.teardown_appcontext
def teardown_db(response_or_exc):
    db = g.pop('db', None)
    if db is not None:
        db.close()

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

We set the g.db property if 'db' isn’t in the g variable.

In the teardown_db function, then we call g.pop method to remove the 'db' property from the object.

Request Context

Flask automatically pushes a request context when handling a request.

For example, if we have:

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def hello():
    print('during view')
    return 'Hello, World!'

@app.teardown_request
def show_teardown(exception):
    print('after with block')

with app.test_request_context():
    print('during with block')

with app.test_client() as client:
    client.get('/')
    print(request.path)

We get the context with the app.test_request_context() method.

The show_teardown method is run after the with blocks are run.

So we get:

during with block
after with block
during view
/
after with block

logged when we run the app.

Conclusion

We can decorate method views. Also, we can get the app context and use it to request data with Flask.

Categories
Flask

Python Web Development with Flask — Pluggable Views

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.

Pluggable Views

Flask has pluggable views.

They use classes to render views instead of functions.

For example, we can write:

app.py

from flask import Flask, render_template
from flask.views import View

app = Flask(__name__)

class ShowUsers(View):

    def dispatch_request(self):
        users = [
            {
                'name': 'james'
            },
            {
                'name': 'mary'
            },
        ]
        return render_template('users.html', users=users)

app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))

templates/users.html

{% for u in users %}
<p>{{u.name}}</p>
{% endfor %}

We add the ShowUsers class which inherited from the View class.

It has the dispatch_request method that renders the template with some data.

Then to map the class to a URL, we call the app.add_url_rule method with the URL path and the view_func parameter.

The ShowUsers.as_view method takes the name of the view that we’ll have.

We can make this code more flexible by providing a base class for rendering the template.

Then we create a subclass with the data to render the view.

To do that, we can write:

app.py

from flask import Flask, render_template
from flask.views import View

app = Flask(__name__)

class ListView(View):
    def get_template_name(self):
        raise NotImplementedError()

    def render_template(self, context):
        return render_template(self.get_template_name(), **context)

    def dispatch_request(self):
        context = {'objects': self.get_objects()}
        return self.render_template(context)

class UserView(ListView):
    def get_template_name(self):
        return 'users.html'

    def get_objects(self):
        return [
            {
                'name': 'james'
            },
            {
                'name': 'mary'
            },
        ]

app.add_url_rule('/users/', view_func=UserView.as_view('show_users'))

templates/users.html

{% for u in objects %}
<p>{{u.name}}</p>
{% endfor %}

The ListView component is the base class for the view.

get_template_name is implement in the subclasses of this class.

render_template calls render_template from Flask. with the template name returned from the get_template_name method.

The rest of the arguments are passed in from the context object.

dispatch_request passes in the context into the render_template method.

The UserView class extends the ListView class and returns the template name in the get_template_name method.

And get_objects has the objects array that we render in the template.

Method Hints

We can set the methods that are allowed.

For example, we can write:

from flask import Flask, request
from flask.views import View

app = Flask(__name__)

class MyView(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        if request.method == 'POST':
            return 'post'
        return 'get'

app.add_url_rule('/myview', view_func=MyView.as_view('myview'))

to add the MyView class.

It has the methods array that sets the request types that are allowed.

In the dispatch_request method, we check the request method with the request.method property and return the response accordingly.

Then we map the class to a URL with the app;.add_url_rule method.

Method Based Dispatching

We can dispatch methods with methods.

For example, we can write:

from flask import Flask, request
from flask.views import MethodView

app = Flask(__name__)

class UserAPI(MethodView):
    def get(self):
        return 'get'

    def post(self):
        return 'post'

app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))

We have the UserAPI class which extends the MethodView class.

Then we add the get method to accept GET requests and the post method to accept POST requests.

And then we call app.add_url_rule to map that class to a URL.

Now when we make a GET request, we see 'get' and when we make a POST request, we see 'post' .

Conclusion

Pluggable views is a useful way for organizing views with Flask.

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.