{"id":877,"library":"hyperlink","title":"hyperlink library","description":"hyperlink is a pure-Python library that provides a featureful, immutable, and semantically correct URL object, adhering to RFC 3986 and RFC 3987 for URIs and IRIs. It emphasizes correctness and ease of use, providing a robust model for URL parsing, construction, and transformation. The current stable version is 21.0.0, with major releases occurring approximately annually.","status":"active","version":"21.0.0","language":"python","source_language":"en","source_url":"https://github.com/python-hyper/hyperlink","tags":["URL parsing","networking","immutable URLs","RFC 3986","RFC 3987"],"install":[{"cmd":"pip install hyperlink","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Used for Internationalized Domain Names (IDNA) processing, replacing Python's built-in facilities for stricter and more correct output.","package":"idna"}],"imports":[{"note":"The lower-level URL class, also available as hyperlink.EncodedURL. Requires manual handling of encoding for special characters.","symbol":"URL","correct":"from hyperlink import URL"},{"note":"The recommended top-level convenience function to parse a textual URL into a DecodedURL object.","symbol":"parse","correct":"from hyperlink import parse"},{"note":"The recommended URL type for most operations, automatically handling encoding and decoding of components.","symbol":"DecodedURL","correct":"from hyperlink import DecodedURL"}],"quickstart":{"code":"from hyperlink import parse\n\n# Parse a URL from text\nurl = parse(u'http://github.com/python-hyper/hyperlink?utm_source=readthedocs')\nprint(f\"Original URL: {url.to_text()}\")\n\n# Create a new URL by replacing components (URL objects are immutable)\nsecure_url = url.replace(scheme=u'https', port=443)\nprint(f\"Secure URL: {secure_url.to_text()}\")\n\n# Navigate to a child path or resolve relative paths\norg_url = secure_url.click(u'.') # Clicks to the root of the domain\nprint(f\"Org URL: {org_url.to_text()}\")\n\n# Access query parameters\nutm_source = secure_url.get(u'utm_source')[0]\nprint(f\"UTM Source: {utm_source}\")\n\n# Build a URL from components\nnew_url_from_parts = URL(scheme=u'https', host=u'example.com', path=[u'api', u'v1'], query=((u'id', u'123'),))\nprint(f\"Built from parts: {new_url_from_parts.to_text()}\")","lang":"python","description":"This quickstart demonstrates parsing a URL from a string, creating modified copies using the immutable API, navigating paths, accessing query parameters, and constructing a URL from individual components."},"warnings":[{"fix":"Always assign the result of modifying methods to a new variable (e.g., `new_url = old_url.replace(...)`).","message":"hyperlink's URL objects are immutable. All 'modifying' methods (e.g., `replace()`, `add()`, `drop()`, `set()`) return a *new* URL object with the changes, rather than modifying the object in place.","severity":"gotcha","affected_versions":"All versions"},{"fix":"For most common use cases, prefer using `hyperlink.parse()` or `hyperlink.DecodedURL` to leverage automatic encoding/decoding. Use `hyperlink.URL` (or `hyperlink.EncodedURL`) only when explicit control over encoding is required.","message":"The `hyperlink.parse()` function, by default (since v18.0.0), returns a `DecodedURL` which automatically handles encoding/decoding of URL components. Directly instantiating `hyperlink.URL` (or `hyperlink.EncodedURL`) requires manual handling of URL-reserved characters, which can easily lead to incorrect encoding if not carefully managed.","severity":"gotcha","affected_versions":"v18.0.0 and later"},{"fix":"If existing code expects the old escaping behavior for query parameter values, it may need to be updated to account for the new standard or apply custom encoding/decoding.","message":"In version 19.0.0, the library changed how 'equals sign' characters are handled in query parameter *values*. Previously, they were escaped; now, they are not, aligning with standard web form encoding. This could be a breaking change if your system relied on the old escaping behavior for parsing or comparison.","severity":"breaking","affected_versions":"v19.0.0 onwards"},{"fix":"Be aware that the `URL` constructor may internally adjust or ignore certain arguments to maintain RFC compliance and validity. Always inspect the resulting `URL` object if relying on specific argument values.","message":"In version 20.0.0, bug fixes related to hidden state on `URL` objects mean that constructor arguments like `rooted` and `uses_netloc` may be ignored if applying them would result in an invalid or unparseable URL. The object prioritizes validity over literal argument adherence in such cases.","severity":"breaking","affected_versions":"v20.0.0 onwards"},{"fix":"Ensure that the 'URL' class is properly imported at the beginning of your script using 'from hyperlink import URL' or use 'hyperlink.URL(...)' after importing the module as 'import hyperlink'.","message":"The 'URL' class, like other components of the 'hyperlink' library, must be explicitly imported (e.g., 'from hyperlink import URL') or accessed via the module namespace (e.g., 'hyperlink.URL') before it can be instantiated or used.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure `from hyperlink import URL` or `import hyperlink` and then use `hyperlink.URL` (or other components) in your script.","message":"The `hyperlink.URL` class and other top-level components must be explicitly imported before use (e.g., `from hyperlink import URL`). Attempting to use them without proper import will result in a `NameError`.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-05-12T20:42:07.670Z","next_check":"2026-06-27T00:00:00.000Z","problems":[{"fix":"Ensure the URL string provided to `hyperlink.parse()` adheres to valid URL syntax, especially concerning scheme, host, and port components. If the URL is external input, validate or sanitize it before parsing.","cause":"This error occurs when `hyperlink.parse()` attempts to parse a URL string that is syntactically malformed, specifically when a port is expected but not provided or is invalid within the scheme.","error":"hyperlink._url.URLParseError: port must not be empty: 'http:'"},{"fix":"Remove any invalid characters or whitespace from the URL scheme. If you intend to parse a less strict 'text' format rather than a strict URL, consider using `URL.from_text()` if it better fits your use case, although `hyperlink.parse()` is generally more robust for standard URLs.","cause":"This `ValueError` is raised by `hyperlink.parse()` when the URL scheme contains invalid characters, such as leading or trailing whitespace, or characters not permitted in a URL scheme.","error":"ValueError: invalid scheme: ' http'. Only alphanumeric, \"+\", \"-\", and \".\" allowed. Did you meant to call URL.from_text()?"},{"fix":"Convert the input to a string (e.g., by decoding a bytes object using `.decode('utf-8')`) before passing it to `hyperlink.parse()`.","cause":"The `hyperlink.parse()` method expects a string as input, but it received a bytes-like object or another non-string type.","error":"TypeError: expected str for text, got b''"},{"fix":"To modify a URL, use the provided fluent methods (e.g., `.replace()`, `.set()`, `.add()`) which return a *new* `URL` object with the desired changes, leaving the original object unchanged. For example, to change the host, use `new_url = old_url.set(host='example.com')`.","cause":"The `hyperlink.URL` object is immutable, meaning its attributes (like `host`, `scheme`, `path`) cannot be modified directly after creation. Attempts to assign new values to these attributes will result in an `AttributeError`.","error":"AttributeError: can't set attribute"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":null,"quickstart_tag":null,"pypi_latest":"21.0.0","cli_name":"","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":2,"disk_size":"18.9M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":2,"disk_size":"18.9M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.6,"import_time_s":0.02,"mem_mb":2,"disk_size":"19M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":2,"disk_size":"19M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":2,"disk_size":"20.8M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.09,"mem_mb":2.1,"disk_size":"20.8M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.05,"mem_mb":2,"disk_size":"21M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":2.1,"disk_size":"21M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.05,"mem_mb":1.8,"disk_size":"12.6M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":1.8,"disk_size":"12.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.6,"import_time_s":0.06,"mem_mb":1.8,"disk_size":"13M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":1.8,"disk_size":"13M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.9,"disk_size":"12.3M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":1.9,"disk_size":"12.3M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.6,"import_time_s":0.04,"mem_mb":1.7,"disk_size":"13M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":1.6,"disk_size":"13M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.9,"disk_size":"18.3M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.9,"disk_size":"18.3M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2,"import_time_s":0.04,"mem_mb":1.9,"disk_size":"19M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.9,"disk_size":"19M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":null,"tag_description":null,"results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}