FlaskPythonWeb Development
Jan HeimesJan HeimesNovember 4, 2024

The Magic of Flask Request and How We Use It at Needle

Simplifying access control in serverless functions with Flask's request object

7 min read

When building a SaaS product like Needle, where the focus is on advanced data extraction and document analysis with our RAG-as-a-Service approach, keeping the codebase tidy is essential to make it more maintainable. After all, data processing is an ever-evolving area.

Furthermore, we have a tiered model for accessing different features. We offer essential tools to all users, such as basic document parsing but keep some of the advanced, resource-heavy features, such as image analysis, exclusive to Pro subscribers. But managing that access control in your code, especially in a serverless environment, like functions framework, can quickly become messy if not done right.

At Needle, we ran into exactly this challenge. We have a complex execution flow for document parsing and we oftentimes, deep inside the execution flow, need to decide whether we should run the next step or not based on the user's subscription plan.

We started by passing the user object as a parameter to every function needing an access check. But as our feature set grew, it didn't take long to see that this approach was not sustainable. It cluttered the code, made it harder to read, and worse, became a maintenance headache.

At Needle, we want to write as little and clean code as possible. We changed our approach to use the global request object instead. By attaching user information directly to request, we could centralize all that access control logic in one place. This makes the code way cleaner and easier to reason. Now, instead of passing around user objects from function to function, the data we need is right there in request, ready to be checked in any function, anytime.

If you're curious to see how Needle manages access control in action, check us out and try our API.

The Challenge: Avoiding Repetitive Parameter Passing for Access Control

Let's take an example. We are processing documents in some ways that is accessible only to Pro users. If a user is on a Free plan omit certain processing features, that are only available in Pro. Initially, we were passing the user object through each function to check their subscription level, something like this:

def analyze_image(user):
    if user.plan == "free":
        return "Do Something."
    # Advanced analysis for Pro users
    return "Do Something + More"

This method works for a single function but quickly becomes unmanageable when additional functions also need to check the user's plan. Passing user around bloats the code and makes it harder to maintain.

The Solution: Attaching User Data to request

Instead of passing user to each function, we leveraged the request object, which is available globally within each request in Functions Framework. By attaching user information to request at the start of each request, we could check the user's plan from any function without passing user it as a parameter.

In Functions Framework, we set this up by attaching user information to request in a middleware function that runs at the start of each request. Here's an example setup:

from flask import request
from functions_framework import create_app

app = create_app()

class User:
    def __init__(self, plan):
        self.plan = plan

@app.before_request
def load_user():
    request.needle_user = User(plan="free")

By attaching user data to request.needle_user, any function can now check the user's plan directly, without additional parameters. This reduces repetitive code and keeps our access control centralized.

Implementing Access Control in Functions

Now that request.needle_user holds our user information, any function can easily check the user's plan. Here's how we can restrict a feature to Pro users without passing user down the chain:

import functions_framework
from flask import request

@functions_framework.http
def analyze_image():
    # Check if the user is on the Free plan and skip analysis if so
    if request.needle_user.plan == "free":
        return "Do Something."
    # Advanced analysis for Pro users
    return "Do Something + More"

This setup makes restricting features based on the user's plan simple. Functions can access request.needle_user to check the user's access level, regardless of where they are in the code.

Adding a Helper for Consistent Pro Plan Checks

For additional consistency, we created a helper function to enforce Pro-only requirements. If the user is on the Free plan, it returns an error, avoiding duplicated access control logic in each function.

def enforce_pro_plan():
    if request.needle_user.plan == "free":
        return "Upgrade to Pro to access this feature.", 403

Now we can call enforce_pro_plan() wherever we need to restrict a feature to Pro users:

@functions_framework.http
def analyze_image(request):
    # Enforce Pro-plan requirements
    error_response = enforce_pro_plan()
    if error_response:
        return error_response

    # Full analysis for Pro users
    return "Done"

Why Using request for User Data Is a Game-Changer

Attaching user data to request streamlines access control, making it more scalable and maintainable. Here's how it adds value:

  • Cleaner Code: By storing user information in request.needle_user, we avoid passing user across functions, which reduces code clutter.

  • Centralized Access Control: All functions within the request lifecycle can directly access the user's plan without changing function signatures, leading to consistent access checks.

  • Easy Future Modifications: If we need to update access rules for Free and Pro plans, we can modify them centrally in one place without adjusting each function.

Conclusion

Using request to hold user data is a simple yet powerful approach to managing access control in serverless functions. It keeps the code clean, centralizes logic, and makes it easy to scale features across Free and Pro users without sacrificing maintainability.

For any SaaS model offering tiered features, using request it as a centralized access point for user data is a best practice that will make your codebase easier to manage as your product grows.

Note: The above code is just an example code to explain the general problem and solution around the usage of request.


Share
    Like many websites, we use cookies to enhance your experience, analyze site traffic and deliver personalized content while you are here. By clicking "Accept", you are giving us your consent to use cookies in this way. Read our more on our cookie policy .