Commit 3472463e by Sébastien Eustace

Add support for specifying dependencies extras

parent 2367ac9a
...@@ -78,22 +78,22 @@ keywords = ['packaging', 'poetry'] ...@@ -78,22 +78,22 @@ keywords = ['packaging', 'poetry']
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.2" # Compatible python versions must be declared here python = "~2.7 || ^3.2" # Compatible python versions must be declared here
toml = "^0.9" toml = "^0.9"
requests = "^2.13" # Dependencies with extras
semantic_version = "^2.6" requests = { version = "^2.13", extras = [ "security" ] }
pygments = "^2.2" # Python specific dependencies with prereleases allowed
twine = "^1.8" pathlib2 = { version = "^2.2", python = "~2.7", allows_prereleases = true }
wheel = "^0.29" # Git dependencies
pip-tools = "^1.8.2"
cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" } cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" }
# Optional dependencies (extras)
pendulum = { version = "^1.4", optional = true}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^3.0" pytest = "^3.0"
pytest-cov = "^2.4" pytest-cov = "^2.4"
coverage = "<4.0"
httpretty = "^0.8.14"
[tool.poetry.scripts] [tool.poetry.scripts]
poet = 'poet:app.run' my-script = 'my_package:main'
``` ```
There are some things we can notice here: There are some things we can notice here:
...@@ -632,21 +632,17 @@ poetry = 'poetry:console.run' ...@@ -632,21 +632,17 @@ poetry = 'poetry:console.run'
Here, we will have the `poetry` script installed which will execute `console.run` in the `poetry` package. Here, we will have the `poetry` script installed which will execute `console.run` in the `poetry` package.
### `features` ### `extras`
Poetry supports features to allow expression of: Poetry supports extras to allow expression of:
* optional dependencies, which enhance a package, but are not required; and * optional dependencies, which enhance a package, but are not required; and
* clusters of optional dependencies. * clusters of optional dependencies.
```toml ```toml
[package] [tool.poetry]
name = "awesome" name = "awesome"
[features]
mysql = ["mysqlclient"]
pgsql = ["psycopg2"]
[dependencies] [dependencies]
# These packages are mandatory and form the core of this package’s distribution. # These packages are mandatory and form the core of this package’s distribution.
mandatory = "^1.0" mandatory = "^1.0"
...@@ -655,13 +651,17 @@ mandatory = "^1.0" ...@@ -655,13 +651,17 @@ mandatory = "^1.0"
# above `features`. They can be opted into by apps. # above `features`. They can be opted into by apps.
psycopg2 = { version = "^2.7", optional = true } psycopg2 = { version = "^2.7", optional = true }
mysqlclient = { version = "^1.3", optional = true } mysqlclient = { version = "^1.3", optional = true }
[tool.poetry.extras]
mysql = ["mysqlclient"]
pgsql = ["psycopg2"]
``` ```
When installing packages, you can specify features by using the `-f|--features` option: When installing packages, you can specify features by using the `-E|--extras` option:
```bash ```bash
poet install --features "mysql pgsql" poet install --extras "mysql pgsql"
poet install -f mysql -f pgsql poet install -E mysql -E pgsql
``` ```
### `plugins` ### `plugins`
......
...@@ -230,7 +230,7 @@ class Installer: ...@@ -230,7 +230,7 @@ class Installer:
if operation.skipped: if operation.skipped:
if self._io.is_verbose() and (self._execute_operations or self.is_dry_run()): if self._io.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.writeln( self._io.writeln(
f' - Skipping <info>{operation.package.name}</> ' f' - Skipping <info>{operation.package.pretty_name}</> '
f'(<comment>{operation.package.full_pretty_version}</>) ' f'(<comment>{operation.package.full_pretty_version}</>) '
f'{operation.skip_reason}') f'{operation.skip_reason}')
...@@ -238,7 +238,7 @@ class Installer: ...@@ -238,7 +238,7 @@ class Installer:
if self._execute_operations or self.is_dry_run(): if self._execute_operations or self.is_dry_run():
self._io.writeln( self._io.writeln(
f' - Installing <info>{operation.package.name}</> ' f' - Installing <info>{operation.package.pretty_name}</> '
f'(<comment>{operation.package.full_pretty_version}</>)' f'(<comment>{operation.package.full_pretty_version}</>)'
) )
...@@ -254,7 +254,7 @@ class Installer: ...@@ -254,7 +254,7 @@ class Installer:
if operation.skipped: if operation.skipped:
if self._io.is_verbose() and (self._execute_operations or self.is_dry_run()): if self._io.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.writeln( self._io.writeln(
f' - Skipping <info>{target.name}</> ' f' - Skipping <info>{target.pretty_name}</> '
f'(<comment>{target.full_pretty_version}</>) ' f'(<comment>{target.full_pretty_version}</>) '
f'{operation.skip_reason}') f'{operation.skip_reason}')
...@@ -262,7 +262,7 @@ class Installer: ...@@ -262,7 +262,7 @@ class Installer:
if self._execute_operations or self.is_dry_run(): if self._execute_operations or self.is_dry_run():
self._io.writeln( self._io.writeln(
f' - Updating <info>{target.name}</> ' f' - Updating <info>{target.pretty_name}</> '
f'(<comment>{source.pretty_version}</>' f'(<comment>{source.pretty_version}</>'
f' -> <comment>{target.pretty_version}</>)' f' -> <comment>{target.pretty_version}</>)'
) )
...@@ -275,7 +275,7 @@ class Installer: ...@@ -275,7 +275,7 @@ class Installer:
def _execute_uninstall(self, operation: Uninstall) -> None: def _execute_uninstall(self, operation: Uninstall) -> None:
if self._execute_operations or self.is_dry_run(): if self._execute_operations or self.is_dry_run():
self._io.writeln( self._io.writeln(
f' - Removing <info>{operation.package.name}</> ' f' - Removing <info>{operation.package.pretty_name}</> '
f'(<comment>{operation.package.full_pretty_version}</>)' f'(<comment>{operation.package.full_pretty_version}</>)'
) )
......
...@@ -33,6 +33,8 @@ class Dependency: ...@@ -33,6 +33,8 @@ class Dependency:
self._platform = '*' self._platform = '*'
self._platform_constraint = self._parser.parse_constraints('*') self._platform_constraint = self._parser.parse_constraints('*')
self._extras = []
@property @property
def name(self): def name(self):
return self._name return self._name
...@@ -73,12 +75,15 @@ class Dependency: ...@@ -73,12 +75,15 @@ 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 = self._parser.parse_constraints(value)
@property @property
def platform_constraint(self): def platform_constraint(self):
return self._platform_constraint return self._platform_constraint
@property
def extras(self) -> list:
return self._extras
def allows_prereleases(self): def allows_prereleases(self):
return self._allows_prereleases return self._allows_prereleases
...@@ -129,6 +134,12 @@ class Dependency: ...@@ -129,6 +134,12 @@ class Dependency:
return requirement return requirement
def activate(self):
"""
Set the dependency as mandatory.
"""
self._optional = False
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, Dependency): if not isinstance(other, Dependency):
return NotImplemented return NotImplemented
......
...@@ -63,6 +63,7 @@ class Package: ...@@ -63,6 +63,7 @@ class Package:
self.requires = [] self.requires = []
self.dev_requires = [] self.dev_requires = []
self.extras = {}
self._parser = VersionParser() self._parser = VersionParser()
...@@ -187,7 +188,7 @@ class Package: ...@@ -187,7 +188,7 @@ class Package:
python_versions = constraint.get('python') python_versions = constraint.get('python')
platform = constraint.get('platform') platform = constraint.get('platform')
optional = optional or python_versions is not None or not platform is not None optional = optional or python_versions is not None or platform is not None
dependency = Dependency( dependency = Dependency(
name, version, name, version,
...@@ -201,6 +202,10 @@ class Package: ...@@ -201,6 +202,10 @@ class Package:
if platform: if platform:
dependency.platform = platform dependency.platform = platform
if 'extras' in constraint:
for extra in constraint['extras']:
dependency.extras.append(extra)
else: else:
dependency = Dependency(name, constraint, category=category) dependency = Dependency(name, constraint, category=category)
......
...@@ -64,7 +64,8 @@ class Provider(SpecificationProvider): ...@@ -64,7 +64,8 @@ class Provider(SpecificationProvider):
packages = self._pool.find_packages( packages = self._pool.find_packages(
dependency.name, dependency.name,
dependency.constraint dependency.constraint,
extras=dependency.extras,
) )
packages.sort( packages.sort(
......
...@@ -16,7 +16,7 @@ class BaseRepository: ...@@ -16,7 +16,7 @@ class BaseRepository:
def package(self, name, version): def package(self, name, version):
raise NotImplementedError() raise NotImplementedError()
def find_packages(self, name, constraint=None): def find_packages(self, name, constraint=None, extras=None):
raise NotImplementedError() raise NotImplementedError()
def search(self, query, mode=SEARCH_FULLTEXT): def search(self, query, mode=SEARCH_FULLTEXT):
......
...@@ -17,10 +17,10 @@ from poetry.semver.constraints import Constraint ...@@ -17,10 +17,10 @@ from poetry.semver.constraints import Constraint
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 .repository import Repository from .pypi_repository import PyPiRepository
class LegacyRepository(Repository): class LegacyRepository(PyPiRepository):
def __init__(self, name, url): def __init__(self, name, url):
if name == 'pypi': if name == 'pypi':
...@@ -51,9 +51,7 @@ class LegacyRepository(Repository): ...@@ -51,9 +51,7 @@ class LegacyRepository(Repository):
} }
}) })
super().__init__() def find_packages(self, name, constraint=None, extras=None):
def find_packages(self, name, constraint=None):
packages = [] packages = []
if constraint is not None and not isinstance(constraint, if constraint is not None and not isinstance(constraint,
...@@ -84,11 +82,11 @@ class LegacyRepository(Repository): ...@@ -84,11 +82,11 @@ class LegacyRepository(Repository):
self._cache.store('matches').put(key, versions, 5) self._cache.store('matches').put(key, versions, 5)
for version in versions: for version in versions:
packages.append(self.package(name, version)) packages.append(self.package(name, version, extras=extras))
return packages return packages
def package(self, name, version) -> 'poetry.packages.Package': def package(self, name, version, extras=None) -> 'poetry.packages.Package':
""" """
Retrieve the release information. Retrieve the release information.
...@@ -107,29 +105,78 @@ class LegacyRepository(Repository): ...@@ -107,29 +105,78 @@ class LegacyRepository(Repository):
return self._packages[index] return self._packages[index]
except ValueError: except ValueError:
if extras is None:
extras = []
release_info = self.get_release_info(name, version) release_info = self.get_release_info(name, version)
package = poetry.packages.Package(name, version, version) package = poetry.packages.Package(name, version, version)
for dependency in release_info['requires_dist']: for req in release_info['requires_dist']:
m = re.match( req = InstallRequirement.from_line(req)
'^(?P<name>[^ ;]+)'
'(?: \((?P<version>.+)\))?' name = req.name
'(?:;(?P<extra>(.+)))?$', version = str(req.req.specifier)
dependency
dependency = Dependency(
name,
version,
optional=req.markers
) )
package.requires.append(
poetry.packages.Dependency( is_extra = False
m.group('name'), if req.markers:
m.group('version') or '*', # Setting extra dependencies and requirements
optional=m.group('extra') is not None requirements = self._convert_markers(
req.markers._markers
) )
)
package.source_type = 'legacy' if 'python_version' in requirements:
package.source_url = self._url ors = []
for or_ in requirements['python_version']:
ands = []
for op, version in or_:
ands.append(f'{op}{version}')
ors.append(' '.join(ands))
dependency.python_versions = ' || '.join(ors)
if 'sys_platform' in requirements:
ors = []
for or_ in requirements['sys_platform']:
ands = []
for op, platform in or_:
ands.append(f'{op}{platform}')
ors.append(' '.join(ands))
dependency.platform = ' || '.join(ors)
if 'extra' in requirements:
is_extra = True
for _extras in requirements['extra']:
for _, extra in _extras:
if extra not in package.extras:
package.extras[extra] = []
package.extras[extra].append(dependency)
if not is_extra:
package.requires.append(dependency)
# Adding description
package.description = release_info.get('summary', '')
# Adding hashes information # Adding hashes information
package.hashes = release_info['digests'] package.hashes = release_info['digests']
# Activate extra dependencies
for extra in extras:
if extra in package.extras:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
self._packages.append(package) self._packages.append(package)
return package return package
......
...@@ -4,7 +4,6 @@ from typing import Union ...@@ -4,7 +4,6 @@ from typing import Union
import poetry.packages import poetry.packages
from .base_repository import BaseRepository from .base_repository import BaseRepository
from .legacy_repository import LegacyRepository
from .repository import Repository from .repository import Repository
...@@ -38,6 +37,8 @@ class Pool(BaseRepository): ...@@ -38,6 +37,8 @@ class Pool(BaseRepository):
Configures a repository based on a source Configures a repository based on a source
specification and add it to the pool. specification and add it to the pool.
""" """
from .legacy_repository import LegacyRepository
if 'url' in source: if 'url' in source:
# PyPI-like repository # PyPI-like repository
if 'name' not in source: if 'name' not in source:
...@@ -68,9 +69,10 @@ class Pool(BaseRepository): ...@@ -68,9 +69,10 @@ class Pool(BaseRepository):
def find_packages(self, def find_packages(self,
name, name,
constraint=None) -> List['poetry.packages.Package']: constraint=None,
extras=None) -> List['poetry.packages.Package']:
for repository in self._repositories: for repository in self._repositories:
packages = repository.find_packages(name, constraint) packages = repository.find_packages(name, constraint, extras=extras)
if packages: if packages:
return packages return packages
......
import re
from pathlib import Path from pathlib import Path
from pip.req import InstallRequirement
from typing import List from typing import List
from typing import Union from typing import Union
...@@ -19,8 +18,9 @@ from .repository import Repository ...@@ -19,8 +18,9 @@ from .repository import Repository
class PyPiRepository(Repository): class PyPiRepository(Repository):
def __init__(self, url='https://pypi.org/'): def __init__(self, url='https://pypi.org/', disable_cache=False):
self._url = url self._url = url
self._disable_cache = disable_cache
self._cache = CacheManager({ self._cache = CacheManager({
'default': 'releases', 'default': 'releases',
'serializer': 'json', 'serializer': 'json',
...@@ -39,7 +39,8 @@ class PyPiRepository(Repository): ...@@ -39,7 +39,8 @@ class PyPiRepository(Repository):
def find_packages(self, def find_packages(self,
name: str, name: str,
constraint: Union[Constraint, None] = None constraint: Union[Constraint, str, None] = None,
extras: Union[list, None] = None
) -> List[Package]: ) -> List[Package]:
""" """
Find packages on the remote server. Find packages on the remote server.
...@@ -62,34 +63,78 @@ class PyPiRepository(Repository): ...@@ -62,34 +63,78 @@ class PyPiRepository(Repository):
versions.append(version) versions.append(version)
for version in versions: for version in versions:
packages.append(self.package(name, version)) packages.append(
self.package(name, version, extras=extras)
)
return packages return packages
def package(self, def package(self,
name: str, name: str,
version: str) -> Package: version: str,
extras: Union[list, None] = None) -> Package:
try: try:
index = self._packages.index(Package(name, version, version)) index = self._packages.index(Package(name, version, version))
return self._packages[index] return self._packages[index]
except ValueError: except ValueError:
if extras is None:
extras = []
release_info = self.get_release_info(name, version) release_info = self.get_release_info(name, version)
package = Package(name, version, version) package = Package(name, version, version)
for dependency in release_info['requires_dist']: for req in release_info['requires_dist']:
m = re.match( req = InstallRequirement.from_line(req)
'^(?P<name>[^ ;]+)'
'(?: \((?P<version>.+)\))?' name = req.name
'(?:;(?P<extra>(.+)))?$', version = str(req.req.specifier)
dependency
dependency = Dependency(
name,
version,
optional=req.markers
) )
package.requires.append(
Dependency( is_extra = False
m.group('name'), if req.markers:
m.group('version') or '*', # Setting extra dependencies and requirements
optional=m.group('extra') is not None requirements = self._convert_markers(
req.markers._markers
) )
)
if 'python_version' in requirements:
ors = []
for or_ in requirements['python_version']:
ands = []
for op, version in or_:
ands.append(f'{op}{version}')
ors.append(' '.join(ands))
dependency.python_versions = ' || '.join(ors)
if 'sys_platform' in requirements:
ors = []
for or_ in requirements['sys_platform']:
ands = []
for op, platform in or_:
ands.append(f'{op}{platform}')
ors.append(' '.join(ands))
dependency.platform = ' || '.join(ors)
if 'extra' in requirements:
is_extra = True
for _extras in requirements['extra']:
for _, extra in _extras:
if extra not in package.extras:
package.extras[extra] = []
package.extras[extra].append(dependency)
if not is_extra:
package.requires.append(dependency)
# Adding description # Adding description
package.description = release_info.get('summary', '') package.description = release_info.get('summary', '')
...@@ -97,6 +142,14 @@ class PyPiRepository(Repository): ...@@ -97,6 +142,14 @@ class PyPiRepository(Repository):
# Adding hashes information # Adding hashes information
package.hashes = release_info['digests'] package.hashes = release_info['digests']
# Activate extra dependencies
for extra in extras:
if extra in package.extras:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
self._packages.append(package) self._packages.append(package)
return package return package
...@@ -130,18 +183,19 @@ class PyPiRepository(Repository): ...@@ -130,18 +183,19 @@ class PyPiRepository(Repository):
The information is returned from the cache if it exists The information is returned from the cache if it exists
or retrieved from the remote server. or retrieved from the remote server.
""" """
if self._disable_cache:
return self._get_package_info(name)
return self._cache.store('packages').remember_forever( return self._cache.store('packages').remember_forever(
f'{name}', f'{name}',
lambda: self._get_package_info(name) lambda: self._get_package_info(name)
) )
def _get_package_info(self, name: str) -> dict: def _get_package_info(self, name: str) -> dict:
json_response = get(self._url + f'pypi/{name}/json') data = self._get(self._url + f'pypi/{name}/json')
if json_response.status_code == 404: if data is None:
raise ValueError(f'Package [{name}] not found.') raise ValueError(f'Package [{name}] not found.')
data = json_response.json()
return data return data
def get_release_info(self, name: str, version: str) -> dict: def get_release_info(self, name: str, version: str) -> dict:
...@@ -151,17 +205,19 @@ class PyPiRepository(Repository): ...@@ -151,17 +205,19 @@ class PyPiRepository(Repository):
The information is returned from the cache if it exists The information is returned from the cache if it exists
or retrieved from the remote server. or retrieved from the remote server.
""" """
if self._disable_cache:
return self._get_release_info(name, version)
return self._cache.remember_forever( return self._cache.remember_forever(
f'{name}:{version}', f'{name}:{version}',
lambda: self._get_release_info(name, version) lambda: self._get_release_info(name, version)
) )
def _get_release_info(self, name: str, version: str) -> dict: def _get_release_info(self, name: str, version: str) -> dict:
json_response = get(self._url + f'pypi/{name}/{version}/json') json_data = self._get(self._url + f'pypi/{name}/{version}/json')
if json_response.status_code == 404: if json_data is None:
raise ValueError(f'Package [{name}] not found.') raise ValueError(f'Package [{name}] not found.')
json_data = json_response.json()
info = json_data['info'] info = json_data['info']
data = { data = {
'name': info['name'], 'name': info['name'],
...@@ -176,3 +232,56 @@ class PyPiRepository(Repository): ...@@ -176,3 +232,56 @@ class PyPiRepository(Repository):
data['digests'].append(file_info['digests']['sha256']) data['digests'].append(file_info['digests']['sha256'])
return data return data
def _get(self, url: str) -> Union[dict, None]:
json_response = get(url)
if json_response.status_code == 404:
return None
json_data = json_response.json()
return json_data
def _group_markers(self, markers):
groups = [[]]
for marker in markers:
assert isinstance(marker, (list, tuple, str))
if isinstance(marker, list):
groups[-1].append(self._group_markers(marker))
elif isinstance(marker, tuple):
lhs, op, rhs = marker
groups[-1].append((lhs.value, op, rhs.value))
else:
assert marker in ["and", "or"]
if marker == "or":
groups.append([])
return groups
def _convert_markers(self, markers):
groups = self._group_markers(markers)[0]
requirements = {}
def _group(_groups, or_=False):
nonlocal requirements
for group in _groups:
if isinstance(group, tuple):
variable, op, value = group
group_name = str(variable)
if group_name not in requirements:
requirements[group_name] = [[]]
elif or_:
requirements[group_name].append([])
requirements[group_name][-1].append((str(op), str(value)))
else:
_group(group, or_=True)
_group(groups)
return requirements
...@@ -27,9 +27,11 @@ class Repository(BaseRepository): ...@@ -27,9 +27,11 @@ class Repository(BaseRepository):
if name == package.name and package.version == version: if name == package.name and package.version == version:
return package return package
def find_packages(self, name, constraint=None): def find_packages(self, name, constraint=None, extras=None):
name = name.lower() name = name.lower()
packages = [] packages = []
if extras is None:
extras = []
if not isinstance(constraint, BaseConstraint): if not isinstance(constraint, BaseConstraint):
parser = VersionParser() parser = VersionParser()
...@@ -40,6 +42,13 @@ class Repository(BaseRepository): ...@@ -40,6 +42,13 @@ class Repository(BaseRepository):
pkg_constraint = Constraint('==', package.version) pkg_constraint = Constraint('==', package.version)
if constraint is None or constraint.matches(pkg_constraint): if constraint is None or constraint.matches(pkg_constraint):
for extra in extras:
if extra in package.extras:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
packages.append(package) packages.append(package)
return packages return packages
......
...@@ -21,7 +21,7 @@ keywords = ["packaging", "dependency", "poetry"] ...@@ -21,7 +21,7 @@ keywords = ["packaging", "dependency", "poetry"]
python = "~2.7 || ^3.6" python = "~2.7 || ^3.6"
cleo = "^0.6" cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" } pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true } requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" } pathlib2 = { version = "^2.2", python = "~2.7" }
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
[[package]]
name = "A"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
C = "^1.0"
[[package]]
name = "C"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
"A" = []
"B" = []
"C" = []
...@@ -11,6 +11,7 @@ from poetry.packages import Locker as BaseLocker ...@@ -11,6 +11,7 @@ from poetry.packages import Locker as BaseLocker
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
...@@ -287,3 +288,25 @@ def test_run_with_optional_and_python_restricted_dependencies(installer, locker, ...@@ -287,3 +288,25 @@ def test_run_with_optional_and_python_restricted_dependencies(installer, locker,
# with B's python constraint # with B's python constraint
assert len(installer.installs) == 3 assert len(installer.installs) == 3
def test_run_with_dependencies_extras(installer, locker, repo, package):
package_a = get_package('A', '1.0')
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_b.extras = {
'foo': [get_dependency('C', '^1.0')]
}
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
package.add_dependency('A', '^1.0')
package.add_dependency('B', {'version': '^1.0', 'extras': ['foo']})
installer.run()
expected = fixture('with-dependencies-extras')
assert locker.written_data == expected
...@@ -324,3 +324,61 @@ def test_solver_solves_optional_and_compatible_packages(solver, repo, package): ...@@ -324,3 +324,61 @@ def test_solver_solves_optional_and_compatible_packages(solver, repo, package):
{'job': 'install', 'package': package_b}, {'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_a}, {'job': 'install', 'package': package_a},
]) ])
def test_solver_does_not_return_extras_if_not_requested(solver, repo, package):
package_a = get_package('A', '1.0')
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_b.extras = {
'foo': [get_dependency('C', '^1.0')]
}
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
dependency_a = get_dependency('A')
dependency_b = get_dependency('B')
request = [
dependency_a,
dependency_b
]
ops = solver.solve(request)
check_solver_result(ops, [
{'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_a},
])
def test_solver_returns_extras_if_requested(solver, repo, package):
package_a = get_package('A', '1.0')
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_b.extras = {
'foo': [get_dependency('C', '^1.0')]
}
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
dependency_a = get_dependency('A')
dependency_b = get_dependency('B')
dependency_b.extras.append('foo')
request = [
dependency_a,
dependency_b
]
ops = solver.solve(request)
check_solver_result(ops, [
{'job': 'install', 'package': package_c},
{'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_a},
])
This source diff could not be displayed because it is too large. You can view the blob instead.
import json
from pathlib import Path
from poetry.repositories.pypi_repository import PyPiRepository
class MockRepository(PyPiRepository):
FIXTURES = Path(__file__).parent / 'fixtures' / 'pypi.org' / 'json'
def __init__(self):
super().__init__(url='http://foo.bar', disable_cache=True)
def _get(self, url: str) -> dict:
fixture = self.FIXTURES / 'requests.json'
with fixture.open() as f:
return json.loads(f.read())
def test_find_packages():
repo = MockRepository()
packages = repo.find_packages('requests', '^2.18')
assert len(packages) == 5
def test_package():
repo = MockRepository()
package = repo.package('requests', '2.18.4')
assert package.name == 'requests'
assert len(package.requires) == 4
assert len(package.extras['security']) == 3
assert len(package.extras['socks']) == 2
win_inet = package.extras['socks'][0]
assert win_inet.name == 'win-inet-pton'
assert win_inet.python_versions == '==2.7 || ==2.6'
assert win_inet.platform == '==win32'
...@@ -44,6 +44,7 @@ def test_poetry(): ...@@ -44,6 +44,7 @@ def test_poetry():
assert not requests.is_vcs() assert not requests.is_vcs()
assert not requests.allows_prereleases() assert not requests.allows_prereleases()
assert requests.is_optional() assert requests.is_optional()
assert requests.extras == ['security']
pathlib2 = dependencies[3] pathlib2 = dependencies[3]
assert pathlib2.pretty_constraint == '^2.2' assert pathlib2.pretty_constraint == '^2.2'
......
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