As an example for discussion, the first time I wrote a Flask-based back-end, I backed myself into a corner almost immediately in the following way.
Firstly, the WSGI file that the web server uses to start the application followed the suggestion in the Flask docs by doing this:
# webserverseesthis.wsgi
from yourapplication import app as application
That’s not so bad, but then I started doing application configuration and loading various Flask plug-ins as side effects of that import: # yourapplication/__init__.py
app = Flask("yourapplication")
# Do some general application configuration.
app.config.from_pyfile("/path/to/configuration/file")
# Set up some overarching security things that modify application behaviour.
from flaskext.securityplugin import SecurityPlugin
sp = SecurityPlugin(app)
This seemed at the time like the obvious place to put such things, but of course, this is really just a variation on the mistake we’re discussing here.To compound the error, I then used Flask’s decorators to wire up routes from various URLs to the relevant parts of my code. Those decorators work on the application object (sticking with ideas common to many Python web frameworks and avoiding getting into anything more Flask-specific like blueprints) so I was effectively creating circular dependencies from almost everything to that top-level package:
# yourapplication/pages/home.py
from yourapplication import app
@app.route('/')
def home_page():
# Render home page
and then from the top-level package onto almost everything so all those decorators could take effect: # After setting up the application object in yourapplication/__init__.py
import yourapplication.pages.home
Now, as long as this kind of code only ever runs as a WSGI application behind a web server, you get away with these dependencies up to a point. In practice, your WSGI set-up imports the top-level application package, which in turn sets up the application object everything is going to depend on and only then imports all the supporting modules/packages, and everything “works”.However, as soon as you want to write tests or otherwise reuse any of the code in a different context, the entire system is a big bowl of spaghetti with all the usual problems. The moment you import any part of the system to run a unit test on something in it, you get much of the rest of the system as well, complete with the side effects of any imports therein.
This was of course all horribly naïve on general programming principles, but the nature of these frameworks tends to push in this direction, and even Flask’s own documentation features various simple examples that follow a similar approach, so I’ll forgive myself for falling into the trap the first time. I’ve since experimented with various techniques to break the cycles and avoid the side effects on imports, with some success, but frankly I’ve never found a satisfying, general strategy for organising larger code bases built around a web framework.
How is everyone else doing this?