Skip to content

Avoid merging incompatible marker constraints - #956

Merged
radoering merged 2 commits into
python-poetry:mainfrom
sakshichitnis27:fix-10978-platform-release-marker
Aug 14, 2026
Merged

radoering merged 2 commits into
python-poetry:mainfrom
sakshichitnis27:fix-10978-platform-release-marker

Conversation

@sakshichitnis27

Copy link
Copy Markdown
Contributor

Fixes python-poetry/poetry#10978

Summary

  • Avoid merging same-name markers when their constraints use incompatible types.
  • Add a regression case for combining a version comparison with a swapped string comparison for platform_release.

Testing

  • pytest tests/version/test_markers.py
  • pytest
  • ruff check src/poetry/core/version/markers.py tests/version/test_markers.py
  • mypy src/poetry/core/version/markers.py tests/version/test_markers.py

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new type guard for constraints only distinguishes VersionConstraint from non-VersionConstraint; consider either documenting why this special case is sufficient or generalizing the check to ensure both constraints are of the same compatible type (e.g., isinstance(marker1.constraint, type(marker2.constraint))).
  • It may be clearer to factor the constraint-compatibility check into a small helper (e.g., _can_merge_constraints(marker1.constraint, marker2.constraint)) to make the merge rules easier to reason about and extend as new constraint types are introduced.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new type guard for constraints only distinguishes `VersionConstraint` from non-`VersionConstraint`; consider either documenting why this special case is sufficient or generalizing the check to ensure both constraints are of the same compatible type (e.g., `isinstance(marker1.constraint, type(marker2.constraint))`).
- It may be clearer to factor the constraint-compatibility check into a small helper (e.g., `_can_merge_constraints(marker1.constraint, marker2.constraint)`) to make the merge rules easier to reason about and extend as new constraint types are introduced.

## Individual Comments

### Comment 1
<location path="src/poetry/core/version/markers.py" line_range="1289-1290" />
<code_context>
     if marker1.name != marker2.name:
         return None

+    if isinstance(marker1.constraint, VersionConstraint) != isinstance(
+        marker2.constraint, VersionConstraint
+    ):
+        return None
</code_context>
<issue_to_address>
**suggestion:** Clarify the type check for VersionConstraint to avoid the non-obvious boolean comparison.

This boolean inequality works but is hard to read. Please use a more explicit check that “exactly one is a VersionConstraint”, e.g. an XOR:

```python
if isinstance(marker1.constraint, VersionConstraint) ^ isinstance(marker2.constraint, VersionConstraint):
    return None
```

or an equivalent `and`/`not` formulation to make the intent clearer for future readers and refactors.
</issue_to_address>

### Comment 2
<location path="tests/version/test_markers.py" line_range="76" />
<code_context>
         '"tegra" not in platform_release',
         '"tegra" in platform_release or "rpi-v8" in platform_release',
         '"tegra" not in platform_release and "rpi-v8" not in platform_release',
+        'platform_release != "1" and "a" not in platform_release',
         # extra starting with "in"
         'extra == "in1" or extra == "in2"',
</code_context>
<issue_to_address>
**suggestion (testing):** Add an explicit test that asserts incompatible marker constraint types are not merged, instead of only relying on this regression example in the aggregate list.

The regression is currently only covered indirectly via the large marker-expression list, while `_merge_single_markers` specifically guards against merging markers whose constraints differ in type (VersionConstraint vs non-VersionConstraint). Please add a dedicated test that builds two markers with the same name but different constraint types and asserts that merging them returns `None`/leaves them unmerged, so this behavior is verified explicitly and remains safe through future refactors of `_merge_single_markers`.

Suggested implementation:

