Commit 2c37180f by Sébastien Eustace

Merge branch 'master' into develop

parents 9f79b899 cb649141
......@@ -15,7 +15,7 @@ install:
# Upgrade to the latest version of pip to avoid it displaying warnings
# about it being out of date.
- "python -m pip install --disable-pip-version-check --user --upgrade pip"
- "python -m pip install --disable-pip-version-check --user pip==18.1"
# Installing Poetry
- "python get-poetry.py --preview -y"
......
......@@ -11,7 +11,7 @@ cache:
- "$HOME/.cache/pre-commit"
install:
- pip install pip -U
- pip install pip==18.1
- python get-poetry.py --preview -y
- source $HOME/.poetry/env
- poetry install -v
......
......@@ -24,6 +24,30 @@
- Fixed transitive extra dependencies being removed when updating a specific dependency.
## [0.12.15] - 2019-05-03
### Fixed
- Fixed an `AttributeError` in the editable builder.
- Fixed resolution of packages with only Python 3 wheels and sdist when resolving for legacy repositories.
- Fixed non-sha256 hashes retrieval for legacy repositories.
## [0.12.14] - 2019-04-26
### Fixed
- Fixed root package installation for pure Python packages.
## [0.12.13] - 2019-04-26
### Fixed
- Fixed root package installation with `pip>=19.0`.
- Fixed packages not being removed after using the `remove` command.
## [0.12.12] - 2019-04-11
### Fixed
......@@ -661,7 +685,10 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.12...develop
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.15...develop
[0.12.15]: https://github.com/sdispater/poetry/releases/tag/0.12.15
[0.12.14]: https://github.com/sdispater/poetry/releases/tag/0.12.14
[0.12.13]: https://github.com/sdispater/poetry/releases/tag/0.12.13
[0.12.12]: https://github.com/sdispater/poetry/releases/tag/0.12.12
[0.12.11]: https://github.com/sdispater/poetry/releases/tag/0.12.11
[0.12.10]: https://github.com/sdispater/poetry/releases/tag/0.12.10
......
......@@ -29,10 +29,8 @@ exist it will look for <comment>pyproject.toml</> and do the same.
def handle(self):
from clikit.io import NullIO
from poetry.installation import Installer
from poetry.masonry.builders import SdistBuilder
from poetry.masonry.builders import EditableBuilder
from poetry.masonry.utils.module import ModuleOrPackageNotFound
from poetry.utils._compat import decode
from poetry.utils.env import NullEnv
installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
......@@ -60,7 +58,7 @@ exist it will look for <comment>pyproject.toml</> and do the same.
return 0
try:
builder = SdistBuilder(self.poetry, NullEnv(), NullIO())
builder = EditableBuilder(self.poetry, self._env, NullIO())
except ModuleOrPackageNotFound:
# This is likely due to the fact that the project is an application
# not following the structure expected by Poetry
......@@ -76,17 +74,6 @@ exist it will look for <comment>pyproject.toml</> and do the same.
if self.option("dry-run"):
return 0
setup = self.poetry.file.parent / "setup.py"
has_setup = setup.exists()
builder.build()
if has_setup:
self.line("<warning>A setup.py file already exists. Using it.</warning>")
else:
with setup.open("w", encoding="utf-8") as f:
f.write(decode(builder.build_setup()))
try:
self.env.run("pip", "install", "-e", str(setup.parent), "--no-deps")
finally:
if not has_setup:
os.remove(str(setup))
return 0
......@@ -211,7 +211,6 @@ class Installer:
# Making a new repo containing the packages
# newly resolved and the ones from the current lock file
locked_repository = self._locker.locked_repository(True)
repo = Repository()
for package in local_repo.packages + locked_repository.packages:
if not repo.has_package(package):
......
......@@ -116,7 +116,11 @@ class PipInstaller(BaseInstaller):
if formatted and not package.source_type:
req = "{}=={}".format(package.name, package.version)
for h in package.hashes:
req += " --hash sha256:{}".format(h)
hash_type = "sha256"
if ":" in h:
hash_type, h = h.split(":")
req += " --hash {}:{}".format(hash_type, h)
req += "\n"
......
from .complete import CompleteBuilder
from .editable import EditableBuilder
from .sdist import SdistBuilder
from .wheel import WheelBuilder
......@@ -37,7 +37,7 @@ class Builder(object):
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"}
def __init__(self, poetry, env, io):
def __init__(self, poetry, env, io): # type: (Poetry, Env, IO) -> None
self._poetry = poetry
self._env = env
self._io = io
......
from __future__ import unicode_literals
import os
import shutil
from collections import defaultdict
from poetry.semver.version import Version
from poetry.utils._compat import decode
from .builder import Builder
from .sdist import SdistBuilder
class EditableBuilder(Builder):
def build(self):
return self._setup_build()
def _setup_build(self):
builder = SdistBuilder(self._poetry, self._env, self._io)
setup = self._path / "setup.py"
has_setup = setup.exists()
if has_setup:
self._io.write_line(
"<warning>A setup.py file already exists. Using it.</warning>"
)
else:
with setup.open("w", encoding="utf-8") as f:
f.write(decode(builder.build_setup()))
try:
if self._env.pip_version < Version(19, 0):
self._env.run("python", "-m", "pip", "install", "-e", str(self._path))
else:
# Temporarily rename pyproject.toml
shutil.move(
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
)
try:
self._env.run(
"python", "-m", "pip", "install", "-e", str(self._path)
)
finally:
shutil.move(
str(self._poetry.file.with_suffix(".tmp")),
str(self._poetry.file),
)
finally:
if not has_setup:
os.remove(str(setup))
def _build_egg_info(self):
egg_info = self._path / "{}.egg-info".format(
self._package.name.replace("-", "_")
)
egg_info.mkdir(exist_ok=True)
with egg_info.joinpath("PKG-INFO").open("w", encoding="utf-8") as f:
f.write(decode(self.get_metadata_content()))
with egg_info.joinpath("entry_points.txt").open("w", encoding="utf-8") as f:
entry_points = self.convert_entry_points()
for group_name in sorted(entry_points):
f.write("[{}]\n".format(group_name))
for ep in sorted(entry_points[group_name]):
f.write(ep.replace(" ", "") + "\n")
f.write("\n")
with egg_info.joinpath("requires.txt").open("w", encoding="utf-8") as f:
f.write(self._generate_requires())
def _build_egg_link(self):
egg_link = self._env.site_packages / "{}.egg-link".format(self._package.name)
with egg_link.open("w", encoding="utf-8") as f:
f.write(str(self._poetry.file.parent.resolve()) + "\n")
f.write(".")
def _add_easy_install_entry(self):
easy_install_pth = self._env.site_packages / "easy-install.pth"
path = str(self._poetry.file.parent.resolve())
content = ""
if easy_install_pth.exists():
with easy_install_pth.open(encoding="utf-8") as f:
content = f.read()
if path in content:
return
content += "{}\n".format(path)
with easy_install_pth.open("w", encoding="utf-8") as f:
f.write(content)
def _generate_requires(self):
extras = defaultdict(list)
requires = ""
for dep in sorted(self._package.requires, key=lambda d: d.name):
marker = dep.marker
if marker.is_any():
requires += "{}\n".format(dep.base_pep_508_name)
continue
extras[str(marker)].append(dep.base_pep_508_name)
if extras:
requires += "\n"
for marker, deps in sorted(extras.items()):
requires += "[:{}]\n".format(marker)
for dep in deps:
requires += dep + "\n"
requires += "\n"
return requires
......@@ -341,6 +341,7 @@ class LegacyRepository(PyPiRepository):
"requires_dist": [],
"requires_python": None,
"digests": [],
"_cache_version": str(self.CACHE_VERSION),
}
links = list(page.links_for_version(Version.parse(version)))
......@@ -363,6 +364,8 @@ class LegacyRepository(PyPiRepository):
hash = link.hash
if link.hash_name == "sha256":
hashes.append(hash)
else:
hashes.append(link.hash_name + ":" + hash)
data["digests"] = hashes
......
......@@ -420,6 +420,13 @@ class PyPiRepository(Repository):
if info:
return info
# Prefer non platform specific wheels
if universal_python3_wheel:
return self._get_info_from_wheel(universal_python3_wheel)
if universal_python2_wheel:
return self._get_info_from_wheel(universal_python2_wheel)
if platform_specific_wheels and "sdist" not in urls:
# Pick the first wheel available and hope for the best
return self._get_info_from_wheel(platform_specific_wheels[0])
......
......@@ -24,7 +24,7 @@ from clikit.api.io import IO
from poetry.config import Config
from poetry.locations import CACHE_DIR
from poetry.semver import Version
from poetry.semver.version import Version
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils._compat import encode
......@@ -516,7 +516,13 @@ class EnvManager(object):
try:
from venv import EnvBuilder
builder = EnvBuilder(with_pip=True)
# use the same defaults as python -m venv
if os.name == "nt":
use_symlinks = False
else:
use_symlinks = True
builder = EnvBuilder(with_pip=True, symlinks=use_symlinks)
build = builder.create
except ImportError:
# We fallback on virtualenv for Python 2.7
......@@ -563,6 +569,7 @@ class Env(object):
self._base = base or path
self._marker_env = None
self._pip_version = None
@property
def path(self): # type: () -> Path
......@@ -609,6 +616,25 @@ class Env(object):
def os(self): # type: () -> str
return os.name
@property
def pip_version(self):
if self._pip_version is None:
self._pip_version = self.get_pip_version()
return self._pip_version
@property
def site_packages(self): # type: () -> Path
if self._is_windows:
return self._path / "Lib" / "site-packages"
return (
self._path
/ "lib"
/ "python{}.{}".format(*self.version_info[:2])
/ "site-packages"
)
@classmethod
def get_base_prefix(cls): # type: () -> Path
if hasattr(sys, "real_prefix"):
......@@ -631,6 +657,9 @@ class Env(object):
def config_var(self, var): # type: (str) -> Any
raise NotImplementedError()
def get_pip_version(self): # type: () -> Version
raise NotImplementedError()
def is_valid_for_marker(self, marker): # type: (BaseMarker) -> bool
return marker.validate(self.marker_env)
......@@ -750,6 +779,11 @@ class SystemEnv(Env):
return
def get_pip_version(self): # type: () -> Version
from pip import __version__
return Version.parse(__version__)
def is_venv(self): # type: () -> bool
return self._path != self._base
......@@ -800,6 +834,14 @@ class VirtualEnv(Env):
return value
def get_pip_version(self): # type: () -> Version
output = self.run("python", "-m", "pip", "--version").strip()
m = re.match("pip (.+?)(?: from .+)?$", output)
if not m:
return Version.parse("0.0")
return Version.parse(m.group(1))
def is_venv(self): # type: () -> bool
return True
......@@ -878,6 +920,7 @@ class MockEnv(NullEnv):
platform="darwin",
os_name="posix",
is_venv=False,
pip_version="19.1",
**kwargs
):
super(MockEnv, self).__init__(**kwargs)
......@@ -887,6 +930,7 @@ class MockEnv(NullEnv):
self._platform = platform
self._os_name = os_name
self._is_venv = is_venv
self._pip_version = Version.parse(pip_version)
@property
def version_info(self): # type: () -> Tuple[int]
......@@ -904,5 +948,9 @@ class MockEnv(NullEnv):
def os(self): # type: () -> str
return self._os_name
@property
def pip_version(self):
return self._pip_version
def is_venv(self): # type: () -> bool
return self._is_venv
......@@ -76,6 +76,7 @@ class Locker(BaseLocker):
package["python-versions"] = python_versions
self._written_data = data
self._lock_data = data
@pytest.fixture()
......@@ -331,7 +332,7 @@ def test_run_whitelist_add(installer, locker, repo, package):
assert locker.written_data == expected
def test_run_whitelist_remove(installer, locker, repo, package):
def test_run_whitelist_remove(installer, locker, repo, package, installed):
locker.locked(True)
locker.mock_lock_data(
{
......@@ -367,6 +368,7 @@ def test_run_whitelist_remove(installer, locker, repo, package):
package_b = get_package("B", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
installed.add_package(package_b)
package.add_dependency("A", "~1.0")
......@@ -377,6 +379,9 @@ def test_run_whitelist_remove(installer, locker, repo, package):
expected = fixture("remove")
assert locker.written_data == expected
assert len(installer.installer.installs) == 1
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 1
def test_add_with_sub_dependencies(installer, locker, repo, package):
......
from poetry.installation.pip_installer import PipInstaller
from poetry.io.null_io import NullIO
from poetry.packages.package import Package
from poetry.utils.env import NullEnv
def test_requirement():
installer = PipInstaller(NullEnv(), NullIO())
package = Package("ipython", "7.5.0")
package.hashes = [
"md5:dbdc53e3918f28fa335a173432402a00",
"e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
]
result = installer.requirement(package, formatted=True)
expected = (
"ipython==7.5.0 "
"--hash md5:dbdc53e3918f28fa335a173432402a00 "
"--hash sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26"
"\n"
)
assert expected == result
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from clikit.io import NullIO
from poetry.masonry.builders import EditableBuilder
from poetry.poetry import Poetry
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
fixtures_dir = Path(__file__).parent / "fixtures"
def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mocker):
move = mocker.patch("shutil.move")
tmp_dir = Path(tmp_dir)
env = MockEnv(path=tmp_dir, pip_version="18.1", execute=False)
env.site_packages.mkdir(parents=True)
module_path = fixtures_dir / "extended"
builder = EditableBuilder(Poetry.create(module_path), env, NullIO())
builder.build()
expected = [["python", "-m", "pip", "install", "-e", str(module_path)]]
assert expected == env.executed
assert 0 == move.call_count
def test_build_should_temporarily_remove_the_pyproject_file(tmp_dir, mocker):
move = mocker.patch("shutil.move")
tmp_dir = Path(tmp_dir)
env = MockEnv(path=tmp_dir, pip_version="19.1", execute=False)
env.site_packages.mkdir(parents=True)
module_path = fixtures_dir / "extended"
builder = EditableBuilder(Poetry.create(module_path), env, NullIO())
builder.build()
expected = [["python", "-m", "pip", "install", "-e", str(module_path)]]
assert expected == env.executed
assert 2 == move.call_count
expected_calls = [
mocker.call(
str(module_path / "pyproject.toml"), str(module_path / "pyproject.tmp")
),
mocker.call(
str(module_path / "pyproject.tmp"), str(module_path / "pyproject.toml")
),
]
assert expected_calls == move.call_args_list
......@@ -8,6 +8,8 @@
<a href="https://files.pythonhosted.org/packages/52/19/aadde98d6bde1667d0bf431fb2d22451f880aaa373e0a241c7e7cb5815a0/ipython-5.7.0-py2-none-any.whl#sha256=707d1bbfc81e41e39ead1012af931bec6f80357b87e520af352e539cf5961dc0">ipython-5.7.0-py2-none-any.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/c7/b6/03e0b5b0972e6161d16c4cec8d41a20372bd0634f8cb4cc0c984b8a91db6/ipython-5.7.0-py3-none-any.whl#sha256=fc0464e68f9c65cd8c453474b4175432cc29ecb6c83775baedf6dbfcee9275ab">ipython-5.7.0-py3-none-any.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/3c/fd/559fead731a29eaa55cc235c8029807b2520976a937c30e9ee603f3bb566/ipython-5.7.0.tar.gz#sha256=8db43a7fb7619037c98626613ff08d03dda9d5d12c84814a4504c78c0da8323c">ipython-5.7.0.tar.gz</a><br/>
<a href="https://files.pythonhosted.org/packages/a9/2e/41dce4ed129057e05a555a7f9629aa2d5f81fdcd4d16568bc24b75a1d2c9/ipython-7.5.0-py3-none-any.whl#md5=dbdc53e3918f28fa335a173432402a00" data-requires-python="&gt;=3.5">ipython-7.5.0-py3-none-any.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/75/74/9b0ef91c8e356c907bb12297000951acb804583b54eeaddc342c5bad4d96/ipython-7.5.0.tar.gz#sha256=e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26" data-requires-python="&gt;=3.5">ipython-7.5.0.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 4837213-->
......@@ -205,3 +205,43 @@ def test_get_package_from_both_py2_and_py3_specific_wheels():
package.requires[4].marker
)
assert 'sys_platform != "win32"' == str(package.requires[5].marker)
def test_get_package_with_dist_and_universal_py3_wheel():
repo = MockRepository()
package = repo.package("ipython", "7.5.0")
assert "ipython" == package.name
assert "7.5.0" == package.version.text
assert ">=3.5" == package.python_versions
expected = [
Dependency("appnope", "*"),
Dependency("backcall", "*"),
Dependency("colorama", "*"),
Dependency("decorator", "*"),
Dependency("jedi", ">=0.10"),
Dependency("pexpect", "*"),
Dependency("pickleshare", "*"),
Dependency("prompt-toolkit", ">=2.0.0,<2.1.0"),
Dependency("pygments", "*"),
Dependency("setuptools", ">=18.5"),
Dependency("traitlets", ">=4.2"),
Dependency("typing", "*"),
Dependency("win-unicode-console", ">=0.5"),
]
assert expected == sorted(package.requires, key=lambda dep: dep.name)
def test_get_package_retrieves_non_sha256_hashes():
repo = MockRepository()
package = repo.package("ipython", "7.5.0")
expected = [
"md5:dbdc53e3918f28fa335a173432402a00",
"e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
]
assert expected == package.hashes
......@@ -450,3 +450,22 @@ def test_remove_also_deactivates(tmp_dir, config, mocker):
envs = envs_file.read()
assert venv_name not in envs
def test_env_has_symlinks_on_nix(tmp_dir, config):
venv_path = Path(tmp_dir)
EnvManager(config).build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
venv_available = False
try:
from venv import EnvBuilder
venv_available = True
except ImportError:
pass
if os.name != "nt" and venv_available:
assert os.path.islink(venv.python)
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