In the Python web development world, we have become very familiar with the word 'framework.' Most of us are using at least one, and some of us are guilty of writing our own, as well.
Recently I was involved in an IRC discussion on the merits of the various web frameworks, and naturally this is about as vicious a conversation as discussing text editors of choice. A topic which came up during that discussion was the ability to start writing your code with a minimum of writing configs or other 'glue'. The ability to just "load up and start writing your app" or "import and use" is often touted, but this often comes with a serious compromise. This balancing act I like to think of as the 'boilerplate dilemma'.
One framework which offers a great 'load up and go' ability is Django. By
running their startproject command, one gets a skeleton project directory
with a lot of things taken care of, some default settings and structure. With
very little changes, you're writing your first view and template, and moving on
with things. Want to change some of the behavior? There's probably a
settings.py setting for that.
The way settings.py is found by the django framework is through an environment
variable, DJANGO_SETTINGS_MODULE. (there's an alternate way to configure
django, but it is not functionally much different) The django management script
takes care of setting that for you, so most django users don't have to know
about it and first. Now, when you import bits from django.db, you're getting
the correct database wrapper because your settings specified them. Models know
what database to create themselves in, and more. When you contrast this with
sqlalchemy's db setup, which oft requires you passing around things such as
MetaData and Engine instances, all of a sudden that DJANGO_SETTINGS_MODULE
compromise doesn't seem so bad.
And now you have fallen into the trap. The issue with the Django config model
is that your settings are now essentially bound to that interpreter, making it
impossible for other code in the same interpreter to use different settings.
For those who want to create reusable Django apps, it's hard to just ship your
package and say to the user "Just import my module", they probably have to add
it to INSTALLED_APPS, TEMPLATE_DIRS, and some other fun wiring. Sometimes
the existence or lack thereof of a setting like APPEND_SLASH can make your
application completely incompatible with the project which embeds it.
Let me quickly scoot over to a different way of handling your framework setup: It can be done via instantiation and dependency injection. Jinja2 is a very good example of this put into practice.
from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader('myapp/templates')) env.globals['url'] = my_url_builder env.globals['get_notices'] = get_notices env.filters['escape_php'] = do_escape_php
With just a little configuration, you now have your own personal Jinja2 environment, which you can pass around as you like, and is separate from other Jinja2 environments which might be instantiated in the same process; you don't have to care. Sure, it looks like glue, but it's much more powerful. In addition, the design of Jinja2 allows for bits to be easily overridden and swapped out: different loaders, extensions, more, not to mention the ease of passing in mocks for testing.
The reason I've been pondering these issues so deeply is that I'm currently in the process of writing my own library/webapp, which I will make available on bitbucket soon-ish. What it will be is both a library you import/use, and an included optional WSGI application to view/manipulate the data it creates. For the WSGI application portion, I will be employing Jinja2 and Werkzeug. While it would not be impossible to use Django for this; the global-settings issue would ironically make it hard for Django users to include my standalone application.
I've no doubt in my mind that I will be using a dependency injection model of configuration for my library/app, which confers some additional benefits: People already using Jinja or wanting to customize bits and pieces will be able to pass in their own loader and/or environment to override my templates and tags, or even integrate them with their own base templates. At the same time, I can provide sensible defaults for those who don't need the customization.
And you can have your cake and eat it too. Using your own factory or a submodule of your project, you can support taking the django-settings to configure it:
from django.conf import settings def django_factory(**kwargs): kwargs.setdefault('debug', settings.DEBUG) kwargs.setdefault('admin_emails', [x[1] for x in settings.ADMINS]) if 'jinja_env' not in kwargs: kwargs['jinja_env'] = Environment(...) return MyFrameworkConfig(**kwargs)
Now you've created a highly reusable componentized application, which can even sense defaults from its users' framework, avoiding repetition.
So I appease you, library authors, don't succumb to the temptation to use module globals instead of passing around dependencies. There are those of us out there who'd like to use your libraries, and can't because you've locked your implementation to a restricted use-case when you didn't have to. Hopefully we don't have to make 'framework' become a dirty word.
Note: I want to make it clear that I'm not hating on Django, in fact I really like Django. As a full-stack framework, it gets a lot of things right, and to new users I will recommend it without hesitation. It's really fun to use, and the right thing to get people to learn how good a python framework can really be. But as a library author myself, sometimes I find it trying to support Django while also supporting other frameworks in my code.