```python
        '"tegra" not in platform_release',
        '"tegra" in platform_release or "rpi-v8" in platform_release',
        '"tegra" not in platform_release and "rpi-v8" not in platform_release',
        'platform_release != "1" and "a" not in platform_release',
        # extra starting with "in"
        'extra == "in1" or extra == "in2"',
        'extra == "in1" and extra == "in2"',


def test_merge_single_markers_does_not_merge_incompatible_constraint_types():
    """
    `_merge_single_markers` must refuse to merge markers with the same name
    but different constraint types (VersionConstraint vs non-VersionConstraint).
    """

    # `platform_release != "1"` is parsed as a version-like constraint,
    # while `"a" not in platform_release` is parsed as a generic/non-version constraint.
    version_like_marker = parse_marker('platform_release != "1"')
    generic_marker = parse_marker('"a" not in platform_release')

    # Both markers must refer to the same name to exercise the incompatible-type guard.
    # Depending on how `parse_marker()` structures the result, we extract the
    # concrete single-markers that share the "platform_release" name.
    version_single_marker = next(
        m for m in version_like_marker.markers
        if getattr(m, "name", None) == "platform_release"
    )
    generic_single_marker = next(
        m for m in generic_marker.markers
        if getattr(m, "name", None) == "platform_release"
    )

    merged = _merge_single_markers(version_single_marker, generic_single_marker)
    merged_reverse = _merge_single_markers(generic_single_marker, version_single_marker)

    assert merged is None
    assert merged_reverse is None

```

To fully integrate this test you will likely need to:

1. Ensure `parse_marker` and `_merge_single_markers` are imported at the top of `tests/version/test_markers.py`, matching existing conventions. For example, if not already present:
   - `from poetry.core.version.markers import parse_marker, _merge_single_markers`
2. Adjust attribute access and container traversal to match the actual marker representation used in this test file:
   - If `parse_marker()` returns a `Marker` with a different attribute than `.markers`, adapt the extraction of `version_single_marker` and `generic_single_marker` to whatever structure is already used in nearby tests (e.g., `.single_markers`, `.items`, or nested tuples).
   - If single markers are represented by a specific class (e.g., `SingleMarker`), and you already construct them directly in other tests, you may prefer to construct:
     `SingleMarker("platform_release", <version_constraint>)` and `SingleMarker("platform_release", <generic_constraint>)` explicitly instead of going through `parse_marker()`.
3. Confirm that the expressions `'platform_release != "1"'` and `'"a" not in platform_release'` indeed produce different underlying constraint types (VersionConstraint vs non-VersionConstraint) in your implementation:
   - If your marker implementation uses different literals to distinguish version vs non-version constraints, update the test expressions accordingly while keeping the same logical intent (same marker name, different constraint types).
4. Place the new `test_merge_single_markers_does_not_merge_incompatible_constraint_types` function at top-level scope in the file (not nested inside another test or helper) and adjust indentation so it matches the rest of the pytest tests once you integrate it into the full file context.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them âœĻ
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/poetry/core/version/markers.py
Comment thread tests/version/test_markers.py
@radoering
radoering force-pushed the fix-10978-platform-release-marker branch from c70ce16 to cf00f95 Compare August 14, 2026 15:08
@radoering
radoering enabled auto-merge (squash) August 14, 2026 15:09
@radoering
radoering merged commit b0d75a4 into python-poetry:main Aug 14, 2026
19 checks passed
mwalbeck pushed a commit to mwalbeck/docker-python-poetry that referenced this pull request Sep 26, 2026
This PR contains the following updates:

