Skip to content

Commit e3a282e

Browse files
authored
Make from_parts a LRU to increase the chance we can preserve the internal cache (#1434)
1 parent 22544b1 commit e3a282e

File tree

2 files changed

+39
-65
lines changed

2 files changed

+39
-65
lines changed

CHANGES/1434.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved cache performance for operations that produce a new :class:`~yarl.URL` object -- by :user:`bdraco`.

yarl/_url.py

Lines changed: 38 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,19 @@ def pre_encoded_url(url_str: str) -> "URL":
213213
return self
214214

215215

216+
@lru_cache
217+
def from_parts(scheme: str, netloc: str, path: str, query: str, fragment: str) -> "URL":
218+
"""Create a new URL from parts."""
219+
self = object.__new__(URL)
220+
self._scheme = scheme
221+
self._netloc = netloc
222+
self._path = path
223+
self._query = query
224+
self._fragment = fragment
225+
self._cache = {}
226+
return self
227+
228+
216229
@rewrite_module
217230
class URL:
218231
# Don't derive from str
@@ -308,10 +321,7 @@ def __new__(
308321
if type(val) is SplitResult:
309322
if not encoded:
310323
raise ValueError("Cannot apply decoding to SplitResult")
311-
self = object.__new__(URL)
312-
self._scheme, self._netloc, self._path, self._query, self._fragment = val
313-
self._cache = {}
314-
return self
324+
return from_parts(*val)
315325
if isinstance(val, str):
316326
return pre_encoded_url(str(val)) if encoded else encode_url(str(val))
317327
if val is UNDEFINED:
@@ -421,20 +431,6 @@ def build(
421431
url._cache = {}
422432
return url
423433

424-
@classmethod
425-
def _from_parts(
426-
cls, scheme: str, netloc: str, path: str, query: str, fragment: str
427-
) -> "URL":
428-
"""Create a new URL from parts."""
429-
self = object.__new__(cls)
430-
self._scheme = scheme
431-
self._netloc = netloc
432-
self._path = path
433-
self._query = query
434-
self._fragment = fragment
435-
self._cache = {}
436-
return self
437-
438434
def __init_subclass__(cls):
439435
raise TypeError(f"Inheriting a class {cls!r} from URL is forbidden")
440436

@@ -587,7 +583,7 @@ def _origin(self) -> "URL":
587583
netloc = make_netloc(None, None, encoded_host, self.explicit_port)
588584
elif not self._path and not self._query and not self._fragment:
589585
return self
590-
return self._from_parts(scheme, netloc, "", "", "")
586+
return from_parts(scheme, netloc, "", "", "")
591587

592588
def relative(self) -> "URL":
593589
"""Return a relative part of the URL.
@@ -597,7 +593,7 @@ def relative(self) -> "URL":
597593
"""
598594
if not self._netloc:
599595
raise ValueError("URL should be absolute")
600-
return self._from_parts("", "", self._path, self._query, self._fragment)
596+
return from_parts("", "", self._path, self._query, self._fragment)
601597

602598
@cached_property
603599
def absolute(self) -> bool:
@@ -916,12 +912,10 @@ def parent(self) -> "URL":
916912
path = self._path
917913
if not path or path == "/":
918914
if self._fragment or self._query:
919-
return self._from_parts(self._scheme, self._netloc, path, "", "")
915+
return from_parts(self._scheme, self._netloc, path, "", "")
920916
return self
921917
parts = path.split("/")
922-
return self._from_parts(
923-
self._scheme, self._netloc, "/".join(parts[:-1]), "", ""
924-
)
918+
return from_parts(self._scheme, self._netloc, "/".join(parts[:-1]), "", "")
925919

926920
@cached_property
927921
def raw_name(self) -> str:
@@ -998,13 +992,13 @@ def _make_child(self, paths: "Sequence[str]", encoded: bool = False) -> "URL":
998992

999993
parsed.reverse()
1000994
if not netloc or not needs_normalize:
1001-
return self._from_parts(self._scheme, netloc, "/".join(parsed), "", "")
995+
return from_parts(self._scheme, netloc, "/".join(parsed), "", "")
1002996

1003997
path = "/".join(normalize_path_segments(parsed))
1004998
# If normalizing the path segments removed the leading slash, add it back.
1005999
if path and path[0] != "/":
10061000
path = f"/{path}"
1007-
return self._from_parts(self._scheme, netloc, path, "", "")
1001+
return from_parts(self._scheme, netloc, path, "", "")
10081002

10091003
def with_scheme(self, scheme: str) -> "URL":
10101004
"""Return a new URL with scheme replaced."""
@@ -1019,9 +1013,7 @@ def with_scheme(self, scheme: str) -> "URL":
10191013
f"relative URLs for the {lower_scheme} scheme"
10201014
)
10211015
raise ValueError(msg)
1022-
return self._from_parts(
1023-
lower_scheme, netloc, self._path, self._query, self._fragment
1024-
)
1016+
return from_parts(lower_scheme, netloc, self._path, self._query, self._fragment)
10251017

10261018
def with_user(self, user: Union[str, None]) -> "URL":
10271019
"""Return a new URL with user replaced.
@@ -1043,9 +1035,7 @@ def with_user(self, user: Union[str, None]) -> "URL":
10431035
raise ValueError("user replacement is not allowed for relative URLs")
10441036
encoded_host = self.host_subcomponent or ""
10451037
netloc = make_netloc(user, password, encoded_host, self.explicit_port)
1046-
return self._from_parts(
1047-
self._scheme, netloc, self._path, self._query, self._fragment
1048-
)
1038+
return from_parts(self._scheme, netloc, self._path, self._query, self._fragment)
10491039

10501040
def with_password(self, password: Union[str, None]) -> "URL":
10511041
"""Return a new URL with password replaced.
@@ -1067,9 +1057,7 @@ def with_password(self, password: Union[str, None]) -> "URL":
10671057
encoded_host = self.host_subcomponent or ""
10681058
port = self.explicit_port
10691059
netloc = make_netloc(self.raw_user, password, encoded_host, port)
1070-
return self._from_parts(
1071-
self._scheme, netloc, self._path, self._query, self._fragment
1072-
)
1060+
return from_parts(self._scheme, netloc, self._path, self._query, self._fragment)
10731061

10741062
def with_host(self, host: str) -> "URL":
10751063
"""Return a new URL with host replaced.
@@ -1090,9 +1078,7 @@ def with_host(self, host: str) -> "URL":
10901078
encoded_host = _encode_host(host, validate_host=True) if host else ""
10911079
port = self.explicit_port
10921080
netloc = make_netloc(self.raw_user, self.raw_password, encoded_host, port)
1093-
return self._from_parts(
1094-
self._scheme, netloc, self._path, self._query, self._fragment
1095-
)
1081+
return from_parts(self._scheme, netloc, self._path, self._query, self._fragment)
10961082

10971083
def with_port(self, port: Union[int, None]) -> "URL":
10981084
"""Return a new URL with port replaced.
@@ -1110,9 +1096,7 @@ def with_port(self, port: Union[int, None]) -> "URL":
11101096
raise ValueError("port replacement is not allowed for relative URLs")
11111097
encoded_host = self.host_subcomponent or ""
11121098
netloc = make_netloc(self.raw_user, self.raw_password, encoded_host, port)
1113-
return self._from_parts(
1114-
self._scheme, netloc, self._path, self._query, self._fragment
1115-
)
1099+
return from_parts(self._scheme, netloc, self._path, self._query, self._fragment)
11161100

11171101
def with_path(
11181102
self,
@@ -1132,7 +1116,7 @@ def with_path(
11321116
path = f"/{path}"
11331117
query = self._query if keep_query else ""
11341118
fragment = self._fragment if keep_fragment else ""
1135-
return self._from_parts(self._scheme, netloc, path, query, fragment)
1119+
return from_parts(self._scheme, netloc, path, query, fragment)
11361120

11371121
@overload
11381122
def with_query(self, query: Query) -> "URL": ...
@@ -1155,9 +1139,7 @@ def with_query(self, *args: Any, **kwargs: Any) -> "URL":
11551139
"""
11561140
# N.B. doesn't cleanup query/fragment
11571141
query = get_str_query(*args, **kwargs) or ""
1158-
return self._from_parts(
1159-
self._scheme, self._netloc, self._path, query, self._fragment
1160-
)
1142+
return from_parts(self._scheme, self._netloc, self._path, query, self._fragment)
11611143

11621144
@overload
11631145
def extend_query(self, query: Query) -> "URL": ...
@@ -1183,9 +1165,7 @@ def extend_query(self, *args: Any, **kwargs: Any) -> "URL":
11831165
query += new_query if query[-1] == "&" else f"&{new_query}"
11841166
else:
11851167
query = new_query
1186-
return self._from_parts(
1187-
self._scheme, self._netloc, self._path, query, self._fragment
1188-
)
1168+
return from_parts(self._scheme, self._netloc, self._path, query, self._fragment)
11891169

11901170
@overload
11911171
def update_query(self, query: Query) -> "URL": ...
@@ -1242,9 +1222,7 @@ def update_query(self, *args: Any, **kwargs: Any) -> "URL":
12421222
"Invalid query type: only str, mapping or "
12431223
"sequence of (key, value) pairs is allowed"
12441224
)
1245-
return self._from_parts(
1246-
self._scheme, self._netloc, self._path, query, self._fragment
1247-
)
1225+
return from_parts(self._scheme, self._netloc, self._path, query, self._fragment)
12481226

12491227
def without_query_params(self, *query_params: str) -> "URL":
12501228
"""Remove some keys from query part and return new URL."""
@@ -1276,7 +1254,7 @@ def with_fragment(self, fragment: Union[str, None]) -> "URL":
12761254
raw_fragment = FRAGMENT_QUOTER(fragment)
12771255
if self._fragment == raw_fragment:
12781256
return self
1279-
return self._from_parts(
1257+
return from_parts(
12801258
self._scheme, self._netloc, self._path, self._query, raw_fragment
12811259
)
12821260

@@ -1316,7 +1294,7 @@ def with_name(
13161294

13171295
query = self._query if keep_query else ""
13181296
fragment = self._fragment if keep_fragment else ""
1319-
return self._from_parts(self._scheme, netloc, "/".join(parts), query, fragment)
1297+
return from_parts(self._scheme, netloc, "/".join(parts), query, fragment)
13201298

13211299
def with_suffix(
13221300
self,
@@ -1364,9 +1342,7 @@ def join(self, url: "URL") -> "URL":
13641342

13651343
# scheme is in uses_authority as uses_authority is a superset of uses_relative
13661344
if (join_netloc := url._netloc) and scheme in USES_AUTHORITY:
1367-
return self._from_parts(
1368-
scheme, join_netloc, url._path, url._query, url._fragment
1369-
)
1345+
return from_parts(scheme, join_netloc, url._path, url._query, url._fragment)
13701346

13711347
orig_path = self._path
13721348
if join_path := url._path:
@@ -1389,16 +1365,13 @@ def join(self, url: "URL") -> "URL":
13891365
else:
13901366
path = orig_path
13911367

1392-
new_url = object.__new__(URL)
1393-
new_url._scheme = scheme
1394-
new_url._netloc = self._netloc
1395-
new_url._path = path
1396-
new_url._query = url._query if join_path or url._query else self._query
1397-
new_url._fragment = (
1398-
url._fragment if join_path or url._fragment else self._fragment
1368+
return from_parts(
1369+
scheme,
1370+
self._netloc,
1371+
path,
1372+
url._query if join_path or url._query else self._query,
1373+
url._fragment if join_path or url._fragment else self._fragment,
13991374
)
1400-
new_url._cache = {}
1401-
return new_url
14021375

14031376
def joinpath(self, *other: str, encoded: bool = False) -> "URL":
14041377
"""Return a new URL with the elements in other appended to the path."""

0 commit comments

Comments
 (0)