summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ritchford <tom@swirly.com>2021-12-05 13:27:45 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2021-12-06 16:57:02 -0500
commit924cc31975f8874d369db6599575e361bdb34be9 (patch)
tree210f4f3ed66439bea14a767acfd436cc49ac665f
parent995fb577a64061a9cbab62b481c65a4c4d3e5a67 (diff)
downloadsqlalchemy-924cc31975f8874d369db6599575e361bdb34be9.tar.gz
Add __copy__, __deepcopy__ to URL. Fixes: #7400
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
-rw-r--r--doc/build/changelog/unreleased_14/7400.rst6
-rw-r--r--lib/sqlalchemy/engine/url.py16
-rw-r--r--test/engine/test_parseconnect.py32
3 files changed, 54 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_14/7400.rst b/doc/build/changelog/unreleased_14/7400.rst
new file mode 100644
index 000000000..799b3b9a3
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/7400.rst
@@ -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.
diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py
index 7cdf25c21..778d2112f 100644
--- a/lib/sqlalchemy/engine/url.py
+++ b/lib/sqlalchemy/engine/url.py
@@ -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))
diff --git a/test/engine/test_parseconnect.py b/test/engine/test_parseconnect.py
index f12d32d5d..19fce5c18 100644
--- a/test/engine/test_parseconnect.py
+++ b/test/engine/test_parseconnect.py
@@ -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",
@@ -243,6 +264,17 @@ class URLTest(fixtures.TestBase):
)
@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",
"foo2=bar22&foo3=bar3",