Commit c7055be9 by Sébastien Eustace Committed by GitHub

New and faster installer implementation (#2595)

* Improve the way packages are installed

* Add support for parallelized operations

* Improve the Chooser class

* Make the new installer the default

* Add an Authenticator class to be able to download from protected urls

* Adapt code and tests to latest changes

* Update lock file and some dependencies

* Improve installations information and caching

* Make the final adjustments and tests for the new installer

* Use the preview version of Poetry for the CI

* Rename Executor.run() to Executor.run_pip()

* Gracefully handle errors when executing operations
parent d6289470
......@@ -16,7 +16,8 @@ test_task:
- pkg install -y git-lite $PYPACKAGE $SQLPACKAGE
pip_script:
- $PYTHON -m ensurepip
- $PYTHON -m pip install -U pip tox poetry
- $PYTHON -m pip install -U pip tox
- $PYTHON -m pip install -U --pre poetry
- poetry config virtualenvs.in-project true
tox_script: $PYTHON -m tox -e py -- -q --junitxml=junit.xml tests
on_failure:
......
......@@ -16,6 +16,7 @@ exclude =
.vscode
.github
poetry/utils/_compat.py
poetry/utils/env_scripts/tags.py
tests/fixtures/
tests/repositories/fixtures/
tests/utils/fixtures/
......@@ -49,7 +49,7 @@ jobs:
shell: bash
run: |
curl -fsS -o get-poetry.py https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
python get-poetry.py -y
python get-poetry.py -y --preview
echo "::set-env name=PATH::$HOME/.poetry/bin:$PATH"
- name: Configure poetry
......
......@@ -9,7 +9,6 @@ version = "1.4.4"
[[package]]
category = "dev"
description = "Atomic file writes."
marker = "python_version >= \"3.5\" and sys_platform == \"win32\" or sys_platform == \"win32\" or python_version < \"3.5\""
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
......@@ -32,7 +31,6 @@ tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.i
[[package]]
category = "dev"
description = "Backport of functools.lru_cache"
marker = "python_version < \"3.2\""
name = "backports.functools-lru-cache"
optional = false
python-versions = ">=2.6"
......@@ -45,7 +43,6 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pyt
[[package]]
category = "dev"
description = "The uncompromising code formatter."
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "black"
optional = false
python-versions = ">=3.6"
......@@ -74,10 +71,7 @@ version = "0.12.6"
[package.dependencies]
msgpack = ">=0.5.2"
requests = "*"
[package.dependencies.lockfile]
optional = true
version = ">=0.9"
lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""}
[package.extras]
filecache = ["lockfile (>=0.9)"]
......@@ -107,7 +101,6 @@ version = "2020.6.20"
[[package]]
category = "main"
description = "Foreign Function Interface for Python calling C code."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"3.6\" and sys_platform == \"linux\" or python_version >= \"3.6\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "cffi"
optional = false
python-versions = "*"
......@@ -119,11 +112,10 @@ pycparser = "*"
[[package]]
category = "dev"
description = "Validate configuration and produce human readable error messages."
marker = "python_version >= \"3.6.1\" and python_version < \"4.0.0\""
name = "cfgv"
optional = false
python-versions = ">=3.6"
version = "3.0.0"
python-versions = ">=3.6.1"
version = "3.1.0"
[[package]]
category = "main"
......@@ -147,7 +139,6 @@ clikit = ">=0.6.0,<0.7.0"
[[package]]
category = "dev"
description = "Composable command line interface toolkit"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\" or python_version >= \"3.6\" and python_version < \"4.0\""
name = "click"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
......@@ -164,27 +155,14 @@ version = "0.6.2"
[package.dependencies]
pastel = ">=0.2.0,<0.3.0"
pylev = ">=1.3,<2.0"
[package.dependencies.crashtest]
python = ">=3.6,<4.0"
version = ">=0.3.0,<0.4.0"
[package.dependencies.enum34]
python = ">=2.7,<2.8"
version = ">=1.1,<2.0"
[package.dependencies.typing]
python = ">=2.7,<2.8 || >=3.4,<3.5"
version = ">=3.6,<4.0"
[package.dependencies.typing-extensions]
python = ">=3.5,<3.5.4"
version = ">=3.6,<4.0"
crashtest = {version = ">=0.3.0,<0.4.0", markers = "python_version >= \"3.6\" and python_version < \"4.0\""}
enum34 = {version = ">=1.1,<2.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
typing = {version = ">=3.6,<4.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""}
typing-extensions = {version = ">=3.6,<4.0", markers = "python_version >= \"3.5\" and python_full_version < \"3.5.4\""}
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\" and python_version != \"3.4\" and python_version < \"3.5\" or platform_system == \"Windows\" or python_version >= \"3.5\" and sys_platform == \"win32\" or sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
......@@ -193,7 +171,6 @@ version = "0.4.3"
[[package]]
category = "main"
description = "Updated configparser from Python 3.7 for Python 2.6+."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version == \"2.7\" and python_version == \"2.7\" or python_version < \"3\""
name = "configparser"
optional = false
python-versions = ">=2.6"
......@@ -206,7 +183,6 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytes
[[package]]
category = "main"
description = "Backports and enhancements for the contextlib module"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.4\""
name = "contextlib2"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
......@@ -226,7 +202,6 @@ toml = ["toml"]
[[package]]
category = "main"
description = "Manage Python errors with ease"
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "crashtest"
optional = false
python-versions = ">=3.6,<4.0"
......@@ -235,7 +210,6 @@ version = "0.3.0"
[[package]]
category = "main"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"3.6\" and sys_platform == \"linux\" or python_version >= \"3.6\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "cryptography"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
......@@ -244,14 +218,8 @@ version = "2.9.2"
[package.dependencies]
cffi = ">=1.8,<1.11.3 || >1.11.3"
six = ">=1.4.1"
[package.dependencies.enum34]
python = "<3"
version = "*"
[package.dependencies.ipaddress]
python = "<3"
version = "*"
enum34 = {version = "*", markers = "python_version < \"3\""}
ipaddress = {version = "*", markers = "python_version < \"3\""}
[package.extras]
docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"]
......@@ -266,29 +234,22 @@ description = "Distribution utilities"
name = "distlib"
optional = false
python-versions = "*"
version = "0.3.0"
[package.dependencies]
nut = "*"
version = "0.3.1"
[[package]]
category = "main"
description = "Discover and load entry points from installed packages."
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "entrypoints"
optional = false
python-versions = ">=2.7"
version = "0.3"
[package.dependencies]
[package.dependencies.configparser]
python = ">=2.7,<2.8"
version = ">=3.5"
configparser = {version = ">=3.5", markers = "python_version == \"2.7\""}
[[package]]
category = "main"
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\")"
name = "enum34"
optional = false
python-versions = "*"
......@@ -305,7 +266,6 @@ version = "3.0.12"
[[package]]
category = "dev"
description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
marker = "python_version < \"3.0\""
name = "funcsigs"
optional = false
python-versions = "*"
......@@ -314,25 +274,22 @@ version = "1.0.2"
[[package]]
category = "main"
description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy."
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "functools32"
optional = false
python-versions = "*"
version = "3.2.3-2"
[[package]]
category = "dev"
description = "Clean single-source support for Python 3 and 2"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "future"
category = "main"
description = "Backport of the concurrent.futures package from Python 3"
name = "futures"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "0.18.2"
python-versions = ">=2.6, <3"
version = "3.3.0"
[[package]]
category = "main"
description = "Version of the glob module that can capture patterns and supports recursive wildcards"
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "glob2"
optional = false
python-versions = "*"
......@@ -370,11 +327,10 @@ six = "*"
[[package]]
category = "dev"
description = "File identification library for Python"
marker = "python_version >= \"3.6.1\" and python_version < \"4.0.0\""
name = "identify"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "1.4.20"
version = "1.4.21"
[package.extras]
license = ["editdistance"]
......@@ -385,31 +341,21 @@ description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.9"
version = "2.10"
[[package]]
category = "main"
description = "Read metadata from Python packages"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.6\" or python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"3.8\" or python_version >= \"3.6\" and python_version < \"3.8\""
name = "importlib-metadata"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.6.1"
version = "1.7.0"
[package.dependencies]
zipp = ">=0.5"
[package.dependencies.configparser]
python = "<3"
version = ">=3.5"
[package.dependencies.contextlib2]
python = "<3"
version = "*"
[package.dependencies.pathlib2]
python = "<3"
version = "*"
configparser = {version = ">=3.5", markers = "python_version < \"3\""}
contextlib2 = {version = "*", markers = "python_version < \"3\""}
pathlib2 = {version = "*", markers = "python_version < \"3\""}
[package.extras]
docs = ["sphinx", "rst.linker"]
......@@ -418,36 +364,17 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]]
category = "main"
description = "Read resources from Python packages"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.7\" or python_version >= \"3.6.1\" and python_version < \"3.7\""
name = "importlib-resources"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "2.0.1"
version = "3.0.0"
[package.dependencies]
[package.dependencies.contextlib2]
python = "<3"
version = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.dependencies.pathlib2]
python = "<3"
version = "*"
[package.dependencies.singledispatch]
python = "<3.4"
version = "*"
[package.dependencies.typing]
python = "<3.5"
version = "*"
[package.dependencies.zipp]
python = "<3.8"
version = ">=0.4"
contextlib2 = {version = "*", markers = "python_version < \"3\""}
pathlib2 = {version = "*", markers = "python_version < \"3\""}
singledispatch = {version = "*", markers = "python_version < \"3.4\""}
typing = {version = "*", markers = "python_version < \"3.5\""}
zipp = {version = ">=0.4", markers = "python_version < \"3.8\""}
[package.extras]
docs = ["sphinx", "rst.linker", "jaraco.packaging"]
......@@ -455,7 +382,6 @@ docs = ["sphinx", "rst.linker", "jaraco.packaging"]
[[package]]
category = "main"
description = "IPv4/IPv6 manipulation library"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\")"
name = "ipaddress"
optional = false
python-versions = "*"
......@@ -464,7 +390,6 @@ version = "1.0.23"
[[package]]
category = "main"
description = "Low-level, pure Python DBus protocol wrapper."
marker = "python_version >= \"3.5\" and python_version < \"3.6\" and sys_platform == \"linux\" or python_version >= \"3.6\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "jeepney"
optional = false
python-versions = ">=3.5"
......@@ -476,7 +401,6 @@ dev = ["testpath"]
[[package]]
category = "dev"
description = "A very fast and expressive template engine."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "jinja2"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
......@@ -489,27 +413,8 @@ MarkupSafe = ">=0.23"
i18n = ["Babel (>=0.8)"]
[[package]]
category = "dev"
description = "Lightweight pipelining: using Python functions as pipeline jobs."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "joblib"
optional = false
python-versions = "*"
version = "0.14.1"
[[package]]
category = "dev"
description = "Lightweight pipelining: using Python functions as pipeline jobs."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "joblib"
optional = false
python-versions = ">=3.6"
version = "0.15.1"
[[package]]
category = "main"
description = "Store and access your passwords safely."
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "keyring"
optional = false
python-versions = ">=2.7"
......@@ -517,11 +422,8 @@ version = "18.0.1"
[package.dependencies]
entrypoints = "*"
pywin32-ctypes = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1"
[package.dependencies.secretstorage]
python = "<3.5"
version = "<3"
pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""}
secretstorage = {version = "<3", markers = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
......@@ -530,19 +432,15 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs", "pytest-flake8
[[package]]
category = "main"
description = "Store and access your passwords safely."
marker = "python_version >= \"3.5\" and python_version < \"3.6\""
name = "keyring"
optional = false
python-versions = ">=3.5"
version = "20.0.1"
[package.dependencies]
pywin32-ctypes = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1"
secretstorage = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""}
secretstorage = {version = "*", markers = "sys_platform == \"linux\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
......@@ -551,20 +449,16 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pyt
[[package]]
category = "main"
description = "Store and access your passwords safely."
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "keyring"
optional = false
python-versions = ">=3.6"
version = "21.2.1"
[package.dependencies]
SecretStorage = ">=3"
jeepney = ">=0.4.2"
pywin32-ctypes = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
SecretStorage = {version = ">=3", markers = "sys_platform == \"linux\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""}
pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
......@@ -573,7 +467,6 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pyt
[[package]]
category = "dev"
description = "Python LiveReload is an awesome tool for web developers"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "livereload"
optional = false
python-versions = "*"
......@@ -582,8 +475,12 @@ version = "2.6.2"
[package.dependencies]
six = "*"
[package.dependencies.tornado]
python = ">=2.8"
[[package.dependencies.tornado]]
markers = "python_version == \"2.7\""
version = "<6"
[[package.dependencies.tornado]]
markers = "python_version > \"2.7\""
version = "*"
[[package]]
......@@ -596,52 +493,12 @@ version = "0.12.2"
[[package]]
category = "dev"
description = "A Python implementation of Lunr.js"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "lunr"
optional = false
python-versions = "*"
version = "0.5.8"
[package.dependencies]
future = ">=0.16.0"
six = ">=1.11.0"
[package.dependencies.nltk]
optional = true
python = ">=2.8"
version = ">=3.2.5"
[package.extras]
languages = ["nltk (>=3.2.5,<3.5)", "nltk (>=3.2.5)"]
[[package]]
category = "dev"
description = "Python implementation of Markdown."
name = "markdown"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "3.1.1"
[package.dependencies]
setuptools = ">=36"
[package.extras]
testing = ["coverage", "pyyaml"]
[[package]]
category = "dev"
description = "Python implementation of Markdown."
name = "markdown"
optional = false
python-versions = ">=3.5"
version = "3.2.2"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.extras]
testing = ["coverage", "pyyaml"]
......@@ -659,7 +516,6 @@ markdown = "*"
[[package]]
category = "dev"
description = "Safely add untrusted strings to HTML/XML markup."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "markupsafe"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
......@@ -668,7 +524,6 @@ version = "1.1.1"
[[package]]
category = "dev"
description = "Project documentation with Markdown."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "mkdocs"
optional = false
python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
......@@ -684,29 +539,7 @@ tornado = ">=5.0"
[[package]]
category = "dev"
description = "Project documentation with Markdown."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "mkdocs"
optional = false
python-versions = ">=3.5"
version = "1.1.2"
[package.dependencies]
Jinja2 = ">=2.10.1"
Markdown = ">=3.2.1"
PyYAML = ">=3.10"
click = ">=3.3"
livereload = ">=2.5.1"
tornado = ">=5.0"
[package.dependencies.lunr]
extras = ["languages"]
version = "0.5.8"
[[package]]
category = "dev"
description = "Rolling backport of unittest.mock for all Pythons"
marker = "python_version < \"3.0\""
name = "mock"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
......@@ -714,10 +547,7 @@ version = "3.0.5"
[package.dependencies]
six = "*"
[package.dependencies.funcsigs]
python = "<3.3"
version = ">=1"
funcsigs = {version = ">=1", markers = "python_version < \"3.3\""}
[package.extras]
build = ["twine", "wheel", "blurb"]
......@@ -727,7 +557,6 @@ test = ["pytest", "pytest-cov"]
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
marker = "python_version <= \"2.7\""
name = "more-itertools"
optional = false
python-versions = "*"
......@@ -754,31 +583,7 @@ version = "1.0.0"
[[package]]
category = "dev"
description = "Natural Language Toolkit"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "nltk"
optional = false
python-versions = "*"
version = "3.5"
[package.dependencies]
click = "*"
joblib = "*"
regex = "*"
tqdm = "*"
[package.extras]
all = ["requests", "numpy", "python-crfsuite", "scikit-learn", "twython", "pyparsing", "scipy", "matplotlib", "gensim"]
corenlp = ["requests"]
machine_learning = ["gensim", "numpy", "python-crfsuite", "scikit-learn", "scipy"]
plot = ["matplotlib"]
tgrep = ["pyparsing"]
twitter = ["twython"]
[[package]]
category = "dev"
description = "Node.js virtual environment builder"
marker = "python_version >= \"3.6.1\" and python_version < \"4.0.0\""
name = "nodeenv"
optional = false
python-versions = "*"
......@@ -786,14 +591,6 @@ version = "1.4.0"
[[package]]
category = "main"
description = "Network utility... things like a UDP/TCP relay."
name = "nut"
optional = false
python-versions = "*"
version = "0.2.0"
[[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false
......@@ -815,7 +612,6 @@ version = "0.2.0"
[[package]]
category = "main"
description = "Object-oriented filesystem paths"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform != \"win32\" or python_version < \"3.5\" or python_version >= \"3.5\" and python_version < \"3.6\""
name = "pathlib2"
optional = false
python-versions = "*"
......@@ -823,15 +619,11 @@ version = "2.3.5"
[package.dependencies]
six = "*"
[package.dependencies.scandir]
python = "<3.5"
version = "*"
scandir = {version = "*", markers = "python_version < \"3.5\""}
[[package]]
category = "dev"
description = "Utility library for gitignore style pattern matching of file paths."
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "pathspec"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
......@@ -876,9 +668,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
......@@ -892,30 +682,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.0.0a8"
[package.dependencies]
[package.dependencies.enum34]
python = ">=2.7,<2.8"
version = ">=1.1.10,<2.0.0"
[package.dependencies.functools32]
python = ">=2.7,<2.8"
version = ">=3.2.3-2,<4.0.0"
[package.dependencies.pathlib2]
python = ">=2.7,<2.8"
version = ">=2.3.5,<3.0.0"
[package.dependencies.typing]
python = ">=2.7,<2.8"
version = ">=3.7.4.1,<4.0.0.0"
enum34 = {version = ">=1.1.10,<2.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
functools32 = {version = ">=3.2.3-2,<4.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
pathlib2 = {version = ">=2.3.5,<3.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
typing = {version = ">=3.7.4.1,<4.0.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
[[package]]
category = "dev"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
marker = "python_version >= \"3.6.1\" and python_version < \"4.0.0\""
name = "pre-commit"
optional = false
python-versions = ">=3.6.1"
version = "2.5.1"
version = "2.6.0"
[package.dependencies]
cfgv = ">=2.0.0"
......@@ -924,14 +702,8 @@ nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
toml = "*"
virtualenv = ">=20.0.8"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.dependencies.importlib-resources]
python = "<3.7"
version = "*"
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
importlib-resources = {version = "*", markers = "python_version < \"3.7\""}
[[package]]
category = "main"
......@@ -952,7 +724,6 @@ version = "1.9.0"
[[package]]
category = "main"
description = "C parser in Python"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"3.6\" and sys_platform == \"linux\" or python_version >= \"3.6\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "pycparser"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
......@@ -968,14 +739,6 @@ version = "2.5.2"
[[package]]
category = "dev"
description = "Pygments is a syntax highlighting package written in Python."
name = "pygments"
optional = false
python-versions = ">=3.5"
version = "2.6.1"
[[package]]
category = "dev"
description = "Pygments Github custom lexers."
name = "pygments-github-lexers"
optional = false
......@@ -1006,18 +769,7 @@ Markdown = ">=3.0.1"
pep562 = "*"
[[package]]
category = "dev"
description = "Extension pack for Python Markdown."
name = "pymdown-extensions"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "6.3"
[package.dependencies]
Markdown = ">=3.2"
[[package]]
category = "dev"
category = "main"
description = "Python parsing module"
name = "pyparsing"
optional = false
......@@ -1040,26 +792,18 @@ pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
six = ">=1.10.0"
wcwidth = "*"
colorama = {version = "*", markers = "sys_platform == \"win32\" and python_version != \"3.4\""}
funcsigs = {version = ">=1.0", markers = "python_version < \"3.0\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""}
[package.dependencies.colorama]
python = "<3.4.0 || >=3.5.0"
version = "*"
[package.dependencies.funcsigs]
python = "<3.0"
version = ">=1.0"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.dependencies.more-itertools]
python = "<2.8"
[[package.dependencies.more-itertools]]
markers = "python_version <= \"2.7\""
version = ">=4.0.0,<6.0.0"
[package.dependencies.pathlib2]
python = "<3.6"
version = ">=2.2.0"
[[package.dependencies.more-itertools]]
markers = "python_version > \"2.7\""
version = ">=4.0.0"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"]
......@@ -1073,22 +817,16 @@ python-versions = ">=3.5"
version = "5.4.3"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.dependencies.pathlib2]
python = "<3.6"
version = ">=2.2.0"
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""}
[package.extras]
checkqa-mypy = ["mypy (v0.761)"]
......@@ -1119,10 +857,7 @@ version = "1.13.0"
[package.dependencies]
pytest = ">=2.7"
[package.dependencies.mock]
python = "<3.0"
version = "*"
mock = {version = "*", markers = "python_version < \"3.0\""}
[package.extras]
dev = ["pre-commit", "tox"]
......@@ -1143,7 +878,6 @@ termcolor = ">=1.1.0"
[[package]]
category = "main"
description = ""
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform == \"win32\" or python_version >= \"3.5\" and python_version < \"3.6\" and sys_platform == \"win32\" or python_version >= \"3.6\" and python_version < \"4.0\" and sys_platform == \"win32\""
name = "pywin32-ctypes"
optional = false
python-versions = "*"
......@@ -1152,7 +886,6 @@ version = "0.2.0"
[[package]]
category = "dev"
description = "YAML parser and emitter for Python"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\" or python_version >= \"3.6.1\" and python_version < \"4.0.0\""
name = "pyyaml"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
......@@ -1161,7 +894,6 @@ version = "5.3.1"
[[package]]
category = "dev"
description = "Alternative regular expression module, to replace re."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\" or python_version >= \"3.6\" and python_version < \"4.0\""
name = "regex"
optional = false
python-versions = "*"
......@@ -1199,7 +931,6 @@ requests = ">=2.0.1,<3.0.0"
[[package]]
category = "main"
description = "scandir, a better directory iterator and faster os.walk()"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform != \"win32\""
name = "scandir"
optional = false
python-versions = "*"
......@@ -1208,7 +939,6 @@ version = "1.10.0"
[[package]]
category = "main"
description = "Python bindings to FreeDesktop.org Secret Service API"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\")"
name = "secretstorage"
optional = false
python-versions = "*"
......@@ -1223,7 +953,6 @@ dbus-python = ["dbus-python"]
[[package]]
category = "main"
description = "Python bindings to FreeDesktop.org Secret Service API"
marker = "python_version >= \"3.5\" and python_version < \"3.6\" and sys_platform == \"linux\" or python_version >= \"3.6\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "secretstorage"
optional = false
python-versions = ">=3.5"
......@@ -1244,7 +973,6 @@ version = "1.3.2"
[[package]]
category = "main"
description = "This library brings functools.singledispatch from Python 3.4 to Python 2.6-3.3."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.4\" or python_version >= \"2.7\" and python_version < \"2.8\" and (python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.4\")"
name = "singledispatch"
optional = false
python-versions = "*"
......@@ -1264,7 +992,6 @@ version = "1.15.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\""
name = "subprocess32"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
......@@ -1295,22 +1022,13 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.5.11"
[package.dependencies]
[package.dependencies.enum34]
python = ">=2.7,<2.8"
version = ">=1.1,<2.0"
[package.dependencies.functools32]
python = ">=2.7,<2.8"
version = ">=3.2.3,<4.0.0"
[package.dependencies.typing]
python = ">=2.7,<2.8 || >=3.4,<3.5"
version = ">=3.6,<4.0"
enum34 = {version = ">=1.1,<2.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
functools32 = {version = ">=3.2.3,<4.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""}
typing = {version = ">=3.6,<4.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""}
[[package]]
category = "dev"
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "tornado"
optional = false
python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*"
......@@ -1322,10 +1040,9 @@ description = "tox is a generic virtualenv management and test command line tool
name = "tox"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "3.15.2"
version = "3.16.1"
[package.dependencies]
colorama = ">=0.4.1"
filelock = ">=3.0.0"
packaging = ">=14"
pluggy = ">=0.12.0"
......@@ -1333,10 +1050,8 @@ py = ">=1.4.17"
six = ">=1.14.0"
toml = ">=0.9.4"
virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12,<2"
colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""}
[package.extras]
docs = ["sphinx (>=2.0.0)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"]
......@@ -1344,20 +1059,7 @@ testing = ["freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "pytest (>=4.0.0)", "py
[[package]]
category = "dev"
description = "Fast, Extensible Progress Meter"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "tqdm"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
version = "4.46.1"
[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "typed-ast"
optional = false
python-versions = "*"
......@@ -1366,7 +1068,6 @@ version = "1.4.1"
[[package]]
category = "main"
description = "Type Hints for Python"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version >= \"2.7\" and python_version < \"2.8\" and (python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.5\") or python_version < \"3.5\""
name = "typing"
optional = false
python-versions = "*"
......@@ -1375,7 +1076,6 @@ version = "3.7.4.1"
[[package]]
category = "main"
description = "Backported and Experimental Type Hints for Python 3.5+"
marker = "python_version >= \"3.5\" and python_full_version < \"3.5.4\""
name = "typing-extensions"
optional = false
python-versions = "*"
......@@ -1407,18 +1107,9 @@ appdirs = ">=1.4.3,<2"
distlib = ">=0.3.0,<1"
filelock = ">=3.0.0,<4"
six = ">=1.9.0,<2"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12,<2"
[package.dependencies.importlib-resources]
python = "<3.7"
version = ">=1.0"
[package.dependencies.pathlib2]
python = "<3.4"
version = ">=2.3.3,<3"
importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""}
importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""}
pathlib2 = {version = ">=2.3.3,<3", markers = "python_version < \"3.4\" and sys_platform != \"win32\""}
[package.extras]
docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"]
......@@ -1433,9 +1124,7 @@ python-versions = "*"
version = "0.2.5"
[package.dependencies]
[package.dependencies."backports.functools-lru-cache"]
python = "<3.2"
version = ">=1.2.1"
"backports.functools-lru-cache" = {version = ">=1.2.1", markers = "python_version < \"3.2\""}
[[package]]
category = "main"
......@@ -1448,36 +1137,20 @@ version = "0.5.1"
[[package]]
category = "main"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.6\" or python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=2.7"
version = "1.2.0"
[package.dependencies]
[package.dependencies.contextlib2]
python = "<3.4"
version = "*"
contextlib2 = {version = "*", markers = "python_version < \"3.4\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"]
[[package]]
category = "main"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.6\" and python_version < \"3.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=3.6"
version = "3.1.0"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
content-hash = "78c1880171165781164556d08eb2cdd58ed2c708b73dfa6a92acd2dd1df3739c"
content-hash = "363cbb8480f9bbda72b318c6edfcaa1de130ceecba7c7d0e774005a3bca91df5"
python-versions = "~2.7 || ^3.5"
[metadata.files]
......@@ -1544,8 +1217,8 @@ cffi = [
{file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"},
]
cfgv = [
{file = "cfgv-3.0.0-py2.py3-none-any.whl", hash = "sha256:f22b426ed59cd2ab2b54ff96608d846c33dfb8766a67f0b4a6ce130ce244414f"},
{file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"},
{file = "cfgv-3.1.0-py2.py3-none-any.whl", hash = "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53"},
{file = "cfgv-3.1.0.tar.gz", hash = "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"},
]
chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
......@@ -1634,7 +1307,8 @@ cryptography = [
{file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"},
]
distlib = [
{file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"},
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
]
entrypoints = [
{file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
......@@ -1657,8 +1331,9 @@ functools32 = [
{file = "functools32-3.2.3-2.tar.gz", hash = "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"},
{file = "functools32-3.2.3-2.zip", hash = "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0"},
]
future = [
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
futures = [
{file = "futures-3.3.0-py2-none-any.whl", hash = "sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16"},
{file = "futures-3.3.0.tar.gz", hash = "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"},
]
glob2 = [
{file = "glob2-0.6.tar.gz", hash = "sha256:f5b0a686ff21f820c4d3f0c4edd216704cea59d79d00fa337e244a2f2ff83ed6"},
......@@ -1671,20 +1346,20 @@ httpretty = [
{file = "httpretty-0.9.7.tar.gz", hash = "sha256:66216f26b9d2c52e81808f3e674a6fb65d4bf719721394a1a9be926177e55fbe"},
]
identify = [
{file = "identify-1.4.20-py2.py3-none-any.whl", hash = "sha256:acf0712ab4042642e8f44e9532d95c26fbe60c0ab8b6e5b654dd1bc6512810e0"},
{file = "identify-1.4.20.tar.gz", hash = "sha256:b2cd24dece806707e0b50517c1b3bcf3044e0b1cb13a72e7d34aa31c91f2a55a"},
{file = "identify-1.4.21-py2.py3-none-any.whl", hash = "sha256:dac33eff90d57164e289fb20bf4e131baef080947ee9bf45efcd0da8d19064bf"},
{file = "identify-1.4.21.tar.gz", hash = "sha256:c4d07f2b979e3931894170a9e0d4b8281e6905ea6d018c326f7ffefaf20db680"},
]
idna = [
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
]
importlib-metadata = [
{file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"},
{file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"},
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
]
importlib-resources = [
{file = "importlib_resources-2.0.1-py2.py3-none-any.whl", hash = "sha256:83985739b3a6679702f9ab33f0ad016ad564664d0568a31ac14d7c64789453e6"},
{file = "importlib_resources-2.0.1.tar.gz", hash = "sha256:f5edfcece1cc9435d0979c19e08739521f4cf1aa1adaf6e571f732df6f568962"},
{file = "importlib_resources-3.0.0-py2.py3-none-any.whl", hash = "sha256:d028f66b66c0d5732dae86ba4276999855e162a749c92620a38c1d779ed138a7"},
{file = "importlib_resources-3.0.0.tar.gz", hash = "sha256:19f745a6eca188b490b1428c8d1d4a0d2368759f32370ea8fb89cad2ab1106c3"},
]
ipaddress = [
{file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"},
......@@ -1698,12 +1373,6 @@ jinja2 = [
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
]
joblib = [
{file = "joblib-0.14.1-py2.py3-none-any.whl", hash = "sha256:bdb4fd9b72915ffb49fde2229ce482dd7ae79d842ed8c2b4c932441495af1403"},
{file = "joblib-0.14.1.tar.gz", hash = "sha256:0630eea4f5664c463f23fbf5dcfc54a2bc6168902719fa8e19daf033022786c8"},
{file = "joblib-0.15.1-py3-none-any.whl", hash = "sha256:6825784ffda353cc8a1be573118085789e5b5d29401856b35b756645ab5aecb5"},
{file = "joblib-0.15.1.tar.gz", hash = "sha256:61e49189c84b3c5d99a969d314853f4d1d263316cc694bec17548ebaa9c47b6e"},
]
keyring = [
{file = "keyring-18.0.1-py2.py3-none-any.whl", hash = "sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6"},
{file = "keyring-18.0.1.tar.gz", hash = "sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838"},
......@@ -1719,15 +1388,9 @@ lockfile = [
{file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"},
{file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"},
]
lunr = [
{file = "lunr-0.5.8-py2.py3-none-any.whl", hash = "sha256:aab3f489c4d4fab4c1294a257a30fec397db56f0a50273218ccc3efdbf01d6ca"},
{file = "lunr-0.5.8.tar.gz", hash = "sha256:c4fb063b98eff775dd638b3df380008ae85e6cb1d1a24d1cd81a10ef6391c26e"},
]
markdown = [
{file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"},
{file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"},
{file = "Markdown-3.2.2-py3-none-any.whl", hash = "sha256:c467cd6233885534bf0fe96e62e3cf46cfc1605112356c4f9981512b8174de59"},
{file = "Markdown-3.2.2.tar.gz", hash = "sha256:1fafe3f1ecabfb514a5285fca634a53c1b32a81cb0feb154264d55bf2ff22c17"},
]
markdown-include = [
{file = "markdown-include-0.5.1.tar.gz", hash = "sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"},
......@@ -1760,18 +1423,11 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
mkdocs = [
{file = "mkdocs-1.0.4-py2.py3-none-any.whl", hash = "sha256:8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"},
{file = "mkdocs-1.0.4.tar.gz", hash = "sha256:17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939"},
{file = "mkdocs-1.1.2-py3-none-any.whl", hash = "sha256:096f52ff52c02c7e90332d2e53da862fde5c062086e1b5356a6e392d5d60f5e9"},
{file = "mkdocs-1.1.2.tar.gz", hash = "sha256:f0b61e5402b99d7789efa032c7a74c90a20220a9c81749da06dbfbcbd52ffb39"},
]
mock = [
{file = "mock-3.0.5-py2.py3-none-any.whl", hash = "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8"},
......@@ -1804,15 +1460,9 @@ msgpack = [
{file = "msgpack-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:39c54fdebf5fa4dda733369012c59e7d085ebdfe35b6cf648f09d16708f1be5d"},
{file = "msgpack-1.0.0.tar.gz", hash = "sha256:9534d5cc480d4aff720233411a1f765be90885750b07df772380b34c10ecb5c0"},
]
nltk = [
{file = "nltk-3.5.zip", hash = "sha256:845365449cd8c5f9731f7cb9f8bd6fd0767553b9d53af9eb1b3abf7700936b35"},
]
nodeenv = [
{file = "nodeenv-1.4.0-py2.py3-none-any.whl", hash = "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"},
]
nut = [
{file = "nut-0.2.0.tar.gz", hash = "sha256:427bd08d2b47d0c1c8f0a76783586df8b31c3427a0800d7930f010c06ff81e85"},
]
packaging = [
{file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
......@@ -1850,8 +1500,8 @@ poetry-core = [
{file = "poetry_core-1.0.0a8-py2.py3-none-any.whl", hash = "sha256:d7d9e702fbae06c05abb1ada63678e940d746b2696f4601b951b27ac7c2c5337"},
]
pre-commit = [
{file = "pre_commit-2.5.1-py2.py3-none-any.whl", hash = "sha256:c5c8fd4d0e1c363723aaf0a8f9cba0f434c160b48c4028f4bae6d219177945b3"},
{file = "pre_commit-2.5.1.tar.gz", hash = "sha256:da463cf8f0e257f9af49047ba514f6b90dbd9b4f92f4c8847a3ccd36834874c7"},
{file = "pre_commit-2.6.0-py2.py3-none-any.whl", hash = "sha256:e8b1315c585052e729ab7e99dcca5698266bedce9067d21dc909c23e3ceed626"},
{file = "pre_commit-2.6.0.tar.gz", hash = "sha256:1657663fdd63a321a4a739915d7d03baedd555b25054449090f97bb0cb30a915"},
]
ptyprocess = [
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
......@@ -1868,8 +1518,6 @@ pycparser = [
pygments = [
{file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
{file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
{file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
{file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
]
pygments-github-lexers = [
{file = "pygments-github-lexers-0.0.5.tar.gz", hash = "sha256:aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"},
......@@ -1882,8 +1530,6 @@ pylev = [
pymdown-extensions = [
{file = "pymdown-extensions-6.2.1.tar.gz", hash = "sha256:3bbe6048275f8a0d13a0fe44e0ea201e67268aa7bb40c2544eef16abbf168f7b"},
{file = "pymdown_extensions-6.2.1-py2.py3-none-any.whl", hash = "sha256:dce5e17b93be0572322b7d06c9a13c13a9d98694d6468277911d50ca87d26f29"},
{file = "pymdown-extensions-6.3.tar.gz", hash = "sha256:cb879686a586b22292899771f5e5bc3382808e92aa938f71b550ecdea709419f"},
{file = "pymdown_extensions-6.3-py2.py3-none-any.whl", hash = "sha256:66fae2683c7a1dac53184f7de57f51f8dad73f9ead2f453e94e85096cb811335"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
......@@ -1986,7 +1632,6 @@ six = [
]
subprocess32 = [
{file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"},
{file = "subprocess32-3.5.4-cp27-cp27mu-manylinux2014_x86_64.whl", hash = "sha256:e45d985aef903c5b7444d34350b05da91a9e0ea015415ab45a21212786c649d0"},
{file = "subprocess32-3.5.4.tar.gz", hash = "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"},
]
termcolor = [
......@@ -2010,12 +1655,8 @@ tornado = [
{file = "tornado-5.1.1.tar.gz", hash = "sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409"},
]
tox = [
{file = "tox-3.15.2-py2.py3-none-any.whl", hash = "sha256:50a188b8e17580c1fb931f494a754e6507d4185f54fb18aca5ba3e12d2ffd55e"},
{file = "tox-3.15.2.tar.gz", hash = "sha256:c696d36cd7c6a28ada2da780400e44851b20ee19ef08cfe73344a1dcebbbe9f3"},
]
tqdm = [
{file = "tqdm-4.46.1-py2.py3-none-any.whl", hash = "sha256:07c06493f1403c1380b630ae3dcbe5ae62abcf369a93bbc052502279f189ab8c"},
{file = "tqdm-4.46.1.tar.gz", hash = "sha256:cd140979c2bebd2311dfb14781d8f19bd5a9debb92dcab9f6ef899c987fcf71f"},
{file = "tox-3.16.1-py2.py3-none-any.whl", hash = "sha256:60c3793f8ab194097ec75b5a9866138444f63742b0f664ec80be1222a40687c5"},
{file = "tox-3.16.1.tar.gz", hash = "sha256:9a746cda9cadb9e1e05c7ab99f98cfcea355140d2ecac5f97520be94657c3bc7"},
]
typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
......@@ -2069,6 +1710,4 @@ webencodings = [
zipp = [
{file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"},
{file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"},
{file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
{file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
]
......@@ -37,6 +37,7 @@ class Config(object):
"in-project": False,
"path": os.path.join("{cache-dir}", "virtualenvs"),
},
"experimental": {"new-installer": True},
}
def __init__(
......
from cleo import argument
from cleo import option
from .env_command import EnvCommand
from .init import InitCommand
from .installer_command import InstallerCommand
class AddCommand(EnvCommand, InitCommand):
class AddCommand(InstallerCommand, InitCommand):
name = "add"
description = "Adds a new dependency to <comment>pyproject.toml</>."
......@@ -56,7 +56,6 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
from poetry.installation.installer import Installer
from poetry.core.semver import parse_constraint
from tomlkit import inline_table
......@@ -149,18 +148,17 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
# Update packages
self.reset_poetry()
installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
)
installer.dry_run(self.option("dry-run"))
installer.update(True)
self._installer.set_package(self.poetry.package)
self._installer.dry_run(self.option("dry-run"))
self._installer.verbose(self._io.is_verbose())
self._installer.update(True)
if self.option("lock"):
installer.lock()
installer.whitelist([r["name"] for r in requirements])
self._installer.lock()
self._installer.whitelist([r["name"] for r in requirements])
try:
status = installer.run()
status = self._installer.run()
except Exception:
self.poetry.file.write(original_content)
......@@ -169,10 +167,10 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
if status != 0 or self.option("dry-run"):
# Revert changes
if not self.option("dry-run"):
self.error(
self.line_error(
"\n"
"Addition failed, reverting pyproject.toml "
"to its original content."
"<error>Failed to add packages, reverting the pyproject.toml file "
"to its original content.</error>"
)
self.poetry.file.write(original_content)
......
......@@ -57,6 +57,11 @@ To remove a repository (repo is a short alias for repositories):
lambda val: str(Path(val)),
str(Path(CACHE_DIR) / "virtualenvs"),
),
"experimental.new-installer": (
boolean_validator,
boolean_normalizer,
True,
),
}
return unique_config_values
......
from cleo import option
from .env_command import EnvCommand
from .installer_command import InstallerCommand
class InstallCommand(EnvCommand):
class InstallCommand(InstallerCommand):
name = "install"
description = "Installs the project dependencies."
......@@ -48,12 +48,11 @@ dependencies and not including the current project, run the command with the
_loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
from poetry.installation.installer import Installer
from poetry.masonry.builders import EditableBuilder
from poetry.core.masonry.utils.module import ModuleOrPackageNotFound
installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False)
)
extras = []
......@@ -63,13 +62,13 @@ dependencies and not including the current project, run the command with the
else:
extras.append(extra)
installer.extras(extras)
installer.dev_mode(not self.option("no-dev"))
installer.dry_run(self.option("dry-run"))
installer.remove_untracked(self.option("remove-untracked"))
installer.verbose(self.option("verbose"))
self._installer.extras(extras)
self._installer.dev_mode(not self.option("no-dev"))
self._installer.dry_run(self.option("dry-run"))
self._installer.remove_untracked(self.option("remove-untracked"))
self._installer.verbose(self._io.is_verbose())
return_code = installer.run()
return_code = self._installer.run()
if return_code != 0:
return return_code
......@@ -85,15 +84,32 @@ dependencies and not including the current project, run the command with the
# If this is a true error it will be picked up later by build anyway.
return 0
self.line(
" - Installing <c1>{}</c1> (<c2>{}</c2>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version
self.line("")
if not self._io.supports_ansi() or self.io.is_debug():
self.line(
"<b>Installing</> the current project: <c1>{}</c1> (<c2>{}</c2>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version
)
)
else:
self.write(
"<b>Installing</> the current project: <c1>{}</c1> (<c2>{}</c2>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version
)
)
)
if self.option("dry-run"):
self.line("")
return 0
builder.build()
if self._io.supports_ansi() and not self.io.is_debug():
self.overwrite(
"<b>Installing</> the current project: <c1>{}</c1> (<success>{}</success>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version
)
)
self.line("")
return 0
from typing import TYPE_CHECKING
from .env_command import EnvCommand
if TYPE_CHECKING:
from poetry.installation.installer import Installer
class InstallerCommand(EnvCommand):
def __init__(self):
self._installer = None
super(InstallerCommand, self).__init__()
@property
def installer(self): # type: () -> Installer
return self._installer
def set_installer(self, installer): # type: (Installer) -> None
self._installer = installer
from .env_command import EnvCommand
from .installer_command import InstallerCommand
class LockCommand(EnvCommand):
class LockCommand(InstallerCommand):
name = "lock"
description = "Locks the project dependencies."
......@@ -17,12 +17,10 @@ file.
loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
from poetry.installation.installer import Installer
installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False)
)
installer.lock()
self._installer.lock()
return installer.run()
return self._installer.run()
from cleo import argument
from cleo import option
from .env_command import EnvCommand
from .installer_command import InstallerCommand
class RemoveCommand(EnvCommand):
class RemoveCommand(InstallerCommand):
name = "remove"
description = "Removes a package from the project dependencies."
......@@ -28,8 +28,6 @@ list of installed packages
loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
from poetry.installation.installer import Installer
packages = self.argument("packages")
is_dev = self.option("dev")
......@@ -62,16 +60,18 @@ list of installed packages
# Update packages
self.reset_poetry()
installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
self._installer.set_package(self.poetry.package)
self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False)
)
installer.dry_run(self.option("dry-run"))
installer.update(True)
installer.whitelist(requirements)
self._installer.dry_run(self.option("dry-run"))
self._installer.verbose(self._io.is_verbose())
self._installer.update(True)
self._installer.whitelist(requirements)
try:
status = installer.run()
status = self._installer.run()
except Exception:
self.poetry.file.write(original_content)
......@@ -80,7 +80,7 @@ list of installed packages
if status != 0 or self.option("dry-run"):
# Revert changes
if not self.option("dry-run"):
self.error(
self.line_error(
"\n"
"Removal failed, reverting pyproject.toml "
"to its original content."
......
from cleo import argument
from cleo import option
from .env_command import EnvCommand
from .installer_command import InstallerCommand
class UpdateCommand(EnvCommand):
class UpdateCommand(InstallerCommand):
name = "update"
description = (
......@@ -28,22 +28,20 @@ class UpdateCommand(EnvCommand):
loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
from poetry.installation.installer import Installer
packages = self.argument("packages")
installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False)
)
if packages:
installer.whitelist({name: "*" for name in packages})
self._installer.whitelist({name: "*" for name in packages})
installer.dev_mode(not self.option("no-dev"))
installer.dry_run(self.option("dry-run"))
installer.execute_operations(not self.option("lock"))
self._installer.dev_mode(not self.option("no-dev"))
self._installer.dry_run(self.option("dry-run"))
self._installer.execute_operations(not self.option("lock"))
# Force update
installer.update(True)
self._installer.update(True)
return installer.run()
return self._installer.run()
......@@ -27,6 +27,7 @@ from clikit.io.output_stream import StandardOutputStream
from poetry.console.commands.command import Command
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.installer_command import InstallerCommand
from poetry.console.logging.io_formatter import IOFormatter
from poetry.console.logging.io_handler import IOHandler
from poetry.utils._compat import PY36
......@@ -37,15 +38,22 @@ class ApplicationConfig(BaseApplicationConfig):
super(ApplicationConfig, self).configure()
self.add_style(Style("c1").fg("cyan"))
self.add_style(Style("c2").fg("green"))
self.add_style(Style("c2").fg("default").bold())
self.add_style(Style("info").fg("blue"))
self.add_style(Style("comment").fg("green"))
self.add_style(Style("error").fg("red").bold())
self.add_style(Style("warning").fg("yellow").bold())
self.add_style(Style("debug").fg("default").dark())
self.add_style(Style("success").fg("green"))
# Dark variants
self.add_style(Style("c1_dark").fg("cyan").dark())
self.add_style(Style("c2_dark").fg("default").bold().dark())
self.add_style(Style("success_dark").fg("green").dark())
self.add_event_listener(PRE_HANDLE, self.register_command_loggers)
self.add_event_listener(PRE_HANDLE, self.set_env)
self.add_event_listener(PRE_HANDLE, self.set_installer)
if PY36:
from poetry.mixology.solutions.providers import (
......@@ -93,6 +101,9 @@ class ApplicationConfig(BaseApplicationConfig):
if not isinstance(command, EnvCommand):
return
if command.env is not None:
return
io = event.io
poetry = command.poetry
......@@ -104,6 +115,32 @@ class ApplicationConfig(BaseApplicationConfig):
command.set_env(env)
def set_installer(
self, event, event_name, _
): # type: (PreHandleEvent, str, Any) -> None
command = event.command.config.handler # type: InstallerCommand
if not isinstance(command, InstallerCommand):
return
# If the command already has an installer
# we skip this step
if command.installer is not None:
return
from poetry.installation.installer import Installer
poetry = command.poetry
installer = Installer(
event.io,
command.env,
poetry.package,
poetry.locker,
poetry.pool,
poetry.config,
)
installer.use_executor(poetry.config.get("experimental.new-installer", False))
command.set_installer(installer)
def resolve_help_command(
self, event, event_name, dispatcher
): # type: (PreResolveEvent, str, EventDispatcher) -> None
......
from typing import TYPE_CHECKING
from poetry.utils._compat import urlparse
from poetry.utils.password_manager import PasswordManager
if TYPE_CHECKING:
from typing import Any
from typing import Optional
from typing import Tuple
from clikit.api.io import IO
from requests import Request # noqa
from requests import Response # noqa
from requests import Session # noqa
from poetry.config.config import Config
class Authenticator(object):
def __init__(self, config, io): # type: (Config, IO) -> None
self._config = config
self._io = io
self._session = None
self._credentials = {}
self._password_manager = PasswordManager(self._config)
@property
def session(self): # type: () -> Session
from requests import Session # noqa
if self._session is None:
self._session = Session()
return self._session
def request(self, method, url, **kwargs): # type: (str, str, Any) -> Response
from requests import Request # noqa
from requests.auth import HTTPBasicAuth
request = Request(method, url)
username, password = self._get_credentials_for_url(url)
if username is not None and password is not None:
request = HTTPBasicAuth(username, password)(request)
session = self.session
prepared_request = session.prepare_request(request)
proxies = kwargs.get("proxies", {})
stream = kwargs.get("stream")
verify = kwargs.get("verify")
cert = kwargs.get("cert")
settings = session.merge_environment_settings(
prepared_request.url, proxies, stream, verify, cert
)
# Send the request.
send_kwargs = {
"timeout": kwargs.get("timeout"),
"allow_redirects": kwargs.get("allow_redirects", True),
}
send_kwargs.update(settings)
resp = session.send(prepared_request, **send_kwargs)
resp.raise_for_status()
return resp
def _get_credentials_for_url(
self, url
): # type: (str) -> Tuple[Optional[str], Optional[str]]
parsed_url = urlparse.urlsplit(url)
netloc = parsed_url.netloc
credentials = self._credentials.get(netloc, (None, None))
if credentials == (None, None):
if "@" not in netloc:
credentials = self._get_credentials_for_netloc_from_config(netloc)
else:
# Split from the right because that's how urllib.parse.urlsplit()
# behaves if more than one @ is present (which can be checked using
# the password attribute of urlsplit()'s return value).
auth, netloc = netloc.rsplit("@", 1)
if ":" in auth:
# Split from the left because that's how urllib.parse.urlsplit()
# behaves if more than one : is present (which again can be checked
# using the password attribute of the return value)
credentials = auth.split(":", 1)
else:
credentials = auth, None
credentials = tuple(
None if x is None else urlparse.unquote(x) for x in credentials
)
if credentials[0] is not None or credentials[1] is not None:
credentials = (credentials[0] or "", credentials[1] or "")
self._credentials[netloc] = credentials
return credentials[0], credentials[1]
def _get_credentials_for_netloc_from_config(
self, netloc
): # type: (str) -> Tuple[Optional[str], Optional[str]]
credentials = (None, None)
for repository_name in self._config.get("http-basic", {}):
repository_config = self._config.get(
"repositories.{}".format(repository_name)
)
if not repository_config:
continue
url = repository_config.get("url")
if not url:
continue
parsed_url = urlparse.urlsplit(url)
if netloc == parsed_url.netloc:
auth = self._password_manager.get_http_auth(repository_name)
if auth is None:
continue
return auth["username"], auth["password"]
return credentials
import hashlib
import json
from typing import TYPE_CHECKING
from poetry.core.packages.utils.link import Link
from poetry.utils._compat import Path
from .chooser import InvalidWheelName
from .chooser import Wheel
if TYPE_CHECKING:
from typing import List
from typing import Optional
from poetry.config.config import Config
from poetry.utils.env import Env
class Chef:
def __init__(self, config, env): # type: (Config, Env) -> None
self._config = config
self._env = env
self._cache_dir = (
Path(config.get("cache-dir")).expanduser().joinpath("artifacts")
)
def prepare(self, archive): # type: (Path) -> Path
return archive
def prepare_sdist(self, archive): # type: (Path) -> Path
return archive
def prepare_wheel(self, archive): # type: (Path) -> Path
return archive
def should_prepare(self, archive): # type: (Path) -> bool
return not self.is_wheel(archive)
def is_wheel(self, archive): # type: (Path) -> bool
return archive.suffix == ".whl"
def get_cached_archive_for_link(self, link): # type: (Link) -> Optional[Link]
# If the archive is already a wheel, there is no need to cache it.
if link.is_wheel:
pass
archives = self.get_cached_archives_for_link(link)
if not archives:
return link
candidates = []
for archive in archives:
if not archive.is_wheel:
candidates.append((float("inf"), archive))
continue
try:
wheel = Wheel(archive.filename)
except InvalidWheelName:
continue
if not wheel.is_supported_by_environment(self._env):
continue
candidates.append(
(wheel.get_minimum_supported_index(self._env.supported_tags), archive),
)
if not candidates:
return link
return min(candidates)[1]
def get_cached_archives_for_link(self, link): # type: (Link) -> List[Link]
cache_dir = self.get_cache_directory_for_link(link)
archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"]
links = []
for archive_type in archive_types:
for archive in cache_dir.glob("*.{}".format(archive_type)):
links.append(Link("file://{}".format(str(archive))))
return links
def get_cache_directory_for_link(self, link): # type: (Link) -> Path
key_parts = {"url": link.url_without_fragment}
if link.hash_name is not None and link.hash is not None:
key_parts[link.hash_name] = link.hash
if link.subdirectory_fragment:
key_parts["subdirectory"] = link.subdirectory_fragment
key_parts["interpreter_name"] = self._env.marker_env["interpreter_name"]
key_parts["interpreter_version"] = "".join(
self._env.marker_env["interpreter_version"].split(".")[:2]
)
key = hashlib.sha256(
json.dumps(
key_parts, sort_keys=True, separators=(",", ":"), ensure_ascii=True
).encode("ascii")
).hexdigest()
split_key = [key[:2], key[2:4], key[4:6], key[6:]]
return self._cache_dir.joinpath(*split_key)
import re
from typing import List
from typing import Tuple
from packaging.tags import Tag
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.repositories.pool import Pool
from poetry.utils.env import Env
from poetry.utils.patterns import wheel_file_re
class InvalidWheelName(Exception):
pass
class Wheel(object):
def __init__(self, filename): # type: (str) -> None
wheel_info = wheel_file_re.match(filename)
if not wheel_info:
raise InvalidWheelName("{} is not a valid wheel filename.".format(filename))
self.filename = filename
self.name = wheel_info.group("name").replace("_", "-")
self.version = wheel_info.group("ver").replace("_", "-")
self.build_tag = wheel_info.group("build")
self.pyversions = wheel_info.group("pyver").split(".")
self.abis = wheel_info.group("abi").split(".")
self.plats = wheel_info.group("plat").split(".")
self.tags = {
Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
}
def get_minimum_supported_index(self, tags):
indexes = [tags.index(t) for t in self.tags if t in tags]
return min(indexes) if indexes else None
def is_supported_by_environment(self, env):
return bool(set(env.supported_tags).intersection(self.tags))
class Chooser:
"""
A Chooser chooses an appropriate release archive for packages.
"""
def __init__(self, pool, env): # type: (Pool, Env) -> None
self._pool = pool
self._env = env
def choose_for(self, package): # type: (Package) -> Link
"""
Return the url of the selected archive for a given package.
"""
links = []
for link in self._get_links(package):
if link.is_wheel and not Wheel(link.filename).is_supported_by_environment(
self._env
):
continue
if link.ext == ".egg":
continue
links.append(link)
if not links:
raise RuntimeError(
"Unable to find installation candidates for {}".format(package)
)
# Get the best link
chosen = max(links, key=lambda link: self._sort_key(package, link))
if not chosen:
raise RuntimeError(
"Unable to find installation candidates for {}".format(package)
)
return chosen
def _get_links(self, package): # type: (Package) -> List[Link]
if not package.source_type:
if not self._pool.has_repository("pypi"):
repository = self._pool.repositories[0]
else:
repository = self._pool.repository("pypi")
else:
repository = self._pool.repository(package.source_reference)
links = repository.find_links_for_package(package)
hashes = [f["hash"] for f in package.files]
if not hashes:
return links
selected_links = []
for link in links:
if not link.hash:
selected_links.append(link)
continue
h = link.hash_name + ":" + link.hash
if h not in hashes:
continue
selected_links.append(link)
return selected_links
def _sort_key(self, package, link): # type: (Package, Link) -> Tuple
"""
Function to pass as the `key` argument to a call to sorted() to sort
InstallationCandidates by preference.
Returns a tuple such that tuples sorting as greater using Python's
default comparison operator are more preferred.
The preference is as follows:
First and foremost, candidates with allowed (matching) hashes are
always preferred over candidates without matching hashes. This is
because e.g. if the only candidate with an allowed hash is yanked,
we still want to use that candidate.
Second, excepting hash considerations, candidates that have been
yanked (in the sense of PEP 592) are always less preferred than
candidates that haven't been yanked. Then:
If not finding wheels, they are sorted by version only.
If finding wheels, then the sort order is by version, then:
1. existing installs
2. wheels ordered via Wheel.support_index_min(self._supported_tags)
3. source archives
If prefer_binary was set, then all wheels are sorted above sources.
Note: it was considered to embed this logic into the Link
comparison operators, but then different sdist links
with the same version, would have to be considered equal
"""
support_num = len(self._env.supported_tags)
build_tag = ()
binary_preference = 0
if link.is_wheel:
wheel = Wheel(link.filename)
if not wheel.is_supported_by_environment(self._env):
raise RuntimeError(
"{} is not a supported wheel for this platform. It "
"can't be sorted.".format(wheel.filename)
)
# TODO: Binary preference
pri = -(wheel.get_minimum_supported_index(self._env.supported_tags))
if wheel.build_tag is not None:
match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
build_tag_groups = match.groups()
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
else: # sdist
pri = -support_num
has_allowed_hash = int(self._is_link_hash_allowed_for_package(link, package))
# TODO: Proper yank value
yank_value = 0
return (
has_allowed_hash,
yank_value,
binary_preference,
package.version,
build_tag,
pri,
)
def _is_link_hash_allowed_for_package(
self, link, package
): # type: (Link, Package) -> bool
if not link.hash:
return True
h = link.hash_name + ":" + link.hash
return h in {f["hash"] for f in package.files}
# -*- coding: utf-8 -*-
from __future__ import division
import itertools
import os
import threading
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import wait
from subprocess import CalledProcessError
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.utils.link import Link
from poetry.io.null_io import NullIO
from poetry.utils._compat import OrderedDict
from poetry.utils._compat import Path
from poetry.utils._compat import cpu_count
from poetry.utils._compat import decode
from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import safe_rmtree
from .authenticator import Authenticator
from .chef import Chef
from .chooser import Chooser
from .operations.install import Install
from .operations.operation import Operation
from .operations.uninstall import Uninstall
from .operations.update import Update
class Executor(object):
def __init__(self, env, pool, config, io, parallel=None):
self._env = env
self._io = io
self._dry_run = False
self._enabled = True
self._verbose = False
self._authenticator = Authenticator(config, self._io)
self._chef = Chef(config, self._env)
self._chooser = Chooser(pool, self._env)
if parallel is None:
parallel = self.supports_fancy_output()
if parallel:
# This should be directly handled by ThreadPoolExecutor
# however, on some systems the number of CPUs cannot be determined
# (it raises a NotImplementedError), so, in this case, we assume
# that the system only has one CPU.
try:
self._max_workers = cpu_count() + 4
except NotImplementedError:
self._max_workers = 5
else:
self._max_workers = 1
self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
self._total_operations = 0
self._executed_operations = 0
self._executed = {"install": 0, "update": 0, "uninstall": 0}
self._skipped = {"install": 0, "update": 0, "uninstall": 0}
self._sections = OrderedDict()
self._lock = threading.Lock()
self._shutdown = False
@property
def installations_count(self): # type: () -> int
return self._executed["install"]
@property
def updates_count(self): # type: () -> int
return self._executed["update"]
@property
def removals_count(self): # type: () -> int
return self._executed["uninstall"]
def supports_fancy_output(self): # type: () -> bool
return self._io.supports_ansi() and not self._dry_run
def disable(self):
self._enabled = False
return self
def dry_run(self, dry_run=True):
self._dry_run = dry_run
return self
def verbose(self, verbose=True):
self._verbose = verbose
return self
def execute(self, operations): # type: (Operation) -> int
self._total_operations = len(operations)
for job_type in self._executed:
self._executed[job_type] = 0
self._skipped[job_type] = 0
if operations and (self._enabled or self._dry_run):
self._display_summary(operations)
# We group operations by priority
groups = itertools.groupby(operations, key=lambda o: -o.priority)
self._sections = OrderedDict()
for _, group in groups:
tasks = []
for operation in group:
if self._shutdown:
break
tasks.append(self._executor.submit(self._execute_operation, operation))
try:
wait(tasks)
except KeyboardInterrupt:
self._shutdown = True
if self._shutdown:
self._executor.shutdown(wait=True)
break
return self._shutdown
def _write(self, operation, line):
if not self.supports_fancy_output() or not self._should_write_operation(
operation
):
return
if self._io.is_debug():
self._lock.acquire()
section = self._sections[id(operation)]
section.write_line(line)
self._lock.release()
return
self._lock.acquire()
section = self._sections[id(operation)]
section.output.clear()
section.write(line)
self._lock.release()
def _execute_operation(self, operation):
try:
if self.supports_fancy_output():
if id(operation) not in self._sections:
if self._should_write_operation(operation):
self._lock.acquire()
self._sections[id(operation)] = self._io.section()
self._sections[id(operation)].write_line(
" <fg=blue;options=bold>•</> {message}: <fg=blue>Pending...</>".format(
message=self.get_operation_message(operation),
),
)
self._lock.release()
else:
if self._should_write_operation(operation):
if not operation.skipped:
self._io.write_line(
" <fg=blue;options=bold>•</> {message}".format(
message=self.get_operation_message(operation),
),
)
else:
self._io.write_line(
" <fg=default;options=bold,dark>•</> {message}: "
"<fg=default;options=bold,dark>Skipped</> "
"<fg=default;options=dark>for the following reason:</> "
"<fg=default;options=bold,dark>{reason}</>".format(
message=self.get_operation_message(operation),
reason=operation.skip_reason,
)
)
try:
result = self._do_execute_operation(operation)
except EnvCommandError as e:
if e.e.returncode == -2:
result = -2
else:
raise
# If we have a result of -2 it means a KeyboardInterrupt
# in the any python subprocess, so we raise a KeyboardInterrupt
# error to be picked up by the error handler.
if result == -2:
raise KeyboardInterrupt
except Exception as e:
from clikit.ui.components.exception_trace import ExceptionTrace
if not self.supports_fancy_output():
io = self._io
else:
message = " <error>•</error> {message}: <error>Failed</error>".format(
message=self.get_operation_message(operation, error=True),
)
self._write(operation, message)
io = self._sections.get(id(operation), self._io)
self._lock.acquire()
trace = ExceptionTrace(e)
trace.render(io)
io.write_line("")
self._shutdown = True
self._lock.release()
except KeyboardInterrupt:
message = " <warning>•</warning> {message}: <warning>Cancelled</warning>".format(
message=self.get_operation_message(operation, warning=True),
)
if not self.supports_fancy_output():
self._io.write_line(message)
else:
self._write(operation, message)
self._lock.acquire()
self._shutdown = True
self._lock.release()
def _do_execute_operation(self, operation):
method = operation.job_type
operation_message = self.get_operation_message(operation)
if operation.skipped:
if self.supports_fancy_output():
self._write(
operation,
" <fg=default;options=bold,dark>•</> {message}: "
"<fg=default;options=bold,dark>Skipped</> "
"<fg=default;options=dark>for the following reason:</> "
"<fg=default;options=bold,dark>{reason}</>".format(
message=operation_message, reason=operation.skip_reason,
),
)
self._skipped[operation.job_type] += 1
return 0
if not self._enabled or self._dry_run:
self._io.write_line(
" <fg=blue;options=bold>•</> {message}".format(
message=operation_message,
)
)
return 0
result = getattr(self, "_execute_{}".format(method))(operation)
if result != 0:
return result
message = " <fg=green;options=bold>•</> {message}".format(
message=self.get_operation_message(operation, done=True),
)
self._write(operation, message)
self._increment_operations_count(operation, True)
return result
def _increment_operations_count(self, operation, executed):
self._lock.acquire()
if executed:
self._executed_operations += 1
self._executed[operation.job_type] += 1
else:
self._skipped[operation.job_type] += 1
self._lock.release()
def run_pip(self, *args, **kwargs): # type: (...) -> int
try:
self._env.run("python", "-m", "pip", *args, **kwargs)
except EnvCommandError as e:
output = decode(e.e.output)
if (
"KeyboardInterrupt" in output
or "ERROR: Operation cancelled by user" in output
):
return -2
raise
return 0
def get_operation_message(self, operation, done=False, error=False, warning=False):
base_tag = "fg=default"
operation_color = "c2"
source_operation_color = "c2"
package_color = "c1"
if error:
operation_color = "error"
elif warning:
operation_color = "warning"
elif done:
operation_color = "success"
if operation.skipped:
base_tag = "fg=default;options=dark"
operation_color += "_dark"
source_operation_color += "_dark"
package_color += "_dark"
if operation.job_type == "install":
return "<{}>Installing <{}>{}</{}> (<{}>{}</>)</>".format(
base_tag,
package_color,
operation.package.name,
package_color,
operation_color,
operation.package.full_pretty_version,
)
if operation.job_type == "uninstall":
return "<{}>Removing <{}>{}</{}> (<{}>{}</>)</>".format(
base_tag,
package_color,
operation.package.name,
package_color,
operation_color,
operation.package.full_pretty_version,
)
if operation.job_type == "update":
return "<{}>Updating <{}>{}</{}> (<{}>{}</{}> -> <{}>{}</>)</>".format(
base_tag,
package_color,
operation.initial_package.name,
package_color,
source_operation_color,
operation.initial_package.full_pretty_version,
source_operation_color,
operation_color,
operation.target_package.full_pretty_version,
)
return ""
def _display_summary(self, operations):
installs = 0
updates = 0
uninstalls = 0
skipped = 0
for op in operations:
if op.skipped:
skipped += 1
continue
if op.job_type == "install":
installs += 1
elif op.job_type == "update":
updates += 1
elif op.job_type == "uninstall":
uninstalls += 1
if not installs and not updates and not uninstalls and not self._verbose:
self._io.write_line("")
self._io.write_line("No dependencies to install or update")
return
self._io.write_line("")
self._io.write_line(
"<b>Package operations</b>: "
"<info>{}</> install{}, "
"<info>{}</> update{}, "
"<info>{}</> removal{}"
"{}".format(
installs,
"" if installs == 1 else "s",
updates,
"" if updates == 1 else "s",
uninstalls,
"" if uninstalls == 1 else "s",
", <info>{}</> skipped".format(skipped)
if skipped and self._verbose
else "",
)
)
self._io.write_line("")
def _execute_install(self, operation): # type: (Install) -> None
return self._install(operation)
def _execute_update(self, operation): # type: (Update) -> None
return self._update(operation)
def _execute_uninstall(self, operation): # type: (Uninstall) -> None
message = " <fg=blue;options=bold>•</> {message}: <info>Removing...</info>".format(
message=self.get_operation_message(operation),
)
self._write(operation, message)
return self._remove(operation)
def _install(self, operation):
package = operation.package
if package.source_type == "directory":
return self._install_directory(operation)
if package.source_type == "git":
return self._install_git(operation)
if package.source_type == "file":
archive = self._prepare_file(operation)
elif package.source_type == "url":
archive = self._download_link(operation, Link(package.source_url))
else:
archive = self._download(operation)
operation_message = self.get_operation_message(operation)
message = " <fg=blue;options=bold>•</> {message}: <info>Installing...</info>".format(
message=operation_message,
)
self._write(operation, message)
args = ["install", "--no-deps", str(archive)]
if operation.job_type == "update":
args.insert(2, "-U")
return self.run_pip(*args)
def _update(self, operation):
return self._install(operation)
def _remove(self, operation):
package = operation.package
# If we have a VCS package, remove its source directory
if package.source_type == "git":
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
safe_rmtree(str(src_dir))
try:
return self.run_pip("uninstall", package.name, "-y")
except CalledProcessError as e:
if "not installed" in str(e):
return 0
raise
def _prepare_file(self, operation):
package = operation.package
message = " <fg=blue;options=bold>•</> {message}: <info>Preparing...</info>".format(
message=self.get_operation_message(operation),
)
self._write(operation, message)
archive = Path(package.source_url)
if not Path(package.source_url).is_absolute() and package.root_dir:
archive = package.root_dir / archive
archive = self._chef.prepare(archive)
return archive
def _install_directory(self, operation):
from poetry.factory import Factory
from poetry.utils.toml_file import TomlFile
package = operation.package
operation_message = self.get_operation_message(operation)
message = " <fg=blue;options=bold>•</> {message}: <info>Building...</info>".format(
message=operation_message,
)
self._write(operation, message)
if package.root_dir:
req = os.path.join(str(package.root_dir), package.source_url)
else:
req = os.path.realpath(package.source_url)
args = ["install", "--no-deps", "-U"]
pyproject = TomlFile(os.path.join(req, "pyproject.toml"))
has_poetry = False
has_build_system = False
if pyproject.exists():
pyproject_content = pyproject.read()
has_poetry = (
"tool" in pyproject_content and "poetry" in pyproject_content["tool"]
)
# Even if there is a build system specified
# some versions of pip (< 19.0.0) don't understand it
# so we need to check the version of pip to know
# if we can rely on the build system
pip_version = self._env.pip_version
pip_version_with_build_system_support = pip_version.__class__(19, 0, 0)
has_build_system = (
"build-system" in pyproject_content
and pip_version >= pip_version_with_build_system_support
)
if has_poetry:
package_poetry = Factory().create_poetry(pyproject.parent)
if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder
# This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip
# to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build()
return 0
elif not has_build_system or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder
# We need to rely on creating a temporary setup.py
# file since the version of pip does not support
# build-systems
# We also need it for non-PEP-517 packages
builder = SdistBuilder(package_poetry)
with builder.setup_py():
if package.develop:
args.append("-e")
args.append(req)
return self.run_pip(*args)
if package.develop:
args.append("-e")
args.append(req)
return self.run_pip(*args)
def _install_git(self, operation):
from poetry.core.vcs import Git
package = operation.package
operation_message = self.get_operation_message(operation)
message = " <fg=blue;options=bold>•</> {message}: <info>Cloning...</info>".format(
message=operation_message,
)
self._write(operation, message)
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
safe_rmtree(str(src_dir))
src_dir.parent.mkdir(exist_ok=True)
git = Git()
git.clone(package.source_url, src_dir)
git.checkout(package.source_reference, src_dir)
# Now we just need to install from the source directory
package.source_url = str(src_dir)
return self._install_directory(operation)
def _download(self, operation): # type: (Operation) -> Path
link = self._chooser.choose_for(operation.package)
return self._download_link(operation, link)
def _download_link(self, operation, link):
package = operation.package
archive = self._chef.get_cached_archive_for_link(link)
if archive is link:
# No cached distributions was found, so we download and prepare it
try:
archive = self._download_archive(operation, link)
except BaseException:
cache_directory = self._chef.get_cache_directory_for_link(link)
cache_directory.joinpath(link.filename).unlink(missing_ok=True)
raise
# TODO: Check readability of the created archive
if not link.is_wheel:
archive = self._chef.prepare(archive)
if package.files:
archive_hash = "sha256:" + FileDependency(package.name, archive).hash()
if archive_hash not in {f["hash"] for f in package.files}:
raise RuntimeError(
"Invalid hash for {} using archive {}".format(package, archive.name)
)
return archive
def _download_archive(self, operation, link): # type: (Operation, Link) -> Path
response = self._authenticator.request("get", link.url, stream=True)
wheel_size = response.headers.get("content-length")
operation_message = self.get_operation_message(operation)
message = " <fg=blue;options=bold>•</> {message}: <info>Downloading...</>".format(
message=operation_message,
)
progress = None
if self.supports_fancy_output():
if wheel_size is None:
self._write(operation, message)
else:
from clikit.ui.components.progress_bar import ProgressBar
progress = ProgressBar(
self._sections[id(operation)].output, max=int(wheel_size)
)
progress.set_format(message + " <b>%percent%%</b>")
if progress:
self._lock.acquire()
progress.start()
self._lock.release()
done = 0
archive = self._chef.get_cache_directory_for_link(link) / link.filename
archive.parent.mkdir(parents=True, exist_ok=True)
with archive.open("wb") as f:
for chunk in response.iter_content(chunk_size=4096):
if not chunk:
break
done += len(chunk)
if progress:
self._lock.acquire()
progress.set_progress(done)
self._lock.release()
f.write(chunk)
if progress:
self._lock.acquire()
progress.finish()
self._lock.release()
return archive
def _should_write_operation(self, operation): # type: (Operation) -> bool
if not operation.skipped:
return True
return self._dry_run or self._verbose
from typing import List
from typing import Optional
from typing import Union
from clikit.api.io import IO
from poetry.config.config import Config
from poetry.core.packages.project_package import ProjectPackage
from poetry.io.null_io import NullIO
from poetry.packages import Locker
from poetry.puzzle import Solver
from poetry.puzzle.operations import Install
from poetry.puzzle.operations import Uninstall
from poetry.puzzle.operations import Update
from poetry.puzzle.operations.operation import Operation
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository
......@@ -18,6 +15,11 @@ from poetry.utils.extras import get_extra_package_names
from poetry.utils.helpers import canonicalize_name
from .base_installer import BaseInstaller
from .executor import Executor
from .operations import Install
from .operations import Uninstall
from .operations import Update
from .operations.operation import Operation
from .pip_installer import PipInstaller
......@@ -29,7 +31,9 @@ class Installer:
package, # type: ProjectPackage
locker, # type: Locker
pool, # type: Pool
installed=None, # type: (Union[InstalledRepository, None])
config, # type: Config
installed=None, # type: Union[InstalledRepository, None]
executor=None, # type: Optional[Executor]
):
self._io = io
self._env = env
......@@ -50,6 +54,12 @@ class Installer:
self._extras = []
if executor is None:
executor = Executor(self._env, self._pool, config, self._io)
self._executor = executor
self._use_executor = False
self._installer = self._get_installer()
if installed is None:
installed = self._get_installed()
......@@ -57,9 +67,18 @@ class Installer:
self._installed_repository = installed
@property
def executor(self):
return self._executor
@property
def installer(self):
return self._installer
def set_package(self, package): # type: (ProjectPackage) -> Installer
self._package = package
return self
def run(self):
# Force update if there is no lock file present
if not self._update and not self._locker.is_locked():
......@@ -71,12 +90,12 @@ class Installer:
self._execute_operations = False
local_repo = Repository()
self._do_install(local_repo)
return 0
return self._do_install(local_repo)
def dry_run(self, dry_run=True): # type: (bool) -> Installer
self._dry_run = dry_run
self._executor.dry_run(dry_run)
return self
......@@ -93,6 +112,7 @@ class Installer:
def verbose(self, verbose=True): # type: (bool) -> Installer
self._verbose = verbose
self._executor.verbose(verbose)
return self
......@@ -128,6 +148,9 @@ class Installer:
def execute_operations(self, execute=True): # type: (bool) -> Installer
self._execute_operations = execute
if not execute:
self._executor.disable()
return self
def whitelist(self, packages): # type: (dict) -> Installer
......@@ -140,7 +163,14 @@ class Installer:
return self
def use_executor(self, use_executor=True): # type: (bool) -> Installer
self._use_executor = use_executor
return self
def _do_install(self, local_repo):
from poetry.puzzle import Solver
locked_repository = Repository()
if self._update:
if self._locker.is_locked() and not self._lock:
......@@ -247,19 +277,30 @@ class Installer:
# or optional and not requested, are dropped
self._filter_operations(ops, local_repo)
self._io.write_line("")
# Execute operations
actual_ops = [op for op in ops if not op.skipped]
if not actual_ops and (self._execute_operations or self._dry_run):
return self._execute(ops)
def _write_lock_file(self, repo): # type: (Repository) -> None
if self._update and self._write_lock:
updated_lock = self._locker.set_lock_data(self._package, repo.packages)
if updated_lock:
self._io.write_line("")
self._io.write_line("<info>Writing lock file</>")
def _execute(self, operations):
if self._use_executor:
return self._executor.execute(operations)
if not operations and (self._execute_operations or self._dry_run):
self._io.write_line("No dependencies to install or update")
if actual_ops and (self._execute_operations or self._dry_run):
if operations and (self._execute_operations or self._dry_run):
installs = 0
updates = 0
uninstalls = 0
skipped = 0
for op in ops:
for op in operations:
if op.skipped:
skipped += 1
elif op.job_type == "install":
......@@ -289,18 +330,13 @@ class Installer:
)
self._io.write_line("")
for op in ops:
self._execute(op)
def _write_lock_file(self, repo): # type: (Repository) -> None
if self._update and self._write_lock:
updated_lock = self._locker.set_lock_data(self._package, repo.packages)
for op in operations:
self._execute_operation(op)
if updated_lock:
self._io.write_line("")
self._io.write_line("<info>Writing lock file</>")
return 0
def _execute(self, operation): # type: (Operation) -> None
def _execute_operation(self, operation): # type: (Operation) -> None
"""
Execute a given operation.
"""
......
......@@ -2,8 +2,8 @@ from .operation import Operation
class Install(Operation):
def __init__(self, package, reason=None):
super(Install, self).__init__(reason)
def __init__(self, package, reason=None, priority=0):
super(Install, self).__init__(reason, priority=priority)
self._package = package
......
......@@ -4,11 +4,14 @@ from typing import Union
class Operation(object):
def __init__(self, reason=None): # type: (Union[str, None]) -> None
def __init__(
self, reason=None, priority=0
): # type: (Union[str, None], int) -> None
self._reason = reason
self._skipped = False
self._skip_reason = None
self._priority = priority
@property
def job_type(self): # type: () -> str
......@@ -27,6 +30,10 @@ class Operation(object):
return self._skip_reason
@property
def priority(self): # type: () -> int
return self._priority
@property
def package(self):
raise NotImplementedError()
......
......@@ -2,8 +2,8 @@ from .operation import Operation
class Uninstall(Operation):
def __init__(self, package, reason=None):
super(Uninstall, self).__init__(reason)
def __init__(self, package, reason=None, priority=float("inf")):
super(Uninstall, self).__init__(reason, priority=priority)
self._package = package
......
......@@ -2,11 +2,11 @@ from .operation import Operation
class Update(Operation):
def __init__(self, initial, target, reason=None):
def __init__(self, initial, target, reason=None, priority=0):
self._initial_package = initial
self._target_package = target
super(Update, self).__init__(reason)
super(Update, self).__init__(reason, priority=priority)
@property
def initial_package(self):
......
......@@ -10,6 +10,10 @@ from clikit.io import ConsoleIO
from poetry.core.packages import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
from poetry.installation.operations.operation import Operation
from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage
......@@ -19,10 +23,6 @@ from poetry.utils.env import Env
from .exceptions import OverrideNeeded
from .exceptions import SolverProblemError
from .operations import Install
from .operations import Uninstall
from .operations import Update
from .operations.operation import Operation
from .provider import Provider
......@@ -78,7 +78,7 @@ class Solver:
)
operations = []
for package in packages:
for i, package in enumerate(packages):
installed = False
for pkg in self._installed.packages:
if package.name == pkg.name:
......@@ -113,23 +113,27 @@ class Solver:
package.source_reference
)
):
operations.append(Update(pkg, package))
operations.append(Update(pkg, package, priority=depths[i]))
else:
operations.append(
Install(package).skip("Already installed")
)
elif package.version != pkg.version:
# Checking version
operations.append(Update(pkg, package))
operations.append(Update(pkg, package, priority=depths[i]))
elif pkg.source_type and package.source_type != pkg.source_type:
operations.append(Update(pkg, package))
operations.append(Update(pkg, package, priority=depths[i]))
else:
operations.append(Install(package).skip("Already installed"))
operations.append(
Install(package, priority=depths[i]).skip(
"Already installed"
)
)
break
if not installed:
operations.append(Install(package))
operations.append(Install(package, priority=depths[i]))
# Checking for removals
for pkg in self._locked.packages:
......@@ -165,15 +169,7 @@ class Solver:
operations.append(Uninstall(installed))
return sorted(
operations,
key=lambda o: (
o.job_type == "uninstall",
# Packages to be uninstalled have no depth so we default to 0
# since it actually doesn't matter since removals are always on top.
-depths[packages.index(o.package)] if o.job_type != "uninstall" else 0,
o.package.name,
o.package.version,
),
operations, key=lambda o: (-o.priority, o.package.name, o.package.version,),
)
def solve_in_compatibility_mode(self, overrides, use_latest=None):
......
......@@ -90,6 +90,7 @@ class InstalledRepository(Repository):
# TODO: handle multiple source directories?
package.source_type = "directory"
package.source_url = paths.pop().as_posix()
continue
src_path = env.path / "src"
......
......@@ -304,6 +304,13 @@ class LegacyRepository(PyPiRepository):
return package
def find_links_for_package(self, package):
page = self._get("/{}/".format(package.name.replace(".", "-")))
if page is None:
return []
return list(page.links_for_version(package.version))
def _get_release_info(self, name, version): # type: (str, str) -> dict
page = self._get("/{}/".format(canonicalize_name(name).replace(".", "-")))
if page is None:
......
......@@ -33,7 +33,15 @@ class Pool(BaseRepository):
def has_default(self): # type: () -> bool
return self._default
def has_repository(self, name): # type: (str) -> bool
name = name.lower() if name is not None else None
return name in self._lookup
def repository(self, name): # type: (str) -> Repository
if name is not None:
name = name.lower()
if name in self._lookup:
return self._repositories[self._lookup[name]]
......@@ -45,6 +53,9 @@ class Pool(BaseRepository):
"""
Adds a repository to the pool.
"""
repository_name = (
repository.name.lower() if repository.name is not None else None
)
if default:
if self.has_default():
raise ValueError("Only one repository can be the default")
......@@ -57,17 +68,17 @@ class Pool(BaseRepository):
if self._secondary_start_idx is not None:
self._secondary_start_idx += 1
self._lookup[repository.name] = 0
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
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
self._lookup[repository_name] = len(self._repositories) - 1
else:
self._repositories.insert(self._secondary_start_idx, repository)
......@@ -77,12 +88,15 @@ class Pool(BaseRepository):
self._lookup[name] += 1
self._lookup[repository.name] = self._secondary_start_idx
self._lookup[repository_name] = self._secondary_start_idx
self._secondary_start_idx += 1
return self
def remove_repository(self, repository_name): # type: (str) -> Pool
if repository_name is not None:
repository_name = repository_name.lower()
idx = self._lookup.get(repository_name)
if idx is not None:
del self._repositories[idx]
......@@ -95,6 +109,9 @@ class Pool(BaseRepository):
def package(
self, name, version, extras=None, repository=None
): # type: (str, str, List[str], str) -> "Package"
if repository is not None:
repository = repository.lower()
if (
repository is not None
and repository not in self._lookup
......@@ -104,9 +121,7 @@ class Pool(BaseRepository):
if repository is not None and not self._ignore_repository_names:
try:
return self._repositories[self._lookup[repository]].package(
name, version, extras=extras
)
return self.repository(repository).package(name, version, extras=extras)
except PackageNotFound:
pass
else:
......@@ -131,6 +146,9 @@ class Pool(BaseRepository):
allow_prereleases=False,
repository=None,
):
if repository is not None:
repository = repository.lower()
if (
repository is not None
and repository not in self._lookup
......@@ -139,7 +157,7 @@ class Pool(BaseRepository):
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(
return self.repository(repository).find_packages(
name, constraint, extras=extras, allow_prereleases=allow_prereleases
)
......
......@@ -241,6 +241,18 @@ class PyPiRepository(RemoteRepository):
return PackageInfo.load(cached)
def find_links_for_package(self, package):
json_data = self._get("pypi/{}/{}/json".format(package.name, package.version))
if json_data is None:
return []
links = []
for url in json_data["urls"]:
h = "sha256={}".format(url["digests"]["sha256"])
links.append(Link(url["url"] + "#" + h))
return links
def _get_release_info(self, name, version): # type: (str, str) -> dict
self._log("Getting info for {} ({}) from PyPI".format(name, version), "debug")
......
......@@ -6,10 +6,10 @@ from .base_repository import BaseRepository
class Repository(BaseRepository):
def __init__(self, packages=None):
def __init__(self, packages=None, name=None):
super(Repository, self).__init__()
self._name = None
self._name = name
if packages is None:
packages = []
......@@ -115,6 +115,9 @@ class Repository(BaseRepository):
if index is not None:
del self._packages[index]
def find_links_for_package(self, package):
return []
def search(self, query):
results = []
......
......@@ -23,6 +23,11 @@ try:
except ImportError:
import urlparse
try:
from os import cpu_count
except ImportError: # Python 2
from multiprocessing import cpu_count
try: # Python 2
long = long
unicode = unicode
......@@ -50,6 +55,14 @@ else:
shell_quote = shlex.quote
if PY34:
from importlib.machinery import EXTENSION_SUFFIXES
else:
from imp import get_suffixes
EXTENSION_SUFFIXES = [suffix[0] for suffix in get_suffixes()]
if PY35:
from pathlib import Path
else:
......
......@@ -7,7 +7,7 @@ import re
import shutil
import sys
import sysconfig
import warnings
import textwrap
from contextlib import contextmanager
from typing import Any
......@@ -20,6 +20,12 @@ import tomlkit
from clikit.api.io import IO
import packaging.tags
from packaging.tags import Tag
from packaging.tags import interpreter_name
from packaging.tags import interpreter_version
from packaging.tags import sys_tags
from poetry.core.semver import parse_constraint
from poetry.core.semver.version import Version
from poetry.core.version.markers import BaseMarker
......@@ -39,6 +45,36 @@ import json
import os
import platform
import sys
import sysconfig
INTERPRETER_SHORT_NAMES = {
"python": "py",
"cpython": "cp",
"pypy": "pp",
"ironpython": "ip",
"jython": "jy",
}
def interpreter_version():
version = sysconfig.get_config_var("interpreter_version")
if version:
version = str(version)
else:
version = _version_nodot(sys.version_info[:2])
return version
def _version_nodot(version):
# type: (PythonVersion) -> str
if any(v >= 10 for v in version):
sep = "_"
else:
sep = ""
return sep.join(map(str, version))
if hasattr(sys, "implementation"):
info = sys.implementation.version
......@@ -50,7 +86,7 @@ if hasattr(sys, "implementation"):
implementation_name = sys.implementation.name
else:
iver = "0"
implementation_name = ""
implementation_name = platform.python_implementation().lower()
env = {
"implementation_name": implementation_name,
......@@ -65,6 +101,9 @@ env = {
"python_version": platform.python_version()[:3],
"sys_platform": sys.platform,
"version_info": tuple(sys.version_info),
# Extra information
"interpreter_name": INTERPRETER_SHORT_NAMES.get(implementation_name, implementation_name),
"interpreter_version": interpreter_version(),
}
print(json.dumps(env))
......@@ -82,12 +121,6 @@ else:
print(sys.prefix)
"""
GET_CONFIG_VAR = """\
import sysconfig
print(sysconfig.get_config_var("{config_var}")),
"""
GET_PYTHON_VERSION = """\
import sys
......@@ -742,6 +775,7 @@ class Env(object):
self._pip_version = None
self._site_packages = None
self._paths = None
self._supported_tags = None
@property
def path(self): # type: () -> Path
......@@ -813,6 +847,13 @@ class Env(object):
return self._paths
@property
def supported_tags(self): # type: () -> List[Tag]
if self._supported_tags is None:
self._supported_tags = self.get_supported_tags()
return self._supported_tags
@classmethod
def get_base_prefix(cls): # type: () -> Path
if hasattr(sys, "real_prefix"):
......@@ -835,7 +876,7 @@ class Env(object):
def get_pip_command(self): # type: () -> List[str]
raise NotImplementedError()
def config_var(self, var): # type: (str) -> Any
def get_supported_tags(self): # type: () -> List[Tag]
raise NotImplementedError()
def get_pip_version(self): # type: () -> Version
......@@ -987,6 +1028,9 @@ class SystemEnv(Env):
return paths
def get_supported_tags(self): # type: () -> List[Tag]
return list(sys_tags())
def get_marker_env(self): # type: () -> Dict[str, Any]
if hasattr(sys, "implementation"):
info = sys.implementation.version
......@@ -1015,16 +1059,11 @@ class SystemEnv(Env):
),
"sys_platform": sys.platform,
"version_info": sys.version_info,
# Extra information
"interpreter_name": interpreter_name(),
"interpreter_version": interpreter_version(),
}
def config_var(self, var): # type: (str) -> Any
try:
return sysconfig.get_config_var(var)
except IOError as e:
warnings.warn("{0}".format(e), RuntimeWarning)
return
def get_pip_version(self): # type: () -> Version
from pip import __version__
......@@ -1068,29 +1107,41 @@ class VirtualEnv(Env):
# so assume that we have a functional pip
return [self._bin("pip")]
def get_supported_tags(self): # type: () -> List[Tag]
file_path = Path(packaging.tags.__file__)
if file_path.suffix == ".pyc":
# Python 2
file_path = file_path.with_suffix(".py")
with file_path.open(encoding="utf-8") as f:
script = decode(f.read())
script = script.replace(
"from ._typing import TYPE_CHECKING, cast",
"TYPE_CHECKING = False\ncast = lambda type_, value: value",
)
script = script.replace(
"from ._typing import MYPY_CHECK_RUNNING, cast",
"MYPY_CHECK_RUNNING = False\ncast = lambda type_, value: value",
)
script += textwrap.dedent(
"""
import json
print(json.dumps([(t.interpreter, t.abi, t.platform) for t in sys_tags()]))
"""
)
output = self.run("python", "-", input_=script)
return [Tag(*t) for t in json.loads(output)]
def get_marker_env(self): # type: () -> Dict[str, Any]
output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO)
return json.loads(output)
def config_var(self, var): # type: (str) -> Any
try:
value = self.run(
"python", "-", input_=GET_CONFIG_VAR.format(config_var=var)
).strip()
except EnvCommandError as e:
warnings.warn("{0}".format(e), RuntimeWarning)
return None
if value == "None":
value = None
elif value == "1":
value = 1
elif value == "0":
value = 0
return value
def get_pip_version(self): # type: () -> Version
output = self.run_pip("--version").strip()
m = re.match("pip (.+?)(?: from .+)?$", output)
......@@ -1188,7 +1239,7 @@ class MockEnv(NullEnv):
pip_version="19.1",
sys_path=None,
marker_env=None,
config_vars=None,
supported_tags=None,
**kwargs
):
super(MockEnv, self).__init__(**kwargs)
......@@ -1201,15 +1252,7 @@ class MockEnv(NullEnv):
self._pip_version = Version.parse(pip_version)
self._sys_path = sys_path
self._mock_marker_env = marker_env
self._config_vars = config_vars
@property
def version_info(self): # type: () -> Tuple[int]
return self._version_info
@property
def python_implementation(self): # type: () -> str
return self._python_implementation
self._supported_tags = supported_tags
@property
def platform(self): # type: () -> str
......@@ -1240,17 +1283,12 @@ class MockEnv(NullEnv):
marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2])
marker_env["python_full_version"] = ".".join(str(v) for v in self._version_info)
marker_env["sys_platform"] = self._platform
marker_env["interpreter_name"] = self._python_implementation.lower()
marker_env["interpreter_version"] = "cp" + "".join(
str(v) for v in self._version_info[:2]
)
return marker_env
def is_venv(self): # type: () -> bool
return self._is_venv
def config_var(self, var): # type: (str) -> Any
if self._config_vars is None:
return super().config_var(var)
else:
try:
return self._config_vars[var]
except KeyError:
return None
......@@ -37,12 +37,15 @@ html5lib = "^1.0"
shellingham = "^1.1"
tomlkit = "^0.5.11"
pexpect = "^4.7.0"
packaging = "^20.4"
# The typing module is not in the stdlib in Python 2.7
typing = { version = "^3.6", python = "~2.7" }
# Use pathlib2 for Python 2.7
pathlib2 = { version = "^2.3", python = "~2.7" }
# Use futures on Python 2.7
futures = { version = "^3.3.0", python = "~2.7" }
# Use glob2 for Python 2.7 and 3.4
glob2 = { version = "^0.6", python = "~2.7" }
# Use virtualenv for Python 2.7 since venv does not exist
......
......@@ -104,11 +104,13 @@ def git_mock(mocker):
@pytest.fixture
def http():
httpretty.enable()
httpretty.reset()
httpretty.enable(allow_net_connect=False)
yield httpretty
httpretty.disable()
httpretty.activate()
httpretty.reset()
@pytest.fixture
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys
import pytest
import pytest
from cleo.testers import CommandTester
from poetry.core.semver import Version
from poetry.installation.installer import Installer
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.utils._compat import Path
from tests.helpers import get_dependency
from tests.helpers import get_package
@pytest.fixture()
def tester(app, poetry, config, executor, env):
tester = CommandTester(app.find("add"))
executor._io = tester.io
installer = Installer(
tester.io,
env,
poetry.package,
poetry.locker,
poetry.pool,
config,
executor=executor,
)
installer.use_executor(True)
tester._command.set_installer(installer)
tester._command.set_env(env)
return tester
@pytest.fixture()
def old_tester(tester):
tester._command.installer.use_executor(False)
return tester
def test_add_no_constraint(app, repo, tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy")
expected = """\
Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dependencies"]
assert content["dependencies"]["cachy"] == "^0.2.0"
def test_add_equal_constraint(app, repo, tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy==0.1.0")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.1.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
def test_add_greater_constraint(app, repo, tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy>=0.1.0")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
def test_add_constraint_with_extras(app, repo, tester):
cachy1 = get_package("cachy", "0.1.0")
cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy1.requires = [msgpack_dep]
repo.add_package(get_package("cachy", "0.2.0"))
repo.add_package(cachy1)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy[msgpack]>=0.1.0,<0.2.0")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing msgpack-python (0.5.3)
• Installing cachy (0.1.0)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
def test_add_constraint_dependencies(app, repo, tester):
cachy2 = get_package("cachy", "0.2.0")
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6")
cachy2.requires = [msgpack_dep]
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy=0.2.0")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing msgpack-python (0.5.3)
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
def test_add_git_constraint(app, repo, tester, tmp_venv):
tester._command.set_env(tmp_venv)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("git+https://github.com/demo/demo.git")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"git": "https://github.com/demo/demo.git"
}
def test_add_git_constraint_with_poetry(app, repo, tester, tmp_venv):
tester._command.set_env(tmp_venv)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("git+https://github.com/demo/pyproject-demo.git")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
def test_add_git_constraint_with_extras(app, repo, tester, tmp_venv):
tester._command.set_env(tmp_venv)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute("git+https://github.com/demo/demo.git[foo,bar]")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 4 installs, 0 updates, 0 removals
• Installing cleo (0.6.5)
• Installing pendulum (1.4.4)
• Installing tomlkit (0.5.5)
• Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert 4 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"git": "https://github.com/demo/demo.git",
"extras": ["foo", "bar"],
}
def test_add_git_ssh_constraint(app, repo, tester, tmp_venv):
tester._command.set_env(tmp_venv)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("git+ssh://git@github.com/demo/demo.git@develop")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"git": "ssh://git@github.com/demo/demo.git",
"rev": "develop",
}
def test_add_directory_constraint(app, repo, tester, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("../git/github.com/demo/demo")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.2 ../git/github.com/demo/demo)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {"path": "../git/github.com/demo/demo"}
def test_add_directory_with_poetry(app, repo, tester, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../git/github.com/demo/pyproject-demo")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.2 ../git/github.com/demo/pyproject-demo)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
def test_add_file_constraint_wheel(app, repo, tester, mocker, poetry):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = poetry.file.parent
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../distributions/demo-0.1.0-py2.py3-none-any.whl")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.0 ../distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"path": "../distributions/demo-0.1.0-py2.py3-none-any.whl"
}
def test_add_file_constraint_sdist(app, repo, tester, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../distributions/demo-0.1.0.tar.gz")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.0 ../distributions/demo-0.1.0.tar.gz)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"path": "../distributions/demo-0.1.0.tar.gz"
}
def test_add_constraint_with_extras_option(app, repo, tester):
cachy2 = get_package("cachy", "0.2.0")
cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy2.requires = [msgpack_dep]
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy=0.2.0 --extras msgpack")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing msgpack-python (0.5.3)
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dependencies"]
assert content["dependencies"]["cachy"] == {
"version": "0.2.0",
"extras": ["msgpack"],
}
def test_add_url_constraint_wheel(app, repo, tester, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute(
"https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
)
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
• Installing pendulum (1.4.4)
• Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
assert expected == tester.io.fetch_output()
assert 2 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
}
def test_add_url_constraint_wheel_with_extras(app, repo, tester, mocker):
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute(
"https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl[foo,bar]"
)
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 4 installs, 0 updates, 0 removals
• Installing cleo (0.6.5)
• Installing pendulum (1.4.4)
• Installing tomlkit (0.5.5)
• Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
assert expected == tester.io.fetch_output()
assert 4 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
"extras": ["foo", "bar"],
}
def test_add_constraint_with_python(app, repo, tester):
cachy2 = get_package("cachy", "0.2.0")
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
tester.execute("cachy=0.2.0 --python >=2.7")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dependencies"]
assert content["dependencies"]["cachy"] == {"version": "0.2.0", "python": ">=2.7"}
def test_add_constraint_with_platform(app, repo, tester, env):
platform = sys.platform
env._platform = platform
cachy2 = get_package("cachy", "0.2.0")
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
tester.execute("cachy=0.2.0 --platform {} -vvv".format(platform))
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dependencies"]
assert content["dependencies"]["cachy"] == {
"version": "0.2.0",
"platform": platform,
}
def test_add_constraint_with_source(app, poetry, tester):
repo = LegacyRepository(name="my-index", url="https://my-index.fake")
repo.add_package(get_package("cachy", "0.2.0"))
repo._cache.store("matches").put("cachy:0.2.0", [Version.parse("0.2.0")], 5)
poetry.pool.add_repository(repo)
tester.execute("cachy=0.2.0 --source my-index")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "cachy" in content["dependencies"]
assert content["dependencies"]["cachy"] == {
"version": "0.2.0",
"source": "my-index",
}
def test_add_constraint_with_source_that_does_not_exist(app, tester):
with pytest.raises(ValueError) as e:
tester.execute("foo --source i-dont-exist")
assert 'Repository "i-dont-exist" does not exist.' == str(e.value)
def test_add_constraint_not_found_with_source(app, poetry, mocker, tester):
repo = LegacyRepository(name="my-index", url="https://my-index.fake")
mocker.patch.object(repo, "find_packages", return_value=[])
poetry.pool.add_repository(repo)
pypi = poetry.pool.repositories[0]
pypi.add_package(get_package("cachy", "0.2.0"))
with pytest.raises(ValueError) as e:
tester.execute("cachy --source my-index")
assert "Could not find a matching version of package cachy" == str(e.value)
def test_add_to_section_that_does_no_exist_yet(app, repo, tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy --dev")
expected = """\
Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
from cleo.testers import CommandTester
Package operations: 1 install, 0 updates, 0 removals
from poetry.core.semver import Version
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.utils._compat import Path
from tests.helpers import get_dependency
from tests.helpers import get_package
• Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
def test_add_no_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
assert "cachy" in content["dev-dependencies"]
assert content["dev-dependencies"]["cachy"] == "^0.2.0"
repo.add_package(get_package("cachy", "0.1.0"))
def test_add_should_not_select_prereleases(app, repo, tester):
repo.add_package(get_package("pyyaml", "3.13"))
repo.add_package(get_package("pyyaml", "4.2b2"))
tester.execute("pyyaml")
expected = """\
Using version ^3.13 for pyyaml
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing pyyaml (3.13)
"""
assert expected == tester.io.fetch_output()
assert 1 == tester._command.installer.executor.installations_count
content = app.poetry.file.read()["tool"]["poetry"]
assert "pyyaml" in content["dependencies"]
assert content["dependencies"]["pyyaml"] == "^3.13"
def test_add_should_display_an_error_when_adding_existing_package_with_no_constraint(
app, repo, tester
):
content = app.poetry.file.read()
content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0"
app.poetry.file.write(content)
repo.add_package(get_package("foo", "1.1.2"))
with pytest.raises(ValueError) as e:
tester.execute("foo")
assert "Package foo is already present" == str(e.value)
def test_add_should_work_when_adding_existing_package_with_latest_constraint(
app, repo, tester
):
content = app.poetry.file.read()
content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0"
app.poetry.file.write(content)
repo.add_package(get_package("foo", "1.1.2"))
tester.execute("foo@latest")
expected = """\
Using version ^1.1.2 for foo
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing foo (1.1.2)
"""
assert expected in tester.io.fetch_output()
content = app.poetry.file.read()["tool"]["poetry"]
assert "foo" in content["dependencies"]
assert content["dependencies"]["foo"] == "^1.1.2"
def test_add_chooses_prerelease_if_only_prereleases_are_available(app, repo, tester):
repo.add_package(get_package("foo", "1.2.3b0"))
repo.add_package(get_package("foo", "1.2.3b1"))
tester.execute("foo")
expected = """\
Using version ^1.2.3-beta.1 for foo
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing foo (1.2.3b1)
"""
assert expected in tester.io.fetch_output()
def test_add_prefers_stable_releases(app, repo, tester):
repo.add_package(get_package("foo", "1.2.3"))
repo.add_package(get_package("foo", "1.2.4b1"))
tester.execute("foo")
expected = """\
Using version ^1.2.3 for foo
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing foo (1.2.3)
"""
assert expected in tester.io.fetch_output()
def test_add_with_lock(app, repo, tester):
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy")
tester.execute("cachy --lock")
expected = """\
Using version ^0.2.0 for cachy
......@@ -27,14 +803,31 @@ Updating dependencies
Resolving dependencies...
Writing lock file
"""
assert expected == tester.io.fetch_output()
def test_add_no_constraint_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
old_tester.execute("cachy")
expected = """\
Using version ^0.2.0 for cachy
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
......@@ -44,14 +837,11 @@ Package operations: 1 install, 0 updates, 0 removals
assert content["dependencies"]["cachy"] == "^0.2.0"
def test_add_equal_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_equal_constraint_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy==0.1.0")
old_tester.execute("cachy==0.1.0")
expected = """\
......@@ -60,25 +850,21 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.1.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
def test_add_greater_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_greater_constraint_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy>=0.1.0")
old_tester.execute("cachy>=0.1.0")
expected = """\
......@@ -87,21 +873,17 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
def test_add_constraint_with_extras(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_constraint_with_extras_old_installer(app, repo, installer, old_tester):
cachy1 = get_package("cachy", "0.1.0")
cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
......@@ -111,7 +893,7 @@ def test_add_constraint_with_extras(app, repo, installer):
repo.add_package(cachy1)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy[msgpack]>=0.1.0,<0.2.0")
old_tester.execute("cachy[msgpack]>=0.1.0,<0.2.0")
expected = """\
......@@ -120,22 +902,18 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing msgpack-python (0.5.3)
- Installing cachy (0.1.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
def test_add_constraint_dependencies(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_constraint_dependencies_old_installer(app, repo, installer, old_tester):
cachy2 = get_package("cachy", "0.2.0")
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6")
cachy2.requires = [msgpack_dep]
......@@ -144,7 +922,7 @@ def test_add_constraint_dependencies(app, repo, installer):
repo.add_package(cachy2)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy=0.2.0")
old_tester.execute("cachy=0.2.0")
expected = """\
......@@ -153,26 +931,22 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing msgpack-python (0.5.3)
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
def test_add_git_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_git_constraint_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("git+https://github.com/demo/demo.git")
old_tester.execute("git+https://github.com/demo/demo.git")
expected = """\
......@@ -181,14 +955,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -200,13 +973,10 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_git_constraint_with_poetry(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_git_constraint_with_poetry_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("git+https://github.com/demo/pyproject-demo.git")
old_tester.execute("git+https://github.com/demo/pyproject-demo.git")
expected = """\
......@@ -215,27 +985,23 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
def test_add_git_constraint_with_extras(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_git_constraint_with_extras_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute("git+https://github.com/demo/demo.git[foo,bar]")
old_tester.execute("git+https://github.com/demo/demo.git[foo,bar]")
expected = """\
......@@ -244,7 +1010,6 @@ Resolving dependencies...
Writing lock file
Package operations: 4 installs, 0 updates, 0 removals
- Installing cleo (0.6.5)
......@@ -253,7 +1018,7 @@ Package operations: 4 installs, 0 updates, 0 removals
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 4
......@@ -266,14 +1031,11 @@ Package operations: 4 installs, 0 updates, 0 removals
}
def test_add_git_ssh_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_git_ssh_constraint_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("git+ssh://git@github.com/demo/demo.git@develop")
old_tester.execute("git+ssh://git@github.com/demo/demo.git@develop")
expected = """\
......@@ -282,14 +1044,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -302,17 +1063,16 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_directory_constraint(app, repo, installer, mocker):
def test_add_directory_constraint_old_installer(
app, repo, installer, mocker, old_tester
):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("../git/github.com/demo/demo")
old_tester.execute("../git/github.com/demo/demo")
expected = """\
......@@ -321,14 +1081,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 ../git/github.com/demo/demo)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -338,16 +1097,15 @@ Package operations: 2 installs, 0 updates, 0 removals
assert content["dependencies"]["demo"] == {"path": "../git/github.com/demo/demo"}
def test_add_directory_with_poetry(app, repo, installer, mocker):
def test_add_directory_with_poetry_old_installer(
app, repo, installer, mocker, old_tester
):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../git/github.com/demo/pyproject-demo")
old_tester.execute("../git/github.com/demo/pyproject-demo")
expected = """\
......@@ -356,28 +1114,26 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 ../git/github.com/demo/pyproject-demo)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
def test_add_file_constraint_wheel(app, repo, installer, mocker):
def test_add_file_constraint_wheel_old_installer(
app, repo, installer, mocker, old_tester
):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../distributions/demo-0.1.0-py2.py3-none-any.whl")
old_tester.execute("../distributions/demo-0.1.0-py2.py3-none-any.whl")
expected = """\
......@@ -386,14 +1142,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.0 ../distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -405,16 +1160,15 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_file_constraint_sdist(app, repo, installer, mocker):
def test_add_file_constraint_sdist_old_installer(
app, repo, installer, mocker, old_tester
):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../distributions/demo-0.1.0.tar.gz")
old_tester.execute("../distributions/demo-0.1.0.tar.gz")
expected = """\
......@@ -423,14 +1177,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.0 ../distributions/demo-0.1.0.tar.gz)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -442,10 +1195,9 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_constraint_with_extras_option(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_constraint_with_extras_option_old_installer(
app, repo, installer, old_tester
):
cachy2 = get_package("cachy", "0.2.0")
cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
......@@ -455,7 +1207,7 @@ def test_add_constraint_with_extras_option(app, repo, installer):
repo.add_package(cachy2)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy=0.2.0 --extras msgpack")
old_tester.execute("cachy=0.2.0 --extras msgpack")
expected = """\
......@@ -464,14 +1216,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing msgpack-python (0.5.3)
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -484,16 +1235,15 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_url_constraint_wheel(app, repo, installer, mocker):
def test_add_url_constraint_wheel_old_installer(
app, repo, installer, mocker, old_tester
):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute(
old_tester.execute(
"https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
)
......@@ -504,14 +1254,13 @@ Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 2
......@@ -523,15 +1272,14 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_url_constraint_wheel_with_extras(app, repo, installer, mocker):
command = app.find("add")
tester = CommandTester(command)
def test_add_url_constraint_wheel_with_extras_old_installer(
app, repo, installer, old_tester
):
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute(
old_tester.execute(
"https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl[foo,bar]"
)
......@@ -542,7 +1290,6 @@ Resolving dependencies...
Writing lock file
Package operations: 4 installs, 0 updates, 0 removals
- Installing cleo (0.6.5)
......@@ -551,7 +1298,7 @@ Package operations: 4 installs, 0 updates, 0 removals
- Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 4
......@@ -564,16 +1311,13 @@ Package operations: 4 installs, 0 updates, 0 removals
}
def test_add_constraint_with_python(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_constraint_with_python_old_installer(app, repo, installer, old_tester):
cachy2 = get_package("cachy", "0.2.0")
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
tester.execute("cachy=0.2.0 --python >=2.7")
old_tester.execute("cachy=0.2.0 --python >=2.7")
expected = """\
......@@ -582,13 +1326,12 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
......@@ -598,18 +1341,18 @@ Package operations: 1 install, 0 updates, 0 removals
assert content["dependencies"]["cachy"] == {"version": "0.2.0", "python": ">=2.7"}
def test_add_constraint_with_platform(app, repo, installer, env):
def test_add_constraint_with_platform_old_installer(
app, repo, installer, env, old_tester
):
platform = sys.platform
env._platform = platform
command = app.find("add")
tester = CommandTester(command)
cachy2 = get_package("cachy", "0.2.0")
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(cachy2)
tester.execute("cachy=0.2.0 --platform {} -vvv".format(platform))
old_tester.execute("cachy=0.2.0 --platform {} -vvv".format(platform))
expected = """\
......@@ -618,13 +1361,12 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
......@@ -637,17 +1379,14 @@ Package operations: 1 install, 0 updates, 0 removals
}
def test_add_constraint_with_source(app, poetry, installer):
def test_add_constraint_with_source_old_installer(app, poetry, installer, old_tester):
repo = LegacyRepository(name="my-index", url="https://my-index.fake")
repo.add_package(get_package("cachy", "0.2.0"))
repo._cache.store("matches").put("cachy:0.2.0", [Version.parse("0.2.0")], 5)
poetry.pool.add_repository(repo)
command = app.find("add")
tester = CommandTester(command)
tester.execute("cachy=0.2.0 --source my-index")
old_tester.execute("cachy=0.2.0 --source my-index")
expected = """\
......@@ -656,13 +1395,12 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
......@@ -675,17 +1413,16 @@ Package operations: 1 install, 0 updates, 0 removals
}
def test_add_constraint_with_source_that_does_not_exist(app):
command = app.find("add")
tester = CommandTester(command)
def test_add_constraint_with_source_that_does_not_exist_old_installer(app, old_tester):
with pytest.raises(ValueError) as e:
tester.execute("foo --source i-dont-exist")
old_tester.execute("foo --source i-dont-exist")
assert 'Repository "i-dont-exist" does not exist.' == str(e.value)
def test_add_constraint_not_found_with_source(app, poetry, mocker):
def test_add_constraint_not_found_with_source_old_installer(
app, poetry, mocker, old_tester
):
repo = LegacyRepository(name="my-index", url="https://my-index.fake")
mocker.patch.object(repo, "find_packages", return_value=[])
......@@ -694,23 +1431,19 @@ def test_add_constraint_not_found_with_source(app, poetry, mocker):
pypi = poetry.pool.repositories[0]
pypi.add_package(get_package("cachy", "0.2.0"))
command = app.find("add")
tester = CommandTester(command)
with pytest.raises(ValueError) as e:
tester.execute("cachy --source my-index")
old_tester.execute("cachy --source my-index")
assert "Could not find a matching version of package cachy" == str(e.value)
def test_add_to_section_that_does_no_exist_yet(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_to_section_that_does_no_exist_yet_old_installer(
app, repo, installer, old_tester
):
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy --dev")
old_tester.execute("cachy --dev")
expected = """\
Using version ^0.2.0 for cachy
......@@ -720,13 +1453,12 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
......@@ -736,14 +1468,13 @@ Package operations: 1 install, 0 updates, 0 removals
assert content["dev-dependencies"]["cachy"] == "^0.2.0"
def test_add_should_not_select_prereleases(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_should_not_select_prereleases_old_installer(
app, repo, installer, old_tester
):
repo.add_package(get_package("pyyaml", "3.13"))
repo.add_package(get_package("pyyaml", "4.2b2"))
tester.execute("pyyaml")
old_tester.execute("pyyaml")
expected = """\
Using version ^3.13 for pyyaml
......@@ -753,13 +1484,12 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing pyyaml (3.13)
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
assert len(installer.installs) == 1
......@@ -769,35 +1499,31 @@ Package operations: 1 install, 0 updates, 0 removals
assert content["dependencies"]["pyyaml"] == "^3.13"
def test_add_should_display_an_error_when_adding_existing_package_with_no_constraint(
app, repo, installer
def test_add_should_display_an_error_when_adding_existing_package_with_no_constraint_old_installer(
app, repo, installer, old_tester
):
content = app.poetry.file.read()
content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0"
app.poetry.file.write(content)
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("foo", "1.1.2"))
with pytest.raises(ValueError) as e:
tester.execute("foo")
old_tester.execute("foo")
assert "Package foo is already present" == str(e.value)
def test_add_should_work_when_adding_existing_package_with_latest_constraint(
app, repo, installer
def test_add_should_work_when_adding_existing_package_with_latest_constraint_old_installer(
app, repo, installer, old_tester
):
content = app.poetry.file.read()
content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0"
app.poetry.file.write(content)
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("foo", "1.1.2"))
tester.execute("foo@latest")
old_tester.execute("foo@latest")
expected = """\
Using version ^1.1.2 for foo
......@@ -807,13 +1533,12 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing foo (1.1.2)
"""
assert expected in tester.io.fetch_output()
assert expected in old_tester.io.fetch_output()
content = app.poetry.file.read()["tool"]["poetry"]
......@@ -821,14 +1546,13 @@ Package operations: 1 install, 0 updates, 0 removals
assert content["dependencies"]["foo"] == "^1.1.2"
def test_add_chooses_prerelease_if_only_prereleases_are_available(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_chooses_prerelease_if_only_prereleases_are_available_old_installer(
app, repo, installer, old_tester
):
repo.add_package(get_package("foo", "1.2.3b0"))
repo.add_package(get_package("foo", "1.2.3b1"))
tester.execute("foo")
old_tester.execute("foo")
expected = """\
Using version ^1.2.3-beta.1 for foo
......@@ -838,23 +1562,19 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing foo (1.2.3b1)
"""
assert expected in tester.io.fetch_output()
assert expected in old_tester.io.fetch_output()
def test_add_preferes_stable_releases(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_preferes_stable_releases_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("foo", "1.2.3"))
repo.add_package(get_package("foo", "1.2.4b1"))
tester.execute("foo")
old_tester.execute("foo")
expected = """\
Using version ^1.2.3 for foo
......@@ -864,22 +1584,18 @@ Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing foo (1.2.3)
"""
assert expected in tester.io.fetch_output()
assert expected in old_tester.io.fetch_output()
def test_add_with_lock(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
def test_add_with_lock_old_installer(app, repo, installer, old_tester):
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy --lock")
old_tester.execute("cachy --lock")
expected = """\
Using version ^0.2.0 for cachy
......@@ -890,4 +1606,4 @@ Resolving dependencies...
Writing lock file
"""
assert expected == tester.io.fetch_output()
assert expected == old_tester.io.fetch_output()
......@@ -13,6 +13,7 @@ def test_list_displays_default_value_if_not_set(app, config):
tester.execute("--list")
expected = """cache-dir = "/foo"
experimental.new-installer = true
virtualenvs.create = true
virtualenvs.in-project = false
virtualenvs.path = {path} # /foo{sep}virtualenvs
......@@ -32,6 +33,7 @@ def test_list_displays_set_get_setting(app, config):
tester.execute("--list")
expected = """cache-dir = "/foo"
experimental.new-installer = true
virtualenvs.create = false
virtualenvs.in-project = false
virtualenvs.path = {path} # /foo{sep}virtualenvs
......@@ -79,6 +81,7 @@ def test_list_displays_set_get_local_setting(app, config):
tester.execute("--list")
expected = """cache-dir = "/foo"
experimental.new-installer = true
virtualenvs.create = false
virtualenvs.in-project = false
virtualenvs.path = {path} # /foo{sep}virtualenvs
......
import os
import re
import pytest
from cleo import ApplicationTester
from poetry.console import Application as BaseApplication
from poetry.core.masonry.utils.helpers import escape_name
from poetry.core.masonry.utils.helpers import escape_version
from poetry.core.packages.utils.link import Link
from poetry.factory import Factory
from poetry.installation.executor import Executor as BaseExecutor
from poetry.installation.noop_installer import NoopInstaller
from poetry.io.null_io import NullIO
from poetry.packages import Locker as BaseLocker
from poetry.poetry import Poetry as BasePoetry
from poetry.repositories import Pool
......@@ -18,6 +24,42 @@ from poetry.utils.toml_file import TomlFile
from tests.helpers import mock_clone
class Executor(BaseExecutor):
def __init__(self, *args, **kwargs):
super(Executor, self).__init__(*args, **kwargs)
self._installs = []
self._updates = []
self._uninstalls = []
@property
def installations(self):
return self._installs
@property
def updates(self):
return self._updates
@property
def removals(self):
return self._uninstalls
def _do_execute_operation(self, operation):
super(Executor, self)._do_execute_operation(operation)
if not operation.skipped:
getattr(self, "_{}s".format(operation.job_type)).append(operation.package)
def _execute_install(self, operation):
return 0
def _execute_update(self, operation):
return 0
def _execute_remove(self, operation):
return 0
@pytest.fixture()
def installer():
return NoopInstaller()
......@@ -39,6 +81,9 @@ def setup(mocker, installer, installed, config, env):
p = mocker.patch("poetry.installation.installer.Installer._get_installer")
p.return_value = installer
# Do not run pip commands of the executor
mocker.patch("poetry.installation.executor.Executor.run_pip")
p = mocker.patch("poetry.installation.installer.Installer._get_installed")
p.return_value = installed
......@@ -144,10 +189,22 @@ class Repository(BaseRepository):
raise PackageNotFound("Package [{}] not found.".format(name))
return packages
def find_links_for_package(self, package):
return [
Link(
"https://foo.bar/files/{}-{}-py2.py3-none-any.whl".format(
escape_name(package.name), escape_version(package.version.text)
)
)
]
@pytest.fixture
def repo():
return Repository()
def repo(http):
http.register_uri(
http.GET, re.compile("^https?://foo.bar/(.+?)$"),
)
return Repository(name="foo")
@pytest.fixture
......@@ -188,3 +245,13 @@ def app(poetry):
@pytest.fixture
def app_tester(app):
return ApplicationTester(app)
@pytest.fixture
def new_installer_disabled(config):
config.merge({"experimental": {"new-installer": False}})
@pytest.fixture()
def executor(poetry, config, env):
return Executor(env, poetry.pool, config, NullIO())
import re
import pytest
from poetry.installation.authenticator import Authenticator
from poetry.io.null_io import NullIO
@pytest.fixture()
def mock_remote(http):
http.register_uri(
http.GET, re.compile("^https?://foo.bar/(.+?)$"),
)
def test_authenticator_uses_url_provided_credentials(config, mock_remote, http):
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": "bar", "password": "baz"}},
}
)
authenticator = Authenticator(config, NullIO())
authenticator.request("get", "https://foo001:bar002@foo.bar/files/foo-0.1.0.tar.gz")
request = http.last_request()
assert "Basic Zm9vMDAxOmJhcjAwMg==" == request.headers["Authorization"]
def test_authenticator_uses_credentials_from_config_if_not_provided(
config, mock_remote, http
):
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": "bar", "password": "baz"}},
}
)
authenticator = Authenticator(config, NullIO())
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz")
request = http.last_request()
assert "Basic YmFyOmJheg==" == request.headers["Authorization"]
def test_authenticator_uses_username_only_credentials(config, mock_remote, http):
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": "bar", "password": "baz"}},
}
)
authenticator = Authenticator(config, NullIO())
authenticator.request("get", "https://foo001@foo.bar/files/foo-0.1.0.tar.gz")
request = http.last_request()
assert "Basic Zm9vMDAxOg==" == request.headers["Authorization"]
def test_authenticator_uses_password_only_credentials(config, mock_remote, http):
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": "bar", "password": "baz"}},
}
)
authenticator = Authenticator(config, NullIO())
authenticator.request("get", "https://:bar002@foo.bar/files/foo-0.1.0.tar.gz")
request = http.last_request()
assert "Basic OmJhcjAwMg==" == request.headers["Authorization"]
def test_authenticator_uses_empty_strings_as_default_password(
config, mock_remote, http
):
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": "bar"}},
}
)
authenticator = Authenticator(config, NullIO())
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz")
request = http.last_request()
assert "Basic YmFyOg==" == request.headers["Authorization"]
def test_authenticator_uses_empty_strings_as_default_username(
config, mock_remote, http
):
config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar/simple/"}},
"http-basic": {"foo": {"username": None, "password": "bar"}},
}
)
authenticator = Authenticator(config, NullIO())
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz")
request = http.last_request()
assert "Basic OmJhcg==" == request.headers["Authorization"]
from packaging.tags import Tag
from poetry.core.packages.utils.link import Link
from poetry.installation.chef import Chef
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
def test_get_cached_archive_for_link(config, mocker):
chef = Chef(
config,
MockEnv(
version_info=(3, 8, 3),
marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"},
supported_tags=[
Tag("cp38", "cp38", "macosx_10_15_x86_64"),
Tag("py3", "none", "any"),
],
),
)
mocker.patch.object(
chef,
"get_cached_archives_for_link",
return_value=[
Link("file:///foo/demo-0.1.0-py2.py3-none-any"),
Link("file:///foo/demo-0.1.0.tar.gz"),
Link("file:///foo/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl"),
Link("file:///foo/demo-0.1.0-cp37-cp37-macosx_10_15_x86_64.whl"),
],
)
archive = chef.get_cached_archive_for_link(
Link("https://files.python-poetry.org/demo-0.1.0.tar.gz")
)
assert Link("file:///foo/demo-0.1.0-cp38-cp38-macosx_10_15_x86_64.whl") == archive
def test_get_cached_archives_for_link(config, mocker):
chef = Chef(
config,
MockEnv(
marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}
),
)
mocker.patch.object(
chef,
"get_cache_directory_for_link",
return_value=Path(__file__).parent.parent.joinpath("fixtures/distributions"),
)
archives = chef.get_cached_archives_for_link(
Link("https://files.python-poetry.org/demo-0.1.0.tar.gz")
)
assert 2 == len(archives)
def test_get_cache_directory_for_link(config):
chef = Chef(
config,
MockEnv(
marker_env={"interpreter_name": "cpython", "interpreter_version": "3.8.3"}
),
)
directory = chef.get_cache_directory_for_link(
Link("https://files.python-poetry.org/poetry-1.1.0.tar.gz")
)
expected = Path(
"/foo/artifacts/ba/63/13/283a3b3b7f95f05e9e6f84182d276f7bb0951d5b0cc24422b33f7a4648"
)
assert expected == directory
import re
import pytest
from packaging.tags import Tag
from poetry.core.packages.package import Package
from poetry.installation.chooser import Chooser
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pool import Pool
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
JSON_FIXTURES = (
Path(__file__).parent.parent / "repositories" / "fixtures" / "pypi.org" / "json"
)
LEGACY_FIXTURES = Path(__file__).parent.parent / "repositories" / "fixtures" / "legacy"
@pytest.fixture()
def env():
return MockEnv(
supported_tags=[
Tag("cp37", "cp37", "macosx_10_15_x86_64"),
Tag("py3", "none", "any"),
]
)
@pytest.fixture()
def mock_pypi(http):
def callback(request, uri, headers):
parts = uri.rsplit("/")
name = parts[-3]
version = parts[-2]
fixture = JSON_FIXTURES / name / (version + ".json")
if not fixture.exists():
fixture = JSON_FIXTURES / (name + ".json")
if not fixture.exists():
return
with fixture.open(encoding="utf-8") as f:
return [200, headers, f.read()]
http.register_uri(
http.GET, re.compile("^https://pypi.org/(.+?)/(.+?)/json$"), body=callback,
)
@pytest.fixture()
def mock_legacy(http):
def callback(request, uri, headers):
parts = uri.rsplit("/")
name = parts[-2]
fixture = LEGACY_FIXTURES / (name + ".html")
with fixture.open(encoding="utf-8") as f:
return [200, headers, f.read()]
http.register_uri(
http.GET, re.compile("^https://foo.bar/simple/(.+?)$"), body=callback,
)
@pytest.fixture()
def pool():
pool = Pool()
pool.add_repository(PyPiRepository(disable_cache=True))
pool.add_repository(
LegacyRepository("foo", "https://foo.bar/simple/", disable_cache=True)
)
return pool
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_universal_wheel_link_if_available(
env, mock_pypi, mock_legacy, source_type, pool
):
chooser = Chooser(pool, env)
package = Package("pytest", "3.5.0")
if source_type == "legacy":
package.source_type = "legacy"
package.source_reference = "foo"
package.source_url = "https://foo.bar/simple/"
link = chooser.choose_for(package)
assert "pytest-3.5.0-py2.py3-none-any.whl" == link.filename
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_specific_python_universal_wheel_link_if_available(
env, mock_pypi, mock_legacy, source_type, pool
):
chooser = Chooser(pool, env)
package = Package("isort", "4.3.4")
if source_type == "legacy":
package.source_type = "legacy"
package.source_reference = "foo"
package.source_url = "https://foo.bar/simple/"
link = chooser.choose_for(package)
assert "isort-4.3.4-py3-none-any.whl" == link.filename
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_system_specific_wheel_link_if_available(
mock_pypi, mock_legacy, source_type, pool
):
env = MockEnv(
supported_tags=[Tag("cp37", "cp37m", "win32"), Tag("py3", "none", "any")]
)
chooser = Chooser(pool, env)
package = Package("pyyaml", "3.13.0")
if source_type == "legacy":
package.source_type = "legacy"
package.source_reference = "foo"
package.source_url = "https://foo.bar/simple/"
link = chooser.choose_for(package)
assert "PyYAML-3.13-cp37-cp37m-win32.whl" == link.filename
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_sdist_if_no_compatible_wheel_link_is_available(
env, mock_pypi, mock_legacy, source_type, pool,
):
chooser = Chooser(pool, env)
package = Package("pyyaml", "3.13.0")
if source_type == "legacy":
package.source_type = "legacy"
package.source_reference = "foo"
package.source_url = "https://foo.bar/simple/"
link = chooser.choose_for(package)
assert "PyYAML-3.13.tar.gz" == link.filename
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_distributions_that_match_the_package_hashes(
env, mock_pypi, mock_legacy, source_type, pool,
):
chooser = Chooser(pool, env)
package = Package("isort", "4.3.4")
package.files = [
{
"hash": "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"filename": "isort-4.3.4.tar.gz",
}
]
if source_type == "legacy":
package.source_type = "legacy"
package.source_reference = "foo"
package.source_url = "https://foo.bar/simple/"
link = chooser.choose_for(package)
assert "isort-4.3.4.tar.gz" == link.filename
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import pytest
from clikit.api.formatter.style import Style
from clikit.io.buffered_io import BufferedIO
from poetry.config.config import Config
from poetry.core.packages.package import Package
from poetry.installation.executor import Executor
from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
from poetry.repositories.pool import Pool
from poetry.utils._compat import PY36
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
from tests.repositories.test_pypi_repository import MockRepository
@pytest.fixture()
def io():
io = BufferedIO()
io.formatter.add_style(Style("c1_dark").fg("cyan").dark())
io.formatter.add_style(Style("c2_dark").fg("default").bold().dark())
io.formatter.add_style(Style("success_dark").fg("green").dark())
io.formatter.add_style(Style("warning").fg("yellow"))
return io
@pytest.fixture()
def pool():
pool = Pool()
pool.add_repository(MockRepository())
return pool
@pytest.fixture()
def mock_file_downloads(http):
def callback(request, uri, headers):
fixture = Path(__file__).parent.parent.joinpath(
"fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"
)
with fixture.open("rb") as f:
return [200, headers, f.read()]
http.register_uri(
http.GET, re.compile("^https://files.pythonhosted.org/.*$"), body=callback,
)
def test_execute_executes_a_batch_of_operations(
config, pool, io, tmp_dir, mock_file_downloads
):
config = Config()
config.merge({"cache-dir": tmp_dir})
env = MockEnv(path=Path(tmp_dir))
executor = Executor(env, pool, config, io)
file_package = Package("demo", "0.1.0")
file_package.source_type = "file"
file_package.source_url = str(
Path(__file__)
.parent.parent.joinpath(
"fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"
)
.resolve()
)
directory_package = Package("simple-project", "1.2.3")
directory_package.source_type = "directory"
directory_package.source_url = str(
Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve()
)
git_package = Package("demo", "0.1.0")
git_package.source_type = "git"
git_package.source_reference = "master"
git_package.source_url = "https://github.com/demo/demo.git"
assert 0 == executor.execute(
[
Install(Package("pytest", "3.5.2")),
Uninstall(Package("attrs", "17.4.0")),
Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")),
Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"),
Install(file_package),
Install(directory_package),
Install(git_package),
]
)
expected = """
Package operations: 4 installs, 1 update, 1 removal
• Installing pytest (3.5.2)
• Removing attrs (17.4.0)
• Updating requests (2.18.3 -> 2.18.4)
• Installing demo (0.1.0 {})
• Installing simple-project (1.2.3 {})
• Installing demo (0.1.0 master)
""".format(
file_package.source_url, directory_package.source_url
)
assert expected == io.fetch_output()
assert 5 == len(env.executed)
def test_execute_shows_skipped_operations_if_verbose(config, pool, io):
config = Config()
config.merge({"cache-dir": "/foo"})
env = MockEnv()
executor = Executor(env, pool, config, io)
executor.verbose()
assert 0 == executor.execute(
[Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed")]
)
expected = """
Package operations: 0 installs, 0 updates, 0 removals, 1 skipped
• Removing clikit (0.2.3): Skipped for the following reason: Not currently installed
"""
assert expected == io.fetch_output()
assert 0 == len(env.executed)
@pytest.mark.skipif(
not PY36, reason="Improved error rendering is only available on Python >=3.6"
)
def test_execute_should_show_errors(config, mocker, io):
env = MockEnv()
executor = Executor(env, pool, config, io)
executor.verbose()
mocker.patch.object(executor, "_install", side_effect=Exception("It failed!"))
assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))])
expected = """
Package operations: 1 install, 0 updates, 0 removals
• Installing clikit (0.2.3)
Exception
It failed!
"""
assert expected in io.fetch_output()
def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_interrupt(
config, mocker, io
):
env = MockEnv()
executor = Executor(env, pool, config, io)
executor.verbose()
# A return code of -2 means KeyboardInterrupt in the pip subprocess
mocker.patch.object(executor, "_install", return_value=-2)
assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))])
expected = """
Package operations: 1 install, 0 updates, 0 removals
• Installing clikit (0.2.3)
• Installing clikit (0.2.3): Cancelled
"""
assert expected == io.fetch_output()
......@@ -8,6 +8,7 @@ from clikit.io import NullIO
from poetry.core.packages import ProjectPackage
from poetry.installation import Installer as BaseInstaller
from poetry.installation.executor import Executor as BaseExecutor
from poetry.installation.noop_installer import NoopInstaller
from poetry.packages import Locker as BaseLocker
from poetry.repositories import Pool
......@@ -34,6 +35,42 @@ class Installer(BaseInstaller):
return NoopInstaller()
class Executor(BaseExecutor):
def __init__(self, *args, **kwargs):
super(Executor, self).__init__(*args, **kwargs)
self._installs = []
self._updates = []
self._uninstalls = []
@property
def installations(self):
return self._installs
@property
def updates(self):
return self._updates
@property
def removals(self):
return self._uninstalls
def _do_execute_operation(self, operation):
super(Executor, self)._do_execute_operation(operation)
if not operation.skipped:
getattr(self, "_{}s".format(operation.job_type)).append(operation.package)
def _execute_install(self, operation):
return 0
def _execute_update(self, operation):
return 0
def _execute_uninstall(self, operation):
return 0
class CustomInstalledRepository(InstalledRepository):
@classmethod
def load(cls, env):
......@@ -122,8 +159,20 @@ def env():
@pytest.fixture()
def installer(package, pool, locker, env, installed):
return Installer(NullIO(), env, package, locker, pool, installed=installed)
def installer(package, pool, locker, env, installed, config):
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor(True)
return installer
def fixture(name):
......@@ -217,14 +266,9 @@ def test_run_update_after_removing_dependencies(
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 1
assert 0 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 1 == installer.executor.removals_count
def test_run_install_no_dev(installer, locker, repo, package, installed):
......@@ -286,14 +330,9 @@ def test_run_install_no_dev(installer, locker, repo, package, installed):
installer.dev_mode(False)
installer.run()
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 1
assert 0 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 1 == installer.executor.removals_count
def test_run_install_remove_untracked(installer, locker, repo, package, installed):
......@@ -339,14 +378,10 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
installer.dev_mode(True).remove_untracked(True)
installer.run()
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert set(r.name for r in removals) == {"b", "c"}
assert 0 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 2 == installer.executor.removals_count
assert {"b", "c"} == set(r.name for r in installer.executor.removals)
def test_run_whitelist_add(installer, locker, repo, package):
......@@ -438,9 +473,9 @@ def test_run_whitelist_remove(installer, locker, repo, package, installed):
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
assert 1 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 1 == installer.executor.removals_count
def test_add_with_sub_dependencies(installer, locker, repo, package):
......@@ -518,13 +553,12 @@ def test_run_with_optional_and_python_restricted_dependencies(
assert locker.written_data == expected
installer = installer.installer
# We should only have 2 installs:
# C,D since python version is not compatible
# with B's python constraint and A is optional
assert len(installer.installs) == 2
assert installer.installs[0].name == "d"
assert installer.installs[1].name == "c"
assert 2 == installer.executor.installations_count
assert "d" == installer.executor.installations[0].name
assert "c" == installer.executor.installations[1].name
def test_run_with_optional_and_platform_restricted_dependencies(
......@@ -555,13 +589,12 @@ def test_run_with_optional_and_platform_restricted_dependencies(
assert locker.written_data == expected
installer = installer.installer
# We should only have 2 installs:
# C,D since the mocked python version is not compatible
# with B's python constraint and A is optional
assert len(installer.installs) == 2
assert installer.installs[0].name == "d"
assert installer.installs[1].name == "c"
assert 2 == installer.executor.installations_count
assert "d" == installer.executor.installations[0].name
assert "c" == installer.executor.installations[1].name
def test_run_with_dependencies_extras(installer, locker, repo, package):
......@@ -609,8 +642,7 @@ def test_run_does_not_install_extras_if_not_requested(installer, locker, repo, p
assert locker.written_data == expected
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 3 # A, B, C
assert 3 == installer.executor.installations_count # A, B, C
def test_run_installs_extras_if_requested(installer, locker, repo, package):
......@@ -638,8 +670,7 @@ def test_run_installs_extras_if_requested(installer, locker, repo, package):
assert locker.written_data == expected
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 4 # A, B, C, D
assert 4 == installer.executor.installations_count # A, B, C, D
def test_run_installs_extras_with_deps_if_requested(installer, locker, repo, package):
......@@ -668,8 +699,7 @@ def test_run_installs_extras_with_deps_if_requested(installer, locker, repo, pac
assert locker.written_data == expected
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 4 # A, B, C, D
assert 4 == installer.executor.installations_count # A, B, C, D
def test_run_installs_extras_with_deps_if_requested_locked(
......@@ -698,16 +728,15 @@ def test_run_installs_extras_with_deps_if_requested_locked(
installer.run()
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 4 # A, B, C, D
assert 4 == installer.executor.installations_count # A, B, C, D
def test_installer_with_pypi_repository(package, locker, installed):
def test_installer_with_pypi_repository(package, locker, installed, config):
pool = Pool()
pool.add_repository(MockRepository())
installer = Installer(
NullIO(), NullEnv(), package, locker, pool, installed=installed
NullIO(), NullEnv(), package, locker, pool, config, installed=installed
)
package.add_dependency("pytest", "^3.5", category="dev")
......@@ -730,7 +759,7 @@ def test_run_installs_with_local_file(installer, locker, repo, package):
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
assert 2 == installer.executor.installations_count
def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package):
......@@ -745,7 +774,7 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa
assert locker.written_data == expected
assert len(installer.installer.installs) == 1
assert 1 == installer.executor.installations_count
def test_run_installs_with_local_poetry_directory_and_extras(
......@@ -764,7 +793,7 @@ def test_run_installs_with_local_poetry_directory_and_extras(
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
assert 2 == installer.executor.installations_count
def test_run_installs_with_local_poetry_directory_transitive(
......@@ -788,7 +817,7 @@ def test_run_installs_with_local_poetry_directory_transitive(
assert locker.written_data == expected
assert len(installer.installer.installs) == 6
assert 6 == installer.executor.installations_count
def test_run_installs_with_local_poetry_file_transitive(
......@@ -812,7 +841,7 @@ def test_run_installs_with_local_poetry_file_transitive(
assert locker.written_data == expected
assert len(installer.installer.installs) == 4
assert 4 == installer.executor.installations_count
def test_run_installs_with_local_setuptools_directory(
......@@ -830,7 +859,7 @@ def test_run_installs_with_local_setuptools_directory(
assert locker.written_data == expected
assert len(installer.installer.installs) == 3
assert 3 == installer.executor.installations_count
def test_run_with_prereleases(installer, locker, repo, package):
......@@ -1049,16 +1078,14 @@ def test_run_install_duplicate_dependencies_different_constraints(
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 3
installs = installer.executor.installations
assert 3 == installer.executor.installations_count
assert installs[0] == package_c12
assert installs[1] == package_b10
assert installs[2] == package_a
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 0
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
def test_run_install_duplicate_dependencies_different_constraints_with_lock(
......@@ -1159,12 +1186,9 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock(
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 3
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 0
assert 3 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
def test_run_update_uninstalls_after_removal_transient_dependency(
......@@ -1218,12 +1242,9 @@ def test_run_update_uninstalls_after_removal_transient_dependency(
installer.update(True)
installer.run()
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 1
assert 0 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 1 == installer.executor.removals_count
def test_run_install_duplicate_dependencies_different_constraints_with_lock_update(
......@@ -1326,12 +1347,9 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 2
updates = installer.installer.updates
assert len(updates) == 1
removals = installer.installer.removals
assert len(removals) == 0
assert 2 == installer.executor.installations_count
assert 1 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
@pytest.mark.skip(
......@@ -1357,16 +1375,14 @@ def test_installer_test_solver_finds_compatible_package_for_dependency_python_no
expected = fixture("with-conditional-dependency")
assert locker.written_data == expected
installs = installer.installer.installs
if sys.version_info >= (3, 5, 0):
assert len(installs) == 1
assert 1 == installer.executor.installations_count
else:
assert len(installs) == 0
assert 0 == installer.executor.installations_count
def test_installer_required_extras_should_not_be_removed_when_updating_single_dependency(
installer, locker, repo, package, installed, env, pool
installer, locker, repo, package, installed, env, pool, config
):
package.add_dependency("A", {"version": "^1.0"})
......@@ -1388,9 +1404,9 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 3
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert 3 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
package.add_dependency("D", "^1.0")
locker.locked(True)
......@@ -1400,62 +1416,102 @@ def test_installer_required_extras_should_not_be_removed_when_updating_single_de
installed.add_package(package_b)
installed.add_package(package_c)
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor()
installer.update(True)
installer.whitelist(["D"])
installer.run()
assert len(installer.installer.installs) == 1
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert 1 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
def test_installer_required_extras_should_not_be_removed_when_updating_single_dependency_pypi_repository(
locker, repo, package, installed, env, mocker
locker, repo, package, installed, env, mocker, config
):
mocker.patch("sys.platform", "darwin")
pool = Pool()
pool.add_repository(MockRepository())
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor()
package.add_dependency("poetry", {"version": "^0.12.0"})
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 3
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert 3 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
package.add_dependency("pytest", "^3.5")
locker.locked(True)
locker.mock_lock_data(locker.written_data)
for pkg in installer.installer.installs:
for pkg in installer.executor.installations:
installed.add_package(pkg)
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor()
installer.update(True)
installer.whitelist(["pytest"])
installer.run()
assert len(installer.installer.installs) == 6 if not PY2 else 7
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert (6 if not PY2 else 7) == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
def test_installer_required_extras_should_be_installed(
locker, repo, package, installed, env, mocker
locker, repo, package, installed, env, config
):
pool = Pool()
pool.add_repository(MockRepository())
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor()
package.add_dependency(
"cachecontrol", {"version": "^0.12.5", "extras": ["filecache"]}
......@@ -1464,21 +1520,31 @@ def test_installer_required_extras_should_be_installed(
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert 2 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
locker.locked(True)
locker.mock_lock_data(locker.written_data)
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor()
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert 2 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
def test_update_multiple_times_with_split_dependencies_is_idempotent(
......@@ -1557,7 +1623,7 @@ def test_update_multiple_times_with_split_dependencies_is_idempotent(
def test_installer_can_install_dependencies_from_forced_source(
locker, package, installed, env
locker, package, installed, env, config
):
package.python_versions = "^3.7"
package.add_dependency("tomlkit", {"version": "^0.5", "source": "legacy"})
......@@ -1566,14 +1632,24 @@ def test_installer_can_install_dependencies_from_forced_source(
pool.add_repository(MockLegacyRepository())
pool.add_repository(MockRepository())
installer = Installer(NullIO(), env, package, locker, pool, installed=installed)
installer = Installer(
NullIO(),
env,
package,
locker,
pool,
config,
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
installer.use_executor()
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 1
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
assert 1 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 0 == installer.executor.removals_count
def test_run_installs_with_url_file(installer, locker, repo, package):
......@@ -1588,7 +1664,7 @@ def test_run_installs_with_url_file(installer, locker, repo, package):
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
assert 2 == installer.executor.installations_count
def test_installer_uses_prereleases_if_they_are_compatible(
......@@ -1616,11 +1692,11 @@ def test_installer_uses_prereleases_if_they_are_compatible(
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
assert 2 == installer.executor.installations_count
def test_installer_can_handle_old_lock_files(
installer, locker, package, repo, installed
installer, locker, package, repo, installed, config
):
pool = Pool()
pool.add_repository(MockRepository())
......@@ -1631,12 +1707,20 @@ def test_installer_can_handle_old_lock_files(
locker.mock_lock_data(fixture("old-lock"))
installer = Installer(
NullIO(), MockEnv(), package, locker, pool, installed=installed
NullIO(),
MockEnv(),
package,
locker,
pool,
config,
installed=installed,
executor=Executor(MockEnv(), pool, config, NullIO(),),
)
installer.use_executor()
installer.run()
assert 6 == len(installer.installer.installs)
assert 6 == installer.executor.installations_count
installer = Installer(
NullIO(),
......@@ -1644,13 +1728,16 @@ def test_installer_can_handle_old_lock_files(
package,
locker,
pool,
config,
installed=installed,
executor=Executor(MockEnv(version_info=(2, 7, 18)), pool, config, NullIO(),),
)
installer.use_executor()
installer.run()
# funcsigs will be added
assert 7 == len(installer.installer.installs)
assert 7 == installer.executor.installations_count
installer = Installer(
NullIO(),
......@@ -1658,10 +1745,15 @@ def test_installer_can_handle_old_lock_files(
package,
locker,
pool,
config,
installed=installed,
executor=Executor(
MockEnv(version_info=(2, 7, 18), platform="win32"), pool, config, NullIO(),
),
)
installer.use_executor()
installer.run()
# colorama will be added
assert 8 == len(installer.installer.installs)
assert 8 == installer.executor.installations_count
from __future__ import unicode_literals
import sys
import pytest
from clikit.io import NullIO
from poetry.core.packages import ProjectPackage
from poetry.installation import Installer as BaseInstaller
from poetry.installation.noop_installer import NoopInstaller
from poetry.packages import Locker as BaseLocker
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository
from poetry.utils._compat import PY2
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
from poetry.utils.env import NullEnv
from poetry.utils.toml_file import TomlFile
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
fixtures_dir = Path("tests/fixtures")
class Installer(BaseInstaller):
def _get_installer(self):
return NoopInstaller()
class CustomInstalledRepository(InstalledRepository):
@classmethod
def load(cls, env):
return cls()
class Locker(BaseLocker):
def __init__(self):
self._written_data = None
self._locked = False
self._content_hash = self._get_content_hash()
@property
def written_data(self):
return self._written_data
def locked(self, is_locked=True):
self._locked = is_locked
return self
def mock_lock_data(self, data):
self._lock_data = data
def is_locked(self):
return self._locked
def is_fresh(self):
return True
def _get_content_hash(self):
return "123456789"
def _write_lock_data(self, data):
for package in data["package"]:
python_versions = str(package["python-versions"])
if PY2:
python_versions = python_versions.decode()
if "requirements" in package:
requirements = {}
for key, value in package["requirements"].items():
requirements[key.decode()] = value.decode()
package["requirements"] = requirements
package["python-versions"] = python_versions
self._written_data = data
self._lock_data = data
@pytest.fixture()
def package():
p = ProjectPackage("root", "1.0")
p.root_dir = Path.cwd()
return p
@pytest.fixture()
def repo():
return Repository()
@pytest.fixture()
def pool(repo):
pool = Pool()
pool.add_repository(repo)
return pool
@pytest.fixture()
def installed():
return CustomInstalledRepository()
@pytest.fixture()
def locker():
return Locker()
@pytest.fixture()
def env():
return NullEnv()
@pytest.fixture()
def installer(package, pool, locker, env, installed, config):
return Installer(NullIO(), env, package, locker, pool, config, installed=installed)
def fixture(name):
file = TomlFile(Path(__file__).parent / "fixtures" / "{}.test".format(name))
return file.read()
def test_run_no_dependencies(installer, locker):
installer.run()
expected = fixture("no-dependencies")
assert locker.written_data == expected
def test_run_with_dependencies(installer, locker, repo, package):
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
package.add_dependency("A", "~1.0")
package.add_dependency("B", "^1.0")
installer.run()
expected = fixture("with-dependencies")
assert locker.written_data == expected
def test_run_update_after_removing_dependencies(
installer, locker, repo, package, installed
):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "B",
"version": "1.1",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "C",
"version": "1.2",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": [], "C": []},
},
}
)
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_c = get_package("C", "1.2")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
package.add_dependency("A", "~1.0")
package.add_dependency("B", "~1.1")
installer.update(True)
installer.run()
expected = fixture("with-dependencies")
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 1
def test_run_install_no_dev(installer, locker, repo, package, installed):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "B",
"version": "1.1",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "C",
"version": "1.2",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": [], "C": []},
},
}
)
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_c = get_package("C", "1.2")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
package.add_dependency("A", "~1.0")
package.add_dependency("B", "~1.1")
package.add_dependency("C", "~1.2", category="dev")
installer.dev_mode(False)
installer.run()
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 1
def test_run_install_remove_untracked(installer, locker, repo, package, installed):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "a",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
}
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"a": []},
},
}
)
package_a = get_package("a", "1.0")
package_b = get_package("b", "1.1")
package_c = get_package("c", "1.2")
package_pip = get_package("pip", "20.0.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_pip)
installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
installed.add_package(package_pip) # Always required and never removed.
installed.add_package(package) # Root package never removed.
package.add_dependency("A", "~1.0")
installer.dev_mode(True).remove_untracked(True)
installer.run()
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert set(r.name for r in removals) == {"b", "c"}
def test_run_whitelist_add(installer, locker, repo, package):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
}
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": []},
},
}
)
package_a = get_package("A", "1.0")
package_a_new = get_package("A", "1.1")
package_b = get_package("B", "1.1")
repo.add_package(package_a)
repo.add_package(package_a_new)
repo.add_package(package_b)
package.add_dependency("A", "~1.0")
package.add_dependency("B", "^1.0")
installer.update(True)
installer.whitelist(["B"])
installer.run()
expected = fixture("with-dependencies")
assert locker.written_data == expected
def test_run_whitelist_remove(installer, locker, repo, package, installed):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "B",
"version": "1.1",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": []},
},
}
)
package_a = get_package("A", "1.0")
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")
installer.update(True)
installer.whitelist(["B"])
installer.run()
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):
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_c = get_package("C", "1.2")
package_d = get_package("D", "1.3")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
package.add_dependency("A", "~1.0")
package.add_dependency("B", "^1.0")
package_a.add_dependency("D", "^1.0")
package_b.add_dependency("C", "~1.2")
installer.run()
expected = fixture("with-sub-dependencies")
assert locker.written_data == expected
def test_run_with_python_versions(installer, locker, repo, package):
package.python_versions = "~2.7 || ^3.4"
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_c12 = get_package("C", "1.2")
package_c12.python_versions = "~2.7 || ^3.3"
package_c13 = get_package("C", "1.3")
package_c13.python_versions = "~3.3"
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c12)
repo.add_package(package_c13)
package.add_dependency("A", "~1.0")
package.add_dependency("B", "^1.0")
package.add_dependency("C", "^1.0")
installer.run()
expected = fixture("with-python-versions")
assert locker.written_data == expected
def test_run_with_optional_and_python_restricted_dependencies(
installer, locker, repo, package
):
package.python_versions = "~2.7 || ^3.4"
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_c12 = get_package("C", "1.2")
package_c13 = get_package("C", "1.3")
package_d = get_package("D", "1.4")
package_c13.add_dependency("D", "^1.2")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c12)
repo.add_package(package_c13)
repo.add_package(package_d)
package.extras = {"foo": [get_dependency("A", "~1.0")]}
package.add_dependency("A", {"version": "~1.0", "optional": True})
package.add_dependency("B", {"version": "^1.0", "python": "~2.4"})
package.add_dependency("C", {"version": "^1.0", "python": "~2.7 || ^3.4"})
installer.run()
expected = fixture("with-optional-dependencies")
assert locker.written_data == expected
installer = installer.installer
# We should only have 2 installs:
# C,D since python version is not compatible
# with B's python constraint and A is optional
assert len(installer.installs) == 2
assert installer.installs[0].name == "d"
assert installer.installs[1].name == "c"
def test_run_with_optional_and_platform_restricted_dependencies(
installer, locker, repo, package, mocker
):
mocker.patch("sys.platform", "darwin")
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_c12 = get_package("C", "1.2")
package_c13 = get_package("C", "1.3")
package_d = get_package("D", "1.4")
package_c13.add_dependency("D", "^1.2")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c12)
repo.add_package(package_c13)
repo.add_package(package_d)
package.extras = {"foo": [get_dependency("A", "~1.0")]}
package.add_dependency("A", {"version": "~1.0", "optional": True})
package.add_dependency("B", {"version": "^1.0", "platform": "custom"})
package.add_dependency("C", {"version": "^1.0", "platform": "darwin"})
installer.run()
expected = fixture("with-platform-dependencies")
assert locker.written_data == expected
installer = installer.installer
# We should only have 2 installs:
# C,D since the mocked python version is not compatible
# with B's python constraint and A is optional
assert len(installer.installs) == 2
assert installer.installs[0].name == "d"
assert installer.installs[1].name == "c"
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")]}
package_b.add_dependency("C", {"version": "^1.0", "optional": True})
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
def test_run_does_not_install_extras_if_not_requested(installer, locker, repo, package):
package.extras["foo"] = [get_dependency("D")]
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
package_c = get_package("C", "1.0")
package_d = get_package("D", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
package.add_dependency("A", "^1.0")
package.add_dependency("B", "^1.0")
package.add_dependency("C", "^1.0")
package.add_dependency("D", {"version": "^1.0", "optional": True})
installer.run()
expected = fixture("extras")
# Extras are pinned in lock
assert locker.written_data == expected
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 3 # A, B, C
def test_run_installs_extras_if_requested(installer, locker, repo, package):
package.extras["foo"] = [get_dependency("D")]
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
package_c = get_package("C", "1.0")
package_d = get_package("D", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
package.add_dependency("A", "^1.0")
package.add_dependency("B", "^1.0")
package.add_dependency("C", "^1.0")
package.add_dependency("D", {"version": "^1.0", "optional": True})
installer.extras(["foo"])
installer.run()
expected = fixture("extras")
# Extras are pinned in lock
assert locker.written_data == expected
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 4 # A, B, C, D
def test_run_installs_extras_with_deps_if_requested(installer, locker, repo, package):
package.extras["foo"] = [get_dependency("C")]
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
package_c = get_package("C", "1.0")
package_d = get_package("D", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
package.add_dependency("A", "^1.0")
package.add_dependency("B", "^1.0")
package.add_dependency("C", {"version": "^1.0", "optional": True})
package_c.add_dependency("D", "^1.0")
installer.extras(["foo"])
installer.run()
expected = fixture("extras-with-dependencies")
# Extras are pinned in lock
assert locker.written_data == expected
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 4 # A, B, C, D
def test_run_installs_extras_with_deps_if_requested_locked(
installer, locker, repo, package
):
locker.locked(True)
locker.mock_lock_data(fixture("extras-with-dependencies"))
package.extras["foo"] = [get_dependency("C")]
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
package_c = get_package("C", "1.0")
package_d = get_package("D", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
package.add_dependency("A", "^1.0")
package.add_dependency("B", "^1.0")
package.add_dependency("C", {"version": "^1.0", "optional": True})
package_c.add_dependency("D", "^1.0")
installer.extras(["foo"])
installer.run()
# But should not be installed
installer = installer.installer
assert len(installer.installs) == 4 # A, B, C, D
def test_installer_with_pypi_repository(package, locker, installed, config):
pool = Pool()
pool.add_repository(MockRepository())
installer = Installer(
NullIO(), NullEnv(), package, locker, pool, config, installed=installed
)
package.add_dependency("pytest", "^3.5", category="dev")
installer.run()
expected = fixture("with-pypi-repository")
assert locker.written_data == expected
def test_run_installs_with_local_file(installer, locker, repo, package):
file_path = fixtures_dir / "distributions/demo-0.1.0-py2.py3-none-any.whl"
package.add_dependency("demo", {"file": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4"))
installer.run()
expected = fixture("with-file-dependency")
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package):
file_path = (
fixtures_dir / "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl"
)
package.add_dependency("demo", {"file": str(file_path)})
installer.run()
expected = fixture("with-wheel-dependency-no-requires-dist")
assert locker.written_data == expected
assert len(installer.installer.installs) == 1
def test_run_installs_with_local_poetry_directory_and_extras(
installer, locker, repo, package, tmpdir
):
file_path = fixtures_dir / "project_with_extras"
package.add_dependency(
"project-with-extras", {"path": str(file_path), "extras": ["extras_a"]}
)
repo.add_package(get_package("pendulum", "1.4.4"))
installer.run()
expected = fixture("with-directory-dependency-poetry")
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
def test_run_installs_with_local_poetry_directory_transitive(
installer, locker, repo, package, tmpdir
):
package.root_dir = fixtures_dir.joinpath("directory")
directory = fixtures_dir.joinpath("directory").joinpath(
"project_with_transitive_directory_dependencies"
)
package.add_dependency(
"project-with-transitive-directory-dependencies",
{"path": str(directory.relative_to(fixtures_dir.joinpath("directory")))},
)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0"))
installer.run()
expected = fixture("with-directory-dependency-poetry-transitive")
assert locker.written_data == expected
assert len(installer.installer.installs) == 6
def test_run_installs_with_local_poetry_file_transitive(
installer, locker, repo, package, tmpdir
):
package.root_dir = fixtures_dir.joinpath("directory")
directory = fixtures_dir.joinpath("directory").joinpath(
"project_with_transitive_file_dependencies"
)
package.add_dependency(
"project-with-transitive-file-dependencies",
{"path": str(directory.relative_to(fixtures_dir.joinpath("directory")))},
)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0"))
installer.run()
expected = fixture("with-file-dependency-transitive")
assert locker.written_data == expected
assert len(installer.installer.installs) == 4
def test_run_installs_with_local_setuptools_directory(
installer, locker, repo, package, tmpdir
):
file_path = fixtures_dir / "project_with_setup/"
package.add_dependency("my-package", {"path": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0"))
installer.run()
expected = fixture("with-directory-dependency-setuptools")
assert locker.written_data == expected
assert len(installer.installer.installs) == 3
def test_run_with_prereleases(installer, locker, repo, package):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0a2",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
}
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": []},
},
}
)
package_a = get_package("A", "1.0a2")
package_b = get_package("B", "1.1")
repo.add_package(package_a)
repo.add_package(package_b)
package.add_dependency("A", {"version": "*", "allows-prereleases": True})
package.add_dependency("B", "^1.1")
installer.update(True)
installer.whitelist({"B": "^1.1"})
installer.run()
expected = fixture("with-prereleases")
assert locker.written_data == expected
def test_run_changes_category_if_needed(installer, locker, repo, package):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "dev",
"optional": True,
"platform": "*",
"python-versions": "*",
"checksum": [],
}
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": []},
},
}
)
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1")
package_b.add_dependency("A", "^1.0")
repo.add_package(package_a)
repo.add_package(package_b)
package.add_dependency("A", {"version": "^1.0", "optional": True}, category="dev")
package.add_dependency("B", "^1.1")
installer.update(True)
installer.whitelist(["B"])
installer.run()
expected = fixture("with-category-change")
assert locker.written_data == expected
def test_run_update_all_with_lock(installer, locker, repo, package):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "dev",
"optional": True,
"platform": "*",
"python-versions": "*",
"checksum": [],
}
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": []},
},
}
)
package_a = get_package("A", "1.1")
repo.add_package(get_package("A", "1.0"))
repo.add_package(package_a)
package.add_dependency("A")
installer.update(True)
installer.run()
expected = fixture("update-with-lock")
assert locker.written_data == expected
def test_run_update_with_locked_extras(installer, locker, repo, package):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"B": "^1.0", "C": "^1.0"},
},
{
"name": "B",
"version": "1.0",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "C",
"version": "1.1",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"requirements": {"python": "~2.7"},
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": [], "C": []},
},
}
)
package_a = get_package("A", "1.0")
package_a.extras["foo"] = [get_dependency("B")]
b_dependency = get_dependency("B", "^1.0", optional=True)
b_dependency.in_extras.append("foo")
c_dependency = get_dependency("C", "^1.0")
c_dependency.python_versions = "~2.7"
package_a.requires.append(b_dependency)
package_a.requires.append(c_dependency)
repo.add_package(package_a)
repo.add_package(get_package("B", "1.0"))
repo.add_package(get_package("C", "1.1"))
repo.add_package(get_package("D", "1.1"))
package.add_dependency("A", {"version": "^1.0", "extras": ["foo"]})
package.add_dependency("D", "^1.0")
installer.update(True)
installer.whitelist("D")
installer.run()
expected = fixture("update-with-locked-extras")
assert locker.written_data == expected
def test_run_install_duplicate_dependencies_different_constraints(
installer, locker, repo, package
):
package.add_dependency("A")
package_a = get_package("A", "1.0")
package_a.add_dependency("B", {"version": "^1.0", "python": "<4.0"})
package_a.add_dependency("B", {"version": "^2.0", "python": ">=4.0"})
package_b10 = get_package("B", "1.0")
package_b20 = get_package("B", "2.0")
package_b10.add_dependency("C", "1.2")
package_b20.add_dependency("C", "1.5")
package_c12 = get_package("C", "1.2")
package_c15 = get_package("C", "1.5")
repo.add_package(package_a)
repo.add_package(package_b10)
repo.add_package(package_b20)
repo.add_package(package_c12)
repo.add_package(package_c15)
installer.run()
expected = fixture("with-duplicate-dependencies")
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 3
assert installs[0] == package_c12
assert installs[1] == package_b10
assert installs[2] == package_a
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 0
def test_run_install_duplicate_dependencies_different_constraints_with_lock(
installer, locker, repo, package
):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {
"B": [
{"version": "^1.0", "python": "<4.0"},
{"version": "^2.0", "python": ">=4.0"},
]
},
},
{
"name": "B",
"version": "1.0",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"C": "1.2"},
"requirements": {"python": "<4.0"},
},
{
"name": "B",
"version": "2.0",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"C": "1.5"},
"requirements": {"python": ">=4.0"},
},
{
"name": "C",
"version": "1.2",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "C",
"version": "1.5",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": [], "C": []},
},
}
)
package.add_dependency("A")
package_a = get_package("A", "1.0")
package_a.add_dependency("B", {"version": "^1.0", "python": "<4.0"})
package_a.add_dependency("B", {"version": "^2.0", "python": ">=4.0"})
package_b10 = get_package("B", "1.0")
package_b20 = get_package("B", "2.0")
package_b10.add_dependency("C", "1.2")
package_b20.add_dependency("C", "1.5")
package_c12 = get_package("C", "1.2")
package_c15 = get_package("C", "1.5")
repo.add_package(package_a)
repo.add_package(package_b10)
repo.add_package(package_b20)
repo.add_package(package_c12)
repo.add_package(package_c15)
installer.update(True)
installer.run()
expected = fixture("with-duplicate-dependencies")
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 3
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 0
def test_run_update_uninstalls_after_removal_transient_dependency(
installer, locker, repo, package, installed
):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"B": {"version": "^1.0", "python": "<2.0"}},
},
{
"name": "B",
"version": "1.0",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": []},
},
}
)
package.add_dependency("A")
package_a = get_package("A", "1.0")
package_a.add_dependency("B", {"version": "^1.0", "python": "<2.0"})
package_b10 = get_package("B", "1.0")
repo.add_package(package_a)
repo.add_package(package_b10)
installed.add_package(get_package("A", "1.0"))
installed.add_package(get_package("B", "1.0"))
installer.update(True)
installer.run()
installs = installer.installer.installs
assert len(installs) == 0
updates = installer.installer.updates
assert len(updates) == 0
removals = installer.installer.removals
assert len(removals) == 1
def test_run_install_duplicate_dependencies_different_constraints_with_lock_update(
installer, locker, repo, package, installed
):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {
"B": [
{"version": "^1.0", "python": "<2.7"},
{"version": "^2.0", "python": ">=2.7"},
]
},
},
{
"name": "B",
"version": "1.0",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"C": "1.2"},
"requirements": {"python": "<2.7"},
},
{
"name": "B",
"version": "2.0",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"C": "1.5"},
"requirements": {"python": ">=2.7"},
},
{
"name": "C",
"version": "1.2",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "C",
"version": "1.5",
"category": "dev",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": [], "C": []},
},
}
)
package.add_dependency("A")
package_a = get_package("A", "1.1")
package_a.add_dependency("B", "^2.0")
package_b10 = get_package("B", "1.0")
package_b20 = get_package("B", "2.0")
package_b10.add_dependency("C", "1.2")
package_b20.add_dependency("C", "1.5")
package_c12 = get_package("C", "1.2")
package_c15 = get_package("C", "1.5")
repo.add_package(package_a)
repo.add_package(package_b10)
repo.add_package(package_b20)
repo.add_package(package_c12)
repo.add_package(package_c15)
installed.add_package(get_package("A", "1.0"))
installer.update(True)
installer.whitelist(["A"])
installer.run()
expected = fixture("with-duplicate-dependencies-update")
assert locker.written_data == expected
installs = installer.installer.installs
assert len(installs) == 2
updates = installer.installer.updates
assert len(updates) == 1
removals = installer.installer.removals
assert len(removals) == 0
@pytest.mark.skip(
"This is not working at the moment due to limitations in the resolver"
)
def test_installer_test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python(
installer, locker, repo, package, installed
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})
package_a101 = get_package("A", "1.0.1")
package_a101.python_versions = ">=3.6"
package_a100 = get_package("A", "1.0.0")
package_a100.python_versions = ">=3.5"
repo.add_package(package_a100)
repo.add_package(package_a101)
installer.run()
expected = fixture("with-conditional-dependency")
assert locker.written_data == expected
installs = installer.installer.installs
if sys.version_info >= (3, 5, 0):
assert len(installs) == 1
else:
assert len(installs) == 0
def test_installer_required_extras_should_not_be_removed_when_updating_single_dependency(
installer, locker, repo, package, installed, env, pool, config
):
package.add_dependency("A", {"version": "^1.0"})
package_a = get_package("A", "1.0.0")
package_a.add_dependency("B", {"version": "^1.0", "extras": ["foo"]})
package_b = get_package("B", "1.0.0")
package_b.add_dependency("C", {"version": "^1.0", "optional": True})
package_b.extras = {"foo": [get_dependency("C")]}
package_c = get_package("C", "1.0.0")
package_d = get_package("D", "1.0.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 3
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
package.add_dependency("D", "^1.0")
locker.locked(True)
locker.mock_lock_data(locker.written_data)
installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
installer = Installer(
NullIO(), env, package, locker, pool, config, installed=installed
)
installer.update(True)
installer.whitelist(["D"])
installer.run()
assert len(installer.installer.installs) == 1
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
def test_installer_required_extras_should_not_be_removed_when_updating_single_dependency_pypi_repository(
locker, repo, package, installed, env, mocker, config
):
mocker.patch("sys.platform", "darwin")
pool = Pool()
pool.add_repository(MockRepository())
installer = Installer(
NullIO(), env, package, locker, pool, config, installed=installed
)
package.add_dependency("poetry", {"version": "^0.12.0"})
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 3
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
package.add_dependency("pytest", "^3.5")
locker.locked(True)
locker.mock_lock_data(locker.written_data)
for pkg in installer.installer.installs:
installed.add_package(pkg)
installer = Installer(
NullIO(), env, package, locker, pool, config, installed=installed
)
installer.update(True)
installer.whitelist(["pytest"])
installer.run()
assert len(installer.installer.installs) == 6 if not PY2 else 7
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
def test_installer_required_extras_should_be_installed(
locker, repo, package, installed, env, config
):
pool = Pool()
pool.add_repository(MockRepository())
installer = Installer(
NullIO(), env, package, locker, pool, config, installed=installed
)
package.add_dependency(
"cachecontrol", {"version": "^0.12.5", "extras": ["filecache"]}
)
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
locker.locked(True)
locker.mock_lock_data(locker.written_data)
installer = Installer(
NullIO(), env, package, locker, pool, config, installed=installed
)
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
assert len(installer.installer.updates) == 0
assert len(installer.installer.removals) == 0
def test_update_multiple_times_with_split_dependencies_is_idempotent(
installer, locker, repo, package
):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "A",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
"dependencies": {"B": ">=1.0"},
},
{
"name": "B",
"version": "1.0.1",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
"checksum": [],
"dependencies": {},
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"A": [], "B": []},
},
}
)
package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", "^1.0")
a10 = get_package("A", "1.0")
a11 = get_package("A", "1.1")
a11.add_dependency("B", ">=1.0.1")
a11.add_dependency("C", {"version": "^1.0", "python": "~2.7"})
a11.add_dependency("C", {"version": "^2.0", "python": "^3.4"})
b101 = get_package("B", "1.0.1")
b110 = get_package("B", "1.1.0")
repo.add_package(a10)
repo.add_package(a11)
repo.add_package(b101)
repo.add_package(b110)
repo.add_package(get_package("C", "1.0"))
repo.add_package(get_package("C", "2.0"))
expected = fixture("with-multiple-updates")
installer.update(True)
installer.run()
assert expected == locker.written_data
locker.mock_lock_data(locker.written_data)
installer.update(True)
installer.run()
assert expected == locker.written_data
locker.mock_lock_data(locker.written_data)
installer.update(True)
installer.run()
assert expected == locker.written_data
def test_installer_can_install_dependencies_from_forced_source(
locker, package, installed, env, config
):
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, config, 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
def test_run_installs_with_url_file(installer, locker, repo, package):
url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
package.add_dependency("demo", {"url": url})
repo.add_package(get_package("pendulum", "1.4.4"))
installer.run()
expected = fixture("with-url-dependency")
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
def test_installer_uses_prereleases_if_they_are_compatible(
installer, locker, package, repo
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency(
"prerelease", {"git": "https://github.com/demo/prerelease.git"}
)
package_b = get_package("b", "2.0.0")
package_b.add_dependency("prerelease", ">=0.19")
repo.add_package(package_b)
installer.run()
del installer.installer.installs[:]
locker.locked(True)
locker.mock_lock_data(locker.written_data)
package.add_dependency("b", "^2.0.0")
installer.whitelist(["b"])
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
def test_installer_can_handle_old_lock_files(
installer, locker, package, repo, installed, config
):
pool = Pool()
pool.add_repository(MockRepository())
package.add_dependency("pytest", "^3.5", category="dev")
locker.locked()
locker.mock_lock_data(fixture("old-lock"))
installer = Installer(
NullIO(), MockEnv(), package, locker, pool, config, installed=installed
)
installer.run()
assert 6 == len(installer.installer.installs)
installer = Installer(
NullIO(),
MockEnv(version_info=(2, 7, 18)),
package,
locker,
pool,
config,
installed=installed,
)
installer.run()
# funcsigs will be added
assert 7 == len(installer.installer.installs)
installer = Installer(
NullIO(),
MockEnv(version_info=(2, 7, 18), platform="win32"),
package,
locker,
pool,
config,
installed=installed,
)
installer.run()
# colorama will be added
assert 8 == len(installer.installer.installs)
<!DOCTYPE html>
<html>
<head>
<title>Links for pytest</title>
</head>
<body>
<h1>Links for pytest</h1><a href="https://files.pythonhosted.org/packages/ed/96/271c93f75212c06e2a7ec3e2fa8a9c90acee0a4838dc05bf379ea09aae31/pytest-3.5.0-py2.py3-none-any.whl#sha256=6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">pytest-3.5.0-py2.py3-none-any.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/2d/56/6019153cdd743300c5688ab3b07702355283e53c83fbf922242c053ffb7b/pytest-3.5.0.tar.gz#sha256=fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1" data-requires-python="&gt;=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*">pytest-3.5.0.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 7198641-->
......@@ -5,6 +5,7 @@
</head>
<body>
<h1>Links for python-language-server</h1>
<a href="https://files.pythonhosted.org/packages/5c/ed/d6557f70daaaab6ee5cd2f8ccf7bedd63081e522e38679c03840e1acc114/PyYAML-3.13-cp37-cp37m-win32.whl#sha256=e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531">PyYAML-3.13-cp37-cp37m-win32.whl</a><br/>
<a href="https://files.pythonhosted.org/packages/9e/a3/1d13970c3f36777c583f136c136f804d70f500168edc1edea6daa7200769/PyYAML-3.13.tar.gz#sha256=3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf">PyYAML-3.13.tar.gz</a><br/>
<a href="https://files.pythonhosted.org/packages/0f/9d/f98ed0a460dc540f720bbe5c6e076f025595cdfa3e318fad27165db13cf9/PyYAML-4.2b2.tar.gz#sha256=406b717f739e2d00c49873068b71f5454c2420157db51b082d4d2beb17ffffb6">PyYAML-4.2b2.tar.gz</a><br/>
</body>
......
......@@ -7,5 +7,5 @@ envlist = py27, py35, py36, py37, py38
whitelist_externals = poetry
skip_install = true
commands =
poetry install -vvv
poetry install -vv
poetry run pytest {posargs} tests/
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