Python Forum
Why is this import running twice?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Why is this import running twice?
#1
I'm new to python and am trying to migrate my flask app to a package structure.

I have run.py sitting in a directory alongside a /myproject directory.:
from myproject import app

if __name__ == '__main__':
    app.run()
When I run python run.py, the debugger confirms that the import runs first on line 1 from myproject import app (as I would expect), and then again when app.run() is executed (as I would not expect). This is a problem because one of the imported modules declares a global object that is only allowed to be instantiated once..

/myproject contains the modules along with an __init__.py, which imports routes.py:

from flask import Flask
app = Flask(__name__)
from myproject import routes
I apologize if this should be obvious, but why is the import happening twice, and can it be prevented?
Reply
#2
How does this work?
from flask import Flask
app = Flask(__name__)
from myproject import routes
routes needs to know about app, but app is only defined in myproject. routes cannot import myproject because that is a circular import. Could you post your routes.py file?
Reply
#3
Hi Dean, thanks for your reply!

Here's what routes.py looks like:
import os, subprocess
from flask import render_template, request, redirect, url_for, jsonify
from myproject import app, db
from myproject.models import Session, Utterance
from myproject.mymodule import myfunction  #this module contains a global that can only be instantiated once
Note that my previous post abbreviated __init__.py for clarity. The entire script also instantiates a db as so:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
# configure the SQLite database, relative to the app instance folder
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

from myproject import routes
I was thinking that since __init__.py defines app before it import routes, it would at that point be safe to import app from inside routes.py (without it becoming circular).

ie, the interpreter sees app in __initi__.py, then import routes. Inside routes.py, when it sees import app, it says, 'ok, I already know about app' and all is well...No?
Reply
#4
Ok, I understand what you are doing.

I'll use a super simple example to demonstrate why app is getting initialized twice.

routes.py
print("top of routes")
from run import app
print("routes app =", app)
print("end of routes")
run.py
print("top of run")
app = None
print("Initialize app", __name__)
app = 1
import routes
print("end of run")
Output:
> python run.py top of run Initialize app __main__ top of routes top of run Initialize app run end of run routes app = 1 end of routes end of run
Notice "app" is initialized twice; once by __main__ and once by run. app is initialized when run.py is loaded into Python, converted into bytecode and executed. app is also initialized when run imports routes, which in turn imports run, There is no "cached" version of run (yet), so Python opens run.py and to get all the symbols so it can create a module object.

You might think "I'll just put an if __name__ == "__main__" in there to prevent this from happening.

run.py
print("top of run")
app = None
if __name__ == "__main__":
    print("Initialize app", __name__)
    app = 1
    import routes

print("end of run")
But that doesn't work.
Output:
> python run.py top of run Initialize app __main__ top of routes top of run end of run routes app = None end of routes end of run
Now app is only initialized once, but it has the wrong value in routes. run is not completely defined, so when route imports run, it opens the run.py file, converts to bytecodes, and executes the file. Now tht "app = 1" is hidden behind "if __name__ == "__main__":", that code is not executed, and the value for app, as far as the run "module" is concerned, is None.

My takeaway is that circular references are bad. At best they are confusing. At worst they don't work as you expect. Usually they just raise an import exception. If you have a circular reference, reorganize to remove it. An obvious solution in your code is have routes create the Flask application, preferably in a function or a class that you would call from your main program.
Reply
#5
I see. So it runs the module again, even if it's seen it before.

But I'm still a little confused. If it weren't for this side effect coming from the definition outside of a function or class, would this be considered a problem in python-world? I mean, it runs the import twice, but then seems to continue merrily along... Is that considered ok?
Reply
#6
I suggest to sanitize the import structure

# myproject/app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
 
app = Flask(__name__)
# configure the SQLite database, relative to the app instance folder
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
Then
#myproject/__init__.py
from .app import app, db
from . import routes
finally
# myproject/routes.py
from .app import app, db
...
Reply
#7
Thank you both for your detailed descriptions and suggestions! (and sorry, Dean, for replying before you had a chance to finish your edits ;-)

There is something about this that my brain is still resisting, but I think it's starting to get clearer. With Dean's "most obvious" suggestion, moving app and db into routes removes the need for any imports between them, and therefore the problem with circular references. In Gribouillis' "less obvious (but more general)" example, the idea is to isolate the two modules that want to refer to each other (routes.py and a new app.py), and then let them find each other by importing them both into __init__.py.

But now I'm confused about run.py, which used to execute app.run(). If I python run.py
from myproject import app
 
if __name__ == '__main__':
    app.run()
Am I not still going to see the imported modules run twice?
Reply
#8
oops, I guess run.py would now just be
from myproject import app
app.run()
Reply
#9
I don't mean to be an annoyance, but I feel like I'm close to getting this. I just could use a nudge to help me over the line...

Thanks to the help of @deanhystad and @Gribouillis and implementing Gribouillis' suggestion, I think I've eliminated the my circular reference and __init__.py should be ready to go:
#myproject/__init__.py
from .app import app, db
from . import routes
But I don't understand how to execute app.run(). If I try
from myproject import app
if __name__ == '__main__':
    app.run()
The import will again run twice. What am I missing??
Reply
#10
If you really want to separate creating your app from creating your routes, read about "blueprints"

https://flask.palletsprojects.com/en/1.1.x/blueprints/

For a small project I would put app and route creation in the same file. But if you really want to split this up put the app = Flask() in it's own file that is imported by both routes and __init__.

file main.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
 
app = Flask(__name__)
# configure the SQLite database, relative to the app instance folder
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
file routes.py
import os, subprocess
from flask import render_template, request, redirect, url_for, jsonify
from myproject.models import Session, Utterance
from main import app, db

# add some routes
file __init__.py
from main import app
import routes

app.run()
I really don't like importing a module just to have it do secret setup work behind closed doors. Modules should contain functions and classes, they should not look like Python scripts. To remove some of the stench I would write a function that adds the routes. For a simple example I'll add a route to the Hello World example from the Flask documentation.
main.py
from flask import Flask
import routes

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

routes.add_routes(app)

app.run()
routes.py
def add_routes(app):

    @app.route('/goodbye')
    def goodbye_world():
        return 'Goodbye, World!'
This works because goodbye_world() is defined inside a scope defined by the add_routes() function. This scope has a "global" variable named "app" which is also an function parameter.

Maybe to-may-to/to-mah-to.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  code keeps running if i use from threading import timer? birddseedd 3 2,612 Jan-25-2019, 05:00 AM
Last Post: stullis
  Running pytest gives " 'cannot import name 'session' " error jasonblais 2 3,636 Oct-10-2018, 05:02 PM
Last Post: jasonblais

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020