Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: python-poetry/poetry-core
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: fcfdea7ac7df5b226240da13924183ebd58093c4
Choose a base ref
..
head repository: python-poetry/poetry-core
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: fe7e5ee244371dd7fd8c6efd6ebb0aebb1c8bb59
Choose a head ref
1 change: 1 addition & 0 deletions .github/workflows/update-licenses.yaml
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ jobs:
- name: Update licenses list
run: |
poetry run python src/poetry/core/spdx/helpers.py
poetry run pre-commit run --all-files || :
- name: Generate token
id: generate_token
2 changes: 2 additions & 0 deletions src/poetry/core/constraints/generic/__init__.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint
from poetry.core.constraints.generic.multi_constraint import MultiConstraint
from poetry.core.constraints.generic.parser import parse_constraint
from poetry.core.constraints.generic.parser import parse_extra_constraint
from poetry.core.constraints.generic.union_constraint import UnionConstraint


@@ -17,4 +18,5 @@
"MultiConstraint",
"UnionConstraint",
"parse_constraint",
"parse_extra_constraint",
)
47 changes: 44 additions & 3 deletions src/poetry/core/constraints/generic/constraint.py
Original file line number Diff line number Diff line change
@@ -137,7 +137,7 @@ def allows_any(self, other: BaseConstraint) -> bool:
return other.is_any()

def invert(self) -> Constraint:
return Constraint(self._value, self._trans_op_inv[self.operator])
return self.__class__(self._value, self._trans_op_inv[self.operator])

def difference(self, other: BaseConstraint) -> Constraint | EmptyConstraint:
if other.allows(self):
@@ -207,8 +207,8 @@ def is_empty(self) -> bool:
return False

def __eq__(self, other: object) -> bool:
if not isinstance(other, Constraint):
return NotImplemented
if not isinstance(other, self.__class__):
return False

return (self.value, self.operator) == (other.value, other.operator)

@@ -220,3 +220,44 @@ def __str__(self) -> str:
return f"'{self._value}' {self._operator}"
op = self._operator if self._operator != "==" else ""
return f"{op}{self._value}"


class ExtraConstraint(Constraint):
def __init__(self, value: str, operator: str = "==") -> None:
super().__init__(value, operator)
# Do the check after calling the super constructor,
# i.e. after the operator has been normalized.
if self._operator not in {"==", "!="}:
raise ValueError(
'Only the operators "==" and "!=" are supported for extra constraints'
)

def intersect(self, other: BaseConstraint) -> BaseConstraint:
from poetry.core.constraints.generic.multi_constraint import (
ExtraMultiConstraint,
)

if isinstance(other, Constraint):
if other == self:
return self

if self._value == other._value and self._operator != other.operator:
return EmptyConstraint()

return ExtraMultiConstraint(self, other)

return super().intersect(other)

def union(self, other: BaseConstraint) -> BaseConstraint:
from poetry.core.constraints.generic.union_constraint import UnionConstraint

if isinstance(other, Constraint):
if other == self:
return self

if self._value == other._value and self._operator != other.operator:
return AnyConstraint()

return UnionConstraint(self, other)

return super().union(other)
65 changes: 58 additions & 7 deletions src/poetry/core/constraints/generic/multi_constraint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import itertools

from typing import TYPE_CHECKING

from poetry.core.constraints.generic import AnyConstraint
@@ -13,8 +15,10 @@


class MultiConstraint(BaseConstraint):
OPERATORS: tuple[str, ...] = ("!=", "in", "not in")

def __init__(self, *constraints: Constraint) -> None:
if any(c.operator == "==" for c in constraints):
if any(c.operator not in self.OPERATORS for c in constraints):
raise ValueError(
"A multi-constraint can only be comprised of negative constraints"
)
@@ -62,7 +66,7 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:
union = list(self.constraints) + [
c for c in other.constraints if c not in ours
]
return MultiConstraint(*union)
return self.__class__(*union)

if not isinstance(other, Constraint):
return other.intersect(self)
@@ -74,16 +78,16 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:
# same value but different operator, e.g. '== "linux"' and '!= "linux"'
return EmptyConstraint()

