Commit 1e23e57a by Sébastien Eustace

Fix handling of extras in wheels metadata

parent aa740147
# Change Log
## [Unreleased]
### Fixed
- Fixed handling of extras in wheels metadata.
## [0.6.4] - 2018-03-21
### Added
......
......@@ -5,7 +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.packages.constraints.generic_constraint import GenericConstraint
from poetry.puzzle import Solver
from poetry.puzzle.operations import Install
from poetry.puzzle.operations import Uninstall
......@@ -398,11 +398,11 @@ class Installer:
continue
if 'platform' in package.requirements:
platform_constraint = PlatformConstraint.parse(
platform_constraint = GenericConstraint.parse(
package.requirements['platform']
)
if not platform_constraint.matches(
PlatformConstraint('=', sys.platform)
GenericConstraint('=', sys.platform)
):
# Incompatible systems
op.skip('Not needed for the current platform')
......
......@@ -226,7 +226,9 @@ class SdistBuilder(Builder):
for extra_name, reqs in package.extras.items():
for req in reqs:
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
requirement = dependency.to_pep_508()
......
......@@ -311,6 +311,9 @@ class WheelBuilder(Builder):
for classifier in self._meta.classifiers:
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:
fp.write(f'Requires-Dist: {dep}\n')
......@@ -318,7 +321,5 @@ class WheelBuilder(Builder):
fp.write(f'Description-Content-Type: '
f'{self._meta.description_content_type}\n')
# TODO: Provides extra
if self._meta.description is not None:
fp.write('\n' + self._meta.description + '\n')
......@@ -28,7 +28,7 @@ class Metadata:
maintainer_email = None
requires_python = None
requires_external = ()
requires_dist = ()
requires_dist = []
provides_dist = ()
obsoletes_dist = ()
project_urls = ()
......@@ -59,6 +59,7 @@ class Metadata:
meta.maintainer = meta.author
meta.maintainer_email = meta.author_email
meta.requires_python = package.python_constraint
meta.requires_dist = [d.to_pep_508() for d in package.requires]
# Requires python
......@@ -73,5 +74,6 @@ class Metadata:
else:
meta.description_content_type = 'text/plain'
# TODO: Provides extra
meta.provides_extra = [e for e in package.extras]
return meta
......@@ -6,7 +6,12 @@ from poetry.semver.constraints import MultiConstraint
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_NE = operator.ne
......@@ -22,7 +27,7 @@ class PlatformConstraint(BaseConstraint):
OP_NE: '!='
}
def __init__(self, operator, platform):
def __init__(self, operator, version):
if operator not in self._trans_op_str:
raise ValueError(
f'Invalid operator "{operator}" given, '
......@@ -30,8 +35,8 @@ class PlatformConstraint(BaseConstraint):
)
self._operator = self._trans_op_str[operator]
self._string_operator = operator
self._platform = platform
self._string_operator = self._trans_op_int[self._operator]
self._version = version
@property
def supported_operators(self) -> list:
......@@ -46,13 +51,13 @@ class PlatformConstraint(BaseConstraint):
return self._string_operator
@property
def platform(self) -> str:
return self._platform
def version(self) -> str:
return self._version
def matches(self, provider):
if not isinstance(provider, (PlatformConstraint, EmptyConstraint)):
if not isinstance(provider, (GenericConstraint, EmptyConstraint)):
raise ValueError(
'Platform constraints can only be compared with each other'
'Generic constraints can only be compared with each other'
)
if isinstance(provider, EmptyConstraint):
......@@ -67,13 +72,13 @@ class PlatformConstraint(BaseConstraint):
is_equal_op and is_provider_equal_op
or is_non_equal_op and is_provider_non_equal_op
):
return self._platform == provider.platform
return self._version == provider.version
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 self._version != provider.version
return False
......@@ -125,10 +130,10 @@ class PlatformConstraint(BaseConstraint):
# Basic Comparators
m = re.match('^(!=|==?)?\s*(.*)', constraint)
if m:
return PlatformConstraint(m.group(1) or '=', m.group(2)),
return GenericConstraint(m.group(1) or '=', m.group(2)),
raise ValueError(
'Could not parse platform constraint: {}'.format(constraint)
'Could not parse generic constraint: {}'.format(constraint)
)
def __str__(self):
......@@ -140,8 +145,8 @@ class PlatformConstraint(BaseConstraint):
return '{}{}'.format(
op,
self._platform
self._version
)
def __repr__(self):
return '<PlatformConstraint \'{}\'>'.format(str(self))
return '<GenericConstraint \'{}\'>'.format(str(self))
......@@ -6,7 +6,7 @@ from poetry.semver.constraints import MultiConstraint
from poetry.semver.constraints.base_constraint import BaseConstraint
from poetry.semver.version_parser import VersionParser
from .constraints.platform_constraint import PlatformConstraint
from .constraints.generic_constraint import GenericConstraint
class Dependency:
......@@ -40,6 +40,7 @@ class Dependency:
self._platform_constraint = EmptyConstraint()
self._extras = []
self._in_extras = []
@property
def name(self):
......@@ -81,7 +82,7 @@ class Dependency:
@platform.setter
def platform(self, value: str):
self._platform = value
self._platform_constraint = PlatformConstraint.parse(value)
self._platform_constraint = GenericConstraint.parse(value)
@property
def platform_constraint(self):
......@@ -91,6 +92,10 @@ class Dependency:
def extras(self) -> list:
return self._extras
@property
def in_extras(self) -> list:
return self._in_extras
def allows_prereleases(self):
return self._allows_prereleases
......@@ -110,7 +115,7 @@ class Dependency:
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}'
if isinstance(self.constraint, MultiConstraint):
......@@ -127,31 +132,50 @@ class Dependency:
if self.python_versions != '*':
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:
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
@classmethod
def from_pep_508(cls):
return
def _create_nested_marker(self, name, constraint):
if isinstance(constraint, MultiConstraint):
parts = []
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 '
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 '
else:
parts = [part[1] for part in parts]
marker = glue.join(parts)
else:
marker = f'{name}{constraint.string_operator}"{constraint.version}"'
marker = f'{name} {constraint.string_operator} "{constraint.version}"'
return marker
......
......@@ -7,7 +7,7 @@ from poetry.semver.helpers import parse_stability
from poetry.semver.version_parser import VersionParser
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 .vcs_dependency import VCSDependency
......@@ -182,7 +182,7 @@ class Package:
@platform.setter
def platform(self, value: str):
self._platform = value
self._platform_constraint = PlatformConstraint.parse(value)
self._platform_constraint = GenericConstraint.parse(value)
@property
def platform_constraint(self):
......
......@@ -107,11 +107,20 @@ class Poetry:
for name, constraint in local_config['dev-dependencies'].items():
package.add_dependency(name, constraint, category='dev')
if 'extras' in local_config:
for extra_name, requirements in local_config['extras'].items():
package.extras[extra_name] = [
Dependency(req, '*') for req in requirements
]
extras = local_config.get('extras', {})
for extra_name, requirements in extras.items():
package.extras[extra_name] = []
# 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:
package.build = local_config['build']
......
......@@ -99,5 +99,30 @@ Generator: poetry {__version__}
Root-Is-Purelib: true
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:
zip.close()
......@@ -66,5 +66,30 @@ def test_to_pep_508():
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")'
'(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")'
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