yarl — Yet Another URL Library
yarl is an immutable, RFC 3986-compliant URL parsing and manipulation library for Python 3. It provides the URL class with automatic percent-encoding/decoding, pathlib-style path operations, query-string helpers, and human-readable representations. The current release is 1.23.0, published March 2026. Releases ship frequently (multiple times per minor version) as part of the aio-libs ecosystem, closely tracking aiohttp.
Warnings
- breaking URL.build() and URL.with_host() raise TypeError when port is passed as a string. Previously a string port silently produced a malformed URL.
- breaking propcache is now a required hard dependency (split out ~v1.17). Environments that vendor or vendor-pin transitive deps without propcache will fail to import yarl.
- deprecated cache_configure() parameters ip_address_size and host_validate_size are deprecated in favour of encode_host_size and will be removed in a future release.
- gotcha URL properties (e.g. .path, .host, .query_string) return percent-DECODED values. Pass these to other URLs or HTTP wire formats and you risk double-encoding. Use the raw_ prefixed variants (.raw_path, .raw_host, .raw_query_string) for wire-safe strings.
- gotcha Passing boolean values to with_query() or URL.build(query=...) raises TypeError. yarl refuses to guess how to serialize True/False.
- gotcha encoded=True skips all auto-encoding but any subsequent mutation method (.with_query(), /, etc.) may re-quote parts, silently corrupting pre-encoded values. The docs explicitly warn against relying on this.
- gotcha The / operator (URL.__truediv__) and URL.joinpath() strip a trailing slash from the base path before appending the segment. URL('https://example.com/api/') / 'v1' gives /api/v1, not /api//v1. This matches pathlib semantics but surprises users expecting query-string-style appending.
Install
-
pip install yarl -
YARL_NO_EXTENSIONS=1 pip install yarl
Imports
- URL
from yarl import URL
- cache_configure
from yarl import cache_configure cache_configure(encode_host_size=256)
Quickstart
from yarl import URL
# Parse an existing URL
url = URL('https://user:pw@api.example.com:8080/v1/items?page=1&q=foo#section')
print(url.scheme) # 'https'
print(url.host) # 'api.example.com' (decoded)
print(url.raw_host) # IDNA-encoded form
print(url.port) # 8080 (explicit); None falls back to scheme default
print(url.path) # '/v1/items' (percent-decoded)
print(url.raw_path) # '/v1/items' (percent-encoded, wire form)
print(url.query) # MultiDictProxy with parsed key/value pairs
print(url.query_string) # 'page=1&q=foo'
print(url.fragment) # 'section'
# Build from components — port must be int, not str
base = URL.build(scheme='https', host='api.example.com', port=8080, path='/v1')
print(base) # https://api.example.com:8080/v1
# Path-join (like pathlib)
endpoint = base / 'items' / '42'
print(endpoint) # https://api.example.com:8080/v1/items/42
# Apply a query string with %
with_query = endpoint % {'expand': 'true', 'format': 'json'}
print(with_query)
# Mutation methods return new URL objects (immutable)
updated = endpoint.with_query({'page': '2'}).with_fragment('results')
print(str(updated)) # wire-safe string for HTTP clients
print(updated.human_repr()) # human-readable (non-ASCII decoded)
# Non-ASCII is encoded automatically
unicode_url = URL('https://example.com/пошук?q=кіт')
print(str(unicode_url)) # percent-encoded
print(unicode_url.human_repr()) # readable