{"record":{"id":"aa8078e88d5b7768","repo":"roboflow/supervision","slug":"invalid-url-url-error","errorCode":null,"errorMessage":"Invalid URL {url}: {error}","messagePattern":"Invalid URL (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/utils/file.py","lineNumber":41,"sourceCode":"    Returns:\n        Normalized URL string.\n\n    Raises:\n        ValueError: If the URL is invalid or uses an unsupported scheme.\n    \"\"\"\n    try:\n        original_parsed_url = urllib.parse.urlparse(url)\n        if \"\\\\\" in original_parsed_url.netloc:\n            raise ValueError(\"URL authority contains a backslash\")\n\n        prepared_request = requests.Request(method=\"GET\", url=url).prepare()\n        prepared_url = prepared_request.url\n        if prepared_url is None:\n            raise ValueError(\"prepared URL is empty\")\n\n        parsed_url = urllib.parse.urlparse(prepared_url)\n    except (requests.RequestException, ValueError) as error:\n        raise ValueError(f\"Invalid URL {url!r}: {error}\") from error\n\n    if parsed_url.scheme not in {\"http\", \"https\"}:\n        raise ValueError(\n            f\"Unsupported URL scheme {parsed_url.scheme!r} in {url!r}. \"\n            \"Only HTTP and HTTPS URLs are supported.\"\n        )\n    if parsed_url.hostname is None:\n        raise ValueError(f\"Invalid URL {url!r}: no host supplied.\")\n\n    return prepared_url\n\n\ndef _download_to_file(\n    url: str, target: Path, *, timeout: float = 30.0, stream: bool = False\n) -> None:\n    \"\"\"\n    Download `url` to `target` atomically using a temporary file and `os.replace`.\n","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/utils/file.py#L23-L59","documentation":"This is the catch-all wrapper: any ValueError or requests.RequestException raised while parsing/preparing the URL is re-raised as `ValueError(f\"Invalid URL {url!r}: {error}\")` with the original error chained. It surfaces malformed URLs — control characters, invalid ports, spaces, unparseable syntax — that `urllib.parse` or `requests.Request.prepare()` reject during normalization.","triggerScenarios":"URLs containing spaces or control characters (`'https://host/a b.png'`); invalid port (`'https://host:99999/'`); unparseable fragments; any requests-side InvalidURL/MissingSchema raised during preparation.","commonSituations":"Unencoded filenames in URLs (spaces, `#`, unicode); URLs assembled from user input without `urllib.parse.quote`; proxies/intermediaries mangling URLs; copy-paste with invisible characters.","solutions":["Encode path/query components: `urllib.parse.quote(filename, safe='')` when building URLs.","Print the chained original error (`except ValueError as e: print(e.__cause__)`) to see the exact parse failure.","Validate user-supplied URLs with a strict regex or `urllib.parse.urlparse` sanity checks before use."],"exampleFix":"# before\nurl = f'https://host/images/{filename}'  # filename='my file.png' -> invalid\n# after\nfrom urllib.parse import quote\nurl = f'https://host/images/{quote(filename)}'  # 'my%20file.png'","handlingStrategy":"try-catch","validationCode":"from urllib.parse import urlparse, quote\n# build safely, encode user-supplied parts\nurl = f'https://host/images/{quote(filename, safe=\"\")}'\nparts = urlparse(url)\nassert parts.scheme in {'http', 'https'} and parts.hostname","typeGuard":null,"tryCatchPattern":"try:\n    path = download_asset(url)\nexcept ValueError as e:\n    log.error('bad URL %s: %s (cause: %s)', url, e, e.__cause__)\n    raise","preventionTips":["Percent-encode all user-supplied path/query components.","Inspect e.__cause__ to find the underlying parse failure.","Run urlparse sanity checks on external input before download."],"tags":["url","download","validation","parsing","encoding"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}