The magic of Flask request and how it can increase your code quality
Use the request object as an elegant solution instead of passing params down to each method
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 passinguser
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
.