You should use a factory pattern, like this: [...] You can't use it at module level though (because there isn't an application context setup by that time), so this doesn't work: [...] Instead, hook up views inside your app factory: [...]
That’s basically what I did on my second iteration. It is an improvement in some respects, particularly breaking the circular dependencies caused by using the decorators on the global application singleton. On the other hand, now you need some variation of God Object that not only imports all your modules that used to have decorators but also knows enough about their internal implementation to set up the routes and things like pre- and post-request logic directly on the application object you get back from the factory.
The next logical step after that then seemed to be having each module/package that contains views or similar logic provide some sort of initialization function that is declared when you import the module and takes an application object as a parameter. Then we can use app.add_url_rule and friends to wire up the various handlers within each package/module but decoupled from any sort of global application object that needs the circular import. This is the tidiest style I’ve found so far, and all my Flask projects in recent years have used something broadly like it. It does only require one import followed by one initialization call for each package/module, which logically seems to be as good as we can get, given that our starting point is a desire to avoid including any initialization implicitly within the import itself and to avoid depending on global singletons.
Somehow, it still doesn’t quite feel right for some reason. I think it’s because even with that general design, I’ve still got a recurring pattern in each of how I create these modules and how I import and then initialize them. My instinct says we ought not to need that extra boilerplate in a highly dynamic language like Python, but I’ve yet to find any alternative that is neater in general. At least in the most simple cases this only adds a couple of extra lines (converting the decorators to an init function in each package/module, and then calling that function at the top level after importing the package/module), which is clearly better than the earlier, more highly connected designs.
That’s basically what I did on my second iteration. It is an improvement in some respects, particularly breaking the circular dependencies caused by using the decorators on the global application singleton. On the other hand, now you need some variation of God Object that not only imports all your modules that used to have decorators but also knows enough about their internal implementation to set up the routes and things like pre- and post-request logic directly on the application object you get back from the factory.
The next logical step after that then seemed to be having each module/package that contains views or similar logic provide some sort of initialization function that is declared when you import the module and takes an application object as a parameter. Then we can use app.add_url_rule and friends to wire up the various handlers within each package/module but decoupled from any sort of global application object that needs the circular import. This is the tidiest style I’ve found so far, and all my Flask projects in recent years have used something broadly like it. It does only require one import followed by one initialization call for each package/module, which logically seems to be as good as we can get, given that our starting point is a desire to avoid including any initialization implicitly within the import itself and to avoid depending on global singletons.
Somehow, it still doesn’t quite feel right for some reason. I think it’s because even with that general design, I’ve still got a recurring pattern in each of how I create these modules and how I import and then initialize them. My instinct says we ought not to need that extra boilerplate in a highly dynamic language like Python, but I’ve yet to find any alternative that is neater in general. At least in the most simple cases this only adds a couple of extra lines (converting the decorators to an init function in each package/module, and then calling that function at the top level after importing the package/module), which is clearly better than the earlier, more highly connected designs.