-
-
Notifications
You must be signed in to change notification settings - Fork 33.1k
Fixed #28236 - Integrate dj-database-url into Django #8562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✨🍰✨ |
Let's get some docs added to this, @orf! |
I'm a bit busy with my filtered aggregates stuff right now, but I've been
following the discussions on the mailing list. There are some good ideas
I'd like to try first before I make any docs.
I would also really like to figure out a generic way of handling other
settings, like caches, and I've got a few ideas about handing off specific
parts of the parsing to related classes.
|
👍 |
If there's anything I can do to help, let me know! |
django/utils/database_url.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've finally got around to pushing what I've got so far - sorry about the delay. So my idea is to add a I think a class based solution is best here, and keeping it inside the database class itself is a good idea. I'll keep working on it, but please let me know what you think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these tests don't actually connect to a database, is it possible to remove the skipUnless
(here and below)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The downside of this approach is that the DB module needs to be imported, which will fail unless the DB-specific adapters are installed (psycopg2
etc).
django/db/backends/__init__.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could register_db_backend
be a decorator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above, these need to be available pre-import
is this going to be make in django 2.0? |
Unfortunately not. There is a fair bit to do on the PR to get it up to scratch. |
django/db/backends/oracle/base.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be classmethod
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, good catch 👍
django/utils/url_config.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps TypeError
would be more appropriate here and in the next place below? Having a method is usually a property of a type.
django/utils/url_config.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a user who writes his own backend makes a typo for backend_type
, this function will return without error but the registration will mysteriously not have worked. The two options I see are to make a whitelist of allowed backend_type
s, or make this function private (by adding an underscore in front of the name) and just expose the convenience functions such as register_db_backend
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I've made them private.
django/utils/url_config.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function's name starts with "add" but the convenience functions' names start with "register". Would it help to keep these uniform?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the base class's method, it checks whether URL
is already parsed. Does this class need to do the same check before calling urlparse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also the SQLite backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit iffy, but I added the check to only the base class so that if the method was over-ridden and the url
was parsed the result could be passed in without needing to re-call urlparse
. config_from_url
, when over-ridden, is always going to parse the URL, but the public API should expect only strings to be passed in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if someone wants to subclass the Postgres backend directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. :<
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ is used elsewhere for gettext. Maybe better to just take the [0] slice instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you define dsn once and do url += dsn to reduce duplication?
Thank you for your review @wkschwartz! All your comments seemed very reasonable so I've made the changes requested. If anyone else has any time to review this it would be awesome. Documentation still needs to be added, but this could be ready for 2.0. |
I've added support for configuring caches as well. It's a little bit more tricky than database configurations. I ended up creating a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know much about caches, so someone else should review those parts of the code.
Thanks for working on this @orf! I'm looking forward to this feature being baked into Django.
django/utils/url_config.py
Outdated
|
||
BACKENDS = defaultdict(dict) | ||
|
||
BaseParsedURL = namedtuple('ParsedURL', 'scheme username password hostname port path options') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you inherit from urllib.parse.ParseResult
to make the class hierarchy easier for people already familiar with the stdlib's data types? Then you could do away with the namedtuple
import and simply do
from urllib import parse
class ParseResult(parse.ParseResult):
@property
def netloc(self):
# ...what you had written below...
This should override ParseResult.netloc
correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yes. I remember why I did all this, it's because really the ParseResult is pretty unsuited for this due to a lot of annoying quirks. I think it should be a class entirely distinct from ParseResult.
@@ -0,0 +1,135 @@ | |||
import inspect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make more sense to have url_config
go in django/conf/
? If you do that, url_config
might be a confusing name. Maybe something like services_url_config
? I'm trying to think of something (like "services") that captures both DBs and caches, and the fact that they might be hosted somewhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think you're right, I think it should be under conf
. service_url
sounds like a good module name I think?
django/utils/url_config.py
Outdated
|
||
class ParsedURL(BaseParsedURL): | ||
@property | ||
def netloc(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helpful if this should be a public class: A docstring for either ParsedURL
, netloc
, or both, explaining what netloc
does and how ParsedURL
differs from urllib.parse.ParseResult
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should definitely have a doc-string, because I've completely forgotten how it differs....!
django/utils/url_config.py
Outdated
return BACKENDS[backend_type][scheme] | ||
|
||
|
||
def configure(backend_type, value): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should configure
be private in the same way/for the same reason that _register_backend
is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep 👍
|
||
|
||
def register_db_backend(scheme, path): | ||
if not path.endswith('.base.DatabaseWrapper'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find the documentation for how to make a database or cache backend. I'm guessing this is not the only place that explicitly requires this path, but if I'm wrong, should we document this for anyone looking to create their own backends?
The other thing I just noticed is that Thoughts? Is something similar necessary for the cache configurator (at least for uniformity's sake)? (I don't know me much caches.) |
I think that needs some thought @wkschwartz, but I agree it's a good feature to have. Implementing overriding individual attributes would be easy, i.e `password='abc', but how we handle nested options (like under 'OPTIONS' etc) would be tricky. Also, thanks for the review, I've fixed up the branch, rebased it and got the tests to pass. I've made a lot of changes but I think it's a lot neater now. The biggest one was removing the named tuple and instead returning a simple, mutable dictionary from I'll spend some more time fixing up a few small things, any feedback on the code would be much appreciated. |
|
||
def close(self, **kwargs): | ||
# libmemcached manages its own connections. Don't call disconnect_all() | ||
# libmemcached managepip install pipenvs its own connections. Don't call disconnect_all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"managepip"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh nuts, I accidentally pasted that there and didn't spot it, I'll remove it.
Maybe we don't even need to support all possible option overrides, just the most common, such as DATABASES = {'default': configure_db(os.environ['DATABASE_URL'])}
DATABASES['default']['OPTIONS'] = {'FOO': 'bar'} |
@orf Need help here? |
Hey @wlonk, feel free to take the ticket and have a go. This is on my to-do list but it’s pretty far down, so please anyone who wants to build on this go for it! |
Closing due to inactivity. |
Ticket: #28236
This PR integrates dj-database-url into Django, allowing users to configure their caches and databases via URLs:
It's extensible, allowing third party adapters to plug in and extend the parsing. It does this by using a
config_from_url
static method to the base cache adapters and DB wrappers, which allows subclasses to adapt the result - i.e mysql parses some options from the querystring into specific configuration options.The test suite is copied/adapted from dj-database-url as are a lot of the quirks parsing, so thanks to @kennethreitz for those.
To-Do: