From: Tom Ritchford Date: Sun, 5 Dec 2021 18:27:45 +0000 (-0500) Subject: Add __copy__, __deepcopy__ to URL. Fixes: #7400 X-Git-Tag: rel_1_4_28~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7e6115d94a8728bba90464ed3f3e79fa16d6c766;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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 (cherry picked from commit 924cc31975f8874d369db6599575e361bdb34be9) --- diff --git a/doc/build/changelog/unreleased_14/7400.rst b/doc/build/changelog/unreleased_14/7400.rst new file mode 100644 index 0000000000..799b3b9a36 --- /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 488f739527..320e69fbc3 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 67d8369b5d..f553b1dab5 100644 --- a/test/engine/test_parseconnect.py +++ b/test/engine/test_parseconnect.py @@ -1,3 +1,5 @@ +import copy + import sqlalchemy as tsa from sqlalchemy import create_engine from sqlalchemy import engine_from_config @@ -11,9 +13,11 @@ import sqlalchemy.engine.url as url from sqlalchemy.testing import assert_raises from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import eq_ +from sqlalchemy.testing import expect_raises_message 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.assertions import expect_deprecated @@ -193,6 +197,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", @@ -239,6 +262,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",