pypa/pip · error · ParserSyntaxError

Expected whitespace after URL

Error message

Expected whitespace after URL

What it means

Raised as a ParserSyntaxError (surfacing as InvalidRequirement) at line 152 when, after reading the URL in a direct-URL requirement, the next token is neither END nor whitespace (WS). The grammar requires whitespace separating the URL from any following marker clause (line 152); only end-of-input is exempt (line 149-150).

Solutions

  1. Insert a space between the URL and the semicolon: 'pkg @ https://x/y.tar.gz ; python_version >= "3.0"'.
  2. When concatenating fragments, always join URL and marker with ' ' rather than '' or ';'.
  3. Round-trip the constructed string through Requirement(...).str() to normalize spacing.

Example fix

// before
Requirement('pkg @ https://x/y.tar.gz;python_version >= "3.0"')

// after
Requirement('pkg @ https://x/y.tar.gz ; python_version >= "3.0"')
Defensive patterns

Strategy: validation

Validate before calling

import re
def normalized_url_req(name, url, marker=None):
    parts = [f'{name} @ {url}']
    if marker:
        parts.append(f'; {marker}')
    return ' '.join(parts)

Try / catch

from packaging.requirements import Requirement, InvalidRequirement
try:
    req = Requirement(raw)
except InvalidRequirement as e:
    if 'whitespace after URL' in str(e):
        raw = raw.replace(';', ' ;', 1)  # attempt space insertion
        req = Requirement(raw)
    raise

Prevention

When it happens

Trigger: A direct-URL requirement with a marker clause attached directly to the URL with no space, e.g. 'pkg @ https://x/y.tar.gz;python_version >= "3.0"'. The ';' (or other token) follows the URL immediately with no WS, so tokenizer.expect("WS", ...) fails.

Common situations: Programmatically building 'name @ ' + url + ';' + marker without inserting a space; minified/compressed requirement strings; copy-paste losing the separating space.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/51729fa729048d1a. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/_parser.py:152

    """
    requirement_details = AT URL (WS requirement_marker?)?
                        | specifier WS? (requirement_marker)?
    """

    specifier = ""
    url = ""
    marker = None

    if tokenizer.check("AT"):
        tokenizer.read()
        tokenizer.consume("WS")

        url_start = tokenizer.position
        url = tokenizer.expect("URL", expected="URL after @").text
        if tokenizer.check("END", peek=True):
            return (url, specifier, marker)

        tokenizer.expect("WS", expected="whitespace after URL")

        # The input might end after whitespace.
        if tokenizer.check("END", peek=True):
            return (url, specifier, marker)

        marker = _parse_requirement_marker(
            tokenizer,
            span_start=url_start,
            expected="semicolon (after URL and whitespace)",
        )
    else:
        specifier_start = tokenizer.position
        specifier = _parse_specifier(tokenizer)
        tokenizer.consume("WS")

        if tokenizer.check("END", peek=True):
            return (url, specifier, marker)

View on GitHub (pinned to f399c37189)