Ticket #1810: django-trunk-r2844-dsn.patch
File django-trunk-r2844-dsn.patch, 12.7 KB (added by , 19 years ago) |
---|
-
django/conf/project_template/settings.py
15 15 DATABASE_PASSWORD = '' # Not used with sqlite3. 16 16 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 17 17 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 18 DATABASE_DSN = '' # Overrides above settings. Examples: 19 # 'postgres://joe:s3cr3t@localhost:8001/addressbook' 20 # 'sqlite3://:memory:' 21 # 'sqlite3:///path/to/addressbook.db' 18 22 19 23 # Local time zone for this installation. All choices can be found here: 20 24 # http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -
django/conf/__init__.py
8 8 9 9 import os 10 10 import sys 11 import urllib 11 12 from django.conf import global_settings 12 13 13 14 ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" … … 55 56 56 57 # move the time zone info into os.environ 57 58 os.environ['TZ'] = self.TIME_ZONE 59 60 # inject DSN attributes only if they were declared ... 61 if getattr(self, 'DATABASE_DSN', None): 62 for k,v in parse_dsn(self.DATABASE_DSN).items(): 63 if v is not None: 64 setattr(self, 'DATABASE_%s' % k.upper(), v) 58 65 66 def parse_dsn(dsn): 67 """returns dict with keys: engine, name, user, password, host, port. 68 69 The keys match the global setting names prefixed with DATABASE_. 70 For example, ``name`` corresponds to ``DATABASE_NAME``. 71 For any value not parsed from the dsn, its value is set to None. 72 73 Since there is no real RFC on the DSN format (that I could find), 74 The format here is based on the `URL RFC`_ and typical DSN handling. 75 76 Some things to note ... 77 The URL RFC says the ``//`` part of ``scheme://path`` denotes an IP-accessible 78 resource so to make things simpler for local engines, you can leave out 79 the ``//`` or use ``/`` instead. 80 Thus, ``sqlite:/:memory:`` is valid, as is ``postgres:``. 81 See the unit tests for a complete list of supported DSN formats. 82 83 .. _URL RFC : http://www.freesoft.org/CIE/RFC/1738/index.htm 84 85 @author Kumar McMillan <kumar dot mcmillan / gmail.com> 86 87 """ 88 parsed = dict(engine=None, name=None, user=None, password=None, host=None, port=None) 89 90 def parse_host(host): 91 if host.find(':') != -1: 92 parsed['host'], parsed['port'] = host.split(':') 93 else: 94 parsed['host'] = host 95 96 def parse_name(name): 97 if name.find('?') != -1: 98 parsed['name'], query = urllib.splitquery(name) 99 for attr in query.split('&'): 100 k,v = urllib.splitvalue(attr) 101 parsed[k] = urllib.unquote_plus(v) 102 else: 103 parsed['name'] = name 104 105 def parse_user(user): 106 if user.find(':') != -1: 107 parsed['user'], parsed['password'] = user.split(':') 108 parsed['password'] = urllib.unquote_plus(parsed['password']) 109 else: 110 parsed['user'] = user 111 parsed['user'] = urllib.unquote_plus(parsed['user']) 112 113 try: 114 esep = dsn.find(':') 115 if esep == -1: 116 raise ValueError, 'no engine detected; expected "engine://"' 117 parsed['engine'] = dsn[0:esep] 118 119 post_scheme = dsn[esep+1:] 120 # pop off the two optional slashes ... 121 for n in range(2): 122 if len(post_scheme) and post_scheme[0] == '/': 123 post_scheme = post_scheme[1:] 124 125 if post_scheme == '': 126 return parsed 127 128 if parsed['engine'].lower().startswith('sqlite'): 129 # special case since we can't split on slashes : 130 parsed['name'] = post_scheme 131 return parsed 132 133 parts = post_scheme.split('/') 134 if len(parts) == 1: 135 parse_name(parts[0]) 136 return parsed 137 138 domain, name = parts 139 140 if domain.find('@') != -1: 141 user, host = domain.split('@') 142 parse_user(user) 143 parse_host(host) 144 parse_name(name) 145 return parsed 146 147 parse_host(domain) 148 parse_name(name) 149 return parsed 150 except: 151 # append DSN to any exception raised .. 152 etype, val, tb = sys.exc_info() 153 val = "%s (dsn: '%s')" % (val, dsn) 154 raise etype, val, tb 155 59 156 # try to load DJANGO_SETTINGS_MODULE 60 157 try: 61 158 settings_module = os.environ[ENVIRONMENT_VARIABLE] -
tests/othertests/conf.py
1 2 """ 3 test the conf module ... 4 5 """ 6 7 from django.conf import Settings 8 from django.conf import parse_dsn 9 from django.conf.project_template import settings as default_settings 10 from django.conf import global_settings 11 import urllib 12 13 def assert_eq(val1, val2): 14 assert val1 == val2, "'%s' != '%s'" % (val1,val2) 15 16 def test_parse_dsn(): 17 def assert_raises(fn, exc, args=tuple(), kw={}): 18 try: fn(*args,**kw) 19 except exc: pass 20 else: raise AssertionError("exected call to %s to raise %s" % (fn,exc)) 21 22 assert_raises(parse_dsn, ValueError, ('',)) 23 assert_raises(parse_dsn, ValueError, ('postgres',)) 24 25 assert_eq( parse_dsn('postgres:'), 26 dict( engine='postgres', name=None, user=None, 27 password=None, host=None, port=None)) 28 assert_eq( parse_dsn('postgres:/'), 29 dict( engine='postgres', name=None, user=None, 30 password=None, host=None, port=None)) 31 assert_eq( parse_dsn('postgres://'), 32 dict( engine='postgres', name=None, user=None, 33 password=None, host=None, port=None)) 34 assert_eq( parse_dsn('PostGres://'), 35 dict( engine='PostGres', name=None, user=None, 36 password=None, host=None, port=None)) 37 assert_eq( parse_dsn('postgres://mydbname'), 38 dict( engine='postgres', name='mydbname', user=None, 39 password=None, host=None, port=None)) 40 41 assert_eq( parse_dsn('sqlite:/:memory:'), 42 dict( engine='sqlite', name=':memory:', user=None, 43 password=None, host=None, port=None)) 44 assert_eq( parse_dsn('sqlite://:memory:'), 45 dict( engine='sqlite', name=':memory:', user=None, 46 password=None, host=None, port=None)) 47 assert_eq( parse_dsn('sqlite:///path/to/db'), 48 dict( engine='sqlite', name='/path/to/db', user=None, 49 password=None, host=None, port=None)) 50 assert_eq( parse_dsn('sqlite3:///path/to/db'), 51 dict( engine='sqlite3', name='/path/to/db', user=None, 52 password=None, host=None, port=None)) 53 assert_eq( parse_dsn('sqlite://relpath/to/db'), 54 dict( engine='sqlite', name='relpath/to/db', user=None, 55 password=None, host=None, port=None)) 56 57 assert_eq( parse_dsn('mysql://myhost/dbname'), 58 dict( engine='mysql', name='dbname', user=None, 59 password=None, host='myhost', port=None)) 60 assert_eq( parse_dsn('mysql://me@myhost/dbname'), 61 dict( engine='mysql', name='dbname', user='me', 62 password=None, host='myhost', port=None)) 63 assert_eq( parse_dsn('mysql://me:mypass@myhost/dbname'), 64 dict( engine='mysql', name='dbname', user='me', 65 password='mypass', host='myhost', port=None)) 66 assert_eq( parse_dsn('mysql://me:mypass@myhost:9000/dbname'), 67 dict( engine='mysql', name='dbname', user='me', 68 password='mypass', host='myhost', port='9000')) 69 70 # always decode ?? or do it lazily. right now we are doing it always 71 p = parse_dsn('postgres://%s:%s@localhost/mydbname' % \ 72 ( urllib.quote_plus('wil/@:dman'), 73 urllib.quote_plus('//craazy%@passW'))) 74 assert_eq( p, dict( engine='postgres', name='mydbname', user='wil/@:dman', 75 password='//craazy%@passW', host='localhost', port=None)) 76 # check a literal password with percent sign ... 77 assert_eq( parse_dsn('postgres://me:me%4pas@localhost/mydbname'), 78 dict( engine='postgres', name='mydbname', user='me', 79 password='me%4pas', host='localhost', port=None)) 80 81 p = parse_dsn( 'mysql:///?host=localhost&name=dbname&' 82 'user=willy&password=pzzwd&port=8000') 83 assert_eq( p, dict( engine='mysql', name='dbname', user='willy', 84 password='pzzwd', host='localhost', port='8000')) 85 p = parse_dsn( 'mysql:///dbname?host=localhost&' 86 'user=willy&password=%s&port=8000' % urllib.quote('pa@swd')) 87 assert_eq( p, dict( engine='mysql', name='dbname', user='willy', 88 password='pa@swd', host='localhost', port='8000')) 89 # just a sanity check to make sure ? params override ... 90 p = parse_dsn( 'mysql://noop:bad@otherhost:81/?host=localhost&name=db&' 91 'user=me&password=cheese&port=80') 92 assert_eq( p, dict( engine='mysql', name='db', user='me', 93 password='cheese', host='localhost', port='80')) 94 95 def test_database_settings(): 96 global global_settings, default_settings 97 98 # this is silly. decouple Settings() a little more? 99 saved_global_settings = global_settings 100 saved_default_settings = default_settings 101 try: 102 settings_path = 'django.conf.project_template.settings' 103 104 my_settings = default_settings 105 my_settings.DATABASE_DSN = 'postgres://me:secr3t@supercool.com.fr:8001/postcards' 106 107 s = Settings(settings_path) 108 assert_eq( s.DATABASE_ENGINE, 'postgres') 109 assert_eq( s.DATABASE_NAME, 'postcards') 110 assert_eq( s.DATABASE_USER, 'me') 111 assert_eq( s.DATABASE_PASSWORD, 'secr3t') 112 assert_eq( s.DATABASE_HOST, 'supercool.com.fr') 113 assert_eq( s.DATABASE_PORT, '8001') 114 115 # we also want to only override what the dsn declares : 116 my_settings.DATABASE_DSN = 'postgres://me@supercool.com/postcards' 117 my_settings.DATABASE_USER = 'kumar' 118 my_settings.DATABASE_PASSWORD = 'boggle' 119 120 s = Settings(settings_path) 121 assert_eq( s.DATABASE_USER, 'me') 122 assert_eq( s.DATABASE_PASSWORD, 'boggle') 123 finally: 124 global_settings = saved_global_settings 125 default_settings = saved_default_settings 126 127 # hmm .. 128 test_parse_dsn() 129 test_database_settings() 130 No newline at end of file -
docs/settings.txt
282 282 283 283 The username to use when connecting to the database. Not used with SQLite. 284 284 285 DATABASE_DSN 286 ------------ 287 288 Default: ``''`` (Empty string) 289 290 DSN (Data Source Name) string to set all configuration values at once. 291 For example, ``postgres://joe:s3cr3t@localhost:8001/addressbook`` will set the following : 292 293 - ``DATABASE_ENGINE='postgres'`` 294 - ``DATABASE_USER='joe'`` 295 - ``DATABASE_PASSWORD='s3cr3t'`` 296 - ``DATABASE_HOST='localhost'`` 297 - ``DATABASE_PORT='8001'`` 298 - ``DATABASE_NAME='addressbook'`` 299 300 Examples of other valid DSNs : 301 302 - ``sqlite3://:memory:`` or ``sqlite3:/:memory:`` 303 - ``sqlite3:///path/to/my/db`` 304 - ``mysql:///?host=localhost&user=joe&password=s3cr3t&port=8001&name=addressbook`` 305 - ``postgres:`` (i.e. everything blank, tell postgres to open a default connection for the current user) 306 307 Any value retrieved from parsing a non-empty DSN string will replace a value 308 already set in its corresponding variable. In other words, ``mysql://joe@localhost/joedb`` 309 will replace the values (if any) of DATABASE_ENGINE, DATABASE_USER, 310 DATABASE_HOST and DATABASE_NAME. However, it will **not** replace a value 311 in DATABASE_PORT. Also note that *USER* and *PASSWORD* must be url encoded 312 if they contain characters like ``@``, ``:``, etc (see built-in ``urllib.quote_plus()``). 313 285 314 DATE_FORMAT 286 315 ----------- 287 316