]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Allow url.password to be an object
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Dec 2017 16:56:14 +0000 (11:56 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Dec 2017 16:56:46 +0000 (11:56 -0500)
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.

Change-Id: I91d101c3b10e135ae7e4de60a5104b51776db84f
Fixes: #4089
doc/build/changelog/unreleased_12/4089.rst [new file with mode: 0644]
lib/sqlalchemy/engine/url.py
test/engine/test_parseconnect.py

diff --git a/doc/build/changelog/unreleased_12/4089.rst b/doc/build/changelog/unreleased_12/4089.rst
new file mode 100644 (file)
index 0000000..96d8120
--- /dev/null
@@ -0,0 +1,10 @@
+.. 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
index 1ca5983fd528ab97fadcdc1b4be8c451cd4a9ee2..18b184878b2147f0efa85f88c04d656352aa4bb8 100644 (file)
@@ -54,7 +54,7 @@ class URL(object):
                  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)
@@ -105,6 +105,17 @@ class URL(object):
             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
index 07fedbe311550b40ef86ac66809bb446b39b02ac..deb4e3c49b0025000129c6c5b74c0639c506ad49 100644 (file)
@@ -1,4 +1,4 @@
-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
@@ -82,6 +82,39 @@ class ParseConnectTest(fixtures.TestBase):
         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):