Skip to content

Commit 0d1c8b7

Browse files
authored
Add a cache for building encoded URLs (#1432)
1 parent e3a282e commit 0d1c8b7

File tree

2 files changed

+83
-51
lines changed

2 files changed

+83
-51
lines changed

CHANGES/1432.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved cache performance when :class:`~yarl.URL` objects are constructed from :py:meth:`~yarl.URL.build` with ``encoded=True`` -- by :user:`bdraco`.

yarl/_url.py

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

215215

216+
@lru_cache
217+
def build_pre_encoded_url(
218+
scheme: str,
219+
authority: str,
220+
user: Union[str, None],
221+
password: Union[str, None],
222+
host: str,
223+
port: Union[int, None],
224+
path: str,
225+
query_string: str,
226+
fragment: str,
227+
) -> "URL":
228+
"""Build a pre-encoded URL from parts."""
229+
self = object.__new__(URL)
230+
self._scheme = scheme
231+
if authority:
232+
self._netloc = authority
233+
elif host:
234+
if port is not None:
235+
port = None if port == DEFAULT_PORTS.get(scheme) else port
236+
if user is None and password is None:
237+
self._netloc = host if port is None else f"{host}:{port}"
238+
else:
239+
self._netloc = make_netloc(user, password, host, port)
240+
else:
241+
self._netloc = ""
242+
self._path = path
243+
self._query = query_string
244+
self._fragment = fragment
245+
self._cache = {}
246+
return self
247+
248+
216249
@lru_cache
217250
def from_parts(scheme: str, netloc: str, path: str, query: str, fragment: str) -> "URL":
218251
"""Create a new URL from parts."""
@@ -375,61 +408,59 @@ def build(
375408
'"query_string", and "fragment" args, use empty string instead.'
376409
)
377410

411+
if query:
412+
query_string = get_str_query(query) or ""
413+
378414
if encoded:
379-
if authority:
380-
netloc = authority
381-
elif host:
382-
if port is not None:
383-
port = None if port == DEFAULT_PORTS.get(scheme) else port
384-
if user is None and password is None:
385-
netloc = host if port is None else f"{host}:{port}"
386-
else:
387-
netloc = make_netloc(user, password, host, port)
388-
else:
389-
netloc = ""
390-
else: # not encoded
391-
_host: Union[str, None] = None
392-
if authority:
393-
user, password, _host, port = split_netloc(authority)
394-
_host = _encode_host(_host, validate_host=False) if _host else ""
395-
elif host:
396-
_host = _encode_host(host, validate_host=True)
415+
return build_pre_encoded_url(
416+
scheme,
417+
authority,
418+
user,
419+
password,
420+
host,
421+
port,
422+
path,
423+
query_string,
424+
fragment,
425+
)
426+
427+
self = object.__new__(URL)
428+
self._scheme = scheme
429+
_host: Union[str, None] = None
430+
if authority:
431+
user, password, _host, port = split_netloc(authority)
432+
_host = _encode_host(_host, validate_host=False) if _host else ""
433+
elif host:
434+
_host = _encode_host(host, validate_host=True)
435+
else:
436+
self._netloc = ""
437+
438+
if _host is not None:
439+
if port is not None:
440+
port = None if port == DEFAULT_PORTS.get(scheme) else port
441+
if user is None and password is None:
442+
self._netloc = _host if port is None else f"{_host}:{port}"
397443
else:
398-
netloc = ""
399-
400-
if _host is not None:
401-
if port is not None:
402-
port = None if port == DEFAULT_PORTS.get(scheme) else port
403-
if user is None and password is None:
404-
netloc = _host if port is None else f"{_host}:{port}"
405-
else:
406-
netloc = make_netloc(user, password, _host, port, True)
407-
408-
path = PATH_QUOTER(path) if path else path
409-
if path and netloc:
410-
if "." in path:
411-
path = normalize_path(path)
412-
if path[0] != "/":
413-
msg = (
414-
"Path in a URL with authority should "
415-
"start with a slash ('/') if set"
416-
)
417-
raise ValueError(msg)
418-
419-
query_string = QUERY_QUOTER(query_string) if query_string else query_string
420-
fragment = FRAGMENT_QUOTER(fragment) if fragment else fragment
444+
self._netloc = make_netloc(user, password, _host, port, True)
421445

422-
if query:
423-
query_string = get_str_query(query) or ""
446+
path = PATH_QUOTER(path) if path else path
447+
if path and self._netloc:
448+
if "." in path:
449+
path = normalize_path(path)
450+
if path[0] != "/":
451+
msg = (
452+
"Path in a URL with authority should "
453+
"start with a slash ('/') if set"
454+
)
455+
raise ValueError(msg)
424456

425-
url = object.__new__(cls)
426-
url._scheme = scheme
427-
url._netloc = netloc
428-
url._path = path
429-
url._query = query_string
430-
url._fragment = fragment
431-
url._cache = {}
432-
return url
457+
self._path = path
458+
if not query and query_string:
459+
query_string = QUERY_QUOTER(query_string)
460+
self._query = query_string
461+
self._fragment = FRAGMENT_QUOTER(fragment) if fragment else fragment
462+
self._cache = {}
463+
return self
433464

434465
def __init_subclass__(cls):
435466
raise TypeError(f"Inheriting a class {cls!r} from URL is forbidden")

0 commit comments

Comments
 (0)