Commit 1e23e57a by Sébastien Eustace

Fix handling of extras in wheels metadata

parent aa740147
# Change Log # Change Log
## [Unreleased]
### Fixed
- Fixed handling of extras in wheels metadata.
## [0.6.4] - 2018-03-21 ## [0.6.4] - 2018-03-21
### Added ### Added
......
...@@ -5,7 +5,7 @@ from typing import List ...@@ -5,7 +5,7 @@ from typing import List
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.packages import Locker from poetry.packages import Locker
from poetry.packages import Package from poetry.packages import Package
from poetry.packages.constraints.platform_constraint import PlatformConstraint from poetry.packages.constraints.generic_constraint import GenericConstraint
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.puzzle.operations import Install from poetry.puzzle.operations import Install
from poetry.puzzle.operations import Uninstall from poetry.puzzle.operations import Uninstall
...@@ -398,11 +398,11 @@ class Installer: ...@@ -398,11 +398,11 @@ class Installer:
continue continue
if 'platform' in package.requirements: if 'platform' in package.requirements:
platform_constraint = PlatformConstraint.parse( platform_constraint = GenericConstraint.parse(
package.requirements['platform'] package.requirements['platform']
) )
if not platform_constraint.matches( if not platform_constraint.matches(
PlatformConstraint('=', sys.platform) GenericConstraint('=', sys.platform)
): ):
# Incompatible systems # Incompatible systems
op.skip('Not needed for the current platform') op.skip('Not needed for the current platform')
......
...@@ -226,7 +226,9 @@ class SdistBuilder(Builder): ...@@ -226,7 +226,9 @@ class SdistBuilder(Builder):
for extra_name, reqs in package.extras.items(): for extra_name, reqs in package.extras.items():
for req in reqs: for req in reqs:
if req.name == dependency.name: if req.name == dependency.name:
extras[extra_name].append(dependency.to_pep_508()) extras[extra_name].append(
dependency.to_pep_508(with_extras=False)
)
continue continue
requirement = dependency.to_pep_508() requirement = dependency.to_pep_508()
......
...@@ -311,6 +311,9 @@ class WheelBuilder(Builder): ...@@ -311,6 +311,9 @@ class WheelBuilder(Builder):
for classifier in self._meta.classifiers: for classifier in self._meta.classifiers:
fp.write(f'Classifier: {classifier}\n') fp.write(f'Classifier: {classifier}\n')
for extra in self._meta.provides_extra:
fp.write(f'Provides-Extra: {extra}\n')
for dep in self._meta.requires_dist: for dep in self._meta.requires_dist:
fp.write(f'Requires-Dist: {dep}\n') fp.write(f'Requires-Dist: {dep}\n')
...@@ -318,7 +321,5 @@ class WheelBuilder(Builder): ...@@ -318,7 +321,5 @@ class WheelBuilder(Builder):
fp.write(f'Description-Content-Type: ' fp.write(f'Description-Content-Type: '
f'{self._meta.description_content_type}\n') f'{self._meta.description_content_type}\n')
# TODO: Provides extra
if self._meta.description is not None: if self._meta.description is not None:
fp.write('\n' + self._meta.description + '\n') fp.write('\n' + self._meta.description + '\n')
...@@ -28,7 +28,7 @@ class Metadata: ...@@ -28,7 +28,7 @@ class Metadata:
maintainer_email = None maintainer_email = None
requires_python = None requires_python = None
requires_external = () requires_external = ()
requires_dist = () requires_dist = []
provides_dist = () provides_dist = ()
obsoletes_dist = () obsoletes_dist = ()
project_urls = () project_urls = ()
...@@ -59,6 +59,7 @@ class Metadata: ...@@ -59,6 +59,7 @@ class Metadata:
meta.maintainer = meta.author meta.maintainer = meta.author
meta.maintainer_email = meta.author_email meta.maintainer_email = meta.author_email
meta.requires_python = package.python_constraint meta.requires_python = 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]
# Requires python # Requires python
...@@ -73,5 +74,6 @@ class Metadata: ...@@ -73,5 +74,6 @@ class Metadata:
else: else:
meta.description_content_type = 'text/plain' meta.description_content_type = 'text/plain'
# TODO: Provides extra meta.provides_extra = [e for e in package.extras]
return meta return meta
...@@ -6,7 +6,12 @@ from poetry.semver.constraints import MultiConstraint ...@@ -6,7 +6,12 @@ from poetry.semver.constraints import MultiConstraint
from poetry.semver.constraints.base_constraint import BaseConstraint from poetry.semver.constraints.base_constraint import BaseConstraint
class PlatformConstraint(BaseConstraint): class GenericConstraint(BaseConstraint):
"""
Represents a generic constraint.
This is particularly useful for platform/system/os/extra constraints.
"""
OP_EQ = operator.eq OP_EQ = operator.eq
OP_NE = operator.ne OP_NE = operator.ne
...@@ -22,7 +27,7 @@ class PlatformConstraint(BaseConstraint): ...@@ -22,7 +27,7 @@ class PlatformConstraint(BaseConstraint):
OP_NE: '!=' OP_NE: '!='
} }
def __init__(self, operator, platform): def __init__(self, operator, version):
if operator not in self._trans_op_str: if operator not in self._trans_op_str:
raise ValueError( raise ValueError(
f'Invalid operator "{operator}" given, ' f'Invalid operator "{operator}" given, '
...@@ -30,8 +35,8 @@ class PlatformConstraint(BaseConstraint): ...@@ -30,8 +35,8 @@ class PlatformConstraint(BaseConstraint):
) )
self._operator = self._trans_op_str[operator] self._operator = self._trans_op_str[operator]
self._string_operator = operator self._string_operator = self._trans_op_int[self._operator]
self._platform = platform self._version = version
@property @property
def supported_operators(self) -> list: def supported_operators(self) -> list:
...@@ -46,13 +51,13 @@ class PlatformConstraint(BaseConstraint): ...@@ -46,13 +51,13 @@ class PlatformConstraint(BaseConstraint):
return self._string_operator return self._string_operator
@property @property
def platform(self) -> str: def version(self) -> str:
return self._platform return self._version
def matches(self, provider): def matches(self, provider):
if not isinstance(provider, (PlatformConstraint, EmptyConstraint)): if not isinstance(provider, (GenericConstraint, EmptyConstraint)):
raise ValueError( raise ValueError(
'Platform constraints can only be compared with each other' 'Generic constraints can only be compared with each other'
) )
if isinstance(provider, EmptyConstraint): if isinstance(provider, EmptyConstraint):
...@@ -67,13 +72,13 @@ class PlatformConstraint(BaseConstraint): ...@@ -67,13 +72,13 @@ class PlatformConstraint(BaseConstraint):
is_equal_op and is_provider_equal_op is_equal_op and is_provider_equal_op
or is_non_equal_op and is_provider_non_equal_op or is_non_equal_op and is_provider_non_equal_op
): ):
return self._platform == provider.platform return self._version == provider.version
if ( if (
is_equal_op and is_provider_non_equal_op is_equal_op and is_provider_non_equal_op
or is_non_equal_op and is_provider_equal_op or is_non_equal_op and is_provider_equal_op
): ):
return self._platform != provider.platform return self._version != provider.version
return False return False
...@@ -125,10 +130,10 @@ class PlatformConstraint(BaseConstraint): ...@@ -125,10 +130,10 @@ class PlatformConstraint(BaseConstraint):
# Basic Comparators # Basic Comparators
m = re.match('^(!=|==?)?\s*(.*)', constraint) m = re.match('^(!=|==?)?\s*(.*)', constraint)
if m: if m:
return PlatformConstraint(m.group(1) or '=', m.group(2)), return GenericConstraint(m.group(1) or '=', m.group(2)),
raise ValueError( raise ValueError(
'Could not parse platform constraint: {}'.format(constraint) 'Could not parse generic constraint: {}'.format(constraint)
) )
def __str__(self): def __str__(self):
...@@ -140,8 +145,8 @@ class PlatformConstraint(BaseConstraint): ...@@ -140,8 +145,8 @@ class PlatformConstraint(BaseConstraint):
return '{}{}'.format( return '{}{}'.format(
op, op,
self._platform self._version
) )
def __repr__(self): def __repr__(self):
return '<PlatformConstraint \'{}\'>'.format(str(self)) return '<GenericConstraint \'{}\'>'.format(str(self))
...@@ -6,7 +6,7 @@ from poetry.semver.constraints import MultiConstraint ...@@ -6,7 +6,7 @@ from poetry.semver.constraints import MultiConstraint
from poetry.semver.constraints.base_constraint import BaseConstraint from poetry.semver.constraints.base_constraint import BaseConstraint
from poetry.semver.version_parser import VersionParser from poetry.semver.version_parser import VersionParser
from .constraints.platform_constraint import PlatformConstraint from .constraints.generic_constraint import GenericConstraint
class Dependency: class Dependency:
...@@ -40,6 +40,7 @@ class Dependency: ...@@ -40,6 +40,7 @@ class Dependency:
self._platform_constraint = EmptyConstraint() self._platform_constraint = EmptyConstraint()
self._extras = [] self._extras = []
self._in_extras = []
@property @property
def name(self): def name(self):
...@@ -81,7 +82,7 @@ class Dependency: ...@@ -81,7 +82,7 @@ class Dependency:
@platform.setter @platform.setter
def platform(self, value: str): def platform(self, value: str):
self._platform = value self._platform = value
self._platform_constraint = PlatformConstraint.parse(value) self._platform_constraint = GenericConstraint.parse(value)
@property @property
def platform_constraint(self): def platform_constraint(self):
...@@ -91,6 +92,10 @@ class Dependency: ...@@ -91,6 +92,10 @@ class Dependency:
def extras(self) -> list: def extras(self) -> list:
return self._extras return self._extras
@property
def in_extras(self) -> list:
return self._in_extras
def allows_prereleases(self): def allows_prereleases(self):
return self._allows_prereleases return self._allows_prereleases
...@@ -110,7 +115,7 @@ class Dependency: ...@@ -110,7 +115,7 @@ class Dependency:
and (not package.is_prerelease() or self.allows_prereleases()) and (not package.is_prerelease() or self.allows_prereleases())
) )
def to_pep_508(self) -> str: def to_pep_508(self, with_extras=True) -> str:
requirement = f'{self.pretty_name}' requirement = f'{self.pretty_name}'
if isinstance(self.constraint, MultiConstraint): if isinstance(self.constraint, MultiConstraint):
...@@ -127,31 +132,50 @@ class Dependency: ...@@ -127,31 +132,50 @@ class Dependency:
if self.python_versions != '*': if self.python_versions != '*':
python_constraint = self.python_constraint python_constraint = self.python_constraint
markers.append(self._create_nested_marker('python_version', python_constraint)) markers.append(
self._create_nested_marker('python_version', python_constraint)
)
in_extras = ' || '.join(self._in_extras)
if in_extras and with_extras:
markers.append(
self._create_nested_marker(
'extra', GenericConstraint.parse(in_extras)
)
)
if markers: if markers:
requirement += f'; {" and ".join(markers)}' if len(markers) > 1:
markers = ['({})'.format(m) for m in markers]
requirement += f'; {" and ".join(markers)}'
else:
requirement += f'; {markers[0]}'
return requirement return requirement
@classmethod
def from_pep_508(cls):
return
def _create_nested_marker(self, name, constraint): def _create_nested_marker(self, name, constraint):
if isinstance(constraint, MultiConstraint): if isinstance(constraint, MultiConstraint):
parts = [] parts = []
for c in constraint.constraints: for c in constraint.constraints:
parts.append(self._create_nested_marker(name, c)) multi = False
if isinstance(c, MultiConstraint):
multi = True
parts.append((multi, self._create_nested_marker(name, c)))
glue = ' and ' glue = ' and '
if constraint.is_disjunctive(): if constraint.is_disjunctive():
parts = [f'({part})' for part in parts] parts = [
f'({part[1]})' if part[0] else f'{part[1]}'
for part in parts
]
glue = ' or ' glue = ' or '
else:
parts = [part[1] for part in parts]
marker = glue.join(parts) marker = glue.join(parts)
else: else:
marker = f'{name}{constraint.string_operator}"{constraint.version}"' marker = f'{name} {constraint.string_operator} "{constraint.version}"'
return marker return marker
......
...@@ -7,7 +7,7 @@ from poetry.semver.helpers import parse_stability ...@@ -7,7 +7,7 @@ from poetry.semver.helpers import parse_stability
from poetry.semver.version_parser import VersionParser from poetry.semver.version_parser import VersionParser
from poetry.version import parse as parse_version from poetry.version import parse as parse_version
from.constraints.platform_constraint import PlatformConstraint from .constraints.generic_constraint import GenericConstraint
from .dependency import Dependency from .dependency import Dependency
from .vcs_dependency import VCSDependency from .vcs_dependency import VCSDependency
...@@ -182,7 +182,7 @@ class Package: ...@@ -182,7 +182,7 @@ class Package:
@platform.setter @platform.setter
def platform(self, value: str): def platform(self, value: str):
self._platform = value self._platform = value
self._platform_constraint = PlatformConstraint.parse(value) self._platform_constraint = GenericConstraint.parse(value)
@property @property
def platform_constraint(self): def platform_constraint(self):
......
...@@ -107,11 +107,20 @@ class Poetry: ...@@ -107,11 +107,20 @@ class Poetry:
for name, constraint in local_config['dev-dependencies'].items(): for name, constraint in local_config['dev-dependencies'].items():
package.add_dependency(name, constraint, category='dev') package.add_dependency(name, constraint, category='dev')
if 'extras' in local_config: extras = local_config.get('extras', {})
for extra_name, requirements in local_config['extras'].items(): for extra_name, requirements in extras.items():
package.extras[extra_name] = [ package.extras[extra_name] = []
Dependency(req, '*') for req in requirements
] # Checking for dependency
for req in requirements:
req = Dependency(req, '*')
for dep in package.requires:
if dep.name == req.name:
dep.in_extras.append(extra_name)
package.extras[extra_name].append(dep)
break
if 'build' in local_config: if 'build' in local_config:
package.build = local_config['build'] package.build = local_config['build']
......
...@@ -99,5 +99,30 @@ Generator: poetry {__version__} ...@@ -99,5 +99,30 @@ Generator: poetry {__version__}
Root-Is-Purelib: true Root-Is-Purelib: true
Tag: py3-none-any Tag: py3-none-any
""" """
wheel_data = zip.read('my_package-1.2.3.dist-info/METADATA').decode()
assert wheel_data == """\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://poetry.eustace.io/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Requires-Python: >= 3.6.0.0, < 4.0.0.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Provides-Extra: time
Requires-Dist: cleo (>=0.6.0.0,<0.7.0.0)
Requires-Dist: pendulum (>=1.4.0.0,<2.0.0.0); extra == "time"
Description-Content-Type: text/x-rst
My Package
==========
"""
finally: finally:
zip.close() zip.close()
...@@ -66,5 +66,30 @@ def test_to_pep_508(): ...@@ -66,5 +66,30 @@ def test_to_pep_508():
result = dependency.to_pep_508() result = dependency.to_pep_508()
assert result == 'Django (>=1.23.0.0,<2.0.0.0); ' \ assert result == 'Django (>=1.23.0.0,<2.0.0.0); ' \
'(python_version>="2.7.0.0" and python_version<"2.8.0.0") ' \ '(python_version >= "2.7.0.0" and python_version < "2.8.0.0") ' \
'or (python_version>="3.6.0.0" and python_version<"4.0.0.0")' 'or (python_version >= "3.6.0.0" and python_version < "4.0.0.0")'
def test_to_pep_508_in_extras():
dependency = Dependency('Django', '^1.23')
dependency.in_extras.append('foo')
result = dependency.to_pep_508()
assert result == 'Django (>=1.23.0.0,<2.0.0.0); extra == "foo"'
dependency.in_extras.append('bar')
result = dependency.to_pep_508()
assert result == 'Django (>=1.23.0.0,<2.0.0.0); extra == "foo" or extra == "bar"'
dependency.python_versions = '~2.7 || ^3.6'
result = dependency.to_pep_508()
assert result == (
'Django (>=1.23.0.0,<2.0.0.0); '
'('
'(python_version >= "2.7.0.0" and python_version < "2.8.0.0") '
'or (python_version >= "3.6.0.0" and python_version < "4.0.0.0")'
') '
'and (extra == "foo" or extra == "bar")'
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment