Back to Notes

Decorators

Decorators are one of Python's most powerful features. They allow you to modify or enhance the behavior of functions or methods without permanently modifying their source code. They are heavily used in web frameworks like Flask (as seen in your code) for tasks like authentication, logging, and routing.

1. The Core Concept

A decorator is a function that takes another function as an argument, adds some functionality, and returns a new function. In your code, you are using Decorators with Arguments. This requires a specific 3-level nested structure:

  1. The Factory (Outermost): Takes the configuration arguments (return_json, update_activity).
  2. The Actual Decorator (Middle): Takes the function being decorated (f).
  3. The Wrapper (Innermost): The function that actually gets executed when the route is hit. It intercepts the call, runs the extra logic, and then calls the original function.

2. Anatomy of Your Code: require_valid_session

Let's break down exactly what happens in your first decorator.

from functools import wraps

# LEVEL 1: The Factory
# This receives the configuration arguments.
def require_valid_session(return_json=False, update_activity=True):
    
    # LEVEL 2: The Decorator
    # This receives the original function 'f' (e.g., your api_endpoint)
    def decorator(f):
        
        # @wraps(f) is CRUCIAL for interviews!
        # It copies the name, docstring, and metadata from 'f' to 'decorated_function'.
        # Without this, Flask gets confused because all your routes would suddenly be named "decorated_function".
        @wraps(f)
        
        # LEVEL 3: The Wrapper
        # This intercepts the actual execution. It takes *args and **kwargs 
        # so it can accept any arguments the original function 'f' was supposed to get.
        def decorated_function(*args, **kwargs):
            
            # --- PRE-PROCESSING LOGIC ---
            is_valid, reason = check_session_validity(session)
            
            if not is_valid:
                # If invalid, STOP execution here and return an error/redirect.
                # The original function 'f' is never called.
                session.clear()
                if return_json:
                    return jsonify({'error': 'Session expired'}), 401
                else:
                    return redirect(url_for('main.login'))

            if update_activity:
                update_activity_timestamp(session)

            # --- EXECUTE ORIGINAL FUNCTION ---
            # If everything is valid, we finally execute the original route logic.
            return f(*args, **kwargs)

        # Returns the wrapper function to replace the original
        return decorated_function
        
    # Returns the decorator function
    return decorator

3. Anatomy of Your Code: require_authorization

This follows the exact same 3-level pattern, but it highlights another great use of decorators: Dependency Injection via callbacks.

  • check_func: Instead of hardcoding the authorization logic inside the decorator, you pass a function. The decorator just runs check_func(). This makes your decorator highly reusable for different personas or roles.

4. Interview Cheat Sheet

If you are asked about decorators in an interview, make sure to hit these keywords:

  • Higher-Order Functions: Decorators work because functions in Python are "first-class citizens." They can be passed around as arguments and returned from other functions.
  • Closure: The innermost wrapper function remembers the variables from the outer functions (like return_json or f), even after those outer functions have finished executing. This relies on the concept of closures.
  • functools.wraps: Always mention this. If you don't use @wraps(f), the original function loses its __name__ and __doc__ attributes, which breaks debugging tools and frameworks like Flask.
  • *args, **kwargs: We use these in the wrapper function so the decorator can be applied to functions with any number of positional or keyword arguments.
  • Separation of Concerns: Decorators extract repetitive logic (like checking sessions) out of the main business logic (the route handler), making the code cleaner and strictly adhering to the DRY (Don't Repeat Yourself) principle.