Commit 694bef2d by Sébastien Eustace Committed by GitHub

Improve support for alternative package repositories (#908)

* Add support for specifying dependency source

* Make installation from forced repositories possible

* Add possibility to declare a custom repository as the default

* Improve handling of sources in pools

* Fallback on the default repository when install via pip

* Add support for declaring repositories as secondary

* Update documentation
parent 43d4b9d8
......@@ -64,7 +64,36 @@ url = "https://foo.bar/simple/"
From now on, Poetry will also look for packages in your private repository.
!!!note
Any custom repository will have precedence over PyPI.
If you still want PyPI to be your primary source for your packages
you can declare custom repositories as secondary.
```toml
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
secondary = true
```
If your private repository requires HTTP Basic Auth be sure to add the username and
password to your `http-basic` config using the example above (be sure to use the
password to your `http-basic` configuration using the example above (be sure to use the
same name that is in the `tool.poetry.source` section). Poetry will use these values
to authenticate to your private repository when downloading or looking for packages.
### Disabling the PyPI repository
If you want your packages to be exclusively looked up from a private
repository, you can set it as the default one by using the `default` keyword
```toml
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
default = true
```
A default source will also be the fallback source if you add other sources.
......@@ -207,7 +207,7 @@ class Installer:
".".join([str(i) for i in self._env.version_info[:3]])
):
# We resolve again by only using the lock file
pool = Pool()
pool = Pool(ignore_repository_names=True)
# Making a new repo containing the packages
# newly resolved and the ones from the current lock file
......@@ -512,7 +512,7 @@ class Installer:
return _extra_packages(extra_packages)
def _get_installer(self): # type: () -> BaseInstaller
return PipInstaller(self._env, self._io)
return PipInstaller(self._env, self._io, self._pool)
def _get_installed(self): # type: () -> InstalledRepository
return InstalledRepository.load(self._env)
......@@ -22,9 +22,10 @@ from .base_installer import BaseInstaller
class PipInstaller(BaseInstaller):
def __init__(self, env, io): # type: (Env, ...) -> None
def __init__(self, env, io, pool): # type: (Env, ...) -> None
self._env = env
self._io = io
self._pool = pool
def install(self, package, update=False):
if package.source_type == "directory":
......@@ -40,6 +41,7 @@ class PipInstaller(BaseInstaller):
args = ["install", "--no-deps"]
if package.source_type == "legacy" and package.source_url:
repository = self._pool.repository(package.source_reference)
parsed = urlparse.urlparse(package.source_url)
if parsed.scheme == "http":
self._io.error(
......@@ -49,21 +51,12 @@ class PipInstaller(BaseInstaller):
)
args += ["--trusted-host", parsed.hostname]
auth = get_http_basic_auth(
Config.create("auth.toml"), package.source_reference
)
if auth:
index_url = "{scheme}://{username}:{password}@{netloc}{path}".format(
scheme=parsed.scheme,
username=auth[0],
password=auth[1],
netloc=parsed.netloc,
path=parsed.path,
)
else:
index_url = package.source_url
index_url = repository.authenticated_url
args += ["--index-url", index_url]
if self._pool.has_default():
if repository.name != self._pool.default.name:
args += ["--extra-index-url", self._pool.default.authenticated_url]
if update:
args.append("-U")
......
......@@ -228,6 +228,10 @@
"items": {
"type": "string"
}
},
"source": {
"type": "string",
"description": "The exclusive source used to search for this dependency."
}
}
},
......@@ -417,6 +421,14 @@
"type": "string",
"description": "The url of the repository",
"format": "uri"
},
"default": {
"type": "boolean",
"description": "Make this repository the default (disable PyPI)"
},
"secondary": {
"type": "boolean",
"description": "Declare this repository as secondary, i.e. it will only be looked up last for packages."
}
}
}
......
from typing import Optional
import poetry.packages
from poetry.semver import parse_constraint
......@@ -23,6 +25,7 @@ class Dependency(object):
optional=False, # type: bool
category="main", # type: str
allows_prereleases=False, # type: bool
source_name=None, # type: Optional[str]
):
self._name = canonicalize_name(name)
self._pretty_name = name
......@@ -45,6 +48,7 @@ class Dependency(object):
)
self._allows_prereleases = allows_prereleases
self._source_name = source_name
self._python_versions = "*"
self._python_constraint = parse_constraint("*")
......@@ -80,6 +84,10 @@ class Dependency(object):
return self._category
@property
def source_name(self):
return self._source_name
@property
def python_versions(self):
return self._python_versions
......
......@@ -54,6 +54,7 @@ class Package(object):
self._license = None
self.readme = None
self.source_name = ""
self.source_type = ""
self.source_reference = ""
self.source_url = ""
......@@ -297,6 +298,7 @@ class Package(object):
optional=optional,
category=category,
allows_prereleases=allows_prereleases,
source_name=constraint.get("source"),
)
marker = AnyMarker()
......
......@@ -44,10 +44,17 @@ class Poetry:
# Configure sources
self._pool = Pool()
for source in self._local_config.get("source", []):
self._pool.add_repository(self.create_legacy_repository(source))
repository = self.create_legacy_repository(source)
self._pool.add_repository(
repository,
source.get("default", False),
secondary=source.get("secondary", False),
)
# Always put PyPI last to prefer private repositories
self._pool.add_repository(PyPiRepository())
# but only if we have no other default source
if not self._pool.has_default():
self._pool.add_repository(PyPiRepository(), True)
@property
def file(self):
......
......@@ -131,6 +131,7 @@ class Provider:
constraint,
extras=dependency.extras,
allow_prereleases=dependency.allows_prereleases(),
repository=dependency.source_name,
)
packages.sort(
......@@ -450,7 +451,10 @@ class Provider:
package = DependencyPackage(
package.dependency,
self._pool.package(
package.name, package.version.text, extras=package.requires_extras
package.name,
package.version.text,
extras=package.requires_extras,
repository=package.dependency.source_name,
),
)
......
......@@ -10,6 +10,14 @@ class Auth(AuthBase):
self._hostname = urlparse.urlparse(url).hostname
self._auth = HTTPBasicAuth(username, password)
@property
def hostname(self): # type: () -> str
return self._hostname
@property
def auth(self): # type: () -> HTTPBasicAuth
return self._auth
def __call__(self, r): # type: (Request) -> Request
if urlparse.urlparse(r.url).hostname != self._hostname:
return r
......
......@@ -162,6 +162,7 @@ class LegacyRepository(PyPiRepository):
self._packages = []
self._name = name
self._url = url.rstrip("/")
self._auth = auth
self._cache_dir = Path(CACHE_DIR) / "cache" / "repositories" / name
self._cache = CacheManager(
......@@ -181,14 +182,25 @@ class LegacyRepository(PyPiRepository):
)
url_parts = urlparse.urlparse(self._url)
if not url_parts.username and auth:
self._session.auth = auth
if not url_parts.username and self._auth:
self._session.auth = self._auth
self._disable_cache = disable_cache
@property
def name(self):
return self._name
def authenticated_url(self): # type: () -> str
if not self._auth:
return self.url
parsed = urlparse.urlparse(self.url)
return "{scheme}://{username}:{password}@{netloc}{path}".format(
scheme=parsed.scheme,
username=self._auth.auth.username,
password=self._auth.auth.password,
netloc=parsed.netloc,
path=parsed.path,
)
def find_packages(
self, name, constraint=None, extras=None, allow_prereleases=False
......
from typing import Dict
from typing import List
from typing import Union
from typing import Optional
from .base_repository import BaseRepository
from .exceptions import PackageNotFound
......@@ -8,66 +8,148 @@ from .repository import Repository
class Pool(BaseRepository):
def __init__(self, repositories=None): # type: (Union[list, None]) -> None
def __init__(
self, repositories=None, ignore_repository_names=False
): # type: (Optional[List[Repository]], bool) -> None
if repositories is None:
repositories = []
self._repositories = []
self._lookup = {} # type: Dict[str, int]
self._repositories = [] # type: List[Repository]
self._default = False
self._secondary_start_idx = None
for repository in repositories:
self.add_repository(repository)
self._ignore_repository_names = ignore_repository_names
super(Pool, self).__init__()
@property
def repositories(self): # type: () -> List[Repository]
return self._repositories
def add_repository(self, repository): # type: (Repository) -> Pool
def has_default(self): # type: () -> bool
return self._default
def repository(self, name): # type: (str) -> Repository
if name in self._lookup:
return self._repositories[self._lookup[name]]
raise ValueError('Repository "{}" does not exist.'.format(name))
def add_repository(
self, repository, default=False, secondary=False
): # type: (Repository, bool, bool) -> Pool
"""
Adds a repository to the pool.
"""
self._repositories.append(repository)
if default:
if self.has_default():
raise ValueError("Only one repository can be the default")
self._default = True
self._repositories.insert(0, repository)
for name in self._lookup:
self._lookup[name] += 1
if self._secondary_start_idx is not None:
self._secondary_start_idx += 1
self._lookup[repository.name] = 0
elif secondary:
if self._secondary_start_idx is None:
self._secondary_start_idx = len(self._repositories)
self._repositories.append(repository)
self._lookup[repository.name] = len(self._repositories) - 1
else:
if self._secondary_start_idx is None:
self._repositories.append(repository)
self._lookup[repository.name] = len(self._repositories) - 1
else:
self._repositories.insert(self._secondary_start_idx, repository)
for name, idx in self._lookup.items():
if idx < self._secondary_start_idx:
continue
self._lookup[name] += 1
self._lookup[repository.name] = self._secondary_start_idx
self._secondary_start_idx += 1
return self
def remove_repository(self, repository_name): # type: (str) -> Pool
for i, repository in enumerate(self._repositories):
if repository.name == repository_name:
del self._repositories[i]
break
idx = self._lookup.get(repository_name)
if idx is not None:
del self._repositories[idx]
return self
def has_package(self, package):
raise NotImplementedError()
def package(self, name, version, extras=None):
for repository in self._repositories:
def package(
self, name, version, extras=None, repository=None
): # type: (str, str, List[str], str) -> Package
if (
repository is not None
and repository not in self._lookup
and not self._ignore_repository_names
):
raise ValueError('Repository "{}" does not exist.'.format(repository))
if repository is not None and not self._ignore_repository_names:
try:
package = repository.package(name, version, extras=extras)
return self._repositories[self._lookup[repository]].package(
name, version, extras=extras
)
except PackageNotFound:
continue
pass
else:
for idx, repo in enumerate(self._repositories):
try:
package = repo.package(name, version, extras=extras)
except PackageNotFound:
continue
if package:
self._packages.append(package)
if package:
self._packages.append(package)
return package
return package
raise PackageNotFound("Package {} ({}) not found.".format(name, version))
def find_packages(
self, name, constraint=None, extras=None, allow_prereleases=False
self,
name,
constraint=None,
extras=None,
allow_prereleases=False,
repository=None,
):
for repository in self._repositories:
packages = repository.find_packages(
if (
repository is not None
and repository not in self._lookup
and not self._ignore_repository_names
):
raise ValueError('Repository "{}" does not exist.'.format(repository))
if repository is not None and not self._ignore_repository_names:
return self._repositories[self._lookup[repository]].find_packages(
name, constraint, extras=extras, allow_prereleases=allow_prereleases
)
packages = []
for idx, repo in enumerate(self._repositories):
packages += repo.find_packages(
name, constraint, extras=extras, allow_prereleases=allow_prereleases
)
if packages:
return packages
return []
return packages
def search(self, query, mode=BaseRepository.SEARCH_FULLTEXT):
from .legacy_repository import LegacyRepository
......
......@@ -57,7 +57,6 @@ class PyPiRepository(Repository):
CACHE_VERSION = parse_constraint("0.12.0")
def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
self._name = "PyPI"
self._url = url
self._disable_cache = disable_cache
self._fallback = fallback
......@@ -80,6 +79,16 @@ class PyPiRepository(Repository):
super(PyPiRepository, self).__init__()
self._name = "PyPI"
@property
def url(self): # type: () -> str
return self._url
@property
def authenticated_url(self): # type: () -> str
return self._url
def find_packages(
self,
name, # type: str
......@@ -105,7 +114,14 @@ class PyPiRepository(Repository):
):
allow_prereleases = True
info = self.get_package_info(name)
try:
info = self.get_package_info(name)
except PackageNotFound:
self._log(
"No packages found for {} {}".format(name, str(constraint)),
level="debug",
)
return []
packages = []
......
......@@ -9,12 +9,18 @@ class Repository(BaseRepository):
def __init__(self, packages=None):
super(Repository, self).__init__()
self._name = None
if packages is None:
packages = []
for package in packages:
self.add_package(package)
@property
def name(self):
return self._name
def package(self, name, version, extras=None):
name = name.lower()
......
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io"
repository = "https://github.com/sdispater/poetry"
documentation = "https://poetry.eustace.io/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" }
orator = { version = "^0.9", optional = true }
# File dependency
demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" }
# Dir dependency with setup.py
my-package = { path = "../project_with_setup/" }
# Dir dependency with pyproject.toml
simple-project = { path = "../simple_project/" }
[tool.poetry.extras]
db = [ "orator" ]
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.scripts]
my-script = "my_package:main"
[tool.poetry.plugins."blogtool.parsers"]
".rst" = "some_module::SomeClass"
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
default = true
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io"
repository = "https://github.com/sdispater/poetry"
documentation = "https://poetry.eustace.io/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" }
orator = { version = "^0.9", optional = true }
# File dependency
demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" }
# Dir dependency with setup.py
my-package = { path = "../project_with_setup/" }
# Dir dependency with pyproject.toml
simple-project = { path = "../simple_project/" }
[tool.poetry.extras]
db = [ "orator" ]
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.scripts]
my-script = "my_package:main"
[tool.poetry.plugins."blogtool.parsers"]
".rst" = "some_module::SomeClass"
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
default = true
[[tool.poetry.source]]
name = "bar"
url = "https://bar.foo/simple/"
default = true
......@@ -20,6 +20,9 @@ from poetry.utils.env import NullEnv
from tests.helpers import get_dependency
from tests.helpers import get_package
from tests.repositories.test_legacy_repository import (
MockRepository as MockLegacyRepository,
)
from tests.repositories.test_pypi_repository import MockRepository
......@@ -1476,3 +1479,23 @@ def test_update_multiple_times_with_split_dependencies_is_idempotent(
installer.run()
assert expected == locker.written_data
def test_installer_can_install_dependencies_from_forced_source(
locker, package, installed, env
):
package.python_versions = "^3.7"
package.add_dependency("tomlkit", {"version": "^0.5", "source": "legacy"})
pool = Pool()
pool.add_repository(MockLegacyRepository())
pool.add_repository(MockRepository())
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 1
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
from poetry.installation.pip_installer import PipInstaller
from poetry.io.null_io import NullIO
from poetry.packages.package import Package
from poetry.repositories.pool import Pool
from poetry.utils.env import NullEnv
def test_requirement():
installer = PipInstaller(NullEnv(), NullIO())
installer = PipInstaller(NullEnv(), NullIO(), Pool())
package = Package("ipython", "7.5.0")
package.hashes = [
......
......@@ -1569,3 +1569,137 @@ def test_multiple_constraints_on_root(package, solver, repo):
ops,
[{"job": "install", "package": foo15}, {"job": "install", "package": foo25}],
)
def test_solver_chooses_most_recent_version_amongst_repositories(
package, installed, locked, io
):
package.python_versions = "^3.7"
package.add_dependency("tomlkit", {"version": "^0.5"})
repo = MockLegacyRepository()
pool = Pool([repo, MockPyPIRepository()])
solver = Solver(package, pool, installed, locked, io)
ops = solver.solve()
check_solver_result(
ops, [{"job": "install", "package": get_package("tomlkit", "0.5.3")}]
)
assert "" == ops[0].package.source_type
assert "" == ops[0].package.source_url
def test_solver_chooses_from_correct_repository_if_forced(
package, installed, locked, io
):
package.python_versions = "^3.7"
package.add_dependency("tomlkit", {"version": "^0.5", "source": "legacy"})
repo = MockLegacyRepository()
pool = Pool([repo, MockPyPIRepository()])
solver = Solver(package, pool, installed, locked, io)
ops = solver.solve()
check_solver_result(
ops, [{"job": "install", "package": get_package("tomlkit", "0.5.2")}]
)
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url
def test_solver_chooses_from_correct_repository_if_forced_and_transitive_dependency(
package, installed, locked, io
):
package.python_versions = "^3.7"
package.add_dependency("foo", "^1.0")
package.add_dependency("tomlkit", {"version": "^0.5", "source": "legacy"})
repo = Repository()
foo = get_package("foo", "1.0.0")
foo.add_dependency("tomlkit", "^0.5.0")
repo.add_package(foo)
pool = Pool([MockLegacyRepository(), repo, MockPyPIRepository()])
solver = Solver(package, pool, installed, locked, io)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": get_package("tomlkit", "0.5.2")},
{"job": "install", "package": foo},
],
)
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url
assert "" == ops[1].package.source_type
assert "" == ops[1].package.source_url
def test_solver_does_not_choose_from_secondary_repository_by_default(
package, installed, locked, io
):
package.python_versions = "^3.7"
package.add_dependency("clikit", {"version": "^0.2.0"})
pool = Pool()
pool.add_repository(MockPyPIRepository(), secondary=True)
pool.add_repository(MockLegacyRepository())
solver = Solver(package, pool, installed, locked, io)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": get_package("pastel", "0.1.0")},
{"job": "install", "package": get_package("pylev", "1.3.0")},
{"job": "install", "package": get_package("clikit", "0.2.4")},
],
)
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url
assert "" == ops[1].package.source_type
assert "" == ops[1].package.source_url
assert "legacy" == ops[2].package.source_type
assert "http://foo.bar" == ops[2].package.source_url
def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, io):
package.python_versions = "^3.7"
package.add_dependency("clikit", {"version": "^0.2.0", "source": "PyPI"})
pool = Pool()
pool.add_repository(MockPyPIRepository(), secondary=True)
pool.add_repository(MockLegacyRepository())
solver = Solver(package, pool, installed, locked, io)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": get_package("pastel", "0.1.0")},
{"job": "install", "package": get_package("pylev", "1.3.0")},
{"job": "install", "package": get_package("clikit", "0.2.4")},
],
)
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url
assert "" == ops[1].package.source_type
assert "" == ops[1].package.source_url
assert "" == ops[2].package.source_type
assert "" == ops[2].package.source_url
<!DOCTYPE html>
<html>
<head>
<title>Links for clikit</title>
</head>
<body>
<h1>Links for clikit</h1>
<a href="https://files.pythonhosted.org/packages/6a/35/3f36466a5b90bc92e3f20dee4a058285d8551afbe7a3d075b08b2abf0429/clikit-0.2.3-py2.py3-none-any.whl#sha256=e23ac911afb7079ae0e4d89af73ee0d31c8952c64ef4fcf76bcd46bfde9aabaa" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">clikit-0.2.3-py2.py3-none-any.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/04/b0/ec6c05c0e182e54e5829390df0bafb8c5b2308973decf60b9fd84ab397c7/clikit-0.2.3.tar.gz#sha256=7bbd38fcb912058c689a03f487a66f8d1eb1f486bc5cf68d4a5798093bd0217f" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">clikit-0.2.3.tar.gz</a><br/>
<a href="https://files.pythonhosted.org/packages/7b/0d/bb4c8a2d0edca8c300373ed736fb4680cf73be5be2ff84544dee5f979c14/clikit-0.2.4-py2.py3-none-any.whl#sha256=a7597999555aeb2ce9946f07187f690ab6864213f337e51250178c4bd19bd810" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">clikit-0.2.4-py2.py3-none-any.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/c5/33/14fad4c82f256b0ef60dd25d4b6d8145b463da5274fd9cd842f06af318ed/clikit-0.2.4.tar.gz#sha256=d6807cf4a41e6b981b056075c0aefca2db1dabc597ed18fa4d92b8b2e2678835" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">clikit-0.2.4.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 5256718-->
<!DOCTYPE html>
<html>
<head>
<title>Links for pastel</title>
</head>
<body>
<h1>Links for pastel</h1>
<a href="https://files.pythonhosted.org/packages/bd/13/a68f2e448b471e8c49e9b596d569ae167a5135ac672b1dc5f24f62f9c15f/pastel-0.1.0.tar.gz#sha256=3108af417ec0fa6d0a620e676ec4f02c839ca13e10611586e5d2174b46aa0bc3">pastel-0.1.0.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 2612998-->
<!DOCTYPE html>
<html>
<head>
<title>Links for tomlkit</title>
</head>
<body>
<h1>Links for tomlkit</h1>
<a href="https://files.pythonhosted.org/packages/f6/8c/c27d292cf7c0f04f0e1b5c75ab95dc328542ccbe9a809a1eada66c897bd2/tomlkit-0.5.2.tar.gz#sha256=a43e0195edc9b3c198cd4b5f0f3d427a395d47c4a76ceba7cc875ed030756c39" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">tomlkit-0.5.2.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 4504211-->
{
"info": {
"author": "Sébastien Eustace",
"author_email": "sebastien@eustace.io",
"bugtrack_url": null,
"classifiers": [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7"
],
"description": "",
"description_content_type": "text/markdown",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/sdispater/clikit",
"keywords": "packaging,dependency,poetry",
"license": "MIT",
"maintainer": "Sébastien Eustace",
"maintainer_email": "sebastien@eustace.io",
"name": "clikit",
"package_url": "https://pypi.org/project/clikit/",
"platform": "",
"project_url": "https://pypi.org/project/clikit/",
"project_urls": {
"Homepage": "https://github.com/sdispater/clikit",
"Repository": "https://github.com/sdispater/clikit"
},
"release_url": "https://pypi.org/project/clikit/0.2.4/",
"requires_dist": [
"pastel (>=0.1.0,<0.2.0)",
"pylev (>=1.3,<2.0)"
],
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"summary": "CliKit is a group of utilities to build beautiful and testable command line interfaces.",
"version": "0.2.4"
},
"last_serial": 5256718,
"releases": {
"0.2.4": [
{
"comment_text": "",
"digests": {
"md5": "280f18c82d0810c9b5ca11380529c04c",
"sha256": "a7597999555aeb2ce9946f07187f690ab6864213f337e51250178c4bd19bd810"
},
"downloads": -1,
"filename": "clikit-0.2.4-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "280f18c82d0810c9b5ca11380529c04c",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 85786,
"upload_time": "2019-05-11T17:09:23",
"url": "https://files.pythonhosted.org/packages/7b/0d/bb4c8a2d0edca8c300373ed736fb4680cf73be5be2ff84544dee5f979c14/clikit-0.2.4-py2.py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "2543daad83b072e960ded5f68074a443",
"sha256": "d6807cf4a41e6b981b056075c0aefca2db1dabc597ed18fa4d92b8b2e2678835"
},
"downloads": -1,
"filename": "clikit-0.2.4.tar.gz",
"has_sig": false,
"md5_digest": "2543daad83b072e960ded5f68074a443",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 50980,
"upload_time": "2019-05-11T17:09:25",
"url": "https://files.pythonhosted.org/packages/c5/33/14fad4c82f256b0ef60dd25d4b6d8145b463da5274fd9cd842f06af318ed/clikit-0.2.4.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "280f18c82d0810c9b5ca11380529c04c",
"sha256": "a7597999555aeb2ce9946f07187f690ab6864213f337e51250178c4bd19bd810"
},
"downloads": -1,
"filename": "clikit-0.2.4-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "280f18c82d0810c9b5ca11380529c04c",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 85786,
"upload_time": "2019-05-11T17:09:23",
"url": "https://files.pythonhosted.org/packages/7b/0d/bb4c8a2d0edca8c300373ed736fb4680cf73be5be2ff84544dee5f979c14/clikit-0.2.4-py2.py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "2543daad83b072e960ded5f68074a443",
"sha256": "d6807cf4a41e6b981b056075c0aefca2db1dabc597ed18fa4d92b8b2e2678835"
},
"downloads": -1,
"filename": "clikit-0.2.4.tar.gz",
"has_sig": false,
"md5_digest": "2543daad83b072e960ded5f68074a443",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 50980,
"upload_time": "2019-05-11T17:09:25",
"url": "https://files.pythonhosted.org/packages/c5/33/14fad4c82f256b0ef60dd25d4b6d8145b463da5274fd9cd842f06af318ed/clikit-0.2.4.tar.gz"
}
]
}
{
"info": {
"author": "Daniel Lindsley",
"author_email": "daniel@toastdriven.com",
"bugtrack_url": null,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3"
],
"description": "",
"description_content_type": null,
"docs_url": null,
"download_url": "UNKNOWN",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "http://github.com/toastdriven/pylev",
"keywords": null,
"license": "UNKNOWN",
"maintainer": null,
"maintainer_email": null,
"name": "pylev",
"package_url": "https://pypi.org/project/pylev/",
"platform": "UNKNOWN",
"project_url": "https://pypi.org/project/pylev/",
"project_urls": {
"Download": "UNKNOWN",
"Homepage": "http://github.com/toastdriven/pylev"
},
"release_url": "https://pypi.org/project/pylev/1.3.0/",
"requires_dist": null,
"requires_python": null,
"summary": "A pure Python Levenshtein implementation that's not freaking GPL'd.",
"version": "1.3.0"
},
"last_serial": 1279536,
"releases": {
"1.3.0": [
{
"comment_text": "",
"digests": {
"md5": "6da14dfce5034873fc5c2d7a6e83dc29",
"sha256": "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"
},
"downloads": -1,
"filename": "pylev-1.3.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "6da14dfce5034873fc5c2d7a6e83dc29",
"packagetype": "bdist_wheel",
"python_version": "2.7",
"requires_python": null,
"size": 4927,
"upload_time": "2014-10-23T00:24:34",
"url": "https://files.pythonhosted.org/packages/40/1c/7dff1d242bf1e19f9c6202f0ba4e6fd18cc7ecb8bc85b17b2d16c806e228/pylev-1.3.0-py2.py3-none-any.whl"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "6da14dfce5034873fc5c2d7a6e83dc29",
"sha256": "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"
},
"downloads": -1,
"filename": "pylev-1.3.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "6da14dfce5034873fc5c2d7a6e83dc29",
"packagetype": "bdist_wheel",
"python_version": "2.7",
"requires_python": null,
"size": 4927,
"upload_time": "2014-10-23T00:24:34",
"url": "https://files.pythonhosted.org/packages/40/1c/7dff1d242bf1e19f9c6202f0ba4e6fd18cc7ecb8bc85b17b2d16c806e228/pylev-1.3.0-py2.py3-none-any.whl"
}
]
}
{
"info": {
"author": "Sébastien Eustace",
"author_email": "sebastien@eustace.io",
"bugtrack_url": null,
"classifiers": [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7"
],
"description": "",
"description_content_type": "text/markdown",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/sdispater/tomlkit",
"keywords": "",
"license": "MIT",
"maintainer": "Sébastien Eustace",
"maintainer_email": "sebastien@eustace.io",
"name": "tomlkit",
"package_url": "https://pypi.org/project/tomlkit/",
"platform": "",
"project_url": "https://pypi.org/project/tomlkit/",
"project_urls": {
"Homepage": "https://github.com/sdispater/tomlkit",
"Repository": "https://github.com/sdispater/tomlkit"
},
"release_url": "https://pypi.org/project/tomlkit/0.5.3/",
"requires_dist": [
"enum34 (>=1.1,<2.0); python_version >= \"2.7\" and python_version < \"2.8\"",
"functools32 (>=3.2.3,<4.0.0); python_version >= \"2.7\" and python_version < \"2.8\"",
"typing (>=3.6,<4.0); python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""
],
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"summary": "Style preserving TOML library",
"version": "0.5.3"
},
"last_serial": 4504211,
"releases": {
"0.5.2": [
{
"comment_text": "",
"digests": {
"md5": "7a7ef7c16a0e9b374933c116a7bb2f9f",
"sha256": "82a8fbb8d8c6af72e96ba00b9db3e20ef61be6c79082552c9363f4559702258b"
},
"downloads": -1,
"filename": "tomlkit-0.5.2-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "7a7ef7c16a0e9b374933c116a7bb2f9f",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 116499,
"upload_time": "2018-11-09T17:09:28",
"url": "https://files.pythonhosted.org/packages/9b/ca/8b60a94c01ee655ffb81d11c11396cb6fff89459317aa1fe3e98ee80f055/tomlkit-0.5.2-py2.py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "7abb629acee08fd77bafe858f4706f47",
"sha256": "a43e0195edc9b3c198cd4b5f0f3d427a395d47c4a76ceba7cc875ed030756c39"
},
"downloads": -1,
"filename": "tomlkit-0.5.2.tar.gz",
"has_sig": false,
"md5_digest": "7abb629acee08fd77bafe858f4706f47",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 29813,
"upload_time": "2018-11-09T17:09:29",
"url": "https://files.pythonhosted.org/packages/f6/8c/c27d292cf7c0f04f0e1b5c75ab95dc328542ccbe9a809a1eada66c897bd2/tomlkit-0.5.2.tar.gz"
}
],
"0.5.3": [
{
"comment_text": "",
"digests": {
"md5": "0a6cf417df5d0fc911f89447c9a662a9",
"sha256": "f077456d35303e7908cc233b340f71e0bec96f63429997f38ca9272b7d64029e"
},
"downloads": -1,
"filename": "tomlkit-0.5.3-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "0a6cf417df5d0fc911f89447c9a662a9",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 116796,
"upload_time": "2018-11-19T20:05:37",
"url": "https://files.pythonhosted.org/packages/71/c6/06c014b92cc48270765d6a9418d82239b158d8a9b69e031b0e2c6598740b/tomlkit-0.5.3-py2.py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "a708470b53d689013f2fc9f0a7902adf",
"sha256": "d6506342615d051bc961f70bfcfa3d29b6616cc08a3ddfd4bc24196f16fd4ec2"
},
"downloads": -1,
"filename": "tomlkit-0.5.3.tar.gz",
"has_sig": false,
"md5_digest": "a708470b53d689013f2fc9f0a7902adf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 29864,
"upload_time": "2018-11-19T20:05:39",
"url": "https://files.pythonhosted.org/packages/f7/f7/bbd9213bfe76cb7821c897f9ed74877fd74993b4ca2fe9513eb5a31030f9/tomlkit-0.5.3.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "0a6cf417df5d0fc911f89447c9a662a9",
"sha256": "f077456d35303e7908cc233b340f71e0bec96f63429997f38ca9272b7d64029e"
},
"downloads": -1,
"filename": "tomlkit-0.5.3-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "0a6cf417df5d0fc911f89447c9a662a9",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 116796,
"upload_time": "2018-11-19T20:05:37",
"url": "https://files.pythonhosted.org/packages/71/c6/06c014b92cc48270765d6a9418d82239b158d8a9b69e031b0e2c6598740b/tomlkit-0.5.3-py2.py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "a708470b53d689013f2fc9f0a7902adf",
"sha256": "d6506342615d051bc961f70bfcfa3d29b6616cc08a3ddfd4bc24196f16fd4ec2"
},
"downloads": -1,
"filename": "tomlkit-0.5.3.tar.gz",
"has_sig": false,
"md5_digest": "a708470b53d689013f2fc9f0a7902adf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
"size": 29864,
"upload_time": "2018-11-19T20:05:39",
"url": "https://files.pythonhosted.org/packages/f7/f7/bbd9213bfe76cb7821c897f9ed74877fd74993b4ca2fe9513eb5a31030f9/tomlkit-0.5.3.tar.gz"
}
]
}
......@@ -28,6 +28,8 @@ class MockRepository(LegacyRepository):
name = parts[1]
fixture = self.FIXTURES / (name + ".html")
if not fixture.exists():
return
with fixture.open() as f:
return Page(self._url + endpoint, f.read(), {})
......
......@@ -2,6 +2,7 @@ import pytest
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.exceptions import PackageNotFound
......@@ -11,3 +12,60 @@ def test_pool_raises_package_not_found_when_no_package_is_found():
with pytest.raises(PackageNotFound):
pool.package("foo", "1.0.0")
def test_pool():
pool = Pool()
assert 0 == len(pool.repositories)
assert not pool.has_default()
def test_pool_with_initial_repositories():
repo = Repository()
pool = Pool([repo])
assert 1 == len(pool.repositories)
assert not pool.has_default()
def test_repository_no_repository():
pool = Pool()
with pytest.raises(ValueError):
pool.repository("foo")
def test_repository_from_normal_pool():
repo = LegacyRepository("foo", "https://foo.bar")
pool = Pool()
pool.add_repository(repo)
assert pool.repository("foo") is repo
def test_repository_from_secondary_pool():
repo = LegacyRepository("foo", "https://foo.bar")
pool = Pool()
pool.add_repository(repo, secondary=True)
assert pool.repository("foo") is repo
def test_repository_with_normal_default_and_secondary_repositories():
secondary = LegacyRepository("secondary", "https://secondary.com")
default = LegacyRepository("default", "https://default.com")
repo1 = LegacyRepository("foo", "https://foo.bar")
repo2 = LegacyRepository("bar", "https://bar.baz")
pool = Pool()
pool.add_repository(repo1)
pool.add_repository(secondary, secondary=True)
pool.add_repository(repo2)
pool.add_repository(default, default=True)
assert pool.repository("secondary") is secondary
assert pool.repository("default") is default
assert pool.repository("foo") is repo1
assert pool.repository("bar") is repo2
assert pool.has_default()
......@@ -33,6 +33,9 @@ class MockRepository(PyPiRepository):
if not fixture.exists():
fixture = self.JSON_FIXTURES / (name + ".json")
if not fixture.exists():
return
with fixture.open() as f:
return json.loads(f.read())
......
......@@ -2,6 +2,8 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest
from poetry.poetry import Poetry
from poetry.utils._compat import PY2
from poetry.utils._compat import Path
......@@ -128,6 +130,19 @@ def test_poetry_with_multi_constraints_dependency():
assert len(package.requires) == 2
def test_poetry_with_default_source():
poetry = Poetry.create(fixtures_dir / "with_default_source")
assert 1 == len(poetry.pool.repositories)
def test_poetry_with_two_default_sources():
with pytest.raises(ValueError) as e:
Poetry.create(fixtures_dir / "with_two_default_sources")
assert "Only one repository can be the default" == str(e.value)
def test_check():
complete = TomlFile(fixtures_dir / "complete.toml")
content = complete.read()["tool"]["poetry"]
......
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