Unverified Commit 7ae7e2b7 authored by Sébastien Eustace's avatar Sébastien Eustace
Browse files

Fix python/platform constraints not being picked up for subdependencies

parent d3491fe7
Loading
Loading
Loading
Loading
+1 −0
Changes for CHANGELOG.md: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@

- Fixed handling of post releases.
- Fixed python restricted dependencies not being checked agaisnt virtualenv version.
- Fixed python/platform constraint not being picked up for subdependencies.


## [0.4.2] - 2018-03-10
+0 −2
Changes for poetry/console/commands/lock.py: 0 added lines, 2 removed lines.
Original line number Diff line number Diff line
from poetry.installation import Installer
from poetry.repositories.pypi_repository import PyPiRepository

from .command import Command

@@ -9,7 +8,6 @@ class LockCommand(Command):
    Locks the project dependencies.

    lock
        { --no-dev : Do not install dev dependencies. }
    """

    help = """The <info>lock</info> command reads the <comment>poetry.toml</> file from
+18 −1
Changes for poetry/installation/installer.py: 18 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ from typing import List
from poetry.packages import Dependency
from poetry.packages import Locker
from poetry.packages import Package
from poetry.packages.constraints.platform_constraint import PlatformConstraint
from poetry.puzzle import Solver
from poetry.puzzle.operations import Install
from poetry.puzzle.operations import Uninstall
@@ -114,7 +115,7 @@ class Installer:
    def _do_install(self, local_repo):
        locked_repository = Repository()
        # initialize locked repo if we are installing from lock
        if not self._update or self._locker.is_locked():
        if not self._update:
            locked_repository = self._locker.locked_repository(True)

        if self._update:
@@ -131,6 +132,9 @@ class Installer:
            if self._whitelist:
                # collect packages to fixate from root requirements
                candidates = []
                if self._locker.is_locked():
                    locked_repository = self._locker.locked_repository(True)

                for package in locked_repository.packages:
                    candidates.append(package)

@@ -146,6 +150,8 @@ class Installer:
                            Dependency(candidate.name, candidate.version)
                        )

            locked_repository = Repository()

            solver = Solver(
                self._package,
                self._pool,
@@ -389,6 +395,17 @@ class Installer:
                    op.skip('Not needed for the current python version')
                    continue

            if 'platform' in package.requirements:
                platform_constraint = PlatformConstraint.parse(
                    package.requirements['platform']
                )
                if not platform_constraint.matches(
                        PlatformConstraint('=', sys.platform)
                ):
                    # Incompatible systems
                    op.skip('Not need for the current platform')
                    continue

            if self._update:
                extras = {}
                for extra, deps in self._package.extras.items():
+0 −0

Empty file added.

+144 −0
Changes for poetry/packages/constraints/platform_constraint.py: 144 added lines, 0 removed lines.
Original line number Diff line number Diff line
import operator
import re

from poetry.semver.constraints import EmptyConstraint
from poetry.semver.constraints import MultiConstraint
from poetry.semver.constraints.base_constraint import BaseConstraint


class PlatformConstraint(BaseConstraint):

    OP_EQ = operator.eq
    OP_NE = operator.ne

    _trans_op_str = {
        '=': OP_EQ,
        '==': OP_EQ,
        '!=': OP_NE
    }

    _trans_op_int = {
        OP_EQ: '==',
        OP_NE: '!='
    }

    def __init__(self, operator, platform):
        if operator not in self._trans_op_str:
            raise ValueError(
                f'Invalid operator "{operator}" given, '
                f'expected one of: {", ".join(self.supported_operators)}'
            )

        self._operator = self._trans_op_str[operator]
        self._string_operator = operator
        self._platform = platform

    @property
    def supported_operators(self) -> list:
        return list(self._trans_op_str.keys())

    @property
    def operator(self):
        return self._operator

    @property
    def string_operator(self):
        return self._string_operator

    @property
    def platform(self) -> str:
        return self._platform

    def matches(self, provider):
        if not isinstance(provider, PlatformConstraint):
            raise ValueError(
                'Platform constraints can only be compared with each other'
            )

        is_equal_op = self.OP_EQ is self._operator
        is_non_equal_op = self.OP_NE is self._operator
        is_provider_equal_op = self.OP_EQ is provider.operator
        is_provider_non_equal_op = self.OP_NE is provider.operator

        if (
            is_equal_op and is_provider_equal_op
            or is_non_equal_op and is_provider_non_equal_op
        ):
            return self._platform == provider.platform

        if (
            is_equal_op and is_provider_non_equal_op
            or is_non_equal_op and is_provider_equal_op
        ):
            return self._platform != provider.platform

        return False

    @classmethod
    def parse(cls, constraints):
        """
        Parses a constraint string into
        MultiConstraint and/or PlatformConstraint objects.
        """
        pretty_constraint = constraints

        or_constraints = re.split('\s*\|\|?\s*', constraints.strip())
        or_groups = []
        for constraints in or_constraints:
            and_constraints = re.split(
                '(?<!^)(?<![ ,]) *(?<!-)[, ](?!-) *(?!,|$)',
                constraints
            )
            if len(and_constraints) > 1:
                constraint_objects = []
                for constraint in and_constraints:
                    for parsed_constraint in cls._parse_constraint(constraint):
                        constraint_objects.append(parsed_constraint)
            else:
                constraint_objects = cls._parse_constraint(and_constraints[0])

            if len(constraint_objects) == 1:
                constraint = constraint_objects[0]
            else:
                constraint = MultiConstraint(constraint_objects)

            or_groups.append(constraint)

        if len(or_groups) == 1:
            constraint = or_groups[0]
        else:
            constraint = MultiConstraint(or_groups, False)

        constraint.pretty_string = pretty_constraint

        return constraint

    @classmethod
    def _parse_constraint(cls, constraint):
        m = re.match('(?i)^v?[xX*](\.[xX*])*$', constraint)
        if m:
            return EmptyConstraint(),

        # Basic Comparators
        m = re.match('^(!=|==?)?\s*(.*)', constraint)
        if m:
            return PlatformConstraint(m.group(1) or '=', m.group(2)),

        raise ValueError(
            'Could not parse platform constraint: {}'.format(constraint)
        )

    def __str__(self):
        op = self._trans_op_int[self._operator]
        if op == '==':
            op = ''
        else:
            op = op + ' '

        return '{}{}'.format(
            op,
            self._platform
        )

    def __repr__(self):
        return '<PlatformConstraint \'{}\'>'.format(str(self))
Loading