pypa/pip · error · SyntaxError

invalid URI: %s

Error message

invalid URI: %s

What it means

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`.

Source

Thrown at src/pip/_vendor/distlib/util.py:184

            m = IDENTIFIER.match(s)
            if not m:
                raise SyntaxError('malformed extra: %s' % s)
            extras.append(m.groups()[0])
            s = s[m.end():]
            if not s:
                break
            if s[0] != ',':
                raise SyntaxError('comma expected in extras: %s' % s)
            s = s[1:].lstrip()
        if not extras:
            extras = None
    if remaining:
        if remaining[0] == '@':
            # it's a URI
            remaining = remaining[1:].lstrip()
            m = NON_SPACE.match(remaining)
            if not m:
                raise SyntaxError('invalid URI: %s' % remaining)
            uri = m.groups()[0]
            t = urlparse(uri)
            # there are issues with Python and URL parsing, so this test
            # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
            # always parse invalid URLs correctly - it should raise
            # exceptions for malformed URLs
            if not (t.scheme and t.netloc):
                raise SyntaxError('Invalid URL: %s' % uri)
            remaining = remaining[m.end():].lstrip()
        else:

            def get_versions(ver_remaining):
                """
                Return a list of operator, version tuples if any are
                specified, else None.
                """
                m = COMPARE_OP.match(ver_remaining)
                versions = None

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Provide the full URL after `@`: `mypkg @ https://host/path/mypkg-1.0.tar.gz`.
  2. Ensure any URL variable substituted into the requirement is non-empty.
  3. Validate direct-URL requirements with `packaging.requirements.Requirement(...)`.

Example fix

# before
mypkg @

# after
mypkg @ https://files.pythonhosted.org/packages/.../mypkg-1.0-py3-none-any.whl
Defensive patterns

Strategy: validation

Validate before calling

from packaging.requirements import Requirement, InvalidRequirement

def validate_requirement(line: str) -> None:
    try:
        Requirement(line)
    except InvalidRequirement as e:
        raise ValueError(f"Bad requirement: {e}") from e

Type guard

def direct_url_present(line: str) -> bool:
    # if there's an '@' it must be followed by a non-space URL token
    import re
    return not re.search(r"@\s*$", line.strip())

Try / catch

null

Prevention

When it happens

Trigger: Parsing a requirement like `mypkg @` or `mypkg @ ` (nothing after the `@`). Triggered when pip parses a PEP 508 direct-URL requirement whose URL was omitted.

Common situations: Truncating a direct-URL requirement line; templating that substituted an empty URL variable after `@`; requirements files with incomplete lines.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/7c463b36859835c5.json. Report an issue: GitHub.