rootkit
rootkit

Reputation: 363

How do I fix 'Popped wrong app context' in Flask with APScheduler

I'm adding background tasks with APScheduler on runtime depending on an API call. In other words, there are no background tasks when the app, starts. When user makes call on an API, tasks are added on runtime. But I'm getting an error that says:

AssertionError: Popped wrong app context

The application works just fine if I comment out the lines where background tasks are scheduled. My app structure is as follows:

/project
  manage.py
  requirements.txt
  /app
    /models
    /routes
    /utils
    /api
    config.py
    __init__.py

My manage.py file looks like this:

app = create_app('dev')
app.app_context().push()
manager = Manager(app)
migrate = Migrate(app, db, render_as_batch=True)
manager.add_command('db', MigrateCommand)

with app.app_context():
    scheduler = BackgroundScheduler()
    scheduler.start()


@manager.command
def run():
    app.run()
    atexit.register(lambda: scheduler.shutdown())


if __name__ == '__main__':
    manager.run()

init.py inside app folder is:

from flask import Flask
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy

from email_scheduler.routes.routes import set_routes
from .config import config_by_name
# from app.models.task import TaskModel

db = SQLAlchemy()


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config_by_name[config_name])
    api = Api(app)
    set_routes(api)
    from email_scheduler.models.api_models import TaskModel, User
    db.init_app(app)
    with app.app_context():
        db.create_all()

    return app

My api.py file is:

class SignUp(Resource):
    def clean_scheduling_time(self, schedule_time):
        day = schedule_time.split(' ')[0].lower()[:3]
        hour, mins = schedule_time.split(' ')[1].split(':')
        return day, hour, mins

    def post(self):
        args = user_parser.parse_args()
        username, password = args.get('username'), args.get('password')
        schedule_time, email_to = args.get('schedule_time'), args.get('email_to')

        if username is None or password is None:
            abort(400)  # missing arguments
        from email_scheduler.models.api_models import User
        if User.query.filter_by(username=username).first() is not None:
            abort(400)  # existing user

        user = User(username=username, schedule_time=schedule_time.split(' ')[1], email_to=email_to)
        user.hash_password(password)
        user.save_to_db()

        from manage import scheduler
        from email_scheduler.utils.utils import send_email
        day, hour, mins = self.clean_scheduling_time(args.get('schedule_time'))
        trigger = CronTrigger(day_of_week=day, hour=int(hour), minute=int(mins))
        scheduler.add_job(send_email, trigger=trigger)
        print(scheduler.get_jobs())

        return make_response(jsonify({'username': username}), 200)

What's weird is that even though I get this error on the terminal, the task somehow gets scheduled and is run. And if I take out the code from api that schedules the tasks, the API runs just fine. What am I doing wrong?

Upvotes: 2

Views: 2440

Answers (1)

ilmoi
ilmoi

Reputation: 2534

The problem is in your manage.py file.

You're running the following line globally:

app.app_context().push()

Which you correctly need for the worker to have access to app context. Move it inside the function that the worker calls.

Ie NOT this:

app = create_app()
app.app_context().push()
def your_async_fn():
    # your code for the worker...

But this:

def your_async_fn():
    app = create_app()
    app.app_context().push()
    # your code for the worker...

Upvotes: 1

Related Questions