| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 | import contextvars
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TypeVar
from flask import Flask, g, has_request_context
T = TypeVar("T")
@contextmanager
def preserve_flask_contexts(
    flask_app: Flask,
    context_vars: contextvars.Context,
) -> Iterator[None]:
    """
    A context manager that handles:
    1. flask-login's UserProxy copy
    2. ContextVars copy
    3. flask_app.app_context()
    This context manager ensures that the Flask application context is properly set up,
    the current user is preserved across context boundaries, and any provided context variables
    are set within the new context.
    Note:
        This manager aims to allow use current_user cross thread and app context,
        but it's not the recommend use, it's better to pass user directly in parameters.
    Args:
        flask_app: The Flask application instance
        context_vars: contextvars.Context object containing context variables to be set in the new context
    Yields:
        None
    Example:
        ```python
        with preserve_flask_contexts(flask_app, context_vars=context_vars):
            # Code that needs Flask app context and context variables
            # Current user will be preserved if available
        ```
    """
    # Set context variables if provided
    if context_vars:
        for var, val in context_vars.items():
            var.set(val)
    # Save current user before entering new app context
    saved_user = None
    if has_request_context() and hasattr(g, "_login_user"):
        saved_user = g._login_user
    # Enter Flask app context
    with flask_app.app_context():
        try:
            # Restore user in new app context if it was saved
            if saved_user is not None:
                g._login_user = saved_user
            # Yield control back to the caller
            yield
        finally:
            # Any cleanup can be added here if needed
            pass
 |