From 930554d03f6a4dd4a67b8fa5c3108a6873851012 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 11:02:39 +0100 Subject: [PATCH 01/23] Add keep_query and keep_fragment arguments to with_path --- tests/test_url.py | 18 ++++++++++++++++++ yarl/_url.py | 17 +++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index 0a8ed908..d9add3d3 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -1235,11 +1235,29 @@ def test_with_path_query(): assert str(url.with_path("/test")) == "http://example.com/test" +def test_with_path_keep_query(): + url = URL("http://example.com?a=b") + url2 = url.with_path("/test", with_query=True) + assert str(url2) == "http://example.com/test?a=b" + + def test_with_path_fragment(): url = URL("http://example.com#frag") assert str(url.with_path("/test")) == "http://example.com/test" +def test_with_path_keep_fragment(): + url = URL("http://example.com#frag") + url2 = url.with_path("/test", with_fragment=True) + assert str(url2) == "http://example.com/test#frag" + + +def test_with_path_keep_fragment_and_query(): + url = URL("http://example.com?a=b#frag") + url2 = url.with_path("/test", with_query=True, with_fragment=True) + assert str(url2) == "http://example.com/test?a=b#frag" + + def test_with_path_empty(): url = URL("http://example.com/test") assert str(url.with_path("")) == "http://example.com" diff --git a/yarl/_url.py b/yarl/_url.py index 780a26eb..b4fec0b4 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -1119,7 +1119,14 @@ def with_port(self, port: Union[int, None]) -> "URL": self._scheme, netloc, self._path, self._query, self._fragment ) - def with_path(self, path: str, *, encoded: bool = False) -> "URL": + def with_path( + self, + path: str, + *, + encoded: bool = False, + with_query: bool = False, + with_fragment: bool = False, + ) -> "URL": """Return a new URL with path replaced.""" netloc = self._netloc if not encoded: @@ -1128,7 +1135,13 @@ def with_path(self, path: str, *, encoded: bool = False) -> "URL": path = normalize_path(path) if "." in path else path if path and path[0] != "/": path = f"/{path}" - return self._from_parts(self._scheme, netloc, path, "", "") + query = "" + fragment = "" + if with_query: + query = self._query + if with_fragment: + fragment = self._fragment + return self._from_parts(self._scheme, netloc, path, query, fragment) @overload def with_query(self, query: Query) -> "URL": ... From f85a0845b37d7c0029fa4c2bff09aa1f4c67a93f Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 11:09:39 +0100 Subject: [PATCH 02/23] Add changelog entries --- CHANGES/111.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGES/111.bugfix.rst diff --git a/CHANGES/111.bugfix.rst b/CHANGES/111.bugfix.rst new file mode 100644 index 00000000..64b14543 --- /dev/null +++ b/CHANGES/111.bugfix.rst @@ -0,0 +1 @@ +Fixed an issue where replacing the path would also replace the query and fragment. From 733589008a18608ec0baaafdd62dab066239c8fd Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 13:22:02 +0100 Subject: [PATCH 03/23] One line to make it more readable --- yarl/_url.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index b4fec0b4..f89fae0d 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -1135,12 +1135,8 @@ def with_path( path = normalize_path(path) if "." in path else path if path and path[0] != "/": path = f"/{path}" - query = "" - fragment = "" - if with_query: - query = self._query - if with_fragment: - fragment = self._fragment + query = self._query if with_query else "" + fragment = self._fragment if with_fragment else "" return self._from_parts(self._scheme, netloc, path, query, fragment) @overload From aa07b9716f51026f0f41a3ff9c899d11a56bba60 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 13:47:17 +0100 Subject: [PATCH 04/23] Update changelog message --- CHANGES/111.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/111.bugfix.rst b/CHANGES/111.bugfix.rst index 64b14543..56515d62 100644 --- a/CHANGES/111.bugfix.rst +++ b/CHANGES/111.bugfix.rst @@ -1 +1 @@ -Fixed an issue where replacing the path would also replace the query and fragment. +Added `keep_query` and `keep_fragment` flags in the `URL.with_path`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path. From bd1d1616eb0d8024e07608879ab738701e5fc63e Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 14:00:09 +0100 Subject: [PATCH 05/23] Update changelog message to pass lint --- CHANGES/111.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/111.bugfix.rst b/CHANGES/111.bugfix.rst index 56515d62..5149878b 100644 --- a/CHANGES/111.bugfix.rst +++ b/CHANGES/111.bugfix.rst @@ -1 +1 @@ -Added `keep_query` and `keep_fragment` flags in the `URL.with_path`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path. +Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path. From eca4ce63599cd756cbcc6bc0a746967623fce62e Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 18:07:45 +0100 Subject: [PATCH 06/23] Add test to cover arguments false. Rename flags --- tests/test_url.py | 12 +++++++++--- yarl/_url.py | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index d9add3d3..4e4912e5 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -1237,7 +1237,7 @@ def test_with_path_query(): def test_with_path_keep_query(): url = URL("http://example.com?a=b") - url2 = url.with_path("/test", with_query=True) + url2 = url.with_path("/test", keep_query=True) assert str(url2) == "http://example.com/test?a=b" @@ -1248,16 +1248,22 @@ def test_with_path_fragment(): def test_with_path_keep_fragment(): url = URL("http://example.com#frag") - url2 = url.with_path("/test", with_fragment=True) + url2 = url.with_path("/test", keep_fragment=True) assert str(url2) == "http://example.com/test#frag" def test_with_path_keep_fragment_and_query(): url = URL("http://example.com?a=b#frag") - url2 = url.with_path("/test", with_query=True, with_fragment=True) + url2 = url.with_path("/test", keep_query=True, keep_fragment=True) assert str(url2) == "http://example.com/test?a=b#frag" +def test_with_path_keep_fragment_and_query_false(): + url = URL("http://example.com?a=b#frag") + url2 = url.with_path("/test", keep_query=False, keep_fragment=False) + assert str(url2) == "http://example.com/test" + + def test_with_path_empty(): url = URL("http://example.com/test") assert str(url.with_path("")) == "http://example.com" diff --git a/yarl/_url.py b/yarl/_url.py index f89fae0d..45f1a433 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -1124,8 +1124,8 @@ def with_path( path: str, *, encoded: bool = False, - with_query: bool = False, - with_fragment: bool = False, + keep_query: bool = False, + keep_fragment: bool = False, ) -> "URL": """Return a new URL with path replaced.""" netloc = self._netloc @@ -1135,8 +1135,8 @@ def with_path( path = normalize_path(path) if "." in path else path if path and path[0] != "/": path = f"/{path}" - query = self._query if with_query else "" - fragment = self._fragment if with_fragment else "" + query = self._query if keep_query else "" + fragment = self._fragment if keep_fragment else "" return self._from_parts(self._scheme, netloc, path, query, fragment) @overload From 32a8cd21db59690c4b5db22ac2b3413c360a3170 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Nov 2024 13:31:45 -0500 Subject: [PATCH 07/23] Update 111.bugfix.rst --- CHANGES/111.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/111.bugfix.rst b/CHANGES/111.bugfix.rst index 5149878b..a3b94f22 100644 --- a/CHANGES/111.bugfix.rst +++ b/CHANGES/111.bugfix.rst @@ -1 +1 @@ -Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path. +Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by :user:`paul-nameless`. From 3bb511fed097257a5244102637177a110038afdd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Nov 2024 12:33:25 -0600 Subject: [PATCH 08/23] changelog --- CHANGES/{111.bugfix.rst => 1421.feature.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGES/{111.bugfix.rst => 1421.feature.rst} (100%) diff --git a/CHANGES/111.bugfix.rst b/CHANGES/1421.feature.rst similarity index 100% rename from CHANGES/111.bugfix.rst rename to CHANGES/1421.feature.rst From 1eb087ed02dde8f4d6ead5d64b59d4f0ad32870b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Nov 2024 12:34:33 -0600 Subject: [PATCH 09/23] symlink --- CHANGES/111.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 120000 CHANGES/111.feature.rst diff --git a/CHANGES/111.feature.rst b/CHANGES/111.feature.rst new file mode 120000 index 00000000..96c7efc7 --- /dev/null +++ b/CHANGES/111.feature.rst @@ -0,0 +1 @@ +1421.feature.rst \ No newline at end of file From 026d05d49398a685a9b8fdbf406f39617d94fe19 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 22:22:39 +0100 Subject: [PATCH 10/23] Update documentation with new flags --- docs/api.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index f9698fb3..bca1a317 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -652,10 +652,12 @@ section generates a new :class:`URL` instance. >>> URL('http://example.com:8888').with_port(None) URL('http://example.com') -.. method:: URL.with_path(path) +.. method:: URL.with_path(path, keep_query=False, keep_fragment=False) Return a new URL with *path* replaced, encode *path* if needed. + If ``keep_query=True`` or ``keep_fragment=True`` it retains the existing query or fragment in the URL. + .. doctest:: >>> URL('http://example.com/').with_path('/path/to') From 9a9a45ae23706723d81c10ccbd5c5f17a44d02ce Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Mon, 18 Nov 2024 22:43:17 +0100 Subject: [PATCH 11/23] Add versionadded --- docs/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index bca1a317..c087b50f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -656,6 +656,8 @@ section generates a new :class:`URL` instance. Return a new URL with *path* replaced, encode *path* if needed. + .. versionadded:: 1.18 + If ``keep_query=True`` or ``keep_fragment=True`` it retains the existing query or fragment in the URL. .. doctest:: From fc373e43ad9a9b2a90b0d3e115e8fb57ecbf1cbb Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 18 Nov 2024 23:49:41 +0100 Subject: [PATCH 12/23] Update docs/api.rst Co-authored-by: Andrew Svetlov --- docs/api.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index c087b50f..82cc37da 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -656,10 +656,11 @@ section generates a new :class:`URL` instance. Return a new URL with *path* replaced, encode *path* if needed. - .. versionadded:: 1.18 - If ``keep_query=True`` or ``keep_fragment=True`` it retains the existing query or fragment in the URL. + .. versionchanged:: 1.18 + + Added *keep_query* and *keep_fragment* parameters. .. doctest:: >>> URL('http://example.com/').with_path('/path/to') From 59d1b92b068ae9b085dc640a061e600817c6cf96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Nov 2024 22:25:23 -0600 Subject: [PATCH 13/23] Update docs/api.rst --- docs/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api.rst b/docs/api.rst index 82cc37da..6e9333ca 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -661,6 +661,7 @@ section generates a new :class:`URL` instance. .. versionchanged:: 1.18 Added *keep_query* and *keep_fragment* parameters. + .. doctest:: >>> URL('http://example.com/').with_path('/path/to') From 1a61eeed6dfc83f8ddbd918b8165aa89696191c0 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 19 Nov 2024 18:58:05 +0100 Subject: [PATCH 14/23] Add flags to with_path and with_suffix --- tests/test_url.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++- yarl/_url.py | 22 +++++++++++++++++---- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index 4e4912e5..72fd7715 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -10,7 +10,7 @@ "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f " ) _VERTICAL_COLON = "\ufe13" # normalizes to ":" -_FULL_WITH_NUMBER_SIGN = "\uFF03" # normalizes to "#" +_FULL_WITH_NUMBER_SIGN = "\uff03" # normalizes to "#" _ACCOUNT_OF = "\u2100" # normalizes to "a/c" @@ -1343,6 +1343,30 @@ def test_with_name(): assert url2.path == "/a/c" +def test_with_name_keep_query(): + url = URL("http://example.com/path/to?a=b") + url2 = url.with_name("newname", keep_query=True) + assert str(url2) == "http://example.com/path/newname?a=b" + + +def test_with_name_keep_fragment(): + url = URL("http://example.com/path/to#frag") + url2 = url.with_name("newname", keep_fragment=True) + assert str(url2) == "http://example.com/path/newname#frag" + + +def test_with_name_keep_fragment_and_query(): + url = URL("http://example.com/path/to?a=b#frag") + url2 = url.with_name("newname", keep_query=True, keep_fragment=True) + assert str(url2) == "http://example.com/path/newname?a=b#frag" + + +def test_with_name_keep_fragment_and_query_false(): + url = URL("http://example.com/path/to?a=b#frag") + url2 = url.with_name("newname", keep_query=False, keep_fragment=False) + assert str(url2) == "http://example.com/path/newname" + + def test_with_name_for_naked_path(): url = URL("http://example.com") url2 = url.with_name("a") @@ -1433,6 +1457,30 @@ def test_with_suffix(): assert url2.path == "/a/b.c" +def test_with_suffix_keep_query(): + url = URL("http://example.com/path/to.txt?a=b") + url2 = url.with_suffix(".md", keep_query=True) + assert str(url2) == "http://example.com/path/to.md?a=b" + + +def test_with_suffix_keep_fragment(): + url = URL("http://example.com/path/to.txt#frag") + url2 = url.with_suffix(".md", keep_fragment=True) + assert str(url2) == "http://example.com/path/to.md#frag" + + +def test_with_suffix_keep_fragment_and_query(): + url = URL("http://example.com/path/to.txt?a=b#frag") + url2 = url.with_suffix(".md", keep_query=True, keep_fragment=True) + assert str(url2) == "http://example.com/path/to.md?a=b#frag" + + +def test_with_suffix_keep_fragment_and_query_false(): + url = URL("http://example.com/path/to.txt?a=b#frag") + url2 = url.with_suffix(".md", keep_query=False, keep_fragment=False) + assert str(url2) == "http://example.com/path/to.md" + + def test_with_suffix_for_naked_path(): url = URL("http://example.com") with pytest.raises(ValueError) as excinfo: diff --git a/yarl/_url.py b/yarl/_url.py index 45f1a433..9088edb4 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -1285,7 +1285,12 @@ def with_fragment(self, fragment: Union[str, None]) -> "URL": self._scheme, self._netloc, self._path, self._query, raw_fragment ) - def with_name(self, name: str) -> "URL": + def with_name( + self, + name: str, + keep_query: bool = False, + keep_fragment: bool = False, + ) -> "URL": """Return a new URL with name (last part of path) replaced. Query and fragment parts are cleaned up. @@ -1312,9 +1317,17 @@ def with_name(self, name: str) -> "URL": parts[-1] = name if parts[0] == "/": parts[0] = "" # replace leading '/' - return self._from_parts(self._scheme, netloc, "/".join(parts), "", "") - def with_suffix(self, suffix: str) -> "URL": + query = self._query if keep_query else "" + fragment = self._fragment if keep_fragment else "" + return self._from_parts(self._scheme, netloc, "/".join(parts), query, fragment) + + def with_suffix( + self, + suffix: str, + keep_query: bool = False, + keep_fragment: bool = False, + ) -> "URL": """Return a new URL with suffix (file extension of name) replaced. Query and fragment parts are cleaned up. @@ -1330,7 +1343,8 @@ def with_suffix(self, suffix: str) -> "URL": raise ValueError(f"{self!r} has an empty name") old_suffix = self.raw_suffix name = name + suffix if not old_suffix else name[: -len(old_suffix)] + suffix - return self.with_name(name) + + return self.with_name(name, keep_query=keep_query, keep_fragment=keep_fragment) def join(self, url: "URL") -> "URL": """Join URLs From 73501a495cbc5183deefb7d795a74089cde696c6 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 19 Nov 2024 19:10:11 +0100 Subject: [PATCH 15/23] Add documentation --- docs/api.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 6e9333ca..800f69c0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -870,6 +870,12 @@ section generates a new :class:`URL` instance. Name is encoded if needed. + If ``keep_query=True`` or ``keep_fragment=True`` it retains the existing query or fragment in the URL. + + .. versionchanged:: 1.18 + + Added *keep_query* and *keep_fragment* parameters. + .. doctest:: >>> URL('http://example.com/path/to?arg#frag').with_name('new') @@ -884,6 +890,12 @@ section generates a new :class:`URL` instance. Name is encoded if needed. + If ``keep_query=True`` or ``keep_fragment=True`` it retains the existing query or fragment in the URL. + + .. versionchanged:: 1.18 + + Added *keep_query* and *keep_fragment* parameters. + .. doctest:: >>> URL('http://example.com/path/to?arg#frag').with_suffix('.doc') From e56e14036f4f51e40c3096e93e7feeab3c56aa1d Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 19 Nov 2024 19:11:00 +0100 Subject: [PATCH 16/23] Update changelog --- CHANGES/1421.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1421.feature.rst b/CHANGES/1421.feature.rst index a3b94f22..cc2929f4 100644 --- a/CHANGES/1421.feature.rst +++ b/CHANGES/1421.feature.rst @@ -1 +1 @@ -Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by :user:`paul-nameless`. +Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, :py:meth:`yarl.URL.with_name` and :py:meth:`yarl.URL.with_suffix`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by :user:`paul-nameless`. From 6b3fefe0b110175e49bfa6654509288c5461c97b Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Tue, 19 Nov 2024 19:37:07 +0100 Subject: [PATCH 17/23] Rewrite tests to use parametrize --- tests/test_url.py | 151 ++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 66 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index 72fd7715..fb15f358 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -1235,33 +1235,26 @@ def test_with_path_query(): assert str(url.with_path("/test")) == "http://example.com/test" -def test_with_path_keep_query(): - url = URL("http://example.com?a=b") - url2 = url.with_path("/test", keep_query=True) - assert str(url2) == "http://example.com/test?a=b" - - def test_with_path_fragment(): url = URL("http://example.com#frag") assert str(url.with_path("/test")) == "http://example.com/test" -def test_with_path_keep_fragment(): - url = URL("http://example.com#frag") - url2 = url.with_path("/test", keep_fragment=True) - assert str(url2) == "http://example.com/test#frag" - - -def test_with_path_keep_fragment_and_query(): - url = URL("http://example.com?a=b#frag") - url2 = url.with_path("/test", keep_query=True, keep_fragment=True) - assert str(url2) == "http://example.com/test?a=b#frag" - - -def test_with_path_keep_fragment_and_query_false(): - url = URL("http://example.com?a=b#frag") - url2 = url.with_path("/test", keep_query=False, keep_fragment=False) - assert str(url2) == "http://example.com/test" +@pytest.mark.parametrize( + "original_url, keep_query, keep_fragment, expected_url", + [ + ("http://example.com?a=b#frag", True, False, "http://example.com/test?a=b"), + ("http://example.com?a=b#frag", False, True, "http://example.com/test#frag"), + ("http://example.com?a=b#frag", True, True, "http://example.com/test?a=b#frag"), + ("http://example.com?a=b#frag", False, False, "http://example.com/test"), + ], +) +def test_with_path_keep_query_keep_fragment_flags( + original_url, keep_query, keep_fragment, expected_url +): + url = URL(original_url) + url2 = url.with_path("/test", keep_query=keep_query, keep_fragment=keep_fragment) + assert str(url2) == expected_url def test_with_path_empty(): @@ -1343,28 +1336,41 @@ def test_with_name(): assert url2.path == "/a/c" -def test_with_name_keep_query(): - url = URL("http://example.com/path/to?a=b") - url2 = url.with_name("newname", keep_query=True) - assert str(url2) == "http://example.com/path/newname?a=b" - - -def test_with_name_keep_fragment(): - url = URL("http://example.com/path/to#frag") - url2 = url.with_name("newname", keep_fragment=True) - assert str(url2) == "http://example.com/path/newname#frag" - - -def test_with_name_keep_fragment_and_query(): - url = URL("http://example.com/path/to?a=b#frag") - url2 = url.with_name("newname", keep_query=True, keep_fragment=True) - assert str(url2) == "http://example.com/path/newname?a=b#frag" - - -def test_with_name_keep_fragment_and_query_false(): - url = URL("http://example.com/path/to?a=b#frag") - url2 = url.with_name("newname", keep_query=False, keep_fragment=False) - assert str(url2) == "http://example.com/path/newname" +@pytest.mark.parametrize( + "original_url, keep_query, keep_fragment, expected_url", + [ + ( + "http://example.com/path/to?a=b#frag", + True, + False, + "http://example.com/path/newname?a=b", + ), + ( + "http://example.com/path/to?a=b#frag", + False, + True, + "http://example.com/path/newname#frag", + ), + ( + "http://example.com/path/to?a=b#frag", + True, + True, + "http://example.com/path/newname?a=b#frag", + ), + ( + "http://example.com/path/to?a=b#frag", + False, + False, + "http://example.com/path/newname", + ), + ], +) +def test_with_name_keep_query_keep_fragment_flags( + original_url, keep_query, keep_fragment, expected_url +): + url = URL(original_url) + url2 = url.with_name("newname", keep_query=keep_query, keep_fragment=keep_fragment) + assert str(url2) == expected_url def test_with_name_for_naked_path(): @@ -1457,28 +1463,41 @@ def test_with_suffix(): assert url2.path == "/a/b.c" -def test_with_suffix_keep_query(): - url = URL("http://example.com/path/to.txt?a=b") - url2 = url.with_suffix(".md", keep_query=True) - assert str(url2) == "http://example.com/path/to.md?a=b" - - -def test_with_suffix_keep_fragment(): - url = URL("http://example.com/path/to.txt#frag") - url2 = url.with_suffix(".md", keep_fragment=True) - assert str(url2) == "http://example.com/path/to.md#frag" - - -def test_with_suffix_keep_fragment_and_query(): - url = URL("http://example.com/path/to.txt?a=b#frag") - url2 = url.with_suffix(".md", keep_query=True, keep_fragment=True) - assert str(url2) == "http://example.com/path/to.md?a=b#frag" - - -def test_with_suffix_keep_fragment_and_query_false(): - url = URL("http://example.com/path/to.txt?a=b#frag") - url2 = url.with_suffix(".md", keep_query=False, keep_fragment=False) - assert str(url2) == "http://example.com/path/to.md" +@pytest.mark.parametrize( + "original_url, keep_query, keep_fragment, expected_url", + [ + ( + "http://example.com/path/to.txt?a=b#frag", + True, + False, + "http://example.com/path/to.md?a=b", + ), + ( + "http://example.com/path/to.txt?a=b#frag", + False, + True, + "http://example.com/path/to.md#frag", + ), + ( + "http://example.com/path/to.txt?a=b#frag", + True, + True, + "http://example.com/path/to.md?a=b#frag", + ), + ( + "http://example.com/path/to.txt?a=b#frag", + False, + False, + "http://example.com/path/to.md", + ), + ], +) +def test_with_suffix_keep_query_keep_fragment_flags( + original_url, keep_query, keep_fragment, expected_url +): + url = URL(original_url) + url2 = url.with_suffix(".md", keep_query=keep_query, keep_fragment=keep_fragment) + assert str(url2) == expected_url def test_with_suffix_for_naked_path(): From f65530bba5c22880f185e881c3f82717dc04ee3f Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Wed, 20 Nov 2024 09:31:05 +0100 Subject: [PATCH 18/23] Update docs. Add required named params to method defition --- docs/api.rst | 4 ++-- yarl/_url.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 800f69c0..3353f9c7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -863,7 +863,7 @@ section generates a new :class:`URL` instance. >>> URL('http://example.com/path#frag').with_fragment(None) URL('http://example.com/path') -.. method:: URL.with_name(name) +.. method:: URL.with_name(name, keep_query=False, keep_fragment=False) Return a new URL with *name* (last part of *path*) replaced and cleaned up *query* and *fragment* parts. @@ -883,7 +883,7 @@ section generates a new :class:`URL` instance. >>> URL('http://example.com/path/to').with_name("ім'я") URL('http://example.com/path/%D1%96%D0%BC%27%D1%8F') -.. method:: URL.with_suffix(suffix) +.. method:: URL.with_suffix(suffix, keep_query=False, keep_fragment=False) Return a new URL with *suffix* (file extension of *name*) replaced and cleaned up *query* and *fragment* parts. diff --git a/yarl/_url.py b/yarl/_url.py index 9088edb4..07d2f26a 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -1288,6 +1288,7 @@ def with_fragment(self, fragment: Union[str, None]) -> "URL": def with_name( self, name: str, + *, keep_query: bool = False, keep_fragment: bool = False, ) -> "URL": @@ -1325,6 +1326,7 @@ def with_name( def with_suffix( self, suffix: str, + *, keep_query: bool = False, keep_fragment: bool = False, ) -> "URL": From 822726f113658b67d7128c1283a13ea80f7dbe04 Mon Sep 17 00:00:00 2001 From: Paul Nameless Date: Wed, 20 Nov 2024 10:38:36 +0100 Subject: [PATCH 19/23] Update tests --- tests/test_url.py | 62 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index fb15f358..da68c2f2 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -1241,12 +1241,36 @@ def test_with_path_fragment(): @pytest.mark.parametrize( - "original_url, keep_query, keep_fragment, expected_url", + ("original_url", "keep_query", "keep_fragment", "expected_url"), [ - ("http://example.com?a=b#frag", True, False, "http://example.com/test?a=b"), - ("http://example.com?a=b#frag", False, True, "http://example.com/test#frag"), - ("http://example.com?a=b#frag", True, True, "http://example.com/test?a=b#frag"), - ("http://example.com?a=b#frag", False, False, "http://example.com/test"), + pytest.param( + "http://example.com?a=b#frag", + True, + False, + "http://example.com/test?a=b", + id="query-only", + ), + pytest.param( + "http://example.com?a=b#frag", + False, + True, + "http://example.com/test#frag", + id="fragment-only", + ), + pytest.param( + "http://example.com?a=b#frag", + True, + True, + "http://example.com/test?a=b#frag", + id="all", + ), + pytest.param( + "http://example.com?a=b#frag", + False, + False, + "http://example.com/test", + id="none", + ), ], ) def test_with_path_keep_query_keep_fragment_flags( @@ -1337,31 +1361,35 @@ def test_with_name(): @pytest.mark.parametrize( - "original_url, keep_query, keep_fragment, expected_url", + ("original_url", "keep_query", "keep_fragment", "expected_url"), [ - ( + pytest.param( "http://example.com/path/to?a=b#frag", True, False, "http://example.com/path/newname?a=b", + id="query-only", ), - ( + pytest.param( "http://example.com/path/to?a=b#frag", False, True, "http://example.com/path/newname#frag", + id="fragment-only", ), - ( + pytest.param( "http://example.com/path/to?a=b#frag", True, True, "http://example.com/path/newname?a=b#frag", + id="all", ), - ( + pytest.param( "http://example.com/path/to?a=b#frag", False, False, "http://example.com/path/newname", + id="none", ), ], ) @@ -1464,31 +1492,35 @@ def test_with_suffix(): @pytest.mark.parametrize( - "original_url, keep_query, keep_fragment, expected_url", + ("original_url", "keep_query", "keep_fragment", "expected_url"), [ - ( + pytest.param( "http://example.com/path/to.txt?a=b#frag", True, False, "http://example.com/path/to.md?a=b", + id="query-only", ), - ( + pytest.param( "http://example.com/path/to.txt?a=b#frag", False, True, "http://example.com/path/to.md#frag", + id="fragment-only", ), - ( + pytest.param( "http://example.com/path/to.txt?a=b#frag", True, True, "http://example.com/path/to.md?a=b#frag", + id="all", ), - ( + pytest.param( "http://example.com/path/to.txt?a=b#frag", False, False, "http://example.com/path/to.md", + id="none", ), ], ) From 6e3cfc3f7d6b8b83f05386ab6db44f4b505002f4 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 20 Nov 2024 12:05:55 +0100 Subject: [PATCH 20/23] Update CHANGES/1421.feature.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1421.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1421.feature.rst b/CHANGES/1421.feature.rst index cc2929f4..bb8f7de2 100644 --- a/CHANGES/1421.feature.rst +++ b/CHANGES/1421.feature.rst @@ -1 +1 @@ -Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, :py:meth:`yarl.URL.with_name` and :py:meth:`yarl.URL.with_suffix`, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by :user:`paul-nameless`. +Added ``keep_query`` and ``keep_fragment`` flags in the :py:meth:`yarl.URL.with_path`, :py:meth:`yarl.URL.with_name` and :py:meth:`yarl.URL.with_suffix` methods, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by :user:`paul-nameless`. From 95d1e4eb1c13f17fc42982bf259e9e85da37fde3 Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 21 Nov 2024 08:27:19 +0100 Subject: [PATCH 21/23] Update docs/api.rst Co-authored-by: Andrew Svetlov --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 3353f9c7..08a03d9b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -652,7 +652,7 @@ section generates a new :class:`URL` instance. >>> URL('http://example.com:8888').with_port(None) URL('http://example.com') -.. method:: URL.with_path(path, keep_query=False, keep_fragment=False) +.. method:: URL.with_path(path, *, keep_query=False, keep_fragment=False) Return a new URL with *path* replaced, encode *path* if needed. From 439bfe8f68487d5785a13c1997f29fe2e2976623 Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 21 Nov 2024 08:27:25 +0100 Subject: [PATCH 22/23] Update docs/api.rst Co-authored-by: Andrew Svetlov --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 08a03d9b..45cd2b55 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -883,7 +883,7 @@ section generates a new :class:`URL` instance. >>> URL('http://example.com/path/to').with_name("ім'я") URL('http://example.com/path/%D1%96%D0%BC%27%D1%8F') -.. method:: URL.with_suffix(suffix, keep_query=False, keep_fragment=False) +.. method:: URL.with_suffix(suffix, *, keep_query=False, keep_fragment=False) Return a new URL with *suffix* (file extension of *name*) replaced and cleaned up *query* and *fragment* parts. From 41ecc98313051145f19e44e15837b30f4d49d845 Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 21 Nov 2024 08:27:34 +0100 Subject: [PATCH 23/23] Update docs/api.rst Co-authored-by: Andrew Svetlov --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 45cd2b55..69417bdd 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -863,7 +863,7 @@ section generates a new :class:`URL` instance. >>> URL('http://example.com/path#frag').with_fragment(None) URL('http://example.com/path') -.. method:: URL.with_name(name, keep_query=False, keep_fragment=False) +.. method:: URL.with_name(name, *, keep_query=False, keep_fragment=False) Return a new URL with *name* (last part of *path*) replaced and cleaned up *query* and *fragment* parts.