| Package | Update | Change | Pending |
|---|---|---|---|
| [poetry](https://github.com/python-poetry/poetry) ([changelog](https://python-poetry.org/history/)) | minor | `2.4.3` → `2.5.0` | `2.5.1` |

---

### Release Notes

<details>
<summary>python-poetry/poetry (poetry)</summary>

### [`v2.5.0`](https://github.com/python-poetry/poetry/blob/HEAD/CHANGELOG.md#250---2026-09-19)

[Compare Source](python-poetry/poetry@2.4.3...2.5.0)

##### Added

- Add an `installer.builtin-uninstall` setting to uninstall packages with a built-in uninstaller instead of invoking `pip uninstall` ([#&#8203;10931](python-poetry/poetry#10931)).
- Add official support for Python 3.15 ([#&#8203;11046](python-poetry/poetry#11046)).

##### Changed

- Do not send credentials configured for an `https` repository via `http` ([#&#8203;11073](python-poetry/poetry#11073)).
- Fail with an error when the current Python version is not compatible with the project and `virtualenvs.create` is `false` ([#&#8203;10941](python-poetry/poetry#10941)).
- Validate version constraints that are entered interactively in `poetry init` ([#&#8203;10909](python-poetry/poetry#10909)).
- Include the path of the `pyproject.toml` file in the message about already present packages in `poetry add` ([#&#8203;10908](python-poetry/poetry#10908)).
- Improve performance of processing package links and repository pages ([#&#8203;10895](python-poetry/poetry#10895),
  [#&#8203;10896](python-poetry/poetry#10896),
  [#&#8203;10903](python-poetry/poetry#10903),
  [#&#8203;10949](python-poetry/poetry#10949),
  [#&#8203;10951](python-poetry/poetry#10951),
  [#&#8203;10953](python-poetry/poetry#10953)).
- Improve performance of dependency resolution ([#&#8203;10907](python-poetry/poetry#10907),
  [#&#8203;10954](python-poetry/poetry#10954)).
- Improve performance of choosing and installing wheels ([#&#8203;10905](python-poetry/poetry#10905),
  [#&#8203;10958](python-poetry/poetry#10958)).
- Improve performance by avoiding redundant keyring lookups for repositories without credentials ([#&#8203;10959](python-poetry/poetry#10959)).
- Improve performance by reducing the number of subprocesses to discover virtual environment data ([#&#8203;11042](python-poetry/poetry#11042)).
- Improve performance of `poetry search` for single-token queries ([#&#8203;10906](python-poetry/poetry#10906)).
- Improve startup time by deferring the import of `requests` ([#&#8203;11004](python-poetry/poetry#11004)).
- Improve performance of schema validation by caching compiled JSON schema validators ([#&#8203;11033](python-poetry/poetry#11033)).

##### Fixed

- Fix an issue where credentials of the wrong repository were used under certain circumstances when multiple repositories were configured on the same host ([#&#8203;11072](python-poetry/poetry#11072)).
- Fix an issue where credentials of a repository on another host were used for git dependencies if the path of the URL was the same ([#&#8203;11074](python-poetry/poetry#11074)).
- Fix an issue where dependency resolution failed for conflicting requirements of different packages even though the requirements had mutually exclusive markers ([#&#8203;10944](python-poetry/poetry#10944)).
- Fix an issue where dependency resolution failed when the same package was required with different extras in several optional dependencies or dependency groups ([#&#8203;10943](python-poetry/poetry#10943)).
- Fix an issue where dependency resolution failed with a `KeyError` ([#&#8203;11008](python-poetry/poetry#11008)).
- Fix an issue where the dependencies of an extra were missing in the lock file after adding the extra to a locked dependency, e.g. a git dependency, in the `pyproject.toml` file ([#&#8203;10987](python-poetry/poetry#10987)).
- Fix an issue where a path or git dependency was not reinstalled when its `develop` setting changed ([#&#8203;11022](python-poetry/poetry#11022)).
- Fix an issue where scripts of type `file` were not installed when installing the project ([#&#8203;10736](python-poetry/poetry#10736)).
- Fix an issue where GUI scripts were not installed when installing the project ([#&#8203;10973](python-poetry/poetry#10973)).
- Fix an issue where a relative path was written to `direct_url.json` for path dependencies ([#&#8203;10917](python-poetry/poetry#10917)).
- Fix an issue where `poetry show <package>` showed a version that was not relevant for the current environment if there were multiple versions of the package in the lock file ([#&#8203;11003](python-poetry/poetry#11003)).
- Fix an issue where `poetry show --outdated` did not find newer versions of packages from sources with `explicit` priority ([#&#8203;10982](python-poetry/poetry#10982)).
- Fix an issue where `poetry env activate` ignored the environment that was determined by the application, e.g. when using `--directory` ([#&#8203;10916](python-poetry/poetry#10916)).
- Fix an issue where `poetry init` proposed an invalid package name if the directory name was not a valid package name ([#&#8203;10975](python-poetry/poetry#10975)).

##### Docs

- Document the `--license` option of `poetry init` and `poetry new` ([#&#8203;11064](python-poetry/poetry#11064)).
- Clarify which dependencies are locked when running `poetry update` with dependency groups ([#&#8203;11024](python-poetry/poetry#11024)).
- Clarify the portability of path dependencies ([#&#8203;11020](python-poetry/poetry#11020)).
- Clarify the usage of `poetry run` with console scripts ([#&#8203;10984](python-poetry/poetry#10984)).
- Clarify what `--no-cache` disables ([#&#8203;10915](python-poetry/poetry#10915)).
- Document how to use package sources for `poetry self update` ([#&#8203;10923](python-poetry/poetry#10923)).
- Fix the stale minimum Python version ([#&#8203;11050](python-poetry/poetry#11050)).
- Update outdated links ([#&#8203;10913](python-poetry/poetry#10913),
  [#&#8203;10938](python-poetry/poetry#10938),
  [#&#8203;11000](python-poetry/poetry#11000),
  [#&#8203;11043](python-poetry/poetry#11043)).

##### poetry-core ([`2.5.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.5.0))

- Add Python 3.15 to the automatically generated classifiers ([#&#8203;961](python-poetry/poetry-core#961)).
- Fix an issue where a `<V` version constraint wrongly allowed pre-releases of `V` in some cases ([#&#8203;939](python-poetry/poetry-core#939)).
- Fix an issue where version ranges with coincident bounds were not recognized as empty ([#&#8203;939](python-poetry/poetry-core#939)).
- Fix an issue where the string representation of a version union did not describe the same constraint after being parsed again ([#&#8203;939](python-poetry/poetry-core#939)).
- Fix an issue where the intersection of a version range with a local version resulted in a wrong constraint ([#&#8203;949](python-poetry/poetry-core#949)).
- Fix an issue where the union of a version range and a public version did not include all local versions of the public version ([#&#8203;950](python-poetry/poetry-core#950)).
- Fix an issue where the union of a public version and one of its local versions did not result in the public version ([#&#8203;966](python-poetry/poetry-core#966)).
- Fix an issue where the difference between a public version and one of its local versions still allowed the local version, which could result in an infinite loop during dependency resolution ([#&#8203;953](python-poetry/poetry-core#953)).
- Fix an issue where a version range that excluded some local versions of a public version was wrongly considered to allow all versions of the public version ([#&#8203;959](python-poetry/poetry-core#959)).
- Fix an issue where a `!= <value>` constraint was wrongly considered to allow all values of a `<value> not in` constraint ([#&#8203;955](python-poetry/poetry-core#955)).
- Fix an issue where merging `platform_release` markers with incompatible constraint types failed ([#&#8203;956](python-poetry/poetry-core#956)).
- Fix an issue where the string representation of a version range whose upper bound only consists of zeros raised an `IndexError` ([#&#8203;964](python-poetry/poetry-core#964)).
- Fix an issue where formatting a Python constraint that only allows unknown Python versions raised an `IndexError` ([#&#8203;971](python-poetry/poetry-core#971)).
- Fix an issue where marker values containing spaces could not be parsed ([#&#8203;972](python-poetry/poetry-core#972)).
- Fix an issue where the upper bound of a `~=` constraint was wrong for versions with more than three release segments ([#&#8203;973](python-poetry/poetry-core#973)).
- Fix an issue where the filename of a link created from a Windows path was wrong and reject filenames containing path separators ([#&#8203;974](python-poetry/poetry-core#974)).

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

ðŸšĶ **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

â™ŧ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI2MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://git.walbeck.it/mwalbeck/docker-python-poetry/pulls/1762
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

poetry-core crashes on platform_release != "x" and "y" not in platform_release

2 participants