Skip to content

Commit

Permalink
Avoid normalizing child paths when there are no dots in the path (#1248)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Oct 13, 2024
1 parent 9107005 commit 404b542
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/1248.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved performance of :py:meth:`~yarl.URL.joinpath` -- by :user:`bdraco`.
18 changes: 18 additions & 0 deletions tests/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,16 @@ def test_joinpath(base, to_join, expected):
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"),
pytest.param(
"path/a/b/c/d/e", "a/../../../../../../c", "path/c", id="long-backtrack"
),
pytest.param(
"path/a/b/c/d/e",
"a/../../../././../../../c",
"path/c",
id="long-backtrack-with-dots",
),
pytest.param("path/a/../../d/e", "a/../c", "d/e/c", id="backtrack-in-both"),
],
)
def test_joinpath_empty_segments(base, to_join, expected):
Expand All @@ -965,6 +975,14 @@ def test_joinpath_empty_segments(base, to_join, expected):
)


def test_joinpath_backtrack_to_base():
url = URL("http://example.com/../../c")
new_url = url.joinpath("../../..")
assert str(new_url) == "http://example.com"
assert new_url.path == "/"
assert new_url.raw_path == "/"


def test_joinpath_single_empty_segments():
"""joining standalone empty segments does not create empty segments"""
a = URL("/1//2///3")
Expand Down
4 changes: 3 additions & 1 deletion yarl/_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,7 @@ def _make_child(self, paths: "Sequence[str]", encoded: bool = False) -> "URL":
keep existing, but do not create new, empty segments
"""
parsed: List[str] = []
needs_normalize: bool = False
for idx, path in enumerate(reversed(paths)):
# empty segment of last is not removed
last = idx == 0
Expand All @@ -957,6 +958,7 @@ def _make_child(self, paths: "Sequence[str]", encoded: bool = False) -> "URL":
f"Appending path {path!r} starting from slash is forbidden"
)
path = path if encoded else self._PATH_QUOTER(path)
needs_normalize |= "." in path
segments = list(reversed(path.split("/")))
# remove trailing empty segment for all but the last path
segment_slice_start = int(not last and segments[0] == "")
Expand All @@ -968,7 +970,7 @@ def _make_child(self, paths: "Sequence[str]", encoded: bool = False) -> "URL":
parsed = [*old_path_segments[:old_path_cutoff], *parsed]

if self.absolute:
parsed = _normalize_path_segments(parsed)
parsed = _normalize_path_segments(parsed) if needs_normalize else parsed
if parsed and parsed[0] != "":
# inject a leading slash when adding a path to an absolute URL
# where there was none before
Expand Down

0 comments on commit 404b542

Please sign in to comment.