{"id":"7c463b36859835c5","repo":"pypa/pip","slug":"invalid-uri-s","errorCode":null,"errorMessage":"invalid URI: %s","messagePattern":"invalid URI: (.+?)","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":184,"sourceCode":"            m = IDENTIFIER.match(s)\n            if not m:\n                raise SyntaxError('malformed extra: %s' % s)\n            extras.append(m.groups()[0])\n            s = s[m.end():]\n            if not s:\n                break\n            if s[0] != ',':\n                raise SyntaxError('comma expected in extras: %s' % s)\n            s = s[1:].lstrip()\n        if not extras:\n            extras = None\n    if remaining:\n        if remaining[0] == '@':\n            # it's a URI\n            remaining = remaining[1:].lstrip()\n            m = NON_SPACE.match(remaining)\n            if not m:\n                raise SyntaxError('invalid URI: %s' % remaining)\n            uri = m.groups()[0]\n            t = urlparse(uri)\n            # there are issues with Python and URL parsing, so this test\n            # is a bit crude. See bpo-20271, bpo-23505. Python doesn't\n            # always parse invalid URLs correctly - it should raise\n            # exceptions for malformed URLs\n            if not (t.scheme and t.netloc):\n                raise SyntaxError('Invalid URL: %s' % uri)\n            remaining = remaining[m.end():].lstrip()\n        else:\n\n            def get_versions(ver_remaining):\n                \"\"\"\n                Return a list of operator, version tuples if any are\n                specified, else None.\n                \"\"\"\n                m = COMPARE_OP.match(ver_remaining)\n                versions = None","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L166-L202","documentation":"Raised by distlib's `parse_requirement` for a requirement specified by direct URL (`name @ <url>`): after the `@`, the parser expects a non-space token (matched by `NON_SPACE`); if nothing follows (empty/whitespace after `@`), it raises `SyntaxError: invalid URI`.","triggerScenarios":"Parsing a requirement like `mypkg @` or `mypkg @   ` (nothing after the `@`). Triggered when pip parses a PEP 508 direct-URL requirement whose URL was omitted.","commonSituations":"Truncating a direct-URL requirement line; templating that substituted an empty URL variable after `@`; requirements files with incomplete lines.","solutions":["Provide the full URL after `@`: `mypkg @ https://host/path/mypkg-1.0.tar.gz`.","Ensure any URL variable substituted into the requirement is non-empty.","Validate direct-URL requirements with `packaging.requirements.Requirement(...)`."],"exampleFix":"# before\nmypkg @\n\n# after\nmypkg @ https://files.pythonhosted.org/packages/.../mypkg-1.0-py3-none-any.whl","handlingStrategy":"validation","validationCode":"from packaging.requirements import Requirement, InvalidRequirement\n\ndef validate_requirement(line: str) -> None:\n    try:\n        Requirement(line)\n    except InvalidRequirement as e:\n        raise ValueError(f\"Bad requirement: {e}\") from e","typeGuard":"def direct_url_present(line: str) -> bool:\n    # if there's an '@' it must be followed by a non-space URL token\n    import re\n    return not re.search(r\"@\\s*$\", line.strip())","tryCatchPattern":"null","preventionTips":["After `name @` always include a full URL token.","In templates, fail if the URL variable is empty before substitution.","Validate direct-URL requirements with `packaging.requirements.Requirement`."],"tags":["distlib","requirements-parsing","url","pep508"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}