pypa/pip · error · ParserSyntaxError
Expected URL after @
Error message
Expected URL after @
What it means
Raised at src/pip/_vendor/packaging/_parser.py:140 inside _parse_requirement_details: once an AT token ('@') is matched and consumed (lines 135-136), the grammar requires a URL token next. tokenizer.expect("URL", expected="URL after @") fails because the URL rule ([^ \t]+, _tokenizer.py:82) cannot match — the position is at end-of-string or at a whitespace-only run. This is the direct-URL requirement syntax (PEP 508 'name @ url') missing its URL.
Source
Thrown at src/pip/_vendor/packaging/_parser.py:140
def _parse_requirement_details(
tokenizer: Tokenizer,
) -> tuple[str, str, MarkerList | None]:
"""
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")View on GitHub (pinned to d7d0d0a394)
Solutions
- Supply a concrete URL after '@', e.g. Requirement('mypkg @ https://example.com/mypkg-1.0.tar.gz').
- If you did not mean a direct-URL install, remove the '@' and use a version specifier instead (Requirement('mypkg==1.0')).
- If building the string programmatically, assert the URL component is non-empty before concatenating.
Example fix
# before
Requirement('mypkg @')
# after
Requirement('mypkg @ https://example.com/mypkg-1.0.tar.gz') Defensive patterns
Strategy: validation
Validate before calling
def has_url_after_at(s: str) -> bool:
if '@' not in s:
return True # not a direct-url requirement
head, _, tail = s.partition('@')
return bool(tail.strip()) # something non-space must follow '@' Type guard
from typing import TypeGuard
from pip._vendor.packaging.requirements import Requirement, InvalidRequirement
def is_valid_requirement(s: str) -> TypeGuard[str]:
try:
Requirement(s)
except InvalidRequirement:
return False
return True Try / catch
from pip._vendor.packaging.requirements import Requirement, InvalidRequirement
try:
req = Requirement(req_str)
except InvalidRequirement as e:
if 'URL after @' in str(e):
raise ValueError(f'missing URL after @ in {req_str!r}') from e
raise Prevention
- When generating direct-URL requirements, assert the URL variable is truthy before interpolating: f'{name} @ {url}'.
- Prefer 'name @ <url>' only when you actually have a URL; otherwise use a version specifier.
When it happens
Trigger: Requirement('mypkg @'), Requirement('mypkg @ '), or pip install 'pkg @' where nothing follows the '@'. Also when the '@' was intended as part of a version/extras but the parser interpreted it as the direct-URL introducer.
Common situations: Typing a direct-URL requirement and forgetting the URL, a templated/CI-generated requirement string where the URL variable expanded to empty, or confusing the legacy 'pkg==1.0' form with the 'pkg @ <url>' form.
Related errors
- Expected end of dependency specifier
- Expected whitespace after URL
- Expected extra name after comma
- {editable_req} is not a valid editable requirement. It shoul
- Editable requirements are not allowed as constraints
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/49ab93a002014db4.json.
Report an issue: GitHub.