Commit 3a00ad8b by Sébastien Eustace

Merge branch 'master' into develop

parents c6a2d6e1 f341073a
......@@ -37,6 +37,16 @@
- The `pyproject.toml` configuration is now properly validated.
## [0.12.17] - 2019-07-03
### Fixed
- Fixed dependency resolution with circular dependencies.
- Fixed encoding errors when reading files on Windows. (Thanks to [@vlcinsky](https://github.com/vlcinsky))
- Fixed unclear errors when executing commands in virtual environments. (Thanks to [@Imaclean74](https://github.com/Imaclean74))
- Fixed handling of `.venv` when it's not a directory. (Thanks to [@mpanarin](https://github.com/mpanarin))
## [0.12.16] - 2019-05-17
### Fixed
......@@ -708,7 +718,8 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.16...develop
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.17...develop
[0.12.17]: https://github.com/sdispater/poetry/releases/tag/0.12.17
[0.12.16]: https://github.com/sdispater/poetry/releases/tag/0.12.16
[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
......
......@@ -33,7 +33,7 @@ from contextlib import closing
from contextlib import contextmanager
from functools import cmp_to_key
from gzip import GzipFile
from io import UnsupportedOperation
from io import UnsupportedOperation, open
try:
from urllib.error import HTTPError
......@@ -58,6 +58,10 @@ try:
except ImportError:
winreg = None
try:
u = unicode
except NameError:
u = str
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
......@@ -191,6 +195,7 @@ POETRY_LIB_BACKUP = os.path.join(POETRY_HOME, "lib-backup")
BIN = """#!/usr/bin/env python
# -*- coding: utf-8 -*-
import glob
import sys
import os
......@@ -205,7 +210,7 @@ if __name__ == "__main__":
main()
"""
BAT = '@echo off\r\npython "{poetry_bin}" %*\r\n'
BAT = u('@echo off\r\npython "{poetry_bin}" %*\r\n')
PRE_MESSAGE = """# Welcome to {poetry}!
......@@ -387,7 +392,9 @@ class Installer:
current_version = None
if os.path.exists(POETRY_LIB):
with open(os.path.join(POETRY_LIB, "poetry", "__version__.py")) as f:
with open(
os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8"
) as f:
version_content = f.read()
current_version_re = re.match(
......@@ -563,15 +570,17 @@ class Installer:
if WINDOWS:
with open(os.path.join(POETRY_BIN, "poetry.bat"), "w") as f:
f.write(
BAT.format(
poetry_bin=os.path.join(POETRY_BIN, "poetry").replace(
os.environ["USERPROFILE"], "%USERPROFILE%"
u(
BAT.format(
poetry_bin=os.path.join(POETRY_BIN, "poetry").replace(
os.environ["USERPROFILE"], "%USERPROFILE%"
)
)
)
)
with open(os.path.join(POETRY_BIN, "poetry"), "w") as f:
f.write(BIN)
with open(os.path.join(POETRY_BIN, "poetry"), "w", encoding="utf-8") as f:
f.write(u(BIN))
if not WINDOWS:
# Making the file executable
......@@ -583,7 +592,7 @@ class Installer:
return
with open(os.path.join(POETRY_HOME, "env"), "w") as f:
f.write(self.get_export_string())
f.write(u(self.get_export_string()))
def update_path(self):
"""
......@@ -608,7 +617,7 @@ class Installer:
if addition not in content:
with open(profile, "a") as f:
f.write(addition)
f.write(u(addition))
updated.append(os.path.relpath(profile, HOME))
......
......@@ -995,6 +995,15 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*"
version = "1.12.0"
[[package]]
category = "main"
description = "A backport of the subprocess module from Python 3 for use on 2.x."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""
name = "subprocess32"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
version = "3.5.4"
[[package]]
category = "dev"
description = "ANSII Color formatting for output in terminal."
name = "termcolor"
......@@ -1139,14 +1148,14 @@ description = "Backport of pathlib-compatible object wrapper for zip files"
name = "zipp"
optional = false
python-versions = ">=2.7"
version = "0.5.1"
version = "0.5.2"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pathlib2", "contextlib2", "unittest2"]
[metadata]
content-hash = "1de9e8afcc93d2a5ad0f30746c26b58ae818c3f821460e3281ba4e34184a0f17"
content-hash = "c556af6b9f162b11c459c8f6f873d466ce46115657c6ca50c0d1b0754ba3a998"
python-versions = "~2.7 || ^3.4"
[metadata.hashes]
......@@ -1223,6 +1232,7 @@ scandir = ["2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", "
secretstorage = ["3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6", "20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92", "7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"]
shellingham = ["77d37a4fd287c1e663006f7ecf1b9deca9ad492d0082587bd813c44eb49e4e62", "985b23bbd1feae47ca6a6365eacd314d93d95a8a16f8f346945074c28fe6f3e0"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
subprocess32 = ["88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b", "eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"]
termcolor = ["1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"]
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
tomlkit = ["a8d806f3a453c2d292afe97918398354e405b93919e2e68771a3fd0a90e89576", "c6b0c11b85e888c12330c7605d43c1446aa148cd421163f90ca46ea813f2c336"]
......@@ -1233,4 +1243,4 @@ urllib3 = ["2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "
virtualenv = ["b7335cddd9260a3dd214b73a2521ffc09647bde3e9457fcca31dc3be3999d04a", "d28ca64c0f3f125f59cabf13e0a150e1c68e5eea60983cc4395d88c584495783"]
wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
webencodings = ["a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", "b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"]
zipp = ["8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", "ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"]
zipp = ["4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", "8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"]
......@@ -12,7 +12,7 @@ class CacheClearCommand(Command):
description = "Clears Poetry's cache."
arguments = [argument("cache", description="The name of the cache to clear.")]
options = [option("all", description="Clear all caches.")]
options = [option("all", description="Clear all entries in cache.")]
def handle(self):
from cachy import CacheManager
......
......@@ -197,7 +197,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
return 1
with (Path.cwd() / "pyproject.toml").open("w") as f:
with (Path.cwd() / "pyproject.toml").open("w", encoding="utf-8") as f:
f.write(content)
def _determine_requirements(
......
......@@ -189,7 +189,7 @@ class PipInstaller(BaseInstaller):
# We also need it for non-PEP-517 packages
builder = SdistBuilder(Poetry.create(pyproject.parent), NullEnv(), NullIO())
with open(setup, "w") as f:
with open(setup, "w", encoding="utf-8") as f:
f.write(decode(builder.build_setup()))
if package.develop:
......
......@@ -3,6 +3,7 @@ import os
import jsonschema
from io import open
from typing import List
SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas")
......@@ -19,7 +20,7 @@ def validate_object(obj, schema_name): # type: (dict, str) -> List[str]
if not os.path.exists(schema):
raise ValueError("Schema {} does not exist.".format(schema_name))
with open(schema) as f:
with open(schema, encoding="utf-8") as f:
schema = json.loads(f.read())
validator = jsonschema.Draft7Validator(schema)
......
......@@ -140,7 +140,7 @@ class Layout(object):
tests.mkdir()
tests_init.touch(exist_ok=False)
with tests_default.open("w") as f:
with tests_default.open("w", encoding="utf-8") as f:
f.write(
TESTS_DEFAULT.format(
package_name=self._package_name, version=self._version
......@@ -152,5 +152,5 @@ class Layout(object):
poetry = path / "pyproject.toml"
with poetry.open("w") as f:
with poetry.open("w", encoding="utf-8") as f:
f.write(content)
......@@ -14,5 +14,5 @@ class SrcLayout(Layout):
package_path.mkdir(parents=True)
with package_init.open("w") as f:
with package_init.open("w", encoding="utf-8") as f:
f.write(DEFAULT.format(version=self._version))
......@@ -14,5 +14,5 @@ class StandardLayout(Layout):
package_path.mkdir()
with package_init.open("w") as f:
with package_init.open("w", encoding="utf-8") as f:
f.write(DEFAULT.format(version=self._version))
......@@ -40,13 +40,13 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
dist_info.mkdir()
if "scripts" in poetry.local_config or "plugins" in poetry.local_config:
with (dist_info / "entry_points.txt").open("w") as f:
with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f:
builder._write_entry_points(f)
with (dist_info / "WHEEL").open("w") as f:
with (dist_info / "WHEEL").open("w", encoding="utf-8") as f:
builder._write_wheel_file(f)
with (dist_info / "METADATA").open("w") as f:
with (dist_info / "METADATA").open("w", encoding="utf-8") as f:
builder._write_metadata_file(f)
return dist_info.name
......
......@@ -46,7 +46,7 @@ class Metadata:
meta.version = normalize_version(package.version.text)
meta.summary = package.description
if package.readme:
with package.readme.open() as f:
with package.readme.open(encoding="utf-8") as f:
meta.description = f.read()
meta.keywords = ",".join(package.keywords)
......
......@@ -391,7 +391,7 @@ class Provider:
reqs = []
requires = egg_info / "requires.txt"
if requires.exists():
with requires.open() as f:
with requires.open(encoding="utf-8") as f:
reqs = parse_requires(f.read())
finally:
os.chdir(current_dir)
......
......@@ -251,7 +251,10 @@ class Solver:
break
if previous and previous["name"] == dependency.name:
break
# We have a circular dependency.
# Since the dependencies are resolved we can
# simply skip it because we already have it
continue
for pkg in packages:
if pkg.name == dependency.name and dependency.constraint.allows(
......
......@@ -549,7 +549,7 @@ class PyPiRepository(Repository):
requires = egg_info / "requires.txt"
if requires.exists():
with requires.open() as f:
with requires.open(encoding="utf-8") as f:
info["requires_dist"] = parse_requires(f.read())
return info
......@@ -561,7 +561,7 @@ class PyPiRepository(Repository):
requires = egg_info / "requires.txt"
if requires.exists():
with requires.open() as f:
with requires.open(encoding="utf-8") as f:
info["requires_dist"] = parse_requires(f.read())
return info
......
import json
import os
from io import open
from .license import License
from .updater import Updater
......@@ -26,7 +28,7 @@ def load_licenses():
licenses_file = os.path.join(os.path.dirname(__file__), "data", "licenses.json")
with open(licenses_file) as f:
with open(licenses_file, encoding="utf-8") as f:
data = json.loads(f.read())
for name, license in data.items():
......
import json
import os
from io import open
try:
from urllib.request import urlopen
except ImportError:
......@@ -20,7 +22,7 @@ class Updater:
licenses_url = self._base_url + "licenses.json"
with open(file, "w") as f:
with open(file, "w", encoding="utf-8") as f:
f.write(
json.dumps(self.get_licenses(licenses_url), indent=2, sort_keys=True)
)
......
......@@ -46,6 +46,13 @@ if PY35:
else:
from pathlib2 import Path
if PY35:
import subprocess as subprocess
from subprocess import CalledProcessError
else:
import subprocess32 as subprocess
from subprocess32 import CalledProcessError
if not PY36:
from collections import OrderedDict
......
......@@ -5,7 +5,6 @@ import os
import platform
import re
import shutil
import subprocess
import sys
import sysconfig
import warnings
......@@ -13,7 +12,6 @@ import warnings
import tomlkit
from contextlib import contextmanager
from subprocess import CalledProcessError
from typing import Any
from typing import Dict
from typing import List
......@@ -25,10 +23,12 @@ from clikit.api.io import IO
from poetry.config import Config
from poetry.locations import CACHE_DIR
from poetry.semver.version import Version
from poetry.utils._compat import CalledProcessError
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command
from poetry.utils._compat import subprocess
from poetry.utils.toml_file import TomlFile
from poetry.version.markers import BaseMarker
......@@ -116,11 +116,14 @@ class EnvError(Exception):
class EnvCommandError(EnvError):
def __init__(self, e): # type: (CalledProcessError) -> None
message = "Command {} errored with the following output: \n{}".format(
e.cmd, decode(e.output)
)
def __init__(self, e, input=None): # type: (CalledProcessError) -> None
self.e = e
message = "Command {} errored with the following return code {}, and output: \n{}".format(
e.cmd, e.returncode, decode(e.output)
)
if input:
message += "input was : {}".format(input)
super(EnvCommandError, self).__init__(message)
......@@ -266,7 +269,7 @@ class EnvManager(object):
if not in_venv or env is not None:
# Checking if a local virtualenv exists
if (cwd / ".venv").exists():
if (cwd / ".venv").exists() and (cwd / ".venv").is_dir():
venv = cwd / ".venv"
return VirtualEnv(venv)
......@@ -682,20 +685,19 @@ class Env(object):
if shell:
cmd = list_to_shell_command(cmd)
try:
if self._is_windows:
kwargs["shell"] = True
if input_:
p = subprocess.Popen(
output = subprocess.run(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
input=encode(input_),
check=True,
**kwargs
)
output = p.communicate(encode(input_))[0]
).stdout
elif call:
return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs)
else:
......@@ -703,7 +705,7 @@ class Env(object):
cmd, stderr=subprocess.STDOUT, **kwargs
)
except CalledProcessError as e:
raise EnvCommandError(e)
raise EnvCommandError(e, input=input_)
return decode(output)
......
......@@ -51,6 +51,8 @@ keyring = [
{ version = "^18.0", python = "~2.7 || ~3.4" },
{ version = "^19.0", python = "^3.5" }
]
# Use subprocess32 for Python 2.7 and 3.4
subprocess32 = { version = "^3.5", python = "~2.7 || ~3.4" }
[tool.poetry.dev-dependencies]
pytest = "^4.1"
......
......@@ -172,7 +172,7 @@ def project_directory():
def poetry(repo, project_directory):
p = Poetry.create(Path(__file__).parent.parent / "fixtures" / project_directory)
with p.file.path.open() as f:
with p.file.path.open(encoding="utf-8") as f:
content = f.read()
p.pool.remove_repository("pypi")
......@@ -180,7 +180,7 @@ def poetry(repo, project_directory):
yield p
with p.file.path.open("w") as f:
with p.file.path.open("w", encoding="utf-8") as f:
f.write(content)
......
......@@ -120,11 +120,11 @@ My Package
assert (dist_info / "WHEEL").exists()
assert (dist_info / "METADATA").exists()
with (dist_info / "entry_points.txt").open() as f:
with (dist_info / "entry_points.txt").open(encoding="utf-8") as f:
assert entry_points == decode(f.read())
with (dist_info / "WHEEL").open() as f:
with (dist_info / "WHEEL").open(encoding="utf-8") as f:
assert wheel_data == decode(f.read())
with (dist_info / "METADATA").open() as f:
with (dist_info / "METADATA").open(encoding="utf-8") as f:
assert metadata == decode(f.read())
......@@ -725,20 +725,27 @@ def test_solver_circular_dependency(solver, repo, package):
package_b = get_package("B", "1.0")
package_b.add_dependency("A", "^1.0")
package_b.add_dependency("C", "^1.0")
package_c = get_package("C", "1.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_c},
{"job": "install", "package": package_b},
{"job": "install", "package": package_a},
],
)
assert "main" == ops[0].package.category
def test_solver_duplicate_dependencies_same_constraint(solver, repo, package):
package.add_dependency("A")
......
......@@ -31,7 +31,7 @@ class MockRepository(LegacyRepository):
if not fixture.exists():
return
with fixture.open() as f:
with fixture.open(encoding="utf-8") as f:
return Page(self._url + endpoint, f.read(), {})
def _download(self, url, dest):
......
......@@ -36,7 +36,7 @@ class MockRepository(PyPiRepository):
if not fixture.exists():
return
with fixture.open() as f:
with fixture.open(encoding="utf-8") as f:
return json.loads(f.read())
def _download(self, url, dest):
......
import os
import pytest
import shutil
import sys
import tomlkit
from clikit.io import NullIO
from poetry.semver import Version
from poetry.utils._compat import Path
from poetry.utils.env import EnvManager
from poetry.utils.env import EnvCommandError
from poetry.utils.env import VirtualEnv
from poetry.utils.toml_file import TomlFile
MINIMAL_SCRIPT = """\
print("Minimal Output"),
"""
# Script expected to fail.
ERRORING_SCRIPT = """\
import nullpackage
print("nullpackage loaded"),
"""
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir, config):
venv_path = Path(tmp_dir) / "Virtual Env"
......@@ -30,6 +45,7 @@ def test_env_get_in_project_venv(tmp_dir, config):
assert venv.path == Path(tmp_dir) / ".venv"
shutil.rmtree(str(venv.path))
CWD = Path(__file__).parent.parent / "fixtures" / "simple_project"
......@@ -452,13 +468,19 @@ def test_remove_also_deactivates(tmp_dir, config, mocker):
assert venv_name not in envs
def test_env_has_symlinks_on_nix(tmp_dir, config):
venv_path = Path(tmp_dir)
@pytest.fixture
def tmp_venv(tmp_dir, config, request):
venv_path = Path(tmp_dir) / "venv"
EnvManager(config).build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
yield venv
shutil.rmtree(str(venv.path))
def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv):
venv_available = False
try:
from venv import EnvBuilder
......@@ -468,4 +490,20 @@ def test_env_has_symlinks_on_nix(tmp_dir, config):
pass
if os.name != "nt" and venv_available:
assert os.path.islink(venv.python)
assert os.path.islink(tmp_venv.python)
def test_run_with_input(tmp_dir, tmp_venv):
result = tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT)
assert result == "Minimal Output" + os.linesep
def test_run_with_input_non_zero_return(tmp_dir, tmp_venv):
with pytest.raises(EnvCommandError) as processError:
# Test command that will return non-zero returncode.
result = tmp_venv.run("python", "-", input_=ERRORING_SCRIPT)
assert processError.value.e.returncode == 1
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