From 2b5809700e1f5744bdfc97633738bfd3871497e9 Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Sun, 9 Jun 2024 09:15:08 +0200 Subject: [PATCH 01/37] Add (failing) test for trailing slash when joining with "" --- tests/test_url.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_url.py b/tests/test_url.py index 725e9465..c71f50c6 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -806,6 +806,15 @@ def test_div_with_dots(): id="cleanup-query-and-fragment", ), pytest.param("", ("path/",), "http://example.com/path/", id="trailing-slash"), + pytest.param( + "", + ( + "path", + "", + ), + "http://example.com/path/", + id="trailing-slash-empty-string", + ), pytest.param( "", ("path/", "to/"), "http://example.com/path/to/", id="duplicate-slash" ), From a4ae0685c908e9feb138a4310c30f3f92ab11021 Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Sun, 9 Jun 2024 09:29:13 +0200 Subject: [PATCH 02/37] Add back trailing slash when joining with "" --- yarl/_url.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index 9cca27ef..f7ee3bf1 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -718,9 +718,7 @@ def _make_child(self, segments, encoded=False): # keep the trailing slash if the last segment ends with / parsed = [""] if segments and segments[-1][-1:] == "/" else [] for seg in reversed(segments): - if not seg: - continue - if seg[0] == "/": + if seg and seg[0] == "/": raise ValueError( f"Appending path {seg!r} starting from slash is forbidden" ) From 6eeabc1658e1e0576508aac72661f411b95edfd9 Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Sun, 9 Jun 2024 09:31:38 +0200 Subject: [PATCH 03/37] Add another test for path joining to "" --- tests/test_url.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_url.py b/tests/test_url.py index c71f50c6..d6c88d44 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -799,6 +799,9 @@ def test_div_with_dots(): pytest.param( "/path/", ("to",), "http://example.com/path/to", id="path-with-slash" ), + pytest.param( + "/path", ("",), "http://example.com/path/", id="path-add-trailing-slash" + ), pytest.param( "/path?a=1#frag", ("to",), From d6de640c372f198809eaceb4434e206477fe1b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Thu, 13 Jun 2024 12:59:18 +0200 Subject: [PATCH 04/37] join path with empty segments --- tests/test_url.py | 21 +++++++++++++++++++++ yarl/_url.py | 18 +++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index d6c88d44..91f20a68 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -839,6 +839,27 @@ def test_joinpath(base, to_join, expected): assert str(url.joinpath(*to_join)) == expected +@pytest.mark.parametrize( + "base,to_join,expected", + [ + pytest.param("path", "a", "path/a", id="default_default"), + pytest.param("path", "./a", "path/a", id="default_relative"), + pytest.param("path/", "a", "path/a", id="empty-segment_default"), + pytest.param("path/", "./a", "path/a", id="empty-segment_relative"), + pytest.param("path", ".//a", "path//a", id="default_empty-segment"), + pytest.param("path/", ".//a", "path//a", id="empty-segment_empty_segment"), + pytest.param("path//", "a", "path//a", id="empty-segments_default"), + pytest.param("path//", "./a", "path//a", id="empty-segments_relative"), + pytest.param("path//", ".//a", "path///a", id="empty-segments_empty-segment"), + pytest.param("path", "a//", "path/a//", id="default_trailing-empty-segment"), + pytest.param("path", "a//b", "path/a//b", id="default_embedded-empty-segment"), + ], +) +def test_joinpath_empty_segments(base, to_join, expected): + url = URL(f"http://example.com/{base}") + assert str(url.joinpath(to_join)) == f"http://example.com/{expected}" + + @pytest.mark.parametrize( "url,to_join,expected", [ diff --git a/yarl/_url.py b/yarl/_url.py index f7ee3bf1..379902ca 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -715,24 +715,24 @@ def _validate_authority_uri_abs_path(host, path): def _make_child(self, segments, encoded=False): """add segments to self._val.path, accounting for absolute vs relative paths""" - # keep the trailing slash if the last segment ends with / - parsed = [""] if segments and segments[-1][-1:] == "/" else [] + parsed = [] for seg in reversed(segments): + last = id(seg) == id(segments[-1]) if seg and seg[0] == "/": raise ValueError( f"Appending path {seg!r} starting from slash is forbidden" ) seg = seg if encoded else self._PATH_QUOTER(seg) - if "/" in seg: - parsed += ( - sub for sub in reversed(seg.split("/")) if sub and sub != "." - ) - elif seg != ".": - parsed.append(seg) + if not (subs := [sub for sub in reversed(seg.split("/")) if sub != "."]): + continue + parsed += subs[1:] if not last and subs[0] == "" else subs parsed.reverse() old_path = self._val.path if old_path: - parsed = [*old_path.rstrip("/").split("/"), *parsed] + old = old_path.split("/") + if old[-1] == "": + del old[-1] + parsed = [*old, *parsed] if self.is_absolute(): parsed = _normalize_path_segments(parsed) if parsed and parsed[0] != "": From 7a0752171b6b3cc58756a5eb346f9d1955cea09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Fri, 14 Jun 2024 07:45:35 +0200 Subject: [PATCH 05/37] refactor - rename segments in _make_child to match the wording in rfc3986 --- yarl/_url.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index 379902ca..903dd132 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -713,26 +713,36 @@ def _validate_authority_uri_abs_path(host, path): "Path in a URL with authority should start with a slash ('/') if set" ) - def _make_child(self, segments, encoded=False): - """add segments to self._val.path, accounting for absolute vs relative paths""" + def _make_child(self, paths, encoded=False): + """ + add paths to self._val.path, accounting for absolute vs relative paths, + keep existing, but do not create new, empty segments + """ parsed = [] - for seg in reversed(segments): - last = id(seg) == id(segments[-1]) - if seg and seg[0] == "/": + for idx, path in enumerate(reversed(paths)): + # empty segment of last is not removed + last = idx == 0 + if path and path[0] == "/": raise ValueError( - f"Appending path {seg!r} starting from slash is forbidden" + f"Appending path {path!r} starting from slash is forbidden" ) - seg = seg if encoded else self._PATH_QUOTER(seg) - if not (subs := [sub for sub in reversed(seg.split("/")) if sub != "."]): + path = path if encoded else self._PATH_QUOTER(path) + if not ( + segments := [ + segment for segment in reversed(path.split("/")) if segment != "." + ] + ): continue - parsed += subs[1:] if not last and subs[0] == "" else subs + # remove trailing empty segment for all but the last path + parsed += segments[1:] if not last and segments[0] == "" else segments parsed.reverse() - old_path = self._val.path - if old_path: - old = old_path.split("/") - if old[-1] == "": + + if self._val.path: + # remove trailing empty segment + if (old := self._val.path.split("/"))[-1] == "": del old[-1] parsed = [*old, *parsed] + if self.is_absolute(): parsed = _normalize_path_segments(parsed) if parsed and parsed[0] != "": From 8a9ad791594b719b01a33064acb189a9e2809fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Fri, 14 Jun 2024 07:48:42 +0200 Subject: [PATCH 06/37] tests - extend tests for empty segments --- tests/test_url.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_url.py b/tests/test_url.py index 91f20a68..cad37a3c 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -851,7 +851,8 @@ def test_joinpath(base, to_join, expected): pytest.param("path//", "a", "path//a", id="empty-segments_default"), pytest.param("path//", "./a", "path//a", id="empty-segments_relative"), pytest.param("path//", ".//a", "path///a", id="empty-segments_empty-segment"), - pytest.param("path", "a//", "path/a//", id="default_trailing-empty-segment"), + pytest.param("path", "a/", "path/a/", id="default_trailing-empty-segment"), + pytest.param("path", "a//", "path/a//", id="default_trailing-empty-segments"), pytest.param("path", "a//b", "path/a//b", id="default_embedded-empty-segment"), ], ) @@ -860,6 +861,14 @@ def test_joinpath_empty_segments(base, to_join, expected): assert str(url.joinpath(to_join)) == f"http://example.com/{expected}" +def test_joinpath_single_empty_segments(): + """joining standalone empty segments does not create empty segments""" + a = URL("/1//2///3") + assert a.parts == ("/", "1", "", "2", "", "", "3") + b = URL("scheme://host").joinpath(*a.parts[1:]) + assert b.path == "/1/2/3" + + @pytest.mark.parametrize( "url,to_join,expected", [ From 9e412aec6972b4e73273e2c43815ed335073486c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Tue, 18 Jun 2024 18:42:47 +0200 Subject: [PATCH 07/37] adding CHANGES/ --- CHANGES/1026.bugfix.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CHANGES/1026.bugfix.rst diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst new file mode 100644 index 00000000..aa16a890 --- /dev/null +++ b/CHANGES/1026.bugfix.rst @@ -0,0 +1,2 @@ +Joining urls with empty segments has been changed +to match RFC3986 -- by :user:`commonism` and :user:`youtux` From b7bcddfb44ff765c485872a60c811dd2877d706b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Tue, 18 Jun 2024 18:55:38 +0200 Subject: [PATCH 08/37] CHANGES - spelling --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index aa16a890..b79b9f9b 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -1,2 +1,2 @@ -Joining urls with empty segments has been changed +Joining URLs with empty segments has been changed to match RFC3986 -- by :user:`commonism` and :user:`youtux` From 38892cd6342959f86f1674d73c08a01c1d0fc822 Mon Sep 17 00:00:00 2001 From: commonism Date: Thu, 20 Jun 2024 15:19:06 +0200 Subject: [PATCH 09/37] Update tests/test_url.py Co-authored-by: Sam Bull --- tests/test_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_url.py b/tests/test_url.py index cad37a3c..0d119a5d 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -858,7 +858,7 @@ def test_joinpath(base, to_join, expected): ) def test_joinpath_empty_segments(base, to_join, expected): url = URL(f"http://example.com/{base}") - assert str(url.joinpath(to_join)) == f"http://example.com/{expected}" + assert str(url.joinpath(to_join)) == str(url / to_join) == f"http://example.com/{expected}" def test_joinpath_single_empty_segments(): From 0a56a8e2a920b54043405a459d17e48337280108 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:20:55 +0000 Subject: [PATCH 10/37] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_url.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_url.py b/tests/test_url.py index 0d119a5d..ebdc38f3 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -858,7 +858,11 @@ def test_joinpath(base, to_join, expected): ) def test_joinpath_empty_segments(base, to_join, expected): url = URL(f"http://example.com/{base}") - assert str(url.joinpath(to_join)) == str(url / to_join) == f"http://example.com/{expected}" + assert ( + str(url.joinpath(to_join)) + == str(url / to_join) + == f"http://example.com/{expected}" + ) def test_joinpath_single_empty_segments(): From 3bc0c09f834de1110aaa8c9a4bce3c61e994adcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Fri, 21 Jun 2024 08:19:29 +0200 Subject: [PATCH 11/37] not overuse := --- yarl/_url.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index 903dd132..339f6aeb 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -727,11 +727,10 @@ def _make_child(self, paths, encoded=False): f"Appending path {path!r} starting from slash is forbidden" ) path = path if encoded else self._PATH_QUOTER(path) - if not ( - segments := [ - segment for segment in reversed(path.split("/")) if segment != "." - ] - ): + segments = [ + segment for segment in reversed(path.split("/")) if segment != "." + ] + if not segments: continue # remove trailing empty segment for all but the last path parsed += segments[1:] if not last and segments[0] == "" else segments From 24b696f9eeb5960e273c7e2351add804cff86681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Thu, 27 Jun 2024 06:54:52 +0200 Subject: [PATCH 12/37] using codecov-action@v3 --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/reusable-linters.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 8c22e2ea..c7b82365 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -330,7 +330,7 @@ jobs: - name: Send coverage data to Codecov if: >- !cancelled() - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 with: token: 26f4a393-24a9-48d9-8fa4-f1344d930846 file: ./coverage.xml diff --git a/.github/workflows/reusable-linters.yml b/.github/workflows/reusable-linters.yml index 1f512166..a0e4d2a6 100644 --- a/.github/workflows/reusable-linters.yml +++ b/.github/workflows/reusable-linters.yml @@ -59,7 +59,7 @@ jobs: run: | make lint - name: Send coverage data to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.codecov-token }} files: >- From 64ccea3e116e3378b251a3ae31c9ae68b1827930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Thu, 27 Jun 2024 07:08:53 +0200 Subject: [PATCH 13/37] codecov-action@v3.1.4 && fail_ci_if_error=true rate limiting prevents uploads --- .github/workflows/ci-cd.yml | 4 ++-- .github/workflows/reusable-linters.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c7b82365..953e6ca0 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -330,7 +330,7 @@ jobs: - name: Send coverage data to Codecov if: >- !cancelled() - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v3.1.4 with: token: 26f4a393-24a9-48d9-8fa4-f1344d930846 file: ./coverage.xml @@ -339,7 +339,7 @@ jobs: OS-${{ runner.os }}, VM-${{ matrix.os }}, Py-${{ steps.python-install.outputs.python-version }} - fail_ci_if_error: false + fail_ci_if_error: true test-summary: name: Test matrix status diff --git a/.github/workflows/reusable-linters.yml b/.github/workflows/reusable-linters.yml index a0e4d2a6..01ee95c9 100644 --- a/.github/workflows/reusable-linters.yml +++ b/.github/workflows/reusable-linters.yml @@ -59,7 +59,7 @@ jobs: run: | make lint - name: Send coverage data to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v3.1.4 with: token: ${{ secrets.codecov-token }} files: >- @@ -69,7 +69,7 @@ jobs: flags: >- CI-GHA, MyPy - fail_ci_if_error: false + fail_ci_if_error: true - name: Install spell checker run: | sudo apt install libenchant-2-dev From be46800a00d32fd1a78bd48944dfdadcbda5c21e Mon Sep 17 00:00:00 2001 From: commonism Date: Fri, 28 Jun 2024 08:11:49 +0200 Subject: [PATCH 14/37] Update CHANGES/1026.bugfix.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index b79b9f9b..9e4ebb3c 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -1,2 +1,2 @@ Joining URLs with empty segments has been changed -to match RFC3986 -- by :user:`commonism` and :user:`youtux` +to match :rfs:`3986` -- by :user:`commonism` and :user:`youtux`. From a9548f44ad95d1312fb34b51944a487e7a8fa26f Mon Sep 17 00:00:00 2001 From: commonism Date: Fri, 28 Jun 2024 08:12:02 +0200 Subject: [PATCH 15/37] Update yarl/_url.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- yarl/_url.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index 339f6aeb..99bd4d14 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -736,11 +736,9 @@ def _make_child(self, paths, encoded=False): parsed += segments[1:] if not last and segments[0] == "" else segments parsed.reverse() - if self._val.path: - # remove trailing empty segment - if (old := self._val.path.split("/"))[-1] == "": - del old[-1] - parsed = [*old, *parsed] + if self._val.path and (old_path_segments := self._val.path.split("/")): + old_path_cutoff = -1 if old_path_segments[-1] == "" else None + parsed = [*old_path_segments[:old_path_cutoff], *parsed] if self.is_absolute(): parsed = _normalize_path_segments(parsed) From 50b2642dd102fdb3461b387fb466298f9921c5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Fri, 28 Jun 2024 08:24:47 +0200 Subject: [PATCH 16/37] requested changes to CHANGES & ci --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/reusable-linters.yml | 2 +- CHANGES/1026.bugfix.rst | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 953e6ca0..a9042d4e 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -339,7 +339,7 @@ jobs: OS-${{ runner.os }}, VM-${{ matrix.os }}, Py-${{ steps.python-install.outputs.python-version }} - fail_ci_if_error: true + fail_ci_if_error: false test-summary: name: Test matrix status diff --git a/.github/workflows/reusable-linters.yml b/.github/workflows/reusable-linters.yml index 01ee95c9..14c8d135 100644 --- a/.github/workflows/reusable-linters.yml +++ b/.github/workflows/reusable-linters.yml @@ -69,7 +69,7 @@ jobs: flags: >- CI-GHA, MyPy - fail_ci_if_error: true + fail_ci_if_error: false - name: Install spell checker run: | sudo apt install libenchant-2-dev diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index 9e4ebb3c..b2a3c917 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -1,2 +1,11 @@ Joining URLs with empty segments has been changed -to match :rfs:`3986` -- by :user:`commonism` and :user:`youtux`. +to match :rfs:`3986`. +Previously empty segments would be removed from path, +breaking use-cases such as +URL("https://web.archive.org/web/") / "https://github.com/" +Now "/" and joinpath() operation keep empty segments, +but do not introduce new empty segments. +e.g. +URL("https://example.org/") / "" +does not introduce an empty segment. +-- by :user:`commonism` and :user:`youtux`. From 8816bb25dfc23cf917f6c7fd7fac288c05e6f2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 29 Jun 2024 08:26:05 +0200 Subject: [PATCH 17/37] keep using codecov @v4 --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/reusable-linters.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a9042d4e..8c22e2ea 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -330,7 +330,7 @@ jobs: - name: Send coverage data to Codecov if: >- !cancelled() - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4 with: token: 26f4a393-24a9-48d9-8fa4-f1344d930846 file: ./coverage.xml diff --git a/.github/workflows/reusable-linters.yml b/.github/workflows/reusable-linters.yml index 14c8d135..1f512166 100644 --- a/.github/workflows/reusable-linters.yml +++ b/.github/workflows/reusable-linters.yml @@ -59,7 +59,7 @@ jobs: run: | make lint - name: Send coverage data to Codecov - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.codecov-token }} files: >- From 0c35ed3780d15e8f95c846babca33c71ffba849a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 29 Jun 2024 08:26:57 +0200 Subject: [PATCH 18/37] changes - formatting & spelling --- CHANGES/1026.bugfix.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index b2a3c917..2e38c8a2 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -1,11 +1,20 @@ Joining URLs with empty segments has been changed -to match :rfs:`3986`. +to match :rfc:`3986`. + Previously empty segments would be removed from path, breaking use-cases such as -URL("https://web.archive.org/web/") / "https://github.com/" -Now "/" and joinpath() operation keep empty segments, + +.. code-block:: python + + URL("https://web.archive.org/web/") / "https://github.com/" + +Now "/" and :spelling:ignore:`joinpath()` operation keep empty segments, but do not introduce new empty segments. e.g. -URL("https://example.org/") / "" + +.. code-block:: python + + URL("https://example.org/") / "" + does not introduce an empty segment. -- by :user:`commonism` and :user:`youtux`. From 9d3dfdb493162e64d2daf36d04bfab9539228ec8 Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 06:29:29 +0200 Subject: [PATCH 19/37] Update CHANGES/1026.bugfix.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index 2e38c8a2..506ec1dd 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -6,7 +6,7 @@ breaking use-cases such as .. code-block:: python - URL("https://web.archive.org/web/") / "https://github.com/" + URL("https://web.archive.org/web/") / "https://github.com/" Now "/" and :spelling:ignore:`joinpath()` operation keep empty segments, but do not introduce new empty segments. From 10e965f4dc441aebbf74a79bf476b807370f2599 Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 06:29:40 +0200 Subject: [PATCH 20/37] Update CHANGES/1026.bugfix.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index 506ec1dd..bcfe7db5 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -14,7 +14,7 @@ e.g. .. code-block:: python - URL("https://example.org/") / "" + URL("https://example.org/") / "" does not introduce an empty segment. -- by :user:`commonism` and :user:`youtux`. From e9d8e85472d8cf405a97433c3d3473eb00baa4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 06:56:03 +0200 Subject: [PATCH 21/37] CHANGES - reference methods --- CHANGES/1026.bugfix.rst | 4 ++-- docs/api.rst | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index bcfe7db5..a4c592cc 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -8,8 +8,8 @@ breaking use-cases such as URL("https://web.archive.org/web/") / "https://github.com/" -Now "/" and :spelling:ignore:`joinpath()` operation keep empty segments, -but do not introduce new empty segments. +Now :meth:`URL.__truediv__` and :meth:`joinpath` operation keep empty +segments, but do not introduce new empty segments. e.g. .. code-block:: python diff --git a/docs/api.rst b/docs/api.rst index 5a4c3259..90734646 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -844,6 +844,20 @@ The path is encoded if needed. .. versionadded:: 1.9 +.. method:: URL.__truediv__(url) + + Shortcut for :meth:`URL.joinpath` with a single element and ``encoded==False``. + + .. doctest:: + + >>> url = URL('http://example.com/path?arg#frag') / 'to' + >>> url + URL('http://example.com/path/to') + >>> url.parts + ('/', 'path', 'to') + + .. versionadded:: 0.9 + .. method:: URL.join(url) Construct a full (“absolute”) URL by combining a “base URL” @@ -870,6 +884,8 @@ The path is encoded if needed. >>> base.join(URL('//python.org/page.html')) URL('http://python.org/page.html') + + Human readable representation ----------------------------- From b766d3265ff3876f8816384ca050838669ffca8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 06:59:48 +0200 Subject: [PATCH 22/37] CHANGES - URL.joinpath --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index a4c592cc..dd3554be 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -8,7 +8,7 @@ breaking use-cases such as URL("https://web.archive.org/web/") / "https://github.com/" -Now :meth:`URL.__truediv__` and :meth:`joinpath` operation keep empty +Now :meth:`URL.__truediv__` and :meth:`URL.joinpath` operation keep empty segments, but do not introduce new empty segments. e.g. From bd0eb9402a3feceeb467f7cdbfa89846d4d6887f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 07:11:42 +0200 Subject: [PATCH 23/37] CHANGES - include module in reference --- CHANGES/1026.bugfix.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index dd3554be..22586f81 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -8,8 +8,8 @@ breaking use-cases such as URL("https://web.archive.org/web/") / "https://github.com/" -Now :meth:`URL.__truediv__` and :meth:`URL.joinpath` operation keep empty -segments, but do not introduce new empty segments. +Now :meth:`yarl.URL.__truediv__` and :meth:`yarl.URL.joinpath` operation +keep empty segments, but do not introduce new empty segments. e.g. .. code-block:: python From 25978a2261ca5bc0b35af131070054c42dc9d3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 07:20:11 +0200 Subject: [PATCH 24/37] CHANGES - shorten method ref --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index 22586f81..23f784b0 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -8,7 +8,7 @@ breaking use-cases such as URL("https://web.archive.org/web/") / "https://github.com/" -Now :meth:`yarl.URL.__truediv__` and :meth:`yarl.URL.joinpath` operation +Now :meth:`~yarl.URL.__truediv__` and :meth:`~yarl.URL.joinpath` operation keep empty segments, but do not introduce new empty segments. e.g. From 1c3b9cec6867637b5af41aa6317adba7d5290d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 07:22:51 +0200 Subject: [PATCH 25/37] docs - == != = --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 90734646..5323be1a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -846,7 +846,7 @@ The path is encoded if needed. .. method:: URL.__truediv__(url) - Shortcut for :meth:`URL.joinpath` with a single element and ``encoded==False``. + Shortcut for :meth:`URL.joinpath` with a single element and ``encoded=False``. .. doctest:: From 84ee421ce0427d2390e67b8b774eaa3930ff01d3 Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 18:02:02 +0200 Subject: [PATCH 26/37] Update CHANGES/1026.bugfix.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index 23f784b0..6b6142b5 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -8,7 +8,7 @@ breaking use-cases such as URL("https://web.archive.org/web/") / "https://github.com/" -Now :meth:`~yarl.URL.__truediv__` and :meth:`~yarl.URL.joinpath` operation +Now :meth:`/ ` and :meth:`URL.joinpath() ` operation keep empty segments, but do not introduce new empty segments. e.g. From fa41f7733cd3addb140d75a10eba764ed9e67d2c Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 18:02:25 +0200 Subject: [PATCH 27/37] Update CHANGES/1026.bugfix.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.bugfix.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index 6b6142b5..b1494f62 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -17,4 +17,5 @@ e.g. URL("https://example.org/") / "" does not introduce an empty segment. --- by :user:`commonism` and :user:`youtux`. + +-- by :user:`commonism` and :user:`youtux` From 9a1e48bdde3d2b4ce69bc4d77f19d20caa4db416 Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 18:03:12 +0200 Subject: [PATCH 28/37] Update docs/api.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- docs/api.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 5323be1a..e8e6eb53 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -884,8 +884,6 @@ The path is encoded if needed. >>> base.join(URL('//python.org/page.html')) URL('http://python.org/page.html') - - Human readable representation ----------------------------- From 572dad4466c9e9dd29a7f4748c503427c718e30b Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 18:03:31 +0200 Subject: [PATCH 29/37] Update yarl/_url.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- yarl/_url.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarl/_url.py b/yarl/_url.py index 99bd4d14..84b9519b 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -733,7 +733,8 @@ def _make_child(self, paths, encoded=False): if not segments: continue # remove trailing empty segment for all but the last path - parsed += segments[1:] if not last and segments[0] == "" else segments + segment_slice_start = int(not last and segments[0] == "") + parsed += segments[segment_slice_start:] parsed.reverse() if self._val.path and (old_path_segments := self._val.path.split("/")): From b265e580f13078988585a253329534d91b59bd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 18:12:54 +0200 Subject: [PATCH 30/37] CHANGES - doc / --- CHANGES/1026.doc.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CHANGES/1026.doc.rst diff --git a/CHANGES/1026.doc.rst b/CHANGES/1026.doc.rst new file mode 100644 index 00000000..273920c6 --- /dev/null +++ b/CHANGES/1026.doc.rst @@ -0,0 +1,3 @@ +Add documentation for :meth:`/ ` + +-- by :user:`commonism` From f6ab864e9d99eb899bd9f95d7254e5f17e066983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 1 Jul 2024 18:17:09 +0200 Subject: [PATCH 31/37] CHANGES - ref __truediv__ --- CHANGES.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e5dd4813..7a54996e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -105,7 +105,7 @@ Bug fixes --------- - Stopped dropping trailing slashes in :py:meth:`~yarl.URL.joinpath` -- by :user:`gmacon`. (:issue:`862`, :issue:`866`) -- Started accepting string subclasses in ``__truediv__()`` operations (``URL / segment``) -- by :user:`mjpieters`. (:issue:`871`, :issue:`884`) +- Started accepting string subclasses in :meth:`__truediv__() ` operations (``URL / segment``) -- by :user:`mjpieters`. (:issue:`871`, :issue:`884`) - Fixed the human representation of URLs with square brackets in usernames and passwords -- by :user:`mjpieters`. (:issue:`876`, :issue:`882`) - Updated type hints to include ``URL.missing_port()``, ``URL.__bytes__()`` and the ``encoding`` argument to :py:meth:`~yarl.URL.joinpath` @@ -174,7 +174,7 @@ Contributor-facing changes Bugfixes -------- -- Fix regression with ``__truediv__`` and absolute URLs with empty paths causing the raw path to lack the leading ``/``. +- Fix regression with :meth:`__truediv__ ` and absolute URLs with empty paths causing the raw path to lack the leading ``/``. (`#854 `_) @@ -196,7 +196,7 @@ Features -------- - Added ``URL.joinpath(*elements)``, to create a new URL appending multiple path elements. (`#704 `_) -- Made ``URL.__truediv__()`` return ``NotImplemented`` if called with an +- Made :meth:`URL.__truediv__() ` return ``NotImplemented`` if called with an unsupported type — by :user:`michaeljpeters`. (`#832 `_) From b06a09d5a797ca9b0c75c4f2cd38b64fdb87a4ac Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 19:39:57 +0200 Subject: [PATCH 32/37] Update CHANGES/1026.doc.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.doc.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGES/1026.doc.rst b/CHANGES/1026.doc.rst index 273920c6..172546c2 100644 --- a/CHANGES/1026.doc.rst +++ b/CHANGES/1026.doc.rst @@ -1,3 +1,2 @@ -Add documentation for :meth:`/ ` - --- by :user:`commonism` +The pre-existing :meth:`/ ` magic method +has been documented in the API reference -- by :user:`commonism`. From 0026b6d808683bc68dfcc1a99007db72a824ea42 Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 19:40:48 +0200 Subject: [PATCH 33/37] Update CHANGES.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7a54996e..eef35442 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -174,7 +174,7 @@ Contributor-facing changes Bugfixes -------- -- Fix regression with :meth:`__truediv__ ` and absolute URLs with empty paths causing the raw path to lack the leading ``/``. +- Fix regression with :meth:`~yarl.URL.__truediv__` and absolute URLs with empty paths causing the raw path to lack the leading ``/``. (`#854 `_) From 583acb167d3b81029f0fee0ac14121a42a5f13ef Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 19:41:03 +0200 Subject: [PATCH 34/37] Update CHANGES/1026.bugfix.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/1026.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.bugfix.rst b/CHANGES/1026.bugfix.rst index b1494f62..eeae24c2 100644 --- a/CHANGES/1026.bugfix.rst +++ b/CHANGES/1026.bugfix.rst @@ -8,7 +8,7 @@ breaking use-cases such as URL("https://web.archive.org/web/") / "https://github.com/" -Now :meth:`/ ` and :meth:`URL.joinpath() ` operation +Now :meth:`/ operation ` and :meth:`URL.joinpath() ` keep empty segments, but do not introduce new empty segments. e.g. From 474926d2c2adca879d7afabfea1c36260ea35400 Mon Sep 17 00:00:00 2001 From: commonism Date: Mon, 1 Jul 2024 19:41:13 +0200 Subject: [PATCH 35/37] Update CHANGES.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index eef35442..d2e92be3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -105,7 +105,7 @@ Bug fixes --------- - Stopped dropping trailing slashes in :py:meth:`~yarl.URL.joinpath` -- by :user:`gmacon`. (:issue:`862`, :issue:`866`) -- Started accepting string subclasses in :meth:`__truediv__() ` operations (``URL / segment``) -- by :user:`mjpieters`. (:issue:`871`, :issue:`884`) +- Started accepting string subclasses in :meth:`~yarl.URL.__truediv__` operations (``URL / segment``) -- by :user:`mjpieters`. (:issue:`871`, :issue:`884`) - Fixed the human representation of URLs with square brackets in usernames and passwords -- by :user:`mjpieters`. (:issue:`876`, :issue:`882`) - Updated type hints to include ``URL.missing_port()``, ``URL.__bytes__()`` and the ``encoding`` argument to :py:meth:`~yarl.URL.joinpath` From 04d2f55ff4ef1830a627165f3a99b601732692ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 1 Jul 2024 20:39:59 +0200 Subject: [PATCH 36/37] Update CHANGES/1026.doc.rst --- CHANGES/1026.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/1026.doc.rst b/CHANGES/1026.doc.rst index 172546c2..daabece4 100644 --- a/CHANGES/1026.doc.rst +++ b/CHANGES/1026.doc.rst @@ -1,2 +1,2 @@ -The pre-existing :meth:`/ ` magic method +The pre-existing :meth:`/ magic method ` has been documented in the API reference -- by :user:`commonism`. From c521e339429b508331e0895439b8dce9a55509bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Wed, 3 Jul 2024 21:33:36 +0200 Subject: [PATCH 37/37] tests - remove chained comparision --- tests/test_url.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_url.py b/tests/test_url.py index ebdc38f3..a8f1351b 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -859,9 +859,8 @@ def test_joinpath(base, to_join, expected): def test_joinpath_empty_segments(base, to_join, expected): url = URL(f"http://example.com/{base}") assert ( - str(url.joinpath(to_join)) - == str(url / to_join) - == f"http://example.com/{expected}" + f"http://example.com/{expected}" == str(url.joinpath(to_join)) + and str(url / to_join) == f"http://example.com/{expected}" )