avatarFelipe F Garcia

Summary

Python context manager is a powerful tool for handling exceptions in Python and Flask applications, allowing for more control over resource allocation and release.

Abstract

Python context manager is a feature that allows for better handling of exceptions in Python and Flask applications. It enables developers to allocate and release resources when they want, providing more control over how interactions occur. The concept of exception handling is essential when dealing with data that can have multiple possible outputs, such as working with databases. The article provides an example of how to use context manager in a Flask application to handle errors when querying a user model.

Opinions

  • Using a custom context manager can provide a cleaner environment to handle exceptions and make code easier to manage.
  • Using try/exception resources is not always the best practice, and custom exceptions should be used instead.
  • Context managers can be used for more than just opening and closing files or connections with a database.
  • The article provides an example of how to use context manager to handle errors when querying a user model in a Flask application.
  • The author recommends using a custom class to implement the methods needed to use the statement "with" in order to create a custom context.
  • The author suggests that context managers can be used to transform exceptions into handled fail situations that can be reused.

Python Context Manager, what is it for, and how it can help you better handle your exceptions together with Flask.

Handling exceptions is one of the most important things to do. Doing it right when working with Python and a web framework like Flask is definitely helpful for you.

Photo by Alex Kotliarskyi on Unsplash

One of the reasons for this is how Python works, is an interpreted language, meaning in a simple way that somethings you will only discover a problem when running.

The concept of exception handling is a very important part when handling data that can have many possible outputs, and you do not know them all, one use case for this is handling/working with databases.

What is Python context?

In simple terms a context or context manager allows you to allocate and release resources when you want.

It does give you more control over how this interaction will happen.

Maybe this looks too generic let’s make this more visible with an example.

Let’s have a problem, so we can understand when to use context.

For this scenario, we will use a Flask application to exemplify what we want to achieve, but remember, this is a Python resource and it will work on any Python code.

For a very simple application we can use this code:

import click
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy_serializer import SerializerMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy()
db.init_app(app)
# Custom commands for your terminal
def create_db():
    """Creates database"""
    db.create_all()
def drop_db():
    """Cleans database"""
    db.drop_all()
def insert_user():
    """Add user to our database"""
    db.session.add(TestModel(user="Felipe"))
    db.session.commit()
# add multiple commands in a bulk
for command in [create_db, drop_db, insert_user]:
    app.cli.add_command(app.cli.command()(command))
# Model to user with our database
class TestModel(db.Model, SerializerMixin):
    user = db.Column(db.String(50), primary_key=True)
# Route to test our data
@app.route("/user/<user_name>")
def show_user(user_name:str):
    user_db = TestModel.query.filter_by(user=user_name).first()
    return jsonify(user_db.to_dict())

Ok, here we have one API that we can query users, for this, we can just insert one user using the terminal, no further explanation here, but I will insert one user using my name using the command that I created, typing this on my terminal: First, create our database:flask create-db Second, create our database:flask insert-user

Let’s try to run our application, hitting: flask run

Remember, you can only use flask run if you have set your Flask application name environment variable properly, if you don’t know much about it I recommend read this post: https://itnext.io/start-using-env-for-your-flask-project-and-stop-using-environment-variables-for-development-247dc12468be

And let’s type in our browser this URL/path to request my user: http://127.0.0.1:5000/user/Felipe

This should be the output

Ok, so far all good, but let’s create a problem, let’s try to get a user that doesn’t exist, like, IronMan. On my browser, I will call like this: http://127.0.0.1:5000/user/IronMan

