Max Azatian
Max Azatian

Reputation: 73

Pushing app context of parent thread in subthread doesnt work

I have an app with following architecture:

architecture

/app is where all code is stored, /reports - for reports generated via ydata_profiling.

The idea is to show user some wait-page with "pls wait" on it, while pinging server once a second whether report is ready or not, and if so - show report in and allow user to download it / open in new webpage.

My current problem is that passing app object, where app_context is stored, doesn't resolve the problem with RuntimeError: Working outside of request context. in line session_data = session.get('sid') succeeding generation of report.

What and how should i pass to generate_statistics_report() in order to remove this error?


Below is the code:

# app.py
from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run(debug=True)
# app/__init__.py
from flask import Flask


def create_app():
    app = Flask(__name__)
    app.secret_key = 'test'

    from .api import statistics
    app.register_blueprint(statistics.bp)

    return app
# app/api/__init__.py
from flask import Blueprint
from . import statistics

bp = Blueprint('api', __name__, url_prefix='/api/v1/')
bp.register_blueprint(statistics.bp)
# app/api/statistics/__init__.py
from flask import Blueprint
from .routes import statistics, check_report_status, download_report

bp = Blueprint('statistics', __name__, url_prefix='/statistics')
bp.add_url_rule('/', 'statistics', statistics)
bp.add_url_rule('/check_report_status', 'check_report_status', check_report_status)
bp.add_url_rule('/download_report', 'download_report', download_report)
# app/api/statistics/routes.py
import os
import threading
from datetime import datetime
import pandas as pd
import ydata_profiling
from flask import render_template, session, jsonify, send_from_directory, url_for, current_app
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
def generate_unique_filename() -> str:
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    unique_filename = f"report_{timestamp}.html"
    return unique_filename

def generate_statistics_report(session_id: str, app) -> None:
    with app.app_context():
        data = {
            'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
            'Age': [24, 27, 22, 32, 29],
            'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
            'Score': [85, 90, 78, 88, 92]
        }

        merged_df = pd.DataFrame(data)

        profile = ydata_profiling.ProfileReport(merged_df)

        report_filename = generate_unique_filename()
        report_path = os.path.join(BASE_DIR, 'reports', report_filename)
        profile.to_file(report_path)

        session_data = session.get('sid')
        session_data['report_ready'] = True
        session_data['report_filename'] = report_filename
        session[session_id] = session_data


def statistics():
    session['sid'] = session.get('sid', os.urandom(16).hex())
    session_id = session.get('sid')
    session[session_id] = {
        'report_ready': False,
        'report_filename': None,
    }

    thread = threading.Thread(target=generate_statistics_report,
                              args=(session_id,
                                    current_app._get_current_object(), ))
    thread.start()
    return render_template('statistics.html')

def check_report_status():
    session_id = session.get('sid')
    session_data = session.get(session_id, {})
    if session_data.get('report_ready'):
        return jsonify({'status': 'ready', 'url': session_data.get('report_filename')})
    return jsonify({'status': 'processing'})

def download_report():
    session_id = session.get('sid')
    session_data = session.get(session_id, {})
    report_filename = session_data.get('report_filename')
    if report_filename:
        return send_from_directory(os.path.join(BASE_DIR, 'reports'), report_filename, as_attachment=True)
    return jsonify({'status': 'report_not_ready'})
<!-- /app/templates/statistics.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Statistics</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
        $(document).ready(function () {
            function checkReportStatus() {
                $.getJSON('{{ url_for("statistics.check_report_status") }}', function (data) {
                    if (data.status === 'ready') {
                        $('#loadingMessage').hide();
                        $('#reportFrame').attr('src', data.url);
                        $('#reportFrame').show();
                        $('#downloadButton').attr('href', data.url);
                        $('#downloadButton').show();
                        $('#downloadReportButton').show(); // Show the download button
                    } else {
                        setTimeout(checkReportStatus, 1000); // Check every second
                    }
                });
            }

            checkReportStatus();
        });
    </script>
</head>
<body>
<div class="container mt-5">
    <h1>Statistics</h1>
    <p>Statistics description</p>
    <div id="loadingMessage" class="text-center">
        <div class="spinner-border" role="status">
            <span class="sr-only">Loading...</span>
        </div>
        <p>Generating report, please wait...</p>
    </div>
    <!-- Download Button, initially hidden -->
    <a href="#" id="downloadButton" target="_blank" class="btn btn-primary my-3"
       style="display:none;">Open in new tab</a>
    <a href="{{ url_for('statistics.download_report') }}" id="downloadReportButton" class="btn btn-secondary my-3" style="display:none;"
       download>Download as file</a>
    <iframe id="reportFrame" style="width: 100%; height: 800px; display:none;"></iframe>
</div>
</body>
</html>

Upvotes: 0

Views: 23

Answers (0)

Related Questions