miguelgrinberg / microblog-api

A modern (as of 2024) Flask API back end.
MIT License
367 stars 99 forks source link

importing app context to different files #20

Closed tharrington closed 1 year ago

tharrington commented 1 year ago

Hi Miguel -

I have been hacking away at this for awhile and I can't seem to get this to work properly. Ultimately I am trying to have an after commit handler on one of my models:

# Define the create_job_after_commit function outside of the create_job_when_accepted function
@event.listens_for(db.session, 'after_commit')
def create_job_after_commit(session):
    with app.app_context():
        for target in session.new:
            if isinstance(target, Estimate) and target.status == 'Accepted':
                est = session.query(Estimate).get(target.id)

                # Extract the vendor_id from the loaded Estimate instance
                vendor_id = est.vendor_id
                job = Job(name=est.name, status='Upcoming', vendor_id=vendor_id, user_id=est.user_id)
                db.session.add(job)

The problem is I am getting an error: RuntimeError: Working outside of application context.

I've tried a bunch of different things to import the current app but to no success.

Initially I tried to do an event on the status field of my model, but this didn't give me access to the vendor_id or user_id fields... Oddly it did give me access to the name fields. I am 100% confident these fields are set when I initialize an estimate record:


@event.listens_for(Estimate.status, 'set')
def create_job_when_accepted(target, value, old_value, initiator):
    if value == 'Accepted' and old_value != 'Accepted':
        # Create a new Job instance
        job = Job(name=target.name, status='Upcoming', vendor_id=target.vendor_id, user_id=target.user_id)
        db.session.add(job)

Any help is appreciated.

miguelgrinberg commented 1 year ago

The problem is that db.session does not mean anything outside of a request. The error is there, not in how you use your application instance inside the function.

Try using db.Session, which is the session class.

tharrington commented 1 year ago

Is this really the best practice here? It seems like the latter code is much better.

miguelgrinberg commented 1 year ago

Is this really the best practice here? It seems like the latter code is much better.

This wasn't the question, at least I did not understand that you were asking me for an opinion on your two options.

Let me clarify. You said you were having application context errors due to not being able to import the app, and I corrected you. The issue is not with your function, it is with the decorator. You had:

@event.listens_for(db.session, 'after_commit')

And this cannot be resolved, because db.session represents a database session assigned to a request. The decorator is evaluated when the web server is starting up, so at that time there is no concept of a client or a request. That's the error. The event should be set on the session class, as follows:

@event.listens_for(db.Session, 'after_commit')

On the second option your function is called when a specific field is set. You can't really count on other fields being set in advance of your function being called because you can't control the order in which the fields are set. I think that might be the problem there. If you set an event handler on one attribute, to me it does not seem robust to assume other attributes are also going to be in an updated state.

tharrington commented 1 year ago

Yes, I understood your comment and was able to resolve the issue. Just wasn't sure on the best practice.

Thank you for your response!