Yet another URL library - comprehensive URL parsing and manipulation for Python
Path manipulation, normalization, and joining operations for URL path components, including filename and extension handling with support for complex path manipulations.
Methods for building and extending URL paths with proper encoding and normalization.
def joinpath(self, *other: str, encoded: bool = False) -> "URL":
"""
Join URL path with additional path segments.
Args:
*other (str): Path segments to append
encoded (bool): Whether path segments are already encoded
Returns:
URL: New URL with joined path segments
Examples:
url.joinpath('api', 'v1', 'users')
url.joinpath('files', 'document.pdf')
url.joinpath('path with spaces', encoded=False)
"""
def join(self, url: "URL") -> "URL":
"""
Join this URL with another URL using RFC 3986 resolution rules.
Args:
url (URL): URL to resolve against this URL
Returns:
URL: New URL after RFC 3986 resolution
Examples:
base.join(URL('path/to/resource'))
base.join(URL('../parent/resource'))
base.join(URL('?query=value'))
"""
def __truediv__(self, name: str) -> "URL":
"""
Append path segment using / operator.
Args:
name (str): Path segment to append
Returns:
URL: New URL with appended path segment
Examples:
url / 'api' / 'v1' / 'users'
url / 'documents' / 'file.pdf'
"""Properties for accessing path components and segments.
@property
def raw_parts(self) -> tuple[str, ...]:
"""
Raw path segments as tuple.
Returns:
tuple[str, ...]: Tuple of raw (encoded) path segments
Examples:
URL('/path/to/file').raw_parts # ('/', 'path', 'to', 'file')
URL('/api/v1/').raw_parts # ('/', 'api', 'v1', '')
"""
@property
def parts(self) -> tuple[str, ...]:
"""
Decoded path segments as tuple.
Returns:
tuple[str, ...]: Tuple of decoded path segments
"""
@property
def parent(self) -> "URL":
"""
Parent URL (removes last path segment).
Returns:
URL: New URL with last path segment removed
Examples:
URL('/path/to/file').parent # URL('/path/to')
URL('/api/v1/users').parent # URL('/api/v1')
URL('/').parent # URL('/')
"""
@property
def raw_path(self) -> str:
"""Raw (encoded) path component"""
@property
def path(self) -> str:
"""Decoded path component"""
@property
def path_safe(self) -> str:
"""Path with safe decoding (preserves important characters like /)"""Methods and properties for working with filename components of paths.
@property
def raw_name(self) -> str:
"""
Raw filename component (last path segment).
Returns:
str: Raw filename or empty string if path ends with /
"""
@property
def name(self) -> str:
"""
Decoded filename component (last path segment).
Returns:
str: Decoded filename or empty string if path ends with /
Examples:
URL('/documents/report.pdf').name # 'report.pdf'
URL('/api/users/').name # ''
URL('/path/to/file').name # 'file'
"""
@property
def raw_suffix(self) -> str:
"""
Raw file extension including leading dot.
Returns:
str: Raw file extension or empty string if no extension
"""
@property
def suffix(self) -> str:
"""
Decoded file extension including leading dot.
Returns:
str: Decoded file extension or empty string if no extension
Examples:
URL('/file.txt').suffix # '.txt'
URL('/archive.tar.gz').suffix # '.gz'
URL('/README').suffix # ''
"""
@property
def raw_suffixes(self) -> tuple[str, ...]:
"""
All raw file extensions as tuple.
Returns:
tuple[str, ...]: Tuple of all raw extensions
"""
@property
def suffixes(self) -> tuple[str, ...]:
"""
All decoded file extensions as tuple.
Returns:
tuple[str, ...]: Tuple of all decoded extensions
Examples:
URL('/file.txt').suffixes # ('.txt',)
URL('/archive.tar.gz').suffixes # ('.tar', '.gz')
URL('/document.backup.old').suffixes # ('.backup', '.old')
"""
def with_name(self, name: str, *, encoded: bool = False) -> "URL":
"""
Return URL with new filename component.
Args:
name (str): New filename to replace current filename
encoded (bool): Whether name is already encoded
Returns:
URL: New URL with updated filename
Raises:
ValueError: If name contains path separators
Examples:
url.with_name('newfile.txt')
url.with_name('document with spaces.pdf')
"""
def with_suffix(self, suffix: str, *, encoded: bool = False) -> "URL":
"""
Return URL with new file extension.
Args:
suffix (str): New file extension (should include leading dot)
encoded (bool): Whether suffix is already encoded
Returns:
URL: New URL with updated file extension
Examples:
url.with_suffix('.json')
url.with_suffix('.backup.txt')
"""Built-in path normalization handles relative path components and ensures clean paths.
Path normalization automatically:
. (current directory) references.. (parent directory) referencesfrom yarl import URL
base_url = URL('https://example.com/api/v1')
# Append path segments
users_url = base_url.joinpath('users')
print(users_url) # https://example.com/api/v1/users
user_url = base_url.joinpath('users', '123')
print(user_url) # https://example.com/api/v1/users/123
# Multiple segments at once
deep_url = base_url.joinpath('resources', 'documents', 'files')
print(deep_url) # https://example.com/api/v1/resources/documents/filesfrom yarl import URL
api_url = URL('https://api.example.com')
# Chain path segments
endpoint = api_url / 'v2' / 'users' / '456' / 'profile'
print(endpoint) # https://api.example.com/v2/users/456/profile
# Mix with other operations
full_url = (api_url
/ 'search'
/ 'documents'
% {'q': 'python', 'limit': 10})
print(full_url) # https://api.example.com/search/documents?q=python&limit=10from yarl import URL
file_url = URL('https://cdn.example.com/documents/report.pdf')
# Access filename components
print(file_url.name) # 'report.pdf'
print(file_url.suffix) # '.pdf'
print(file_url.suffixes) # ('.pdf',)
# Modify filename
new_file = file_url.with_name('summary.pdf')
print(new_file) # https://cdn.example.com/documents/summary.pdf
# Change extension
txt_version = file_url.with_suffix('.txt')
print(txt_version) # https://cdn.example.com/documents/report.txt
# Work with parent directories
parent = file_url.parent
print(parent) # https://cdn.example.com/documents
grandparent = file_url.parent.parent
print(grandparent) # https://cdn.example.comfrom yarl import URL
archive_url = URL('https://example.com/files/backup.tar.gz')
print(archive_url.suffix) # '.gz' (last extension)
print(archive_url.suffixes) # ('.tar', '.gz') (all extensions)
# Change just the compression
uncompressed = archive_url.with_suffix('.tar')
print(uncompressed) # https://example.com/files/backup.tar
# Multiple extensions
config_url = URL('https://example.com/config.local.dev.json')
print(config_url.suffixes) # ('.local', '.dev', '.json')
# Remove all extensions by changing name
base_name = config_url.name.split('.')[0] # 'config'
clean_url = config_url.with_name(base_name)
print(clean_url) # https://example.com/configfrom yarl import URL
deep_url = URL('https://example.com/api/v1/users/123/documents/456')
# Access path segments
print(deep_url.parts) # ('/', 'api', 'v1', 'users', '123', 'documents', '456')
# Navigate up the hierarchy
print(deep_url.parent) # https://example.com/api/v1/users/123/documents
print(deep_url.parent.parent) # https://example.com/api/v1/users/123
print(deep_url.parent.parent.parent) # https://example.com/api/v1/users
# Build new paths from segments
segments = deep_url.parts[1:4] # ('api', 'v1', 'users')
new_base = URL('https://example.com').joinpath(*segments)
print(new_base) # https://example.com/api/v1/usersfrom yarl import URL
# Relative path resolution
messy_url = URL('https://example.com/api/../docs/./guide/../tutorial/basics.html')
print(messy_url) # Automatically normalized to: https://example.com/docs/tutorial/basics.html
# Join with relative paths
base = URL('https://example.com/api/v1/users')
relative_join = base.join(URL('../docs/guide.html'))
print(relative_join) # https://example.com/api/v1/docs/guide.html
# Complex relative navigation
complex_base = URL('https://example.com/app/module/submodule/')
back_and_forward = complex_base.join(URL('../../other/module/file.js'))
print(back_and_forward) # https://example.com/app/other/module/file.jsfrom yarl import URL
base_url = URL('https://example.com/app/current/page')
# Absolute URL resolution
absolute_result = base_url.join(URL('https://other.com/resource'))
print(absolute_result) # https://other.com/resource
# Relative path resolution
relative_result = base_url.join(URL('other/resource'))
print(relative_result) # https://example.com/app/current/other/resource
# Parent directory resolution
parent_result = base_url.join(URL('../sibling/resource'))
print(parent_result) # https://example.com/app/sibling/resource
# Query-only resolution
query_result = base_url.join(URL('?query=value'))
print(query_result) # https://example.com/app/current/page?query=value
# Fragment-only resolution
fragment_result = base_url.join(URL('#section'))
print(fragment_result) # https://example.com/app/current/page#sectionfrom yarl import URL
# Root path operations
root_url = URL('https://example.com/')
api_from_root = root_url / 'api' / 'v1'
print(api_from_root) # https://example.com/api/v1
# Empty path components
empty_url = URL('https://example.com')
with_empty = empty_url.joinpath('', 'api', '', 'users')
print(with_empty) # https://example.com/api/users (empty segments ignored)
# Trailing slash preservation
trailing_url = URL('https://example.com/api/')
print(trailing_url.joinpath('users')) # https://example.com/api/users
print(trailing_url / 'users') # https://example.com/api/users
# Special characters in paths
special_url = URL('https://example.com').joinpath('files', 'my document.pdf')
print(special_url) # https://example.com/files/my%20document.pdf (automatically encoded)Install with Tessl CLI
npx tessl i tessl/pypi-yarl