Besides the error say about `NoneType` what happened is that we don`t have this user in our database

We have a fatal error, and the reason is simple, only when we have a successful response that we have a user object, and from this, we can indeed transform it into a dictionary using our helper “to_dict”.

In this case, we need to handle this error, and most probably the initial thought on this would be, let’s just use “try exception” right?

It would be something like this:

@app.route("/user/<user_name>")
def show_user(user_name:str):
		user_db = TestModel.query.filter_by(user=user_name).first()
   
    try:
        return jsonify(user_db.to_dict())
    except:
        abort(404, "can't find a user")

This would be your result now:

A straightforward error handling

It’s also ok, and of course, it depends on your rule how you want to handle your error.

But imagine that you have more than 5, 10 routes that handle user model, you will need to do a try-catch for each one of those, you could argue that you can create a class to abstract it, but, there’s an alternative, and it’s use context manager.

Creating a custom context to handle your error.

Besides what looks like, a custom context handle, or the use of the statement “with” was indeed created to be an alternative to the try/finally statement.

You can read more about it on PEP 343: https://www.python.org/dev/peps/pep-0343/

As we want an alternative way to handle it, let’s create one custom class, that will implement the methods that we need in order to be able to use the statement “with”.

As for the sake of simplicity we are using the same file for everything, I will also add the class in the same file, your file should become this:

from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy_serializer import SerializerMixin

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy()
db.init_app(app)

class TestModel(db.Model, SerializerMixin):
    user = db.Column(db.String(50), primary_key=True)

class UserResponseBuilder():
    
    user = None
    
    def __init__(self, user_model:TestModel):
        
        print("Init")
        if user_model is not None:
            self.user = user_model.user
        
    
    def __enter__(self):
        print("Enter")
        
        if self.user is None:
             self.user = "No valid user found"
        return self
    
    def __exit__(self, type, value, traceback):
        print("Exit")
        app.logger.info(f"User handled {self.user}")
@app.route("/user/<user_name>")
def show_user(user_name:str):
    user_db = TestModel.query.filter_by(user=user_name).first()
    with UserResponseBuilder(user_db) as user_builder:
        return jsonify(user_name=user_builder.user)

It’s pretty simple, let’s explain each important part.

  1. How to use a “context”, we use the statement “with” in our router method, as we are creating an object that we pass our fetched user, is expected to be ok and the output of this creation or the keyword “as” be a variable “user_builder”
  2. Our custom class “UserResponseBuilder”, is a normal class, that has an initializer but also 2 important methods to be able to use the statement “with”. 2.1 — __enter__ function, this will be executed right after the initializer. 2.2 — __exit__(self, type, value, traceback) function, with all these parameters, even if you don’t use them, this method will be called after you successfully finished the creation of this class.
  3. The implementation is a bit different, now create a new object with the name of the user, but, inside the initializer, we will evaluate if is not empty, it is, we don’t set the user name.
  4. Inside the __enter__ method is the one that is executed right after the initializer we will check if we don’t have a user and will provide a default value to that.
  5. Inside the __exit__ method we can for example check if is not set or any rule and perhaps log it, so we can check if something wrong is happening. For now, we are not keeping it as None, so we just log. Remember, this will be executed when is finished, there’s nothing that you can do with your object/class any more.

Each one of these methods and steps is what you need in order to create a custom context and use the statement “with”.

Running it again this will be our output:

We have a default value being set

You may ask, why not use try/exception? There’s no reason why not, but the important to notice here is how we can control and make our code easier to handle.

Anywhere that I want to use my user I can just use this class and use the statement “with” to validate it, and if I need to add more rules, change it, I can, and anywhere that I use will be up-to-date.

Final considerations.

What you should get from this is that we can have a cleaner environment to handle exceptions, and in this case, we “transform” our exception into a handled fail situation, that can be reused.

When you use the try/exception resource you not only need to handle the exception itself, but the good practice is to have your custom exception, never raise the base exception.

A context manager is usually explained using open and close files or connections with the database, which indeed is a very good example, but is not only for this, I demonstrate here how you can use it to help your code be cleaner and better handled.

Conclusion

I hope you enjoyed reading this. If you’d like to support me as a writer, consider signing up to become a Medium member. It’s just $5 a month and you get unlimited access to Medium also if you liked, consider sharing with others that also want to learn more about this.

Also, I started a new channel on YouTube, I want to teach in videos what I write about, so please, subscribe if you liked this tutorial.

Youtube channel:

Linkedin:

Instagram: @thedevproject

Python
Flask Framework
Flask
Web Development
Error Handling
Recommended from ReadMedium
avatarAbhay Kumar
OOPs in Python

An easy guide

10 min read