--- /dev/null
+.. change::
+ :tags: feature, engine
+ :tickets: 4089
+
+ The "password" attribute of the :class:`.url.URL` object can now be
+ any user-defined or user-subclassed string object that responds to the
+ Python ``str()`` builtin. The object passed will be maintained as the
+ datamember :attr:`.url.URL.password_original` and will be consulted
+ when the :attr:`.url.URL.password` attribute is read to produce the
+ string value.
\ No newline at end of file
host=None, port=None, database=None, query=None):
self.drivername = drivername
self.username = username
- self.password = password
+ self.password_original = password
self.host = host
if port is not None:
self.port = int(port)
self.database == other.database and \
self.query == other.query
+ @property
+ def password(self):
+ if self.password_original is None:
+ return None
+ else:
+ return util.text_type(self.password_original)
+
+ @password.setter
+ def password(self, password):
+ self.password_original = password
+
def get_backend_name(self):
if '+' not in self.drivername:
return self.drivername
-from sqlalchemy.testing import assert_raises, eq_, assert_raises_message
+from sqlalchemy.testing import assert_raises, eq_, is_
import sqlalchemy.engine.url as url
from sqlalchemy import create_engine, engine_from_config, exc, pool
from sqlalchemy.engine.default import DefaultDialect
eq_(u.password, 'pass/word')
eq_(str(u), 'dbtype://username:pass%2Fword@hostspec/database')
+ def test_password_custom_obj(self):
+ class SecurePassword(str):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return self.value
+
+ sp = SecurePassword("secured_password")
+ u = url.URL(
+ "dbtype",
+ username="x", password=sp,
+ host="localhost"
+ )
+
+ eq_(u.password, "secured_password")
+ eq_(str(u), "dbtype://x:secured_password@localhost")
+
+ # test in-place modification
+ sp.value = "new_secured_password"
+ eq_(u.password, "new_secured_password")
+ eq_(str(u), "dbtype://x:new_secured_password@localhost")
+
+ u.password = "hi"
+
+ eq_(u.password, "hi")
+ eq_(str(u), "dbtype://x:hi@localhost")
+
+ u.password = None
+
+ is_(u.password, None)
+ eq_(str(u), "dbtype://x@localhost")
+
class DialectImportTest(fixtures.TestBase):
def test_import_base_dialects(self):