:func:`.make_url` function **no longer URL encode the password**.
Database passwords that include characters like spaces, plus signs
and anything else should now represent these characters directly,
without any URL escaping. [ticket:2873]
.. changelog::
:version: 0.9.0b2
+ .. change::
+ :tags: bug, engine
+ :tickets: 2873
+
+ The :func:`.create_engine` routine and the related
+ :func:`.make_url` function **no longer URL encode the password**.
+ Database passwords that include characters like spaces, plus signs
+ and anything else should now represent these characters directly,
+ without any URL escaping.
+
+ .. seealso::
+
+ :ref:`migration_2873`
+
+
.. change::
:tags: bug, orm
:tickets: 2872
and SQLAlchemy version 0.9, which is expected for release
in late 2013.
- Document last updated: October 23, 2013
+ Document last updated: November 24, 2013
Introduction
============
and also documents changes which affect users migrating
their applications from the 0.8 series of SQLAlchemy to 0.9.
-Version 0.9 is a faster-than-usual push from version 0.8,
-featuring a more versatile codebase with regards to modern
-Python versions. See :ref:`behavioral_changes_09` for
+Please carefully review
+:ref:`behavioral_changes_orm_09` and :ref:`behavioral_changes_core_09` for
potentially backwards-incompatible changes.
Platform Support
:ticket:`2161`
-.. _behavioral_changes_09:
+.. _behavioral_changes_orm_09:
Behavioral Changes - ORM
========================
:ticket:`2787`
+.. _behavioral_changes_core_09:
Behavioral Changes - Core
=========================
+.. _migration_2873:
+
+The "password" portion of a ``create_engine()`` URL is no longer URL encoded
+----------------------------------------------------------------------------
+
+For whatever reason, the Python function ``unquote_plus()`` was applied to the
+"password" field of a URL, likely as a means of allowing the usage of escapes
+(e.g. "%2F" or similar) to be used, and perhaps as some way of allowing spaces
+to be present. However, this is not complaint with `RFC 1738 <http://www.ietf.org/rfc/rfc1738.txt>`_
+which has no reserved characters within the password field and does not specify
+URL quoting - so the quote_plus routines are **no longer applied** to the password
+field.
+
+Examples of URLs with characters such as colons, @ symbols, spaces, and plus signs
+include::
+
+ # password: "pass word + other:words"
+ dbtype://user:pass word + other:words@host/dbname
+
+ # password: "apples%2Foranges"
+ dbtype://username:apples%2Foranges@hostspec/database
+
+ # password: "apples@oranges@@"
+ dbtype://username:apples@oranges@@@hostspec/database
+
+ # password: '', username is "username@"
+ dbtype://username@:@hostspec/database
+
+
+:ticket:`2873`
+
+
+
.. _migration_2850:
A bindparam() construct with no type gets upgraded via copy when a type is available
if self.username is not None:
s += self.username
if self.password is not None:
- s += ':' + ('***' if hide_password
- else util.quote_plus(self.password))
+ s += ':' + ('***' if hide_password else self.password)
s += "@"
if self.host is not None:
if ':' in self.host:
(?P<name>[\w\+]+)://
(?:
(?P<username>[^:/]*)
- (?::(?P<password>[^/]*))?
+ (?::(?P<password>.*))?
@)?
(?:
(?:
query = None
components['query'] = query
- if components['password'] is not None:
- components['password'] = \
- util.unquote_plus(components['password'])
-
ipv4host = components.pop('ipv4host')
ipv6host = components.pop('ipv6host')
components['host'] = ipv4host or ipv6host
'dbtype://',
'dbtype://username:password@/database',
'dbtype:////usr/local/_xtest@example.com/members.db',
- 'dbtype://username:apples%2Foranges@hostspec/database',
+ 'dbtype://username:apples/oranges@hostspec/database',
'dbtype://username:password@[2001:da8:2004:1000:202:116:160:90]/database?foo=bar',
'dbtype://username:password@[2001:da8:2004:1000:202:116:160:90]:80/database?foo=bar'
):
'E:/work/src/LEM/db/hello.db', None), u.database
eq_(str(u), text)
+ def test_rfc1738_password(self):
+ u = url.make_url("dbtype://user:pass word + other:words@host/dbname")
+ eq_(u.password, "pass word + other:words")
+ eq_(str(u), "dbtype://user:pass word + other:words@host/dbname")
+
+ u = url.make_url('dbtype://username:apples%2Foranges@hostspec/database')
+ eq_(u.password, "apples%2Foranges")
+ eq_(str(u), 'dbtype://username:apples%2Foranges@hostspec/database')
+
+ u = url.make_url('dbtype://username:apples@oranges@@@hostspec/database')
+ eq_(u.password, "apples@oranges@@")
+ eq_(str(u), 'dbtype://username:apples@oranges@@@hostspec/database')
+
+ u = url.make_url('dbtype://username@:@hostspec/database')
+ eq_(u.password, '')
+ eq_(u.username, "username@")
+ eq_(str(u), 'dbtype://username@:@hostspec/database')
+
+ u = url.make_url('dbtype://username:pass/word@hostspec/database')
+ eq_(u.password, 'pass/word')
+ eq_(str(u), 'dbtype://username:pass/word@hostspec/database')
+
class DialectImportTest(fixtures.TestBase):
def test_import_base_dialects(self):