python-poetry / python-poetry/poetry
`poetry add` with `||` operator breaks further poetry commands
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 34.3k
- Forks
- 2.5k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 30
Description
Description
I am able to add a dependency like this : poetry add 'pytest=^4|^6. However if I run poetry install or similar command that needs to read the pyproject.toml, I see this error:
The requirement is invalid: Unexpected character at column 16
pytest (>=4,<5 || >=6,<7)
Similarly, if I try to add the dependency this this: poetry add 'pytest>=4,<7,!=5.*. I get a similar error when I run something like poetry install:
The requirement is invalid: Unexpected character at column 21
pytest (>=4,<5.dev0 || ==6.*)
Workarounds
- Manually remove the dependency from
pyproject.toml - Run
poetry update - Manually add this:
[tool.poetry.dependencies] pytest = "^4|^6" - Run
poetry update
Poetry Installation Method
pip inside a virtualenv
Operating System
Ubuntu 24.04
Poetry Version
2.2.1
Poetry Configuration
cache-dir = "/home/rzuckerm/.cache/pypoetry"
data-dir = "/home/rzuckerm/.local/share/pypoetry"
installer.max-workers = null
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
python.installation-dir = "{data-dir}/python" # /home/rzuckerm/.local/share/pypoetry/python
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = false
virtualenvs.options.no-pip = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /home/rzuckerm/.cache/pypoetry/virtualenvs
virtualenvs.prompt = "{project_name}-py{python_version}"
virtualenvs.use-poetry-python = false
Python Sysconfig
sysconfig.log
cache-dir = "/home/rzuckerm/.cache/pypoetry"
data-dir = "/home/rzuckerm/.local/share/pypoetry"
installer.max-workers = null
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
python.installation-dir = "{data-dir}/python" # /home/rzuckerm/.local/share/pypoetry/python
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = false
virtualenvs.options.no-pip = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /home/rzuckerm/.cache/pypoetry/virtualenvs
virtualenvs.prompt = "{project_name}-py{python_version}"
virtualenvs.use-poetry-python = false
Example pyproject.toml
Before poetry add:
[project]
name = "poetry-example"
version = "0.1.0"
description = ""
authors = [
{name = "rzuckerm"}
]
readme = "README.md"
requires-python = ">=3.10, <4"
dependencies = []
[tool.poetry]
packages = [{include = "poetry_example", from = "src"}]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
After poetry add:
[project]
name = "poetry-example"
version = "0.1.0"
description = ""
authors = [
{name = "rzuckerm"}
]
readme = "README.md"
requires-python = ">=3.10, <4"
dependencies = ["pytest (>=4,<5 || >=6,<7)"]
[tool.poetry]
packages = [{include = "poetry_example", from = "src"}]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
Poetry Runtime Logs
poetry-runtime.log
Stack trace:
1 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/lexer.py:665 in lex
663│ while True:
664│ lexer = self.lexers[parser_state.position]
→ 665│ yield lexer.next_token(lexer_state, parser_state)
666│ except EOFError:
667│ pass
UnexpectedCharacters
No terminal matches '|' in the current parser context, at line 1 col 16
pytest (>=4,<5 || >=6,<7)
^
Expected one of:
* _R_PAREN
* _MARKER_SEPARATOR
* _COMMA
Previous tokens: Token('LEGACY_VERSION_CONSTRAINT', '<5')
at ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/lexer.py:598 in next_token
594│ if not res:
595│ allowed = self.scanner.allowed_types - self.ignore_types
596│ if not allowed:
597│ allowed = {"<END-OF-FILE>"}
→ 598│ raise UnexpectedCharacters(lex_state.text, line_ctr.char_pos, line_ctr.line, line_ctr.column,
599│ allowed=allowed, token_history=lex_state.last_token and [lex_state.last_token],
600│ state=parser_state, terminals_by_name=self.terminals_by_name)
601│
602│ value, type_ = res
The following error occurred when trying to handle this error:
Stack trace:
8 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/version/requirements.py:43 in __init__
41│
42│ try:
→ 43│ parsed = _parser.parse(requirement_string)
44│ except (UnexpectedCharacters, UnexpectedToken) as e:
45│ raise InvalidRequirementError(
7 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/version/parser.py:31 in parse
29│ )
30│
→ 31│ return self._lark.parse(text=text, **kwargs)
32│
6 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/lark.py:655 in parse
653│
654│ """
→ 655│ return self.parser.parse(text, start=start, on_error=on_error)
656│
657│
5 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/parser_frontends.py:104 in parse
102│ kw = {} if on_error is None else {'on_error': on_error}
103│ stream = self._make_lexer_thread(text)
→ 104│ return self.parser.parse(stream, chosen_start, **kw)
105│
106│ def parse_interactive(self, text: Optional[str]=None, start=None):
4 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/parsers/lalr_parser.py:42 in parse
40│ def parse(self, lexer, start, on_error=None):
41│ try:
→ 42│ return self.parser.parse(lexer, start)
43│ except UnexpectedInput as e:
44│ if on_error is None:
3 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/parsers/lalr_parser.py:88 in parse
86│ if start_interactive:
87│ return InteractiveParser(self, parser_state, parser_state.lexer)
→ 88│ return self.parse_from_state(parser_state)
89│
90│
2 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/parsers/lalr_parser.py:111 in parse_from_state
109│ except NameError:
110│ pass
→ 111│ raise e
112│ except Exception as e:
113│ if self.debug:
1 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/parsers/lalr_parser.py:100 in parse_from_state
98│ try:
99│ token = last_token
→ 100│ for token in state.lexer.lex(state):
101│ assert token is not None
102│ state.feed_token(token)
UnexpectedToken
Unexpected token Token('URI', '||') at line 1, column 16.
Expected one of:
* _R_PAREN
* _COMMA
Previous tokens: [Token('LEGACY_VERSION_CONSTRAINT', '<5')]
at ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/_vendor/lark/lexer.py:674 in lex
670│ # This tests the input against the global context, to provide a nicer error.
671│ try:
672│ last_token = lexer_state.last_token # Save last_token. Calling root_lexer.next_token will change this to the wrong token
673│ token = self.root_lexer.next_token(lexer_state, parser_state)
→ 674│ raise UnexpectedToken(token, e.allowed, state=parser_state, token_history=[last_token], terminals_by_name=self.root_lexer.terminals_by_name)
675│ except UnexpectedCharacters:
676│ raise e # Raise the original UnexpectedCharacters. The root lexer raises it with the wrong expected set.
677│
678│ ###}
The following error occurred when trying to handle this error:
Stack trace:
16 ~/virtualenvs/poetry/lib/python3.12/site-packages/cleo/application.py:327 in run
325│
326│ try:
→ 327│ exit_code = self._run(io)
328│ except BrokenPipeError:
329│ # If we are piped to another process, it may close early and send a
15 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/console/application.py:260 in _run
258│
259│ try:
→ 260│ exit_code = super()._run(io)
261│ except PoetryRuntimeError as e:
262│ io.write_error_line("")
14 ~/virtualenvs/poetry/lib/python3.12/site-packages/cleo/application.py:431 in _run
429│ io.input.interactive(interactive)
430│
→ 431│ exit_code = self._run_command(command, io)
432│ self._running_command = None
433│
13 ~/virtualenvs/poetry/lib/python3.12/site-packages/cleo/application.py:473 in _run_command
471│
472│ if error is not None:
→ 473│ raise error
474│
475│ return terminate_event.exit_code
12 ~/virtualenvs/poetry/lib/python3.12/site-packages/cleo/application.py:454 in _run_command
452│
453│ try:
→ 454│ self._event_dispatcher.dispatch(command_event, COMMAND)
455│
456│ if command_event.command_should_run():
11 ~/virtualenvs/poetry/lib/python3.12/site-packages/cleo/events/event_dispatcher.py:26 in dispatch
24│
25│ if listeners:
→ 26│ self._do_dispatch(listeners, event_name, event)
27│
28│ return event
10 ~/virtualenvs/poetry/lib/python3.12/site-packages/cleo/events/event_dispatcher.py:85 in _do_dispatch
83│ break
84│
→ 85│ listener(event, event_name, self)
86│
87│ def _sort_listeners(self, event_name: str) -> None:
9 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/console/application.py:578 in configure_env
576│
577│ io = event.io
→ 578│ poetry = command.poetry
579│
580│ env_manager = EnvManager(poetry, io=io)
8 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/console/commands/command.py:24 in poetry
22│ def poetry(self) -> Poetry:
23│ if self._poetry is None:
→ 24│ return self.get_application().poetry
25│
26│ return self._poetry
7 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/console/application.py:199 in poetry
197│ return self._poetry
198│
→ 199│ self._poetry = Factory().create_poetry(
200│ cwd=self.project_directory,
201│ io=self._io,
6 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/factory.py:60 in create_poetry
58│ io = NullIO()
59│
→ 60│ base_poetry = super().create_poetry(cwd=cwd, with_groups=with_groups)
61│
62│ if version_str := base_poetry.local_config.get("requires-poetry"):
5 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/factory.py:78 in create_poetry
76│ assert isinstance(version, str)
77│ package = self.get_package(name, version)
→ 78│ self.configure_package(
79│ package, pyproject, poetry_file.parent, with_groups=with_groups
80│ )
4 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/factory.py:164 in configure_package
162│ cls._configure_package_metadata(package, project, tool_poetry, root)
163│ cls._configure_entry_points(package, project, tool_poetry)
→ 164│ cls._configure_package_dependencies(
165│ package=package,
166│ project=project,
3 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/factory.py:396 in _configure_package_dependencies
394│ for constraint in dependencies:
395│ group.add_dependency(
→ 396│ Dependency.create_from_pep_508(
397│ constraint, relative_to=package.root_dir
398│ )
2 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/packages/dependency.py:370 in create_from_pep_508
368│ name += " ;" + rest.split(" ;", 1)[1]
369│
→ 370│ req = parse_requirement(name)
371│
372│ name = req.name
1 ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/version/requirements.py:118 in parse_requirement
116│ @functools.cache
117│ def parse_requirement(requirement_string: str) -> Requirement:
→ 118│ return Requirement(requirement_string)
119│
InvalidRequirementError
The requirement is invalid: Unexpected character at column 16
pytest (>=4,<5 || >=6,<7)
^
at ~/virtualenvs/poetry/lib/python3.12/site-packages/poetry/core/version/requirements.py:45 in __init__
41│
42│ try:
43│ parsed = _parser.parse(requirement_string)
44│ except (UnexpectedCharacters, UnexpectedToken) as e:
→ 45│ raise InvalidRequirementError(
46│ "The requirement is invalid: Unexpected character at column"
47│ f" {e.column}\n\n{e.get_context(requirement_string)}"
48│ )
49│
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the failure with poetry add 'pytest=^4|^6' and then poetry install; the stack trace points to poetry/core/version/requirements.py and poetry/core/version/parser.py. Trace how the || constraint is parsed, then verify that the generated pyproject.toml can be read by subsequent Poetry commands and that both reported constraint forms work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100