]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add __copy__, __deepcopy__ to URL. Fixes: #7400
authorTom Ritchford <tom@swirly.com>
Sun, 5 Dec 2021 18:27:45 +0000 (13:27 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 6 Dec 2021 21:57:02 +0000 (16:57 -0500)
Added support for ``copy()`` and ``deepcopy()`` to the :class:`_url.URL`
class. Pull request courtesy Tom Ritchford.

Fixes: #7400
Closes: #7401
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/7401
Pull-request-sha: a2c1b8992f5d153c6210178cda47b8ae96b91fb5

Change-Id: I55977338b2655a7d4f733ae786d31e589185e9ca

doc/build/changelog/unreleased_14/7400.rst [new file with mode: 0644]
lib/sqlalchemy/engine/url.py
test/engine/test_parseconnect.py

diff --git a/doc/build/changelog/unreleased_14/7400.rst b/doc/build/changelog/unreleased_14/7400.rst
new file mode 100644 (file)
index 0000000..799b3b9
--- /dev/null
@@ -0,0 +1,6 @@
+.. change::
+    :tags: usecase, engine
+    :tickets: 7400
+
+    Added support for ``copy()`` and ``deepcopy()`` to the :class:`_url.URL`
+    class. Pull request courtesy Tom Ritchford.
index 7cdf25c21aadc6e4937c53dc98230b60f9accb9f..778d2112fef387da19f48bd3f8be018cd0438efe 100644 (file)
@@ -560,6 +560,22 @@ class URL(
     def __repr__(self):
         return self.render_as_string()
 
+    def __copy__(self):
+        return self.__class__.create(
+            self.drivername,
+            self.username,
+            self.password,
+            self.host,
+            self.port,
+            self.database,
+            # note this is an immutabledict of str-> str / tuple of str,
+            # also fully immutable.  does not require deepcopy
+            self.query,
+        )
+
+    def __deepcopy__(self, memo):
+        return self.__copy__()
+
     def __hash__(self):
         return hash(str(self))
 
index f12d32d5d0e558cf12d9375288fafd0c43ec9673..19fce5c1897e820c8a508b6a15de5e07ba1573a2 100644 (file)
@@ -1,3 +1,4 @@
+import copy
 from unittest.mock import call
 from unittest.mock import MagicMock
 from unittest.mock import Mock
@@ -18,6 +19,7 @@ from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_false
+from sqlalchemy.testing import is_not
 from sqlalchemy.testing import is_true
 from sqlalchemy.testing import mock
 from sqlalchemy.testing import ne_
@@ -196,6 +198,25 @@ class URLTest(fixtures.TestBase):
         is_true(url1 != url3)
         is_false(url1 == url3)
 
+    def test_copy(self):
+        url1 = url.make_url(
+            "dialect://user:pass@host/db?arg1%3D=param1&arg2=param+2"
+        )
+        url2 = copy.copy(url1)
+        eq_(url1, url2)
+        is_not(url1, url2)
+
+    def test_deepcopy(self):
+        url1 = url.make_url(
+            "dialect://user:pass@host/db?arg1%3D=param1&arg2=param+2"
+        )
+        url2 = copy.deepcopy(url1)
+        eq_(url1, url2)
+        is_not(url1, url2)
+        is_not(url1.query, url2.query)  # immutabledict of immutable k/v,
+        # but it copies it on constructor
+        # in any case if params are present
+
     @testing.combinations(
         "drivername",
         "username",
@@ -242,6 +263,17 @@ class URLTest(fixtures.TestBase):
             url.make_url("drivername:///?%s" % expected),
         )
 
+    @testing.combinations(
+        "drivername://",
+        "drivername://?foo=bar",
+        "drivername://?foo=bar&foo=bat",
+    )
+    def test_query_dict_immutable(self, urlstr):
+        url_obj = url.make_url(urlstr)
+
+        with expect_raises_message(TypeError, ".*immutable"):
+            url_obj.query["foo"] = "hoho"
+
     @testing.combinations(
         (
             "foo1=bar1&foo2=bar2",