if other.operator == "==":
if other.operator == "==" and "==" not in self.OPERATORS:
return other

return MultiConstraint(*self._constraints, other)
return self.__class__(*self._constraints, other)

def union(self, other: BaseConstraint) -> BaseConstraint:
if isinstance(other, MultiConstraint):
theirs = set(other.constraints)
common = [c for c in self.constraints if c in theirs]
return MultiConstraint(*common)
return self.__class__(*common)

if not isinstance(other, Constraint):
return other.union(self)
@@ -102,10 +106,10 @@ def union(self, other: BaseConstraint) -> BaseConstraint:
if len(constraints) == 1:
return constraints[0]

return MultiConstraint(*constraints)
return self.__class__(*constraints)

def __eq__(self, other: object) -> bool:
if not isinstance(other, MultiConstraint):
if not isinstance(other, self.__class__):
return False

return self._constraints == other._constraints
@@ -116,3 +120,50 @@ def __hash__(self) -> int:
def __str__(self) -> str:
constraints = [str(constraint) for constraint in self._constraints]
return ", ".join(constraints)


class ExtraMultiConstraint(MultiConstraint):
# Since the extra marker can have multiple values at the same time,
# "==extra1, ==extra2" is not empty!
OPERATORS = ("==", "!=")

def intersect(self, other: BaseConstraint) -> BaseConstraint:
if isinstance(other, MultiConstraint):
op_values = {}
for op in self.OPERATORS:
op_values[op] = {
c.value
for c in itertools.chain(self._constraints, other.constraints)
if c.operator == op
}
if op_values["=="] & op_values["!="]:
return EmptyConstraint()

return super().intersect(other)

def union(self, other: BaseConstraint) -> BaseConstraint:
from poetry.core.constraints.generic import UnionConstraint

if isinstance(other, MultiConstraint):
if set(other.constraints) == set(self._constraints):
return self
return UnionConstraint(self, other)

if isinstance(other, Constraint):
if other in self._constraints:
return other

if len(self._constraints) == 2 and other.value in (
c.value for c in self._constraints
):
# same value but different operator
constraints: list[BaseConstraint] = [
*(c for c in self._constraints if c.value != other.value),
other,
]
else:
constraints = [self, other]

return UnionConstraint(*constraints)

return super().union(other)
28 changes: 23 additions & 5 deletions src/poetry/core/constraints/generic/parser.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@

from poetry.core.constraints.generic.any_constraint import AnyConstraint
from poetry.core.constraints.generic.constraint import Constraint
from poetry.core.constraints.generic.constraint import ExtraConstraint
from poetry.core.constraints.generic.union_constraint import UnionConstraint
from poetry.core.constraints.version.exceptions import ParseConstraintError

@@ -29,6 +30,17 @@

@functools.cache
def parse_constraint(constraints: str) -> BaseConstraint:
return _parse_constraint(constraints, Constraint)


@functools.cache
def parse_extra_constraint(constraints: str) -> BaseConstraint:
return _parse_constraint(constraints, ExtraConstraint)


def _parse_constraint(
constraints: str, constraint_type: type[Constraint]
) -> BaseConstraint:
if constraints == "*":
return AnyConstraint()

@@ -40,9 +52,13 @@ def parse_constraint(constraints: str) -> BaseConstraint:

if len(and_constraints) > 1:
for constraint in and_constraints:
constraint_objects.append(parse_single_constraint(constraint))
constraint_objects.append(
_parse_single_constraint(constraint, constraint_type)
)
else:
constraint_objects.append(parse_single_constraint(and_constraints[0]))
constraint_objects.append(
_parse_single_constraint(and_constraints[0], constraint_type)
)

if len(constraint_objects) == 1:
constraint = constraint_objects[0]
@@ -59,12 +75,14 @@ def parse_constraint(constraints: str) -> BaseConstraint:
return UnionConstraint(*or_groups)


def parse_single_constraint(constraint: str) -> Constraint:
def _parse_single_constraint(
constraint: str, constraint_type: type[Constraint]
) -> Constraint:
# string comparator
if m := STR_CMP_CONSTRAINT.match(constraint):
op = m.group("op")
value = m.group("value").strip()
return Constraint(value, op)
return constraint_type(value, op)

# Basic comparator

@@ -75,6 +93,6 @@ def parse_single_constraint(constraint: str) -> Constraint:

version = m.group(2).strip()

return Constraint(version, op)
return constraint_type(version, op)

raise ParseConstraintError(f"Could not parse version constraint: {constraint}")
19 changes: 18 additions & 1 deletion src/poetry/core/constraints/generic/union_constraint.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,9 @@
from poetry.core.constraints.generic import AnyConstraint
from poetry.core.constraints.generic.base_constraint import BaseConstraint
from poetry.core.constraints.generic.constraint import Constraint
from poetry.core.constraints.generic.constraint import ExtraConstraint
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint
from poetry.core.constraints.generic.multi_constraint import ExtraMultiConstraint
from poetry.core.constraints.generic.multi_constraint import MultiConstraint


@@ -48,7 +50,11 @@ def invert(self) -> MultiConstraint:
raise NotImplementedError(
"Inversion of complex union constraints not implemented"
)
return MultiConstraint(*inverted_constraints) # type: ignore[arg-type]
if any(isinstance(c, ExtraConstraint) for c in inverted_constraints):
multi_type: type[MultiConstraint] = ExtraMultiConstraint
else:
multi_type = MultiConstraint
return multi_type(*inverted_constraints) # type: ignore[arg-type]

def intersect(self, other: BaseConstraint) -> BaseConstraint:
if other.is_any():
@@ -57,6 +63,14 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:
if other.is_empty():
return other

if isinstance(other, UnionConstraint) and set(other.constraints) == set(
self._constraints
):
return self

if isinstance(other, ExtraConstraint) and other in self._constraints:
return other

if isinstance(other, Constraint):
# (A or B) and C => (A and C) or (B and C)
# just a special case of UnionConstraint
@@ -99,6 +113,9 @@ def union(self, other: BaseConstraint) -> BaseConstraint:
if other.is_empty():
return self

if other == self:
return self

if isinstance(other, Constraint):
# (A or B) or C => A or B or C
# just a special case of UnionConstraint
6 changes: 5 additions & 1 deletion src/poetry/core/constraints/version/version_range.py
Original file line number Diff line number Diff line change
@@ -64,7 +64,11 @@ def allows(self, other: Version) -> bool:

assert _this is not None

if not _this.is_postrelease() and _other.is_postrelease():
if (
not self._include_min
and not _this.is_postrelease()
and _other.is_postrelease()
):
# The exclusive ordered comparison >V MUST NOT allow a post-release
# of the given version unless V itself is a post release.
# https://peps.python.org/pep-0440/#exclusive-ordered-comparison
11 changes: 9 additions & 2 deletions src/poetry/core/factory.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
from typing import Any
from typing import Literal
from typing import Union
from typing import cast

from packaging.utils import canonicalize_name

@@ -182,9 +183,15 @@ def _configure_package_metadata(
else:
raw_license = project_license.get("text", "")
if not raw_license and (
license_file := project_license.get("file", "")
license_file := cast(str, project_license.get("file", ""))
):
raw_license = (root / license_file).read_text(encoding="utf-8")
license_path = (root / license_file).absolute()
try:
raw_license = Path(license_path).read_text(encoding="utf-8")
except FileNotFoundError as e:
raise FileNotFoundError(
f"Poetry: license file '{license_path}' not found"
) from e
else:
raw_license = tool_poetry.get("license", "")
try:
6 changes: 5 additions & 1 deletion src/poetry/core/masonry/metadata.py
Original file line number Diff line number Diff line change
@@ -95,7 +95,11 @@ def from_package(cls, package: ProjectPackage) -> Metadata:
elif package.python_versions != "*":
meta.requires_python = format_python_constraint(package.python_constraint)

meta.requires_dist = [d.to_pep_508() for d in package.requires]
meta.requires_dist = [
d.to_pep_508()
for d in package.requires
if not d.is_optional() or d.in_extras
]

# Version 2.1
if package.readme_content_type:
Loading