Hi folks,
Let me make a stab at the 1000ft view for this.
TLDR: Django modules should work as libraries (e.g. ORM, mail, etc). "from django.conf import settings" bootstrap undermines this. unsetting begins a path to support the legacy structure, but still allows for the 'librarification' of Django modules. Then, we can move on with more options to whether we want to refactor things or not and how.
For the TLDR code entry point check out:
On to the 'too long' version:
This work started at the Chicago DjangoCon sprint this past September. Justin Holmes and I were talking to Adrian (and then roped in Jacob). After discussing the problem and working out a possible way to move forward, it seemed like they both liked the idea. Even more promising, during the sprint, Justin and I found that it was *working* as we started attacking different sections.
So, what is unsetting? Basically, we see it as a problem that a large amount of Django code is not structured in 'library' form. In order for most django libraries to be imported, settings need to be bootstrapped.
Jacob's use-case was "I'd like to import django.core.mail and use it even if I'm not running Django"
My use-case is Django's awesome (yes, I know opinions differ), simple-to-use ORM.
Stipulating that Django should work as a library without bootstrapping with settings, how do we get there? We have some different options:
1. Refactor all the libraries to be more functional, and somehow pass settings through either the request object or other 'world-state' passing
2. A different refactor project that would move around all the code, so all the 'good' code is functional, and then all the public interfaces get the settings and call the 'good' code functionally. Outside importers would then import the 'good' modules directly.
3. Unsetting: decorate all the methods/classes that use setting so they map to newly functional arguments. If setting is there, and the functional arguments aren't sent in, then the setting bootstrap isn't necessary.
Attacking #1 and #2. First, #1 would be controversial at every step, and for every module. However world-state was passed around, it would be very difficult to make it backwards compatible. #2 would mostly result in a documentation issue -- where to import from would be different for non-django importers than those in django. This is not even to mention the settings references in several __init__.py files.
The general idea with #3 is trying to separate out the setting dependency *first*, and then opportunistically over time, we can start looking for opportunities to pass settings in a more functional way through our call chain.
django.utils.unsetting has the following example of how to migrate older code with some 'magical' decorators:
The typical use case is a an old function/method looks like:
def foobar(a, b, c=None):
if getattr(settings, 'FOO_ENABLE_C', False):
do_something(c)
...
This decorator makes it easy to remove the need for a django settings import.
You would modify this function above to something like:
@uses_settings('FOO_ENABLE_C', 'enable_c')
def foobar(a, b, c=None, enable_c=False):
if enable_c:
do_something(c)
...
For the first trial at this approach, we tried out utils.timezone:
and it seemed to have worked, so we moved on to django.core.mail, and other modules.
Actually, I, myself, have mostly been sitting on my butt the last few months. However Justin and James have been working through other modules proving that this pretty light approach can work with no backwards incompatibility.
So, what do y'all think? And let us know if we can answer any other questions about the approach.
cheers,
sky