Commit 7a12c390 by Sébastien Eustace Committed by GitHub

Resolver improvements (#472)

* Improve resolver

* Improve handling of environment markers

* Update lock file

* Fix recursion error on duplicate dependencies with only different extras
parent a4aefded
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
- Improved virtualenv detection and management. - Improved virtualenv detection and management.
- Wilcard `python` dependencies are now equivalent to `~2.7 || ^3.4`. - Wilcard `python` dependencies are now equivalent to `~2.7 || ^3.4`.
- Changed behavior of the resolver for conditional dependencies.
### Fixed
- Fixed a memory leak in the resolver.
## [0.11.5] - 2018-09-04 ## [0.11.5] - 2018-09-04
......
...@@ -318,11 +318,11 @@ and installs them. ...@@ -318,11 +318,11 @@ and installs them.
poetry install poetry install
``` ```
If there is a `pyproject.lock` file in the current directory, If there is a `poetry.lock` file in the current directory,
it will use the exact versions from there instead of resolving them. it will use the exact versions from there instead of resolving them.
This ensures that everyone using the library will get the same versions of the dependencies. This ensures that everyone using the library will get the same versions of the dependencies.
If there is no `pyproject.lock` file, Poetry will create one after dependency resolution. If there is no `poetry.lock` file, Poetry will create one after dependency resolution.
You can specify to the command that you do not want the development dependencies installed by passing You can specify to the command that you do not want the development dependencies installed by passing
the `--no-dev` option. the `--no-dev` option.
...@@ -346,14 +346,14 @@ poetry install -E mysql -E pgsql ...@@ -346,14 +346,14 @@ poetry install -E mysql -E pgsql
### update ### update
In order to get the latest versions of the dependencies and to update the `pyproject.lock` file, In order to get the latest versions of the dependencies and to update the `poetry.lock` file,
you should use the `update` command. you should use the `update` command.
```bash ```bash
poetry update poetry update
``` ```
This will resolve all dependencies of the project and write the exact versions into `pyproject.lock`. This will resolve all dependencies of the project and write the exact versions into `poetry.lock`.
If you just want to update a few packages and not all, you can list them as such: If you just want to update a few packages and not all, you can list them as such:
......
[[package]] [[package]]
category = "dev" category = "dev"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "appdirs" name = "appdirs"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.4.3" version = "1.4.3"
[package.requirements]
python = ">=3.6,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "A few extensions to pyyaml." description = "A few extensions to pyyaml."
name = "aspy.yaml" name = "aspy.yaml"
optional = false optional = false
platform = "all"
python-versions = "*" python-versions = "*"
version = "1.1.1" version = "1.1.1"
...@@ -27,27 +23,26 @@ category = "dev" ...@@ -27,27 +23,26 @@ category = "dev"
description = "Atomic file writes." description = "Atomic file writes."
name = "atomicwrites" name = "atomicwrites"
optional = false optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.2.1" version = "1.2.1"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "attrs" name = "attrs"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "18.2.0" version = "18.2.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "black" name = "black"
optional = false optional = false
platform = "*"
python-versions = ">=3.6" python-versions = ">=3.6"
version = "18.6b4" version = "18.9b0"
[package.dependencies] [package.dependencies]
appdirs = "*" appdirs = "*"
...@@ -55,15 +50,11 @@ attrs = ">=17.4.0" ...@@ -55,15 +50,11 @@ attrs = ">=17.4.0"
click = ">=6.5" click = ">=6.5"
toml = ">=0.9.4" toml = ">=0.9.4"
[package.requirements]
python = ">=3.6,<4.0"
[[package]] [[package]]
category = "main" category = "main"
description = "httplib2 caching for requests" description = "httplib2 caching for requests"
name = "cachecontrol" name = "cachecontrol"
optional = false optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.12.5" version = "0.12.5"
...@@ -77,7 +68,6 @@ category = "dev" ...@@ -77,7 +68,6 @@ category = "dev"
description = "A decorator for caching properties in classes." description = "A decorator for caching properties in classes."
name = "cached-property" name = "cached-property"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.5.1" version = "1.5.1"
...@@ -86,7 +76,6 @@ category = "main" ...@@ -86,7 +76,6 @@ category = "main"
description = "Cachy provides a simple yet effective caching library." description = "Cachy provides a simple yet effective caching library."
name = "cachy" name = "cachy"
optional = false optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.2.0" version = "0.2.0"
...@@ -95,7 +84,6 @@ category = "main" ...@@ -95,7 +84,6 @@ category = "main"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "2018.8.24" version = "2018.8.24"
...@@ -104,7 +92,6 @@ category = "dev" ...@@ -104,7 +92,6 @@ category = "dev"
description = "Validate configuration and produce human readable error messages." description = "Validate configuration and produce human readable error messages."
name = "cfgv" name = "cfgv"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.1.0" version = "1.1.0"
...@@ -116,7 +103,6 @@ category = "main" ...@@ -116,7 +103,6 @@ category = "main"
description = "Universal encoding detector for Python 2 and 3" description = "Universal encoding detector for Python 2 and 3"
name = "chardet" name = "chardet"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "3.0.4" version = "3.0.4"
...@@ -125,7 +111,6 @@ category = "main" ...@@ -125,7 +111,6 @@ category = "main"
description = "Cleo allows you to create beautiful and testable command-line interfaces." description = "Cleo allows you to create beautiful and testable command-line interfaces."
name = "cleo" name = "cleo"
optional = false optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.6.8" version = "0.6.8"
...@@ -135,79 +120,62 @@ pylev = ">=1.3,<2.0" ...@@ -135,79 +120,62 @@ pylev = ">=1.3,<2.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "A simple wrapper around optparse for powerful command line utilities." 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" name = "click"
optional = false optional = false
platform = "*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = "*" version = "7.0"
version = "6.7"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama" name = "colorama"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "0.3.9" version = "0.3.9"
[package.requirements]
platform = "win32"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
name = "coverage" name = "coverage"
optional = false optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
version = "4.5.1" version = "4.5.1"
[[package]] [[package]]
category = "main" category = "main"
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" 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\""
name = "enum34" name = "enum34"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "1.1.6" version = "1.1.6"
[package.requirements]
python = ">=2.7,<2.8"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
marker = "python_version < \"3.0\""
name = "funcsigs" name = "funcsigs"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "1.0.2" version = "1.0.2"
[package.requirements]
python = "<3.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy." description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy."
marker = "python_version == \"2.7\""
name = "functools32" name = "functools32"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "3.2.3-2" version = "3.2.3-2"
[package.requirements]
python = ">=2.7,<2.8"
[[package]] [[package]]
category = "main" category = "main"
description = "HTML parser based on the WHATWG HTML specification" description = "HTML parser based on the WHATWG HTML specification"
name = "html5lib" name = "html5lib"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.0.1" version = "1.0.1"
...@@ -220,40 +188,34 @@ category = "dev" ...@@ -220,40 +188,34 @@ category = "dev"
description = "File identification library for Python" description = "File identification library for Python"
name = "identify" name = "identify"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.1.6" version = "1.1.7"
[[package]] [[package]]
category = "main" category = "main"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "2.7" version = "2.7"
[[package]] [[package]]
category = "dev" category = "dev"
description = "A small but fast and easy to use stand-alone template engine written in pure python." description = "A small but fast and easy to use stand-alone template engine written in pure python."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "jinja2" name = "jinja2"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "2.10" version = "2.10"
[package.dependencies] [package.dependencies]
MarkupSafe = ">=0.23" MarkupSafe = ">=0.23"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]] [[package]]
category = "main" category = "main"
description = "An implementation of JSON Schema validation for Python" description = "An implementation of JSON Schema validation for Python"
name = "jsonschema" name = "jsonschema"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "2.6.0" version = "2.6.0"
...@@ -265,9 +227,9 @@ version = "*" ...@@ -265,9 +227,9 @@ version = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python LiveReload is an awesome tool for web developers" 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" name = "livereload"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "2.5.2" version = "2.5.2"
...@@ -275,45 +237,39 @@ version = "2.5.2" ...@@ -275,45 +237,39 @@ version = "2.5.2"
six = "*" six = "*"
tornado = "*" tornado = "*"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Platform-independent file locking module" description = "Platform-independent file locking module"
marker = "extra == \"filecache\""
name = "lockfile" name = "lockfile"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "0.12.2" version = "0.12.2"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python implementation of Markdown." description = "Python implementation of Markdown."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "markdown" name = "markdown"
optional = false optional = false
platform = "*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
python-versions = "*" version = "3.0.1"
version = "2.6.11"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Implements a XML/HTML/XHTML Markup safe string for Python" description = "Implements a XML/HTML/XHTML Markup safe string for Python"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "markupsafe" name = "markupsafe"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "1.0" version = "1.0"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Project documentation with Markdown." 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" name = "mkdocs"
optional = false optional = false
platform = "*"
python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.0.4" version = "1.0.4"
...@@ -325,15 +281,12 @@ click = ">=3.3" ...@@ -325,15 +281,12 @@ click = ">=3.3"
livereload = ">=2.5.1" livereload = ">=2.5.1"
tornado = ">=5.0" tornado = ">=5.0"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Rolling backport of unittest.mock for all Pythons" description = "Rolling backport of unittest.mock for all Pythons"
marker = "python_version < \"3.0\""
name = "mock" name = "mock"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "2.0.0" version = "2.0.0"
...@@ -345,15 +298,11 @@ six = ">=1.9" ...@@ -345,15 +298,11 @@ six = ">=1.9"
python = "<3.3" python = "<3.3"
version = ">=1" version = ">=1"
[package.requirements]
python = "<3.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "More routines for operating on iterables, beyond itertools" description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools" name = "more-itertools"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "4.3.0" version = "4.3.0"
...@@ -365,7 +314,6 @@ category = "main" ...@@ -365,7 +314,6 @@ category = "main"
description = "MessagePack (de)serializer." description = "MessagePack (de)serializer."
name = "msgpack" name = "msgpack"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "0.5.6" version = "0.5.6"
...@@ -374,7 +322,6 @@ category = "dev" ...@@ -374,7 +322,6 @@ category = "dev"
description = "Node.js virtual environment builder" description = "Node.js virtual environment builder"
name = "nodeenv" name = "nodeenv"
optional = false optional = false
platform = "any"
python-versions = "*" python-versions = "*"
version = "1.3.2" version = "1.3.2"
...@@ -383,16 +330,15 @@ category = "main" ...@@ -383,16 +330,15 @@ category = "main"
description = "Bring colors to your terminal." description = "Bring colors to your terminal."
name = "pastel" name = "pastel"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "0.1.0" version = "0.1.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Object-oriented filesystem paths" description = "Object-oriented filesystem paths"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version < \"3.6\""
name = "pathlib2" name = "pathlib2"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "2.3.2" version = "2.3.2"
...@@ -403,27 +349,20 @@ six = "*" ...@@ -403,27 +349,20 @@ six = "*"
python = "<3.5" python = "<3.5"
version = "*" version = "*"
[package.requirements]
python = "<3.6"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python Build Reasonableness" description = "Python Build Reasonableness"
marker = "python_version < \"3.0\""
name = "pbr" name = "pbr"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "4.2.0" version = "4.3.0"
[package.requirements]
python = "<3.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Query metadatdata from sdists / bdists / installed packages." description = "Query metadatdata from sdists / bdists / installed packages."
name = "pkginfo" name = "pkginfo"
optional = false optional = false
platform = "Unix"
python-versions = "*" python-versions = "*"
version = "1.4.2" version = "1.4.2"
...@@ -432,7 +371,6 @@ category = "dev" ...@@ -432,7 +371,6 @@ category = "dev"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
name = "pluggy" name = "pluggy"
optional = false optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.7.1" version = "0.7.1"
...@@ -441,9 +379,8 @@ category = "dev" ...@@ -441,9 +379,8 @@ category = "dev"
description = "A framework for managing and maintaining multi-language pre-commit hooks." description = "A framework for managing and maintaining multi-language pre-commit hooks."
name = "pre-commit" name = "pre-commit"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.11.0" version = "1.11.1"
[package.dependencies] [package.dependencies]
"aspy.yaml" = "*" "aspy.yaml" = "*"
...@@ -461,7 +398,6 @@ category = "dev" ...@@ -461,7 +398,6 @@ category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities" description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py" name = "py"
optional = false optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.6.0" version = "1.6.0"
...@@ -470,7 +406,6 @@ category = "dev" ...@@ -470,7 +406,6 @@ category = "dev"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
name = "pygments" name = "pygments"
optional = false optional = false
platform = "any"
python-versions = "*" python-versions = "*"
version = "2.2.0" version = "2.2.0"
...@@ -479,7 +414,6 @@ category = "dev" ...@@ -479,7 +414,6 @@ category = "dev"
description = "Pygments Github custom lexers." description = "Pygments Github custom lexers."
name = "pygments-github-lexers" name = "pygments-github-lexers"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "0.0.5" version = "0.0.5"
...@@ -491,7 +425,6 @@ category = "main" ...@@ -491,7 +425,6 @@ category = "main"
description = "A pure Python Levenshtein implementation that's not freaking GPL'd." description = "A pure Python Levenshtein implementation that's not freaking GPL'd."
name = "pylev" name = "pylev"
optional = false optional = false
platform = "UNKNOWN"
python-versions = "*" python-versions = "*"
version = "1.3.0" version = "1.3.0"
...@@ -500,7 +433,6 @@ category = "dev" ...@@ -500,7 +433,6 @@ category = "dev"
description = "Extension pack for Python Markdown." description = "Extension pack for Python Markdown."
name = "pymdown-extensions" name = "pymdown-extensions"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "4.12" version = "4.12"
...@@ -512,16 +444,14 @@ category = "main" ...@@ -512,16 +444,14 @@ category = "main"
description = "Python parsing module" description = "Python parsing module"
name = "pyparsing" name = "pyparsing"
optional = false optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.2.1" version = "2.2.2"
[[package]] [[package]]
category = "main" category = "main"
description = "Persistent/Functional/Immutable data structures" description = "Persistent/Functional/Immutable data structures"
name = "pyrsistent" name = "pyrsistent"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "0.14.4" version = "0.14.4"
...@@ -533,23 +463,19 @@ category = "dev" ...@@ -533,23 +463,19 @@ category = "dev"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
name = "pytest" name = "pytest"
optional = false optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.8.0" version = "3.8.2"
[package.dependencies] [package.dependencies]
atomicwrites = ">=1.0" atomicwrites = ">=1.0"
attrs = ">=17.4.0" attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0" more-itertools = ">=4.0.0"
pluggy = ">=0.7" pluggy = ">=0.7"
py = ">=1.5.0" py = ">=1.5.0"
setuptools = "*" setuptools = "*"
six = ">=1.10.0" six = ">=1.10.0"
[package.dependencies.colorama]
platform = "win32"
version = "*"
[package.dependencies.funcsigs] [package.dependencies.funcsigs]
python = "<3.0" python = "<3.0"
version = "*" version = "*"
...@@ -563,7 +489,6 @@ category = "dev" ...@@ -563,7 +489,6 @@ category = "dev"
description = "Pytest plugin for measuring coverage." description = "Pytest plugin for measuring coverage."
name = "pytest-cov" name = "pytest-cov"
optional = false optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.6.0" version = "2.6.0"
...@@ -576,7 +501,6 @@ category = "dev" ...@@ -576,7 +501,6 @@ category = "dev"
description = "Thin-wrapper around the mock package for easier use with py.test" description = "Thin-wrapper around the mock package for easier use with py.test"
name = "pytest-mock" name = "pytest-mock"
optional = false optional = false
platform = "any"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.10.0" version = "1.10.0"
...@@ -590,9 +514,9 @@ version = "*" ...@@ -590,9 +514,9 @@ version = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "YAML parser and emitter for Python" 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\""
name = "pyyaml" name = "pyyaml"
optional = false optional = false
platform = "Any"
python-versions = "*" python-versions = "*"
version = "3.13" version = "3.13"
...@@ -601,7 +525,6 @@ category = "main" ...@@ -601,7 +525,6 @@ category = "main"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
name = "requests" name = "requests"
optional = false optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.19.1" version = "2.19.1"
...@@ -616,7 +539,6 @@ category = "main" ...@@ -616,7 +539,6 @@ category = "main"
description = "A utility belt for advanced users of python-requests" description = "A utility belt for advanced users of python-requests"
name = "requests-toolbelt" name = "requests-toolbelt"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "0.8.0" version = "0.8.0"
...@@ -626,48 +548,43 @@ requests = ">=2.0.1,<3.0.0" ...@@ -626,48 +548,43 @@ requests = ">=2.0.1,<3.0.0"
[[package]] [[package]]
category = "main" category = "main"
description = "scandir, a better directory iterator and faster os.walk()" description = "scandir, a better directory iterator and faster os.walk()"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.5\""
name = "scandir" name = "scandir"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.9.0" version = "1.9.0"
[package.requirements]
python = "<3.5"
[[package]] [[package]]
category = "main" category = "main"
description = "Tool to Detect Surrounding Shell" description = "Tool to Detect Surrounding Shell"
name = "shellingham" name = "shellingham"
optional = false optional = false
platform = "*"
python-versions = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3" python-versions = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3"
version = "1.2.6" version = "1.2.6"
[[package]] [[package]]
category = "main" category = "main"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version < \"3.6\" or python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "six" name = "six"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.11.0" version = "1.11.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language" description = "Python Library for Tom's Obvious, Minimal Language"
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "toml" name = "toml"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "0.9.6" version = "0.10.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Style preserving TOML library" description = "Style preserving TOML library"
name = "tomlkit" name = "tomlkit"
optional = false optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.4" version = "0.4.4"
...@@ -683,21 +600,17 @@ version = ">=3.6,<4.0" ...@@ -683,21 +600,17 @@ version = ">=3.6,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 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" name = "tornado"
optional = false optional = false
platform = "*"
python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*" python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*"
version = "5.1.1" version = "5.1.1"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "virtualenv-based automation of test activities" description = "virtualenv-based automation of test activities"
name = "tox" name = "tox"
optional = false optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.4.0" version = "3.4.0"
...@@ -712,30 +625,26 @@ virtualenv = ">=1.11.2" ...@@ -712,30 +625,26 @@ virtualenv = ">=1.11.2"
[[package]] [[package]]
category = "main" category = "main"
description = "Type Hints for Python" 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\""
name = "typing" name = "typing"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "3.6.6" version = "3.6.6"
[package.requirements]
python = ">=2.7,<2.8 || >=3.4,<3.5"
[[package]] [[package]]
category = "main" category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3" name = "urllib3"
optional = false optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.23" version = "1.23"
[[package]] [[package]]
category = "main" category = "main"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "virtualenv" name = "virtualenv"
optional = false optional = false
platform = "*"
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
version = "16.0.0" version = "16.0.0"
...@@ -744,13 +653,11 @@ category = "main" ...@@ -744,13 +653,11 @@ category = "main"
description = "Character encoding aliases for legacy web content" description = "Character encoding aliases for legacy web content"
name = "webencodings" name = "webencodings"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "0.5.1" version = "0.5.1"
[metadata] [metadata]
content-hash = "39b7f1660472b5b5d4db23bdf13c56cae616f260870026c753d762cfdb64281a" content-hash = "39b7f1660472b5b5d4db23bdf13c56cae616f260870026c753d762cfdb64281a"
platform = "*"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
[metadata.hashes] [metadata.hashes]
...@@ -758,7 +665,7 @@ appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", " ...@@ -758,7 +665,7 @@ appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "
"aspy.yaml" = ["04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", "0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa"] "aspy.yaml" = ["04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", "0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa"]
atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"] atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"]
attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"] attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"]
black = ["22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", "4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"] black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"]
cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"] cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"]
cached-property = ["3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", "9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504"] cached-property = ["3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", "9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504"]
cachy = ["b71513e5a38ce90c1280c02b7d8d6bb3fdf64666c9cc0584f2479afea097d56c", "b71e8e7ddb5b386e23e81befdfac8a93885406139b8681bedc17b3444fcb8fca"] cachy = ["b71513e5a38ce90c1280c02b7d8d6bb3fdf64666c9cc0584f2479afea097d56c", "b71e8e7ddb5b386e23e81befdfac8a93885406139b8681bedc17b3444fcb8fca"]
...@@ -766,20 +673,20 @@ certifi = ["376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", " ...@@ -766,20 +673,20 @@ certifi = ["376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", "
cfgv = ["73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", "d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa"] cfgv = ["73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", "d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa"]
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
cleo = ["85a63076b72ca376fb06668be1fc7758dc16740b394783d5cc65200c4b32f71b", "9b7f79f1aa470a025c0d28c76aa225ee9e65028d32f80032e871aa3500df61b8"] cleo = ["85a63076b72ca376fb06668be1fc7758dc16740b394783d5cc65200c4b32f71b", "9b7f79f1aa470a025c0d28c76aa225ee9e65028d32f80032e871aa3500df61b8"]
click = ["29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", "f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"] colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"]
coverage = ["03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", "0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", "104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", "10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", "15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", "198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", "23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", "28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", "2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", "2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", "337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", "3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", "3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", "3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", "3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", "4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", "56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", "5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", "69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", "6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", "701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", "7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", "76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", "7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", "7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", "7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", "8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", "9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", "9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", "ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", "b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", "be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", "c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", "de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", "e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", "e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", "f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"] coverage = ["03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", "0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", "104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", "10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", "15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", "198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", "23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", "28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", "2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", "2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", "337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", "3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", "3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", "3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", "3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", "4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", "56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", "5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", "69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", "6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", "701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", "7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", "76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", "7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", "7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", "7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", "8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", "9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", "9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", "ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", "b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", "be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", "c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", "de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", "e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", "e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", "f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"]
enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"] enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"]
funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"] funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"]
functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"] functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"]
html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"] html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"]
identify = ["a425281e5578eb08651cd205e64b5c3660de5ba33860d16450dd5f6e478f50ff", "baa10ef6a82d45a29e4b179866dce7cf41a271d1e6c678c293922b50375d42be"] identify = ["5e956558a9a1e3b3891d7c6609fc9709657a11878af288ace484d1a46a93922b", "623086059219cc7b86c77a3891f3700cb175d4ce02b8fb8802b047301d71e783"]
idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"] idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"]
jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"] jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"]
jsonschema = ["000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", "6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"] jsonschema = ["000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", "6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"]
livereload = ["583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", "dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"] livereload = ["583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", "dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"]
lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"] lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"]
markdown = ["9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", "a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81"] markdown = ["c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa", "d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"]
markupsafe = ["a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"] markupsafe = ["a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"]
mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"] mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"]
mock = ["5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", "b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"] mock = ["5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", "b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"]
...@@ -788,18 +695,18 @@ msgpack = ["0b3b1773d2693c70598585a34ca2715873ba899565f0a7c9a1545baef7e7fbdc", " ...@@ -788,18 +695,18 @@ msgpack = ["0b3b1773d2693c70598585a34ca2715873ba899565f0a7c9a1545baef7e7fbdc", "
nodeenv = ["aa040ab5189bae17d272175609010be6c5b589ec4b8dbd832cc50c9e9cb7496f"] nodeenv = ["aa040ab5189bae17d272175609010be6c5b589ec4b8dbd832cc50c9e9cb7496f"]
pastel = ["3108af417ec0fa6d0a620e676ec4f02c839ca13e10611586e5d2174b46aa0bc3", "d1fee8079534f99f1805a044fef946d23eee6d6a7cd34292c30e6c16be9a80b9"] pastel = ["3108af417ec0fa6d0a620e676ec4f02c839ca13e10611586e5d2174b46aa0bc3", "d1fee8079534f99f1805a044fef946d23eee6d6a7cd34292c30e6c16be9a80b9"]
pathlib2 = ["8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", "d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"] pathlib2 = ["8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", "d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"]
pbr = ["1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", "b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"] pbr = ["1be135151a0da949af8c5d0ee9013d9eafada71237eb80b3ba8896b4f12ec5dc", "cf36765bf2218654ae824ec8e14257259ba44e43b117fd573c8d07a9895adbdd"]
pkginfo = ["5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", "a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"] pkginfo = ["5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", "a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"]
pluggy = ["6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"] pluggy = ["6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"]
pre-commit = ["18ef2892ff6b6518945bc7dcf423e3c969033a4ca132b6a1ae0d52eb2e51ea27", "88d59872610a7069d937b6868632ba534187bda58c4665de12b25c8c549ddd0e"] pre-commit = ["3ce9dd5a912e1c0d0fa0c5991927429a62661ca7ed00ee3dc4ca2c743ec259ce", "e7efe81063b8cb2a72190db06109191aac67971fc7a31d9e9485907f6be76954"]
py = ["06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"] py = ["06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"]
pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"] pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"]
pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"] pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"]
pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"] pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"]
pymdown-extensions = ["20f2ae1067ab850cab92fcf57487267a7fd1365a7b1e7c5394e1e0778455eec1", "7d3fcbb4c5d70a78d1f4c2c7eef02dbe7e1ba08b06cb72e08b3d1027eb77458b"] pymdown-extensions = ["20f2ae1067ab850cab92fcf57487267a7fd1365a7b1e7c5394e1e0778455eec1", "7d3fcbb4c5d70a78d1f4c2c7eef02dbe7e1ba08b06cb72e08b3d1027eb77458b"]
pyparsing = ["905d8090c335314568b5faee0025b1829f27bb974604a5762a6cdef3a7dfc3b7", "f493ee323be1e94929416b3585eefcc04943115cecbaaa35a8c86d1a2368af19"] pyparsing = ["bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", "d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"]
pyrsistent = ["4024f838472cba9ea1ccbc638e0bcafec2efda28594a9905177ec365f1a95fea"] pyrsistent = ["4024f838472cba9ea1ccbc638e0bcafec2efda28594a9905177ec365f1a95fea"]
pytest = ["453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", "a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d"] pytest = ["7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e", "9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"]
pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"] pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"]
pytest-mock = ["53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928", "d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"] pytest-mock = ["53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928", "d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"]
pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"]
...@@ -808,7 +715,7 @@ requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4a ...@@ -808,7 +715,7 @@ requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4a
scandir = ["04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", "1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", "1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", "346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", "44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", "61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", "a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", "c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", "c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", "c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", "f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"] scandir = ["04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", "1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", "1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", "346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", "44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", "61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", "a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", "c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", "c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", "c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", "f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"]
shellingham = ["52e8047d76abf52edbdf1ffed8cd46cab4dbef4f71a1baa1085afdaa272cef83", "58e4a0f5fecd7b73e097f2ac5d924e81cc1161e2ce760d5aed07ee2f87923074"] shellingham = ["52e8047d76abf52edbdf1ffed8cd46cab4dbef4f71a1baa1085afdaa272cef83", "58e4a0f5fecd7b73e097f2ac5d924e81cc1161e2ce760d5aed07ee2f87923074"]
six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"] six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"]
toml = ["380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", "a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
tomlkit = ["8ab16e93162fc44d3ad83d2aa29a7140b8f7d996ae1790a73b9a7aed6fb504ac", "ca181cee7aee805d455628f7c94eb8ae814763769a93e69157f250fe4ebe1926"] tomlkit = ["8ab16e93162fc44d3ad83d2aa29a7140b8f7d996ae1790a73b9a7aed6fb504ac", "ca181cee7aee805d455628f7c94eb8ae814763769a93e69157f250fe4ebe1926"]
tornado = ["0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", "4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", "732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", "8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", "8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", "d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", "e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444"] tornado = ["0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", "4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", "732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", "8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", "8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", "d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", "e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444"]
tox = ["7f802b37fffd3b5ef2aab104943fa5dad24bf9564bb7e732e54b8d0cfec2fca0", "cc97859bd7f38aa5b3b8ba55ffe7ee9952e7050faad1aedc0829cd3db2fb61d6"] tox = ["7f802b37fffd3b5ef2aab104943fa5dad24bf9564bb7e732e54b8d0cfec2fca0", "cc97859bd7f38aa5b3b8ba55ffe7ee9952e7050faad1aedc0829cd3db2fb61d6"]
......
...@@ -14,16 +14,17 @@ class DebugResolveCommand(Command): ...@@ -14,16 +14,17 @@ class DebugResolveCommand(Command):
{ --E|extras=* : Extras to activate for the dependency. } { --E|extras=* : Extras to activate for the dependency. }
{ --python= : Python version(s) to use for resolution. } { --python= : Python version(s) to use for resolution. }
{ --tree : Displays the dependency tree. } { --tree : Displays the dependency tree. }
{ --install : Show what would be installed for the current system. }
""" """
_loggers = ["poetry.repositories.pypi_repository"] _loggers = ["poetry.repositories.pypi_repository"]
def handle(self): def handle(self):
from poetry.packages import Dependency
from poetry.packages import ProjectPackage from poetry.packages import ProjectPackage
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.utils.env import Env
packages = self.argument("package") packages = self.argument("package")
...@@ -35,7 +36,6 @@ class DebugResolveCommand(Command): ...@@ -35,7 +36,6 @@ class DebugResolveCommand(Command):
) )
requirements = self._format_requirements(packages) requirements = self._format_requirements(packages)
dependencies = []
for name, constraint in requirements.items(): for name, constraint in requirements.items():
dep = package.add_dependency(name, constraint) dep = package.add_dependency(name, constraint)
extras = [] extras = []
...@@ -48,13 +48,13 @@ class DebugResolveCommand(Command): ...@@ -48,13 +48,13 @@ class DebugResolveCommand(Command):
for ex in extras: for ex in extras:
dep.extras.append(ex) dep.extras.append(ex)
package.python_versions = ( package.python_versions = self.option("python") or (
self.option("python") or self.poetry.package.python_versions self.poetry.package.python_versions
) )
solver = Solver( pool = self.poetry.pool
package, self.poetry.pool, Repository(), Repository(), self.output
) solver = Solver(package, pool, Repository(), Repository(), self.output)
ops = solver.solve() ops = solver.solve()
...@@ -79,16 +79,28 @@ class DebugResolveCommand(Command): ...@@ -79,16 +79,28 @@ class DebugResolveCommand(Command):
return 0 return 0
env = Env.get()
current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info)
)
for op in ops: for op in ops:
package = op.package pkg = op.package
if self.option("install"):
if not pkg.python_constraint.allows(
current_python_version
) or not env.is_valid_for_marker(pkg.marker):
continue
self.line( self.line(
" - <info>{}</info> (<comment>{}</comment>)".format( " - <info>{}</info> (<comment>{}</comment>)".format(
package.name, package.version pkg.name, pkg.version
) )
) )
if package.requirements: if not pkg.python_constraint.is_any():
for req_name, req_value in package.requirements.items(): self.line(" - python: {}".format(pkg.python_versions))
self.line(" - {}: {}".format(req_name, req_value))
if not pkg.marker.is_any():
self.line(" - marker: {}".format(pkg.marker))
def _determine_requirements(self, requires): # type: (List[str]) -> List[str] def _determine_requirements(self, requires): # type: (List[str]) -> List[str]
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
...@@ -97,7 +109,6 @@ class DebugResolveCommand(Command): ...@@ -97,7 +109,6 @@ class DebugResolveCommand(Command):
return [] return []
requires = self._parse_name_version_pairs(requires) requires = self._parse_name_version_pairs(requires)
result = []
for requirement in requires: for requirement in requires:
if "version" in requirement: if "version" in requirement:
parse_constraint(requirement["version"]) parse_constraint(requirement["version"])
......
...@@ -23,7 +23,9 @@ lists all packages available.""" ...@@ -23,7 +23,9 @@ lists all packages available."""
colors = ["green", "yellow", "cyan", "magenta", "blue"] colors = ["green", "yellow", "cyan", "magenta", "blue"]
def handle(self): def handle(self):
from poetry.packages.constraints.generic_constraint import GenericConstraint from poetry.packages.constraints import (
parse_constraint as parse_generic_constraint,
)
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.semver import Version from poetry.semver import Version
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
...@@ -99,17 +101,13 @@ lists all packages available.""" ...@@ -99,17 +101,13 @@ lists all packages available."""
installed_repo = InstalledRepository.load(self.env) installed_repo = InstalledRepository.load(self.env)
skipped = [] skipped = []
platform = sys.platform
python = Version.parse(".".join([str(i) for i in self.env.version_info[:3]])) python = Version.parse(".".join([str(i) for i in self.env.version_info[:3]]))
# Computing widths # Computing widths
for locked in locked_packages: for locked in locked_packages:
python_constraint = parse_constraint(locked.requirements.get("python", "*")) python_constraint = locked.python_constraint
platform_constraint = GenericConstraint.parse( if not python_constraint.allows(python) or not self.env.is_valid_for_marker(
locked.requirements.get("platform", "*") locked.marker
)
if not python_constraint.allows(python) or not platform_constraint.matches(
GenericConstraint("=", platform)
): ):
skipped.append(locked) skipped.append(locked)
......
...@@ -7,7 +7,7 @@ from poetry.io import NullIO ...@@ -7,7 +7,7 @@ from poetry.io import NullIO
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.packages import Locker from poetry.packages import Locker
from poetry.packages import Package from poetry.packages import Package
from poetry.packages.constraints.generic_constraint import GenericConstraint from poetry.packages.constraints import parse_constraint as parse_generic_constraint
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.puzzle.operations import Install from poetry.puzzle.operations import Install
from poetry.puzzle.operations import Uninstall from poetry.puzzle.operations import Uninstall
...@@ -457,29 +457,13 @@ class Installer: ...@@ -457,29 +457,13 @@ class Installer:
if op.skipped: if op.skipped:
op.unskip() op.unskip()
python = Version.parse( current_python = parse_constraint(
".".join([str(i) for i in self._env.version_info[:3]]) ".".join(str(v) for v in self._env.version_info[:3])
) )
if "python" in package.requirements: if not package.python_constraint.allows(
python_constraint = parse_constraint(package.requirements["python"]) current_python
if not python_constraint.allows(python): ) or not self._env.is_valid_for_marker(package.marker):
# Incompatible python versions op.skip("Not needed for the current environment")
op.skip("Not needed for the current python version")
continue
if not package.python_constraint.allows(python):
op.skip("Not needed for the current python version")
continue
if "platform" in package.requirements:
platform_constraint = GenericConstraint.parse(
package.requirements["platform"]
)
if not platform_constraint.matches(
GenericConstraint("=", sys.platform)
):
# Incompatible systems
op.skip("Not needed for the current platform")
continue continue
if self._update: if self._update:
......
...@@ -151,7 +151,7 @@ class Term(object): ...@@ -151,7 +151,7 @@ class Term(object):
return ( return (
self.dependency.is_root self.dependency.is_root
or other.is_root or other.is_root
or (other.name == self.dependency.name) or other.name == self.dependency.name
) )
def _non_empty_term(self, constraint, is_positive): def _non_empty_term(self, constraint, is_positive):
......
...@@ -34,7 +34,7 @@ def dependency_from_pep_508(name): ...@@ -34,7 +34,7 @@ def dependency_from_pep_508(name):
req = Requirement(name) req = Requirement(name)
if req.marker: if req.marker:
markers = convert_markers(req.marker.markers) markers = convert_markers(req.marker)
else: else:
markers = {} markers = {}
...@@ -128,28 +128,8 @@ def dependency_from_pep_508(name): ...@@ -128,28 +128,8 @@ def dependency_from_pep_508(name):
dep.python_versions = " || ".join(ors) dep.python_versions = " || ".join(ors)
if "sys_platform" in markers: if req.marker:
ors = [] dep.marker = req.marker
for or_ in markers["sys_platform"]:
ands = []
for op, platform in or_:
if op == "==":
op = ""
elif op == "in":
platforms = []
for v in re.split("[ ,]+", platform):
platforms.append(v)
if platforms:
ands.append(" || ".join(platforms))
continue
ands.append("{}{}".format(op, platform))
ors.append(" ".join(ands))
dep.platform = " || ".join(ors)
# Extras # Extras
for extra in req.extras: for extra in req.extras:
......
import re
from .any_constraint import AnyConstraint
from .constraint import Constraint
from .union_constraint import UnionConstraint
BASIC_CONSTRAINT = re.compile(r"^(!?==?)?\s*([^\s]+?)\s*$")
def parse_constraint(constraints):
if constraints == "*":
return AnyConstraint()
or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip())
or_groups = []
for constraints in or_constraints:
and_constraints = re.split(
r"(?<!^)(?<![=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints
)
constraint_objects = []
if len(and_constraints) > 1:
for constraint in and_constraints:
constraint_objects.append(parse_single_constraint(constraint))
else:
constraint_objects.append(parse_single_constraint(and_constraints[0]))
if len(constraint_objects) == 1:
constraint = constraint_objects[0]
else:
constraint = constraint_objects[0]
for next_constraint in constraint_objects[1:]:
constraint = constraint.intersect(next_constraint)
or_groups.append(constraint)
if len(or_groups) == 1:
return or_groups[0]
else:
return UnionConstraint(*or_groups)
def parse_single_constraint(constraint): # type: (str) -> BaseConstraint
# Basic comparator
m = BASIC_CONSTRAINT.match(constraint)
if m:
op = m.group(1)
if op is None:
op = "=="
version = m.group(2).strip()
return Constraint(version, op)
raise ValueError("Could not parse version constraint: {}".format(constraint))
from .base_constraint import BaseConstraint
from .empty_constraint import EmptyConstraint
class AnyConstraint(BaseConstraint):
def allows(self, other):
return True
def allows_all(self, other):
return True
def allows_any(self, other):
return True
def difference(self, other):
if other.is_any():
return EmptyConstraint()
return other
def intersect(self, other):
return other
def union(self, other):
return AnyConstraint()
def is_any(self):
return True
def is_empty(self):
return False
def __str__(self):
return "*"
def __eq__(self, other):
return other.is_any()
class BaseConstraint(object): class BaseConstraint(object):
def matches(self, provider):
raise NotImplementedError()
def allows_all(self, other): def allows_all(self, other):
raise NotImplementedError() raise NotImplementedError()
...@@ -14,5 +11,17 @@ class BaseConstraint(object): ...@@ -14,5 +11,17 @@ class BaseConstraint(object):
def intersect(self, other): def intersect(self, other):
raise NotImplementedError() raise NotImplementedError()
def union(self, other):
raise NotImplementedError()
def is_any(self):
return False
def is_empty(self): def is_empty(self):
return False return False
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self))
def __eq__(self, other):
raise NotImplementedError()
import operator
from .base_constraint import BaseConstraint
from .empty_constraint import EmptyConstraint
class Constraint(BaseConstraint):
OP_EQ = operator.eq
OP_NE = operator.ne
_trans_op_str = {"=": OP_EQ, "==": OP_EQ, "!=": OP_NE}
_trans_op_int = {OP_EQ: "==", OP_NE: "!="}
def __init__(self, version, operator="=="):
if operator == "=":
operator = "=="
self._version = version
self._operator = operator
self._op = self._trans_op_str[operator]
@property
def version(self):
return self._version
@property
def operator(self):
return self._operator
def allows(self, other):
is_equal_op = self._operator == "=="
is_non_equal_op = self._operator == "!="
is_other_equal_op = other.operator == "=="
is_other_non_equal_op = other.operator == "!="
if is_equal_op and is_other_equal_op:
return self._version == other.version
if (
is_equal_op
and is_other_non_equal_op
or is_non_equal_op
and is_other_equal_op
or is_non_equal_op
and is_other_non_equal_op
):
return self._version != other.version
return False
def allows_all(self, other):
if not isinstance(other, Constraint):
return other.is_empty()
return other == self
def allows_any(self, other):
if isinstance(other, Constraint):
is_non_equal_op = self._operator == "!="
is_other_non_equal_op = other.operator == "!="
if is_non_equal_op and is_other_non_equal_op:
return self._version != other.version
return other.allows(self)
def difference(self, other):
if other.allows(self):
return EmptyConstraint()
return self
def intersect(self, other):
from .multi_constraint import MultiConstraint
if isinstance(other, Constraint):
if other == self:
return self
if self.operator == "!=" and other.operator == "==" and self.allows(other):
return other
if other.operator == "!=" and self.operator == "==" and other.allows(self):
return self
if other.operator == "!=" and self.operator == "!=":
return MultiConstraint(self, other)
return EmptyConstraint()
return other.intersect(self)
def union(self, other):
if isinstance(other, Constraint):
from .union_constraint import UnionConstraint
return UnionConstraint(self, other)
return other.union(self)
def is_any(self):
return False
def is_empty(self):
return False
def __eq__(self, other):
if not isinstance(other, Constraint):
return NotImplemented
return (self.version, self.operator) == (other.version, other.operator)
def __hash__(self):
return hash((self._operator, self._version))
def __str__(self):
return "{}{}".format(
self._operator if self._operator != "==" else "", self._version
)
...@@ -23,5 +23,8 @@ class EmptyConstraint(BaseConstraint): ...@@ -23,5 +23,8 @@ class EmptyConstraint(BaseConstraint):
def difference(self, other): def difference(self, other):
return return
def __eq__(self, other):
return other.is_empty()
def __str__(self): def __str__(self):
return "*" return ""
import operator
import re
from .base_constraint import BaseConstraint
from .empty_constraint import EmptyConstraint
from .multi_constraint import MultiConstraint
class GenericConstraint(BaseConstraint):
"""
Represents a generic constraint.
This is particularly useful for platform/system/os/extra constraints.
"""
OP_EQ = operator.eq
OP_NE = operator.ne
_trans_op_str = {"=": OP_EQ, "==": OP_EQ, "!=": OP_NE}
_trans_op_int = {OP_EQ: "==", OP_NE: "!="}
def __init__(self, operator, version):
if operator not in self._trans_op_str:
raise ValueError(
'Invalid operator "{}" given, '
"expected one of: {}".format(
operator, ", ".join(self.supported_operators)
)
)
self._operator = self._trans_op_str[operator]
self._string_operator = self._trans_op_int[self._operator]
self._version = version
@property
def supported_operators(self):
return list(self._trans_op_str.keys())
@property
def operator(self):
return self._operator
@property
def string_operator(self):
return self._string_operator
@property
def version(self):
return self._version
def matches(self, provider):
if not isinstance(provider, GenericConstraint):
return provider.matches(self)
is_equal_op = self.OP_EQ is self._operator
is_non_equal_op = self.OP_NE is self._operator
is_provider_equal_op = self.OP_EQ is provider.operator
is_provider_non_equal_op = self.OP_NE is provider.operator
if (
is_equal_op
and is_provider_equal_op
or is_non_equal_op
and is_provider_non_equal_op
):
return self._version == provider.version
if (
is_equal_op
and is_provider_non_equal_op
or is_non_equal_op
and is_provider_equal_op
):
return self._version != provider.version
return False
@classmethod
def parse(cls, constraints):
"""
Parses a constraint string into
MultiConstraint and/or PlatformConstraint objects.
"""
pretty_constraint = constraints
or_constraints = re.split("\s*\|\|?\s*", constraints.strip())
or_groups = []
for constraints in or_constraints:
and_constraints = re.split(
"(?<!^)(?<![ ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints
)
if len(and_constraints) > 1:
constraint_objects = []
for constraint in and_constraints:
for parsed_constraint in cls._parse_constraint(constraint):
constraint_objects.append(parsed_constraint)
else:
constraint_objects = cls._parse_constraint(and_constraints[0])
if len(constraint_objects) == 1:
constraint = constraint_objects[0]
else:
constraint = MultiConstraint(constraint_objects)
or_groups.append(constraint)
if len(or_groups) == 1:
constraint = or_groups[0]
else:
constraint = MultiConstraint(or_groups, False)
constraint.pretty_string = pretty_constraint
return constraint
@classmethod
def _parse_constraint(cls, constraint):
m = re.match("(?i)^v?[xX*](\.[xX*])*$", constraint)
if m:
return (EmptyConstraint(),)
# Basic Comparators
m = re.match("^(!=|==?)?\s*(.*)", constraint)
if m:
return (GenericConstraint(m.group(1) or "=", m.group(2)),)
raise ValueError("Could not parse generic constraint: {}".format(constraint))
def __str__(self):
op = self._trans_op_int[self._operator]
if op == "==":
op = ""
else:
op = op + " "
return "{}{}".format(op, self._version)
def __repr__(self):
return "<GenericConstraint '{}'>".format(str(self))
from .base_constraint import BaseConstraint from .base_constraint import BaseConstraint
from .constraint import Constraint
class MultiConstraint(BaseConstraint): class MultiConstraint(BaseConstraint):
def __init__(self, constraints, conjunctive=True): def __init__(self, *constraints):
self._constraints = tuple(constraints) if any(c.operator == "==" for c in constraints):
self._conjunctive = conjunctive raise ValueError(
"A multi-constraint can only be comprised of negative constraints"
)
self._constraints = constraints
@property @property
def constraints(self): def constraints(self):
return self._constraints return self._constraints
def is_conjunctive(self): def allows(self, other):
return self._conjunctive for constraint in self._constraints:
if not constraint.allows(other):
return False
def is_disjunctive(self): return True
return not self._conjunctive
def matches(self, provider): def allows_all(self, other):
if self.is_disjunctive(): if other.is_any():
for constraint in self._constraints: return False
if constraint.matches(provider):
if other.is_empty():
return True
if isinstance(other, Constraint):
return self.allows(other)
our_constraints = iter(self._constraints)
their_constraints = iter(other.constraints)
our_constraint = next(our_constraints, None)
their_constraint = next(their_constraints, None)
while our_constraint and their_constraint:
if our_constraint.allows_all(their_constraint):
their_constraint = next(their_constraints, None)
else:
our_constraint = next(our_constraints, None)
return their_constraint is None
def allows_any(self, other):
if other.is_any():
return True
if other.is_empty():
return True
if isinstance(other, Constraint):
return self.allows(other)
if isinstance(other, MultiConstraint):
for c1 in self.constraints:
for c2 in other.constraints:
if c1.allows(c2):
return True return True
return False return False
for constraint in self._constraints: def intersect(self, other):
if not constraint.matches(provider): if isinstance(other, Constraint):
constraints = [c for c in self._constraints if c == other]
if len(constraints) == 1:
return constraints[0]
return MultiConstraint(*constraints)
def __eq__(self, other):
if not isinstance(other, MultiConstraint):
return False return False
return True return sorted(
self._constraints, key=lambda c: (c.operator, c.version)
) == sorted(other.constraints, key=lambda c: (c.operator, c.version))
def __str__(self): def __str__(self):
constraints = [] constraints = []
for constraint in self._constraints: for constraint in self._constraints:
constraints.append(str(constraint)) constraints.append(str(constraint))
return "{}".format((", " if self._conjunctive else " || ").join(constraints)) return "{}".format(", ").join(constraints)
from .base_constraint import BaseConstraint
from .constraint import Constraint
from .empty_constraint import EmptyConstraint
from .multi_constraint import MultiConstraint
class UnionConstraint(BaseConstraint):
def __init__(self, *constraints):
self._constraints = constraints
@property
def constraints(self):
return self._constraints
def allows(self, other):
for constraint in self._constraints:
if constraint.allows(other):
return True
return False
def allows_any(self, other):
if other.is_empty():
return False
if other.is_any():
return True
if isinstance(other, Constraint):
constraints = [other]
else:
constraints = other.constraints
for our_constraint in self._constraints:
for their_constraint in constraints:
if our_constraint.allows_any(their_constraint):
return True
return False
def allows_all(self, other):
if other.is_any():
return False
if other.is_empty():
return True
if isinstance(other, Constraint):
constraints = [other]
else:
constraints = other.constraints
our_constraints = iter(self._constraints)
their_constraints = iter(constraints)
our_constraint = next(our_constraints, None)
their_constraint = next(their_constraints, None)
while our_constraint and their_constraint:
if our_constraint.allows_all(their_constraint):
their_constraint = next(their_constraints, None)
else:
our_constraint = next(our_constraints, None)
return their_constraint is None
def intersect(self, other):
if other.is_any():
return self
if other.is_empty():
return other
if isinstance(other, Constraint):
if self.allows(other):
return other
return EmptyConstraint()
new_constraints = []
for our_constraint in self._constraints:
for their_constraint in other.constraints:
intersection = our_constraint.intersect(their_constraint)
if not intersection.is_empty() and intersection not in new_constraints:
new_constraints.append(intersection)
if not new_constraints:
return EmptyConstraint()
return UnionConstraint(*new_constraints)
def union(self, other):
if isinstance(other, Constraint):
constraints = self._constraints
if other not in self._constraints:
constraints += (other,)
return UnionConstraint(*constraints)
def __eq__(self, other):
if not isinstance(other, UnionConstraint):
return False
return sorted(
self._constraints, key=lambda c: (c.operator, c.version)
) == sorted(other.constraints, key=lambda c: (c.operator, c.version))
def __str__(self):
constraints = []
for constraint in self._constraints:
constraints.append(str(constraint))
return "{}".format(" || ").join(constraints)
...@@ -6,10 +6,14 @@ from poetry.semver import VersionConstraint ...@@ -6,10 +6,14 @@ from poetry.semver import VersionConstraint
from poetry.semver import VersionRange from poetry.semver import VersionRange
from poetry.semver import VersionUnion from poetry.semver import VersionUnion
from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import canonicalize_name
from poetry.version.markers import AnyMarker
from poetry.version.markers import parse_marker
from .constraints.empty_constraint import EmptyConstraint from .constraints import parse_constraint as parse_generic_constraint
from .constraints.generic_constraint import GenericConstraint from .constraints.any_constraint import AnyConstraint
from .constraints.constraint import Constraint
from .constraints.multi_constraint import MultiConstraint from .constraints.multi_constraint import MultiConstraint
from .constraints.union_constraint import UnionConstraint
class Dependency(object): class Dependency(object):
...@@ -45,8 +49,6 @@ class Dependency(object): ...@@ -45,8 +49,6 @@ class Dependency(object):
self._python_versions = "*" self._python_versions = "*"
self._python_constraint = parse_constraint("*") self._python_constraint = parse_constraint("*")
self._platform = "*"
self._platform_constraint = EmptyConstraint()
self._extras = [] self._extras = []
self._in_extras = [] self._in_extras = []
...@@ -54,6 +56,7 @@ class Dependency(object): ...@@ -54,6 +56,7 @@ class Dependency(object):
self._activated = not self._optional self._activated = not self._optional
self.is_root = False self.is_root = False
self.marker = AnyMarker()
@property @property
def name(self): def name(self):
...@@ -83,25 +86,20 @@ class Dependency(object): ...@@ -83,25 +86,20 @@ class Dependency(object):
def python_versions(self, value): def python_versions(self, value):
self._python_versions = value self._python_versions = value
self._python_constraint = parse_constraint(value) self._python_constraint = parse_constraint(value)
if not self._python_constraint.is_any():
self.marker = self.marker.intersect(
parse_marker(
self._create_nested_marker(
"python_version", self._python_constraint
)
)
)
@property @property
def python_constraint(self): def python_constraint(self):
return self._python_constraint return self._python_constraint
@property @property
def platform(self):
return self._platform
@platform.setter
def platform(self, value):
self._platform = value
self._platform_constraint = GenericConstraint.parse(value)
@property
def platform_constraint(self):
return self._platform_constraint
@property
def extras(self): # type: () -> list def extras(self): # type: () -> list
return self._extras return self._extras
...@@ -152,9 +150,15 @@ class Dependency(object): ...@@ -152,9 +150,15 @@ class Dependency(object):
elif not self.constraint.is_any(): elif not self.constraint.is_any():
requirement += " ({})".format(str(self.constraint).replace(" ", "")) requirement += " ({})".format(str(self.constraint).replace(" ", ""))
# Markers
markers = [] markers = []
if not self.marker.is_any():
marker = self.marker
if not with_extras:
marker = marker.without_extras()
if not marker.is_empty():
markers.append(str(marker))
else:
# Python marker # Python marker
if self.python_versions != "*": if self.python_versions != "*":
python_constraint = self.python_constraint python_constraint = self.python_constraint
...@@ -163,17 +167,10 @@ class Dependency(object): ...@@ -163,17 +167,10 @@ class Dependency(object):
self._create_nested_marker("python_version", python_constraint) self._create_nested_marker("python_version", python_constraint)
) )
if self.platform != "*":
platform_constraint = self.platform_constraint
markers.append(
self._create_nested_marker("sys_platform", platform_constraint)
)
in_extras = " || ".join(self._in_extras) in_extras = " || ".join(self._in_extras)
if in_extras and with_extras: if in_extras and with_extras:
markers.append( markers.append(
self._create_nested_marker("extra", GenericConstraint.parse(in_extras)) self._create_nested_marker("extra", parse_generic_constraint(in_extras))
) )
if markers: if markers:
...@@ -186,17 +183,17 @@ class Dependency(object): ...@@ -186,17 +183,17 @@ class Dependency(object):
return requirement return requirement
def _create_nested_marker(self, name, constraint): def _create_nested_marker(self, name, constraint):
if isinstance(constraint, MultiConstraint): if isinstance(constraint, (MultiConstraint, UnionConstraint)):
parts = [] parts = []
for c in constraint.constraints: for c in constraint.constraints:
multi = False multi = False
if isinstance(c, MultiConstraint): if isinstance(c, (MultiConstraint, UnionConstraint)):
multi = True multi = True
parts.append((multi, self._create_nested_marker(name, c))) parts.append((multi, self._create_nested_marker(name, c)))
glue = " and " glue = " and "
if constraint.is_disjunctive(): if isinstance(constraint, UnionConstraint):
parts = [ parts = [
"({})".format(part[1]) if part[0] else part[1] for part in parts "({})".format(part[1]) if part[0] else part[1] for part in parts
] ]
...@@ -205,10 +202,8 @@ class Dependency(object): ...@@ -205,10 +202,8 @@ class Dependency(object):
parts = [part[1] for part in parts] parts = [part[1] for part in parts]
marker = glue.join(parts) marker = glue.join(parts)
elif isinstance(constraint, GenericConstraint): elif isinstance(constraint, Constraint):
marker = '{} {} "{}"'.format( marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version)
name, constraint.string_operator, constraint.version
)
elif isinstance(constraint, VersionUnion): elif isinstance(constraint, VersionUnion):
parts = [] parts = []
for c in constraint.ranges: for c in constraint.ranges:
...@@ -278,7 +273,6 @@ class Dependency(object): ...@@ -278,7 +273,6 @@ class Dependency(object):
new.is_root = self.is_root new.is_root = self.is_root
new.python_versions = self.python_versions new.python_versions = self.python_versions
new.platform = self.platform
for extra in self.extras: for extra in self.extras:
new.extras.append(extra) new.extras.append(extra)
......
from .package import Package class DependencyPackage(object):
class DependencyPackage:
def __init__(self, dependency, package): def __init__(self, dependency, package):
self._dependency = dependency self._dependency = dependency
self._package = package self._package = package
...@@ -17,6 +14,12 @@ class DependencyPackage: ...@@ -17,6 +14,12 @@ class DependencyPackage:
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self._package, name) return getattr(self._package, name)
def __setattr__(self, key, value):
if key in {"_dependency", "_package"}:
return super(DependencyPackage, self).__setattr__(key, value)
setattr(self._package, key, value)
def __str__(self): def __str__(self):
return str(self._package) return str(self._package)
......
...@@ -73,7 +73,6 @@ class DirectoryDependency(Dependency): ...@@ -73,7 +73,6 @@ class DirectoryDependency(Dependency):
self._package.dev_requires += package.dev_requires self._package.dev_requires += package.dev_requires
self._package.extras = package.extras self._package.extras = package.extras
self._package.python_versions = package.python_versions self._package.python_versions = package.python_versions
self._package.platform = package.platform
else: else:
# Execute egg_info # Execute egg_info
current_dir = os.getcwd() current_dir = os.getcwd()
......
...@@ -5,11 +5,11 @@ import poetry.repositories ...@@ -5,11 +5,11 @@ import poetry.repositories
from hashlib import sha256 from hashlib import sha256
from tomlkit import document from tomlkit import document
from tomlkit import inline_table
from typing import List from typing import List
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from poetry.version.markers import parse_marker
class Locker: class Locker:
...@@ -86,6 +86,22 @@ class Locker: ...@@ -86,6 +86,22 @@ class Locker:
package.hashes = lock_data["metadata"]["hashes"][info["name"]] package.hashes = lock_data["metadata"]["hashes"][info["name"]]
package.python_versions = info["python-versions"] package.python_versions = info["python-versions"]
if "marker" in info:
package.marker = parse_marker(info["marker"])
else:
# Compatibility for old locks
if "requirements" in info:
dep = poetry.packages.Dependency("foo", "0.0.0")
for name, value in info["requirements"].items():
if name == "python":
dep.python_versions = value
elif name == "platform":
dep.platform = value
split_dep = dep.to_pep_508(False).split(";")
if len(split_dep) > 1:
package.marker = parse_marker(split_dep[1].strip())
for dep_name, constraint in info.get("dependencies", {}).items(): for dep_name, constraint in info.get("dependencies", {}).items():
if isinstance(constraint, list): if isinstance(constraint, list):
for c in constraint: for c in constraint:
...@@ -95,9 +111,6 @@ class Locker: ...@@ -95,9 +111,6 @@ class Locker:
package.add_dependency(dep_name, constraint) package.add_dependency(dep_name, constraint)
if "requirements" in info:
package.requirements = info["requirements"]
if "source" in info: if "source" in info:
package.source_type = info["source"]["type"] package.source_type = info["source"]["type"]
package.source_url = info["source"]["url"] package.source_url = info["source"]["url"]
...@@ -129,7 +142,6 @@ class Locker: ...@@ -129,7 +142,6 @@ class Locker:
lock["metadata"] = { lock["metadata"] = {
"python-versions": root.python_versions, "python-versions": root.python_versions,
"platform": root.platform,
"content-hash": self._content_hash, "content-hash": self._content_hash,
"hashes": hashes, "hashes": hashes,
} }
...@@ -198,9 +210,6 @@ class Locker: ...@@ -198,9 +210,6 @@ class Locker:
if not dependency.python_constraint.is_any(): if not dependency.python_constraint.is_any():
constraint["python"] = str(dependency.python_constraint) constraint["python"] = str(dependency.python_constraint)
if dependency.platform != "*":
constraint["platform"] = dependency.platform
if len(constraint) == 1: if len(constraint) == 1:
dependencies[dependency.pretty_name].append(constraint["version"]) dependencies[dependency.pretty_name].append(constraint["version"])
else: else:
...@@ -217,9 +226,10 @@ class Locker: ...@@ -217,9 +226,10 @@ class Locker:
"category": package.category, "category": package.category,
"optional": package.optional, "optional": package.optional,
"python-versions": package.python_versions, "python-versions": package.python_versions,
"platform": package.platform,
"hashes": sorted(package.hashes), "hashes": sorted(package.hashes),
} }
if not package.marker.is_any():
data["marker"] = str(package.marker)
if dependencies: if dependencies:
for k, constraints in dependencies.items(): for k, constraints in dependencies.items():
...@@ -235,7 +245,4 @@ class Locker: ...@@ -235,7 +245,4 @@ class Locker:
"reference": package.source_reference, "reference": package.source_reference,
} }
if package.requirements:
data["requirements"] = package.requirements
return data return data
...@@ -11,13 +11,15 @@ from poetry.spdx import license_by_id ...@@ -11,13 +11,15 @@ from poetry.spdx import license_by_id
from poetry.spdx import License from poetry.spdx import License
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import canonicalize_name
from poetry.version.markers import AnyMarker
from poetry.version.markers import parse_marker
from .constraints.empty_constraint import EmptyConstraint from .constraints import parse_constraint as parse_generic_constraint
from .constraints.generic_constraint import GenericConstraint
from .dependency import Dependency from .dependency import Dependency
from .directory_dependency import DirectoryDependency from .directory_dependency import DirectoryDependency
from .file_dependency import FileDependency from .file_dependency import FileDependency
from .vcs_dependency import VCSDependency from .vcs_dependency import VCSDependency
from .utils.utils import create_nested_marker
AUTHOR_REGEX = re.compile("(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?)>)?$") AUTHOR_REGEX = re.compile("(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?)>)?$")
...@@ -63,15 +65,13 @@ class Package(object): ...@@ -63,15 +65,13 @@ class Package(object):
self.hashes = [] self.hashes = []
self.optional = False self.optional = False
# Requirements for making it mandatory
self.requirements = {}
self.classifiers = [] self.classifiers = []
self._python_versions = "*" self._python_versions = "*"
self._python_constraint = parse_constraint("*") self._python_constraint = parse_constraint("*")
self._platform = "*" self._python_marker = AnyMarker()
self._platform_constraint = EmptyConstraint()
self.marker = AnyMarker()
self.root_dir = None self.root_dir = None
...@@ -153,19 +153,26 @@ class Package(object): ...@@ -153,19 +153,26 @@ class Package(object):
def python_versions(self, value): def python_versions(self, value):
self._python_versions = value self._python_versions = value
self._python_constraint = parse_constraint(value) self._python_constraint = parse_constraint(value)
self._python_marker = parse_marker(
create_nested_marker("python_version", self._python_constraint)
)
@property @property
def python_constraint(self): def python_constraint(self):
return self._python_constraint return self._python_constraint
@property @property
def python_marker(self):
return self._python_marker
@property
def platform(self): # type: () -> str def platform(self): # type: () -> str
return self._platform return self._platform
@platform.setter @platform.setter
def platform(self, value): # type: (str) -> None def platform(self, value): # type: (str) -> None
self._platform = value self._platform = value
self._platform_constraint = GenericConstraint.parse(value) self._platform_constraint = parse_generic_constraint(value)
@property @property
def platform_constraint(self): def platform_constraint(self):
...@@ -282,11 +289,28 @@ class Package(object): ...@@ -282,11 +289,28 @@ class Package(object):
allows_prereleases=allows_prereleases, allows_prereleases=allows_prereleases,
) )
marker = AnyMarker()
if python_versions: if python_versions:
dependency.python_versions = python_versions dependency.python_versions = python_versions
marker = marker.intersect(
parse_marker(
create_nested_marker(
"python_version", dependency.python_constraint
)
)
)
if platform: if platform:
dependency.platform = platform marker = marker.intersect(
parse_marker(
create_nested_marker(
"sys_platform", parse_generic_constraint(platform)
)
)
)
if not marker.is_any():
dependency.marker = marker
if "extras" in constraint: if "extras" in constraint:
for extra in constraint["extras"]: for extra in constraint["extras"]:
...@@ -319,7 +343,6 @@ class Package(object): ...@@ -319,7 +343,6 @@ class Package(object):
clone.category = self.category clone.category = self.category
clone.optional = self.optional clone.optional = self.optional
clone.python_versions = self.python_versions clone.python_versions = self.python_versions
clone.platform = self.platform
clone.extras = self.extras clone.extras = self.extras
clone.source_type = self.source_type clone.source_type = self.source_type
clone.source_url = self.source_url clone.source_url = self.source_url
......
from poetry.semver import VersionRange from poetry.semver import VersionRange
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.version.markers import parse_marker
from .package import Package from .package import Package
from .utils.utils import create_nested_marker
class ProjectPackage(Package): class ProjectPackage(Package):
...@@ -33,7 +35,11 @@ class ProjectPackage(Package): ...@@ -33,7 +35,11 @@ class ProjectPackage(Package):
@python_versions.setter @python_versions.setter
def python_versions(self, value): def python_versions(self, value):
self._python_versions = value self._python_versions = value
if value == "*" or value == VersionRange(): if value == "*" or value == VersionRange():
value = "~2.7 || >=3.4" value = "~2.7 || >=3.4"
self._python_constraint = parse_constraint(value) self._python_constraint = parse_constraint(value)
self._python_marker = parse_marker(
create_nested_marker("python_version", self._python_constraint)
)
...@@ -2,6 +2,15 @@ import os ...@@ -2,6 +2,15 @@ import os
import posixpath import posixpath
import re import re
from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.multi_constraint import MultiConstraint
from poetry.packages.constraints.union_constraint import UnionConstraint
from poetry.semver import Version
from poetry.semver import VersionUnion
from poetry.version.markers import MarkerUnion
from poetry.version.markers import MultiMarker
from poetry.version.markers import SingleMarker
try: try:
import urllib.parse as urlparse import urllib.parse as urlparse
except ImportError: except ImportError:
...@@ -105,28 +114,27 @@ def splitext(path): ...@@ -105,28 +114,27 @@ def splitext(path):
return base, ext return base, ext
def group_markers(markers): def group_markers(markers, or_=False):
groups = [[]] groups = [[]]
for marker in markers: for marker in markers:
assert isinstance(marker, (list, tuple, str)) if or_:
groups.append([])
if isinstance(marker, list): if isinstance(marker, (MultiMarker, MarkerUnion)):
groups[-1].append(group_markers(marker)) groups[-1].append(
elif isinstance(marker, tuple): group_markers(marker.markers, isinstance(marker, MarkerUnion))
lhs, op, rhs = marker )
elif isinstance(marker, SingleMarker):
lhs, op, rhs = marker.name, marker.operator, marker.value
groups[-1].append((lhs.value, op, rhs.value)) groups[-1].append((lhs, op, rhs))
else:
assert marker in ["and", "or"]
if marker == "or":
groups.append([])
return groups return groups
def convert_markers(markers): def convert_markers(marker):
groups = group_markers(markers) groups = group_markers([marker])
requirements = {} requirements = {}
...@@ -149,3 +157,70 @@ def convert_markers(markers): ...@@ -149,3 +157,70 @@ def convert_markers(markers):
_group(groups) _group(groups)
return requirements return requirements
def create_nested_marker(name, constraint):
if constraint.is_any():
return ""
if isinstance(constraint, (MultiConstraint, UnionConstraint)):
parts = []
for c in constraint.constraints:
multi = False
if isinstance(c, (MultiConstraint, UnionConstraint)):
multi = True
parts.append((multi, create_nested_marker(name, c)))
glue = " and "
if isinstance(constraint, UnionConstraint):
parts = ["({})".format(part[1]) if part[0] else part[1] for part in parts]
glue = " or "
else:
parts = [part[1] for part in parts]
marker = glue.join(parts)
elif isinstance(constraint, Constraint):
marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version)
elif isinstance(constraint, VersionUnion):
parts = []
for c in constraint.ranges:
parts.append(create_nested_marker(name, c))
glue = " or "
parts = ["({})".format(part) for part in parts]
marker = glue.join(parts)
elif isinstance(constraint, Version):
marker = '{} == "{}"'.format(name, constraint.text)
else:
if constraint.min is not None:
op = ">="
if not constraint.include_min:
op = ">"
version = constraint.min.text
if constraint.max is not None:
text = '{} {} "{}"'.format(name, op, version)
op = "<="
if not constraint.include_max:
op = "<"
version = constraint.max
text += ' and {} {} "{}"'.format(name, op, version)
return text
elif constraint.max is not None:
op = "<="
if not constraint.include_max:
op = "<"
version = constraint.max
else:
return ""
marker = '{} {} "{}"'.format(name, op, version)
return marker
...@@ -26,6 +26,10 @@ class Operation(object): ...@@ -26,6 +26,10 @@ class Operation(object):
def skip_reason(self): # type: () -> Union[str, None] def skip_reason(self): # type: () -> Union[str, None]
return self._skip_reason return self._skip_reason
@property
def package(self):
raise NotImplementedError()
def format_version(self, package): # type: (...) -> str def format_version(self, package): # type: (...) -> str
return package.full_pretty_version return package.full_pretty_version
......
...@@ -105,8 +105,27 @@ class Provider: ...@@ -105,8 +105,27 @@ class Provider:
if dependency.is_root: if dependency.is_root:
return PackageCollection(dependency, [self._package]) return PackageCollection(dependency, [self._package])
if dependency in self._search_for: for constraint in self._search_for.keys():
return PackageCollection(dependency, self._search_for[dependency]) if (
constraint.name == dependency.name
and constraint.constraint.intersect(dependency.constraint)
== dependency.constraint
):
packages = [
p
for p in self._search_for[constraint]
if dependency.constraint.allows(p.version)
]
packages.sort(
key=lambda p: (
not p.is_prerelease() and not dependency.allows_prereleases(),
p.version,
),
reverse=True,
)
return PackageCollection(dependency, packages)
if dependency.is_vcs(): if dependency.is_vcs():
packages = self.search_for_vcs(dependency) packages = self.search_for_vcs(dependency)
...@@ -134,7 +153,7 @@ class Provider: ...@@ -134,7 +153,7 @@ class Provider:
self._search_for[dependency] = packages self._search_for[dependency] = packages
return PackageCollection(dependency, self._search_for[dependency]) return PackageCollection(dependency, packages)
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package]
""" """
...@@ -316,41 +335,15 @@ class Provider: ...@@ -316,41 +335,15 @@ class Provider:
if not package.python_constraint.allows_all( if not package.python_constraint.allows_all(
self._package.python_constraint self._package.python_constraint
): ):
# The package Python requirement is not compatible
# with the root package python requirement.
#
# However, it should be accepted if it comes from
# a dependency with a compatible Python constraint.
#
# An example of this is:
# - The root package is compatible with Python ~2.7 || ^3.6
# - The root package depends on black for Python ^3.6
# - black is only compatible with Python >=3.6
# - black should be authorized.
#
# In this particular case, we notify the resolver that it needs
# to branch the dependency tree. What this means is if we have
# root Python ~2.7 || ^3.6 and dependency Python >=3.6
# we have to resolve for ^3.6 (>=3.6, <4.0) and for ~2.7 || <3.6
if ( if (
not package.dependency.python_constraint.is_any() package.dependency.python_constraint.is_any()
and not package.python_constraint.intersect( or not self._package.python_constraint.allows_all(
package.dependency.python_constraint package.dependency.python_constraint
).is_empty()
):
self.debug(
"<warning>Found conditional dependency for {} (Python {}).</warning>".format(
package, package.dependency.python_constraint
)
) )
intersection = self._package.python_constraint.intersect( or not package.python_constraint.allows_all(
package.dependency.python_constraint package.dependency.python_constraint
) )
raise CompatibilityError( ):
str(intersection),
str(self._package.python_constraint.difference(intersection)),
)
return [ return [
Incompatibility( Incompatibility(
[Term(package.to_dependency(), True)], [Term(package.to_dependency(), True)],
...@@ -360,22 +353,11 @@ class Provider: ...@@ -360,22 +353,11 @@ class Provider:
) )
] ]
if not self._package.platform_constraint.matches(
package.platform_constraint
):
return [
Incompatibility(
[Term(package.to_dependency(), True)],
PlatformCause(package.platform),
)
]
dependencies = [ dependencies = [
dep dep
for dep in dependencies for dep in dependencies
if dep.name not in self.UNSAFE_PACKAGES if dep.name not in self.UNSAFE_PACKAGES
and self._package.python_constraint.allows_any(dep.python_constraint) and self._package.python_constraint.allows_any(dep.python_constraint)
and self._package.platform_constraint.matches(dep.platform_constraint)
] ]
return [ return [
...@@ -403,7 +385,6 @@ class Provider: ...@@ -403,7 +385,6 @@ class Provider:
for r in package.requires for r in package.requires
if r.is_activated() if r.is_activated()
and self._package.python_constraint.allows_any(r.python_constraint) and self._package.python_constraint.allows_any(r.python_constraint)
and self._package.platform_constraint.matches(r.platform_constraint)
] ]
# Searching for duplicate dependencies # Searching for duplicate dependencies
...@@ -454,7 +435,7 @@ class Provider: ...@@ -454,7 +435,7 @@ class Provider:
for constraint, _deps in by_constraint.items(): for constraint, _deps in by_constraint.items():
new_markers = [] new_markers = []
for dep in _deps: for dep in _deps:
pep_508_dep = dep.to_pep_508() pep_508_dep = dep.to_pep_508(False)
if ";" not in pep_508_dep: if ";" not in pep_508_dep:
continue continue
...@@ -473,7 +454,7 @@ class Provider: ...@@ -473,7 +454,7 @@ class Provider:
dep = _deps[0] dep = _deps[0]
new_requirement = "{}; {}".format( new_requirement = "{}; {}".format(
dep.to_pep_508().split(";")[0], " or ".join(new_markers) dep.to_pep_508(False).split(";")[0], " or ".join(new_markers)
) )
new_dep = dependency_from_pep_508(new_requirement) new_dep = dependency_from_pep_508(new_requirement)
if dep.is_optional() and not dep.is_activated(): if dep.is_optional() and not dep.is_activated():
...@@ -501,7 +482,7 @@ class Provider: ...@@ -501,7 +482,7 @@ class Provider:
_deps = [value[0] for value in by_constraint.values()] _deps = [value[0] for value in by_constraint.values()]
seen = set() seen = set()
for _dep in _deps: for _dep in _deps:
pep_508_dep = _dep.to_pep_508() pep_508_dep = _dep.to_pep_508(False)
if ";" not in pep_508_dep: if ";" not in pep_508_dep:
_requirements = "" _requirements = ""
else: else:
......
import time
from typing import Any
from typing import Dict
from typing import List from typing import List
from poetry.mixology import resolve_version from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage from poetry.packages import DependencyPackage
from poetry.packages.constraints.generic_constraint import GenericConstraint
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.version.markers import AnyMarker
from .exceptions import CompatibilityError from .exceptions import CompatibilityError
from .exceptions import SolverProblemError from .exceptions import SolverProblemError
...@@ -26,12 +29,25 @@ class Solver: ...@@ -26,12 +29,25 @@ class Solver:
self._locked = locked self._locked = locked
self._io = io self._io = io
self._provider = Provider(self._package, self._pool, self._io) self._provider = Provider(self._package, self._pool, self._io)
self._branches = []
def solve(self, use_latest=None): # type: (...) -> List[Operation] def solve(self, use_latest=None): # type: (...) -> List[Operation]
with self._provider.progress(): with self._provider.progress():
start = time.time()
packages, depths = self._solve(use_latest=use_latest) packages, depths = self._solve(use_latest=use_latest)
end = time.time()
requested = self._package.all_requires if len(self._branches) > 1:
self._provider.debug(
"Complete version solving took {:.3f} seconds for {} branches".format(
end - start, len(self._branches[1:])
)
)
self._provider.debug(
"Resolved for branches: {}".format(
", ".join("({})".format(b) for b in self._branches[1:])
)
)
operations = [] operations = []
for package in packages: for package in packages:
...@@ -79,6 +95,7 @@ class Solver: ...@@ -79,6 +95,7 @@ class Solver:
# since it actually doesn't matter since removals are always on top. # since it actually doesn't matter since removals are always on top.
-depths[packages.index(o.package)] if o.job_type != "uninstall" else 0, -depths[packages.index(o.package)] if o.job_type != "uninstall" else 0,
o.package.name, o.package.name,
o.package.version,
), ),
) )
...@@ -104,15 +121,21 @@ class Solver: ...@@ -104,15 +121,21 @@ class Solver:
packages.append(package) packages.append(package)
depths.append(_depths[index]) depths.append(_depths[index])
continue continue
else:
idx = packages.index(package)
pkg = packages[idx]
depths[idx] = max(depths[idx], _depths[index])
pkg.marker = pkg.marker.union(package.marker)
current_package = packages[packages.index(package)]
for dep in package.requires: for dep in package.requires:
if dep not in current_package.requires: if dep not in pkg.requires:
current_package.requires.append(dep) pkg.requires.append(dep)
return packages, depths return packages, depths
def _solve(self, use_latest=None): def _solve(self, use_latest=None):
self._branches.append(self._package.python_versions)
locked = {} locked = {}
for package in self._locked.packages: for package in self._locked.packages:
locked[package.name] = DependencyPackage(package.to_dependency(), package) locked[package.name] = DependencyPackage(package.to_dependency(), package)
...@@ -134,58 +157,37 @@ class Solver: ...@@ -134,58 +157,37 @@ class Solver:
depths = [] depths = []
for package in packages: for package in packages:
category, optional, python, platform, depth = self._get_tags_for_package( category, optional, marker, depth = self._get_tags_for_package(
package, graph package, graph
) )
depths.append(depth) depths.append(depth)
package.category = category package.category = category
package.optional = optional package.optional = optional
package.marker = marker
# If requirements are empty, drop them
requirements = {}
if python is not None and python != "*":
requirements["python"] = python
if platform is not None and platform != "*":
requirements["platform"] = platform
package.requirements = requirements
return packages, depths return packages, depths
def _build_graph( def _build_graph(
self, package, packages, previous=None, previous_dep=None, dep=None self, package, packages, previous=None, previous_dep=None, dep=None
): ): # type: (...) -> Dict[str, Any]
if not previous: if not previous:
category = "dev" category = "dev"
optional = True optional = True
python_version = "*" marker = package.marker
platform = "*"
else: else:
category = dep.category category = dep.category
optional = dep.is_optional() and not dep.is_activated() optional = dep.is_optional() and not dep.is_activated()
python_version = str( intersection = previous["marker"].intersect(previous_dep.marker)
parse_constraint(previous["python_version"]).intersect(
previous_dep.python_constraint marker = intersection
)
)
platform = str(
previous_dep.platform
if GenericConstraint.parse(previous["platform"]).matches(
previous_dep.platform_constraint
)
and previous_dep.platform != "*"
else previous["platform"]
)
graph = { graph = {
"name": package.name, "name": package.name,
"category": category, "category": category,
"optional": optional, "optional": optional,
"python_version": python_version, "marker": marker,
"platform": platform, "children": [], # type: List[Dict[str, Any]]
"children": [],
} }
if previous_dep and previous_dep is not dep and previous_dep.name == dep.name: if previous_dep and previous_dep is not dep and previous_dep.name == dep.name:
...@@ -237,10 +239,8 @@ class Solver: ...@@ -237,10 +239,8 @@ class Solver:
) )
if existing: if existing:
existing["python_version"] = str( existing["marker"] = existing["marker"].union(
parse_constraint(existing["python_version"]).union( child_graph["marker"]
parse_constraint(child_graph["python_version"])
)
) )
continue continue
...@@ -251,37 +251,27 @@ class Solver: ...@@ -251,37 +251,27 @@ class Solver:
def _get_tags_for_package(self, package, graph, depth=0): def _get_tags_for_package(self, package, graph, depth=0):
categories = ["dev"] categories = ["dev"]
optionals = [True] optionals = [True]
python_versions = [] markers = []
platforms = []
_depths = [0] _depths = [0]
children = graph["children"] children = graph["children"]
found = False
for child in children: for child in children:
if child["name"] == package.name: if child["name"] == package.name:
category = child["category"] category = child["category"]
optional = child["optional"] optional = child["optional"]
python_version = child["python_version"] marker = child["marker"]
platform = child["platform"]
_depths.append(depth) _depths.append(depth)
else: else:
( (category, optional, marker, _depth) = self._get_tags_for_package(
category, package, child, depth=depth + 1
optional, )
python_version,
platform,
_depth,
) = self._get_tags_for_package(package, child, depth=depth + 1)
_depths.append(_depth) _depths.append(_depth)
categories.append(category) categories.append(category)
optionals.append(optional) optionals.append(optional)
if python_version is not None: if not marker.is_any():
python_versions.append(python_version) markers.append(marker)
if platform is not None:
platforms.append(platform)
if "main" in categories: if "main" in categories:
category = "main" category = "main"
...@@ -290,37 +280,13 @@ class Solver: ...@@ -290,37 +280,13 @@ class Solver:
optional = all(optionals) optional = all(optionals)
if not python_versions: depth = max(*(_depths + [0]))
python_version = None
else:
# Find the least restrictive constraint
python_version = python_versions[0]
for constraint in python_versions[1:]:
previous = parse_constraint(python_version)
current = parse_constraint(constraint)
if python_version == "*":
continue
elif constraint == "*":
python_version = constraint
elif current.allows_all(previous):
python_version = constraint
if not platforms: if not markers:
platform = None marker = AnyMarker()
else: else:
platform = platforms[0] marker = markers[0]
for constraint in platforms[1:]: for m in markers[1:]:
previous = GenericConstraint.parse(platform) marker = marker.union(m)
current = GenericConstraint.parse(constraint)
if platform == "*":
continue
elif constraint == "*":
platform = constraint
elif current.matches(previous):
platform = constraint
depth = max(*(_depths + [0]))
return category, optional, python_version, platform, depth return category, optional, marker, depth
...@@ -168,6 +168,9 @@ class VersionUnion(VersionConstraint): ...@@ -168,6 +168,9 @@ class VersionUnion(VersionConstraint):
return True return True
while True: while True:
if state["their_range"] is None:
break
if state["their_range"].is_strictly_lower(state["current"]): if state["their_range"].is_strictly_lower(state["current"]):
if not their_next_range(): if not their_next_range():
break break
......
import json
import os import os
import platform import platform
import subprocess import subprocess
...@@ -8,6 +9,7 @@ import warnings ...@@ -8,6 +9,7 @@ import warnings
from contextlib import contextmanager from contextlib import contextmanager
from subprocess import CalledProcessError from subprocess import CalledProcessError
from typing import Any from typing import Any
from typing import Dict
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
...@@ -15,6 +17,7 @@ from poetry.config import Config ...@@ -15,6 +17,7 @@ from poetry.config import Config
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.version.markers import BaseMarker
class EnvError(Exception): class EnvError(Exception):
...@@ -47,8 +50,7 @@ class Env(object): ...@@ -47,8 +50,7 @@ class Env(object):
self._base = base or path self._base = base or path
self._version_info = self.get_version_info() self._marker_env = None
self._python_implementation = self.get_python_implementation()
@property @property
def path(self): # type: () -> Path def path(self): # type: () -> Path
...@@ -60,11 +62,11 @@ class Env(object): ...@@ -60,11 +62,11 @@ class Env(object):
@property @property
def version_info(self): # type: () -> Tuple[int] def version_info(self): # type: () -> Tuple[int]
return self._version_info return tuple(self.marker_env["version_info"])
@property @property
def python_implementation(self): # type: () -> str def python_implementation(self): # type: () -> str
return self._python_implementation return self.marker_env["platform_python_implementation"]
@property @property
def python(self): # type: () -> str def python(self): # type: () -> str
...@@ -74,6 +76,13 @@ class Env(object): ...@@ -74,6 +76,13 @@ class Env(object):
return self._bin("python") return self._bin("python")
@property @property
def marker_env(self):
if self._marker_env is None:
self._marker_env = self.get_marker_env()
return self._marker_env
@property
def pip(self): # type: () -> str def pip(self): # type: () -> str
""" """
Path to current pip executable Path to current pip executable
...@@ -214,9 +223,15 @@ class Env(object): ...@@ -214,9 +223,15 @@ class Env(object):
def get_python_implementation(self): # type: () -> str def get_python_implementation(self): # type: () -> str
raise NotImplementedError() raise NotImplementedError()
def config_var(self, var): # type: () -> Any def get_marker_env(self): # type: () -> Dict[str, Any]
raise NotImplementedError() raise NotImplementedError()
def config_var(self, var): # type: (str) -> Any
raise NotImplementedError()
def is_valid_for_marker(self, marker): # type: (BaseMarker) -> bool
return marker.validate(self.marker_env)
def run(self, bin, *args, **kwargs): def run(self, bin, *args, **kwargs):
""" """
Run a command inside the Python environment. Run a command inside the Python environment.
...@@ -262,7 +277,7 @@ class Env(object): ...@@ -262,7 +277,7 @@ class Env(object):
return str(bin_path) return str(bin_path)
def __repr__(self): def __repr__(self):
return '{}("{}")'.format(self.__class__.__name__, self._base) return '{}("{}")'.format(self.__class__.__name__, self._path)
class SystemEnv(Env): class SystemEnv(Env):
...@@ -276,6 +291,34 @@ class SystemEnv(Env): ...@@ -276,6 +291,34 @@ class SystemEnv(Env):
def get_python_implementation(self): # type: () -> str def get_python_implementation(self): # type: () -> str
return platform.python_implementation() return platform.python_implementation()
def get_marker_env(self): # type: () -> Dict[str, Any]
if hasattr(sys, "implementation"):
info = sys.implementation.version
iver = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != "final":
iver += kind[0] + str(info.serial)
implementation_name = sys.implementation.name
else:
iver = "0"
implementation_name = ""
return {
"implementation_name": implementation_name,
"implementation_version": iver,
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": platform.python_version()[:3],
"sys_platform": sys.platform,
"version_info": sys.version_info,
}
def config_var(self, var): # type: (str) -> Any def config_var(self, var): # type: (str) -> Any
try: try:
return sysconfig.get_config_var(var) return sysconfig.get_config_var(var)
...@@ -314,7 +357,7 @@ class VirtualEnv(Env): ...@@ -314,7 +357,7 @@ class VirtualEnv(Env):
" )" " )"
')"', ')"',
shell=True, shell=True,
) ).strip()
) )
def get_version_info(self): # type: () -> Tuple[int] def get_version_info(self): # type: () -> Tuple[int]
...@@ -328,12 +371,35 @@ class VirtualEnv(Env): ...@@ -328,12 +371,35 @@ class VirtualEnv(Env):
return tuple([int(s) for s in output.strip().split(".")]) return tuple([int(s) for s in output.strip().split(".")])
def get_python_implementation(self): # type: () -> str def get_python_implementation(self): # type: () -> str
return self.run( return self.marker_env["platform_python_implementation"]
def get_marker_env(self): # type: () -> Dict[str, Any]
output = self.run(
"python", "python",
"-c", "-c",
'"import platform; print(platform.python_implementation())"', '"import json; import os; import platform; import sys; '
"implementation = getattr(sys, 'implementation', None); "
"iver = '{0.major}.{0.minor}.{0.micro}'.format(implementation.version) if implementation else '0'; "
"implementation_name = implementation.name if implementation else ''; "
"env = {"
"'implementation_name': implementation_name,"
"'implementation_version': iver,"
"'os_name': os.name,"
"'platform_machine': platform.machine(),"
"'platform_release': platform.release(),"
"'platform_system': platform.system(),"
"'platform_version': platform.version(),"
"'python_full_version': platform.python_version(),"
"'platform_python_implementation': platform.python_implementation(),"
"'python_version': platform.python_version()[:3],"
"'sys_platform': sys.platform,"
"'version_info': sys.version_info[:3],"
"};"
'print(json.dumps(env))"',
shell=True, shell=True,
).strip() )
return json.loads(output)
def config_var(self, var): # type: (str) -> Any def config_var(self, var): # type: (str) -> Any
try: try:
...@@ -344,7 +410,7 @@ class VirtualEnv(Env): ...@@ -344,7 +410,7 @@ class VirtualEnv(Env):
"print(sysconfig.get_config_var('{}'))\"".format(var), "print(sysconfig.get_config_var('{}'))\"".format(var),
shell=True, shell=True,
).strip() ).strip()
except VenvCommandError as e: except EnvCommandError as e:
warnings.warn("{0}".format(e), RuntimeWarning) warnings.warn("{0}".format(e), RuntimeWarning)
return None return None
......
...@@ -86,3 +86,8 @@ def get_http_basic_auth(repository_name): # type: (str) -> tuple ...@@ -86,3 +86,8 @@ def get_http_basic_auth(repository_name): # type: (str) -> tuple
if repo_auth: if repo_auth:
return repo_auth["username"], repo_auth["password"] return repo_auth["username"], repo_auth["password"]
return None return None
def constraint_to_marker(constraint): # type: (Any) -> Marker
if constraint.is_any():
return AnyMarker()
import operator import re
from pyparsing import ParseException, ParseResults, stringStart, stringEnd from pyparsing import ParseException, ParseResults, stringStart, stringEnd
from pyparsing import ZeroOrMore, Group, Forward, QuotedString from pyparsing import ZeroOrMore, Group, Forward, QuotedString
from pyparsing import Literal as L # noqa from pyparsing import Literal as L # noqa
from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
class InvalidMarker(ValueError): class InvalidMarker(ValueError):
""" """
...@@ -111,6 +116,9 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) ...@@ -111,6 +116,9 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
MARKER = stringStart + MARKER_EXPR + stringEnd MARKER = stringStart + MARKER_EXPR + stringEnd
_undefined = object()
def _coerce_parse_result(results): def _coerce_parse_result(results):
if isinstance(results, ParseResults): if isinstance(results, ParseResults):
return [_coerce_parse_result(i) for i in results] return [_coerce_parse_result(i) for i in results]
...@@ -144,42 +152,504 @@ def _format_marker(marker, first=True): ...@@ -144,42 +152,504 @@ def _format_marker(marker, first=True):
return marker return marker
_operators = { class BaseMarker(object):
"in": lambda lhs, rhs: lhs in rhs, def intersect(self, other): # type: (BaseMarker) -> BaseMarker
"not in": lambda lhs, rhs: lhs not in rhs, raise NotImplementedError()
"<": operator.lt,
"<=": operator.le, def union(self, other): # type: (BaseMarker) -> BaseMarker
"==": operator.eq, raise NotImplementedError()
"!=": operator.ne,
">=": operator.ge, def is_any(self): # type: () -> bool
">": operator.gt, return False
}
def is_empty(self): # type: () -> bool
return False
def validate(self, environment): # type: (Dict[str, Any]) -> bool
raise NotImplementedError()
def without_extras(self): # type: () -> BaseMarker
raise NotImplementedError()
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self))
class AnyMarker(BaseMarker):
def intersect(self, other):
return other
def union(self, other):
return self
def is_any(self):
return True
def is_empty(self): # type: () -> bool
return False
def validate(self, environment):
return True
def without_extras(self):
return self
def __str__(self):
return ""
def __repr__(self):
return "<AnyMarker>"
class EmptyMarker(BaseMarker):
def intersect(self, other):
return self
def union(self, other):
return other
def is_any(self):
return False
def is_empty(self): # type: () -> bool
return True
def validate(self, environment):
return False
def without_extras(self):
return self
def __str__(self):
return "<empty>"
def __repr__(self):
return "<EmptyMarker>"
def format_full_version(info):
version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != "final":
version += kind[0] + str(info.serial)
return version
class SingleMarker(BaseMarker):
class Marker(object): _CONSTRAINT_RE = re.compile(r"(?i)^(~=|!=|>=?|<=?|==?|in|not in)?\s*(.+)$")
def __init__(self, marker):
try: def __init__(self, name, constraint):
self._markers = _coerce_parse_result(MARKER.parseString(marker)) from poetry.packages.constraints import (
except ParseException as e: parse_constraint as parse_generic_constraint,
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc : e.loc + 8]
) )
raise InvalidMarker(err_str) from poetry.semver import parse_constraint
self._name = name
self._constraint_string = str(constraint)
# Extract operator and value
m = self._CONSTRAINT_RE.match(self._constraint_string)
self._operator = m.group(1)
if self._operator is None:
self._operator = "=="
self._value = m.group(2)
self._parser = parse_generic_constraint
if self._name == "python_version":
self._parser = parse_constraint
if name == "python_version":
if self._operator in {"in", "not in"}:
versions = []
for v in re.split("[ ,]+", self._value):
split = v.split(".")
if len(split) in [1, 2]:
split.append("*")
op = "" if self._operator == "in" else "!="
else:
op = "==" if self._operator == "in" else "!="
versions.append(op + ".".join(split))
glue = ", "
if self._operator == "in":
glue = " || "
self._constraint = self._parser(glue.join(versions))
else:
self._constraint = self._parser(self._constraint_string)
else:
self._constraint = self._parser(self._constraint_string)
@property
def name(self):
return self._name
@property
def constraint_string(self):
if self._operator in {"in", "not in"}:
return "{} {}".format(self._operator, self._value)
return self._constraint_string
@property
def constraint(self):
return self._constraint
@property
def operator(self):
return self._operator
@property
def value(self):
return self._value
def intersect(self, other):
if isinstance(other, SingleMarker):
if other.name != self.name:
return MultiMarker(self, other)
if self == other:
return self
if self._operator in {"in", "not in"} or other.operator in {"in", "not in"}:
return MultiMarker.of(self, other)
new_constraint = self._constraint.intersect(other.constraint)
if new_constraint.is_empty():
return EmptyMarker()
if new_constraint == self._constraint or new_constraint == other.constraint:
return SingleMarker(self._name, new_constraint)
return MultiMarker.of(self, other)
return other.intersect(self)
def union(self, other):
if isinstance(other, SingleMarker):
if self == other:
return self
return MarkerUnion(self, other)
return other.union(self)
def validate(self, environment):
if environment is None:
return True
if self._name not in environment:
return True
return self._constraint.allows(self._parser(environment[self._name]))
def without_extras(self):
if self.name == "extra":
return EmptyMarker()
return self
def __eq__(self, other):
if not isinstance(other, SingleMarker):
return False
return self._name == other.name and self._constraint == other.constraint
def __hash__(self):
return hash((self._name, self._constraint_string))
def __str__(self):
return _format_marker(
(Variable(self._name), Op(self._operator), Value(self._value))
)
def _flatten_markers(
markers, flatten_class
): # type: (Iterator[BaseMarker], Any) -> List[BaseMarker]
flattened = []
for marker in markers:
if isinstance(marker, flatten_class):
flattened += _flatten_markers(marker.markers, flatten_class)
else:
flattened.append(marker)
return flattened
class MultiMarker(BaseMarker):
def __init__(self, *markers):
self._markers = []
markers = _flatten_markers(markers, MultiMarker)
for m in markers:
self._markers.append(m)
@classmethod
def of(cls, *markers):
new_markers = []
markers = _flatten_markers(markers, MultiMarker)
for marker in markers:
if marker in new_markers or marker.is_empty():
continue
if isinstance(marker, SingleMarker):
intersected = False
for i, mark in enumerate(new_markers):
if (
not isinstance(mark, SingleMarker)
or isinstance(mark, SingleMarker)
and mark.name != marker.name
):
continue
intersection = mark.constraint.intersect(marker.constraint)
if intersection == mark.constraint:
intersected = True
break
elif intersection == marker.constraint:
new_markers[i] = marker
intersected = True
break
elif intersection.is_empty():
return EmptyMarker()
if intersected:
continue
new_markers.append(marker)
if not new_markers:
return EmptyMarker()
return MultiMarker(*new_markers)
@property @property
def markers(self): def markers(self):
return self._markers return self._markers
def intersect(self, other):
if other.is_any():
return self
if other.is_empty():
return other
if other in self._markers:
return other
new_markers = self._markers + [other]
return MultiMarker.of(*new_markers)
def union(self, other):
if isinstance(other, (SingleMarker, MultiMarker)):
return MarkerUnion(self, other)
return other.union(self)
def validate(self, environment):
for m in self._markers:
if not m.validate(environment):
return False
return True
def without_extras(self):
new_markers = []
for m in self._markers:
marker = m.without_extras()
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def __eq__(self, other):
if not isinstance(other, MultiMarker):
return False
return set(self._markers) == set(other.markers)
def __hash__(self):
h = hash("multi")
for m in self._markers:
h |= hash(m)
return h
def __str__(self): def __str__(self):
return _format_marker(self._markers) elements = []
for m in self._markers:
if isinstance(m, SingleMarker):
elements.append(str(m))
elif isinstance(m, MultiMarker):
elements.append(str(m))
else:
elements.append("({})".format(str(m)))
def __repr__(self): return " and ".join(elements)
return "<Marker({0!r})>".format(str(self))
class MarkerUnion(BaseMarker):
def __init__(self, *markers):
self._markers = []
markers = _flatten_markers(markers, MarkerUnion)
for marker in markers:
if marker in self._markers:
continue
if isinstance(marker, SingleMarker) and marker.name == "python_version":
intersected = False
for i, mark in enumerate(self._markers):
if (
not isinstance(mark, SingleMarker)
or isinstance(mark, SingleMarker)
and mark.name != marker.name
):
continue
intersection = mark.constraint.union(marker.constraint)
if intersection == mark.constraint:
intersected = True
break
elif intersection == marker.constraint:
self._markers[i] = marker
intersected = True
break
if intersected:
continue
self._markers.append(marker)
@property
def markers(self):
return self._markers
def append(self, marker):
if marker in self._markers:
return
self._markers.append(marker)
def intersect(self, other):
if other.is_any():
return self
if other.is_empty():
return other
new_markers = []
if isinstance(other, (SingleMarker, MultiMarker)):
for marker in self._markers:
intersection = marker.intersect(other)
if not intersection.is_empty():
new_markers.append(intersection)
elif isinstance(other, MarkerUnion):
for our_marker in self._markers:
for their_marker in other.markers:
intersection = our_marker.intersect(their_marker)
if not intersection.is_empty():
new_markers.append(intersection)
return MarkerUnion(*new_markers)
def union(self, other):
if other.is_any():
return other
if other.is_empty():
return self
new_markers = self._markers + [other]
return MarkerUnion(*new_markers)
def validate(self, environment):
for m in self._markers:
if m.validate(environment):
return True
return False
def without_extras(self):
new_markers = []
for m in self._markers:
marker = m.without_extras()
if not marker.is_empty():
new_markers.append(marker)
return MarkerUnion(*new_markers)
def __eq__(self, other):
if not isinstance(other, MarkerUnion):
return False
return set(self._markers) == set(other.markers)
def __hash__(self):
h = hash("union")
for m in self._markers:
h |= hash(m)
return h
def __str__(self):
return " or ".join(str(m) for m in self._markers)
def parse_marker(marker):
if marker == "<empty>":
return EmptyMarker()
if not marker or marker == "*":
return AnyMarker()
markers = _coerce_parse_result(MARKER.parseString(marker))
return _compact_markers(markers)
def _compact_markers(markers):
groups = [MultiMarker()]
for marker in markers:
if isinstance(marker, list):
groups[-1] = MultiMarker.of(groups[-1], _compact_markers(marker))
elif isinstance(marker, tuple):
lhs, op, rhs = marker
if isinstance(lhs, Variable):
name = lhs.value
value = rhs.value
else:
value = lhs.value
name = rhs.value
groups[-1] = MultiMarker.of(
groups[-1], SingleMarker(name, "{}{}".format(op, value))
)
else:
if marker == "or":
groups.append(MultiMarker())
for i, group in enumerate(reversed(groups)):
if group.is_empty():
del groups[len(groups) - 1 - i]
continue
if isinstance(group, MultiMarker) and len(group.markers) == 1:
groups[len(groups) - 1 - i] = group.markers[0]
if not groups:
return EmptyMarker()
if len(groups) == 1:
return groups[0]
return MarkerUnion(*groups)
...@@ -17,7 +17,8 @@ from pyparsing import Literal as L # noqa ...@@ -17,7 +17,8 @@ from pyparsing import Literal as L # noqa
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from .markers import MARKER_EXPR, Marker from .markers import MARKER_EXPR
from .markers import parse_marker
LEGACY_REGEX = r""" LEGACY_REGEX = r"""
...@@ -171,7 +172,7 @@ VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) ...@@ -171,7 +172,7 @@ VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction( MARKER_EXPR.setParseAction(
lambda s, l, t: Marker(s[t._original_start : t._original_end]) lambda s, l, t: parse_marker(s[t._original_start : t._original_end])
) )
MARKER_SEPERATOR = SEMICOLON MARKER_SEPERATOR = SEMICOLON
MARKER = MARKER_SEPERATOR + MARKER_EXPR MARKER = MARKER_SEPERATOR + MARKER_EXPR
......
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import absolute_import, division, print_function
import abc
import functools
import itertools
import re
import sys
from .legacy_version import LegacyVersion
from .version import Version
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
# flake8: noqa
if PY3:
string_types = (str,)
else:
string_types = (basestring,)
def with_metaclass(meta, *bases):
"""
Create a base class with a metaclass.
"""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, "temporary_class", (), {})
def parse(version):
"""
Parse the given version string and return either a :class:`Version` object
or a :class:`LegacyVersion` object depending on if the given version is
a valid PEP 440 version or a legacy version.
"""
try:
return Version(version)
except InvalidVersion:
return LegacyVersion(version)
class InvalidVersion(ValueError):
"""
An invalid version was found, users should refer to PEP 440.
"""
class InvalidSpecifier(ValueError):
"""
An invalid specifier was found, users should refer to PEP 440.
"""
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod
def __str__(self):
"""
Returns the str representation of this Specifier like object. This
should be representative of the Specifier itself.
"""
@abc.abstractmethod
def __hash__(self):
"""
Returns a hash value for this Specifier like object.
"""
@abc.abstractmethod
def __eq__(self, other):
"""
Returns a boolean representing whether or not the two Specifier like
objects are equal.
"""
@abc.abstractmethod
def __ne__(self, other):
"""
Returns a boolean representing whether or not the two Specifier like
objects are not equal.
"""
@abc.abstractproperty
def prereleases(self):
"""
Returns whether or not pre-releases as a whole are allowed by this
specifier.
"""
@prereleases.setter
def prereleases(self, value):
"""
Sets whether or not pre-releases as a whole are allowed by this
specifier.
"""
@abc.abstractmethod
def contains(self, item, prereleases=None):
"""
Determines if the given item is contained within this specifier.
"""
@abc.abstractmethod
def filter(self, iterable, prereleases=None):
"""
Takes an iterable of items and filters them so that only items which
are contained within this specifier are allowed in it.
"""
class _IndividualSpecifier(BaseSpecifier):
_operators = {}
def __init__(self, spec="", prereleases=None):
match = self._regex.search(spec)
if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
self._spec = (match.group("operator").strip(), match.group("version").strip())
# Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases
def __repr__(self):
pre = (
", prereleases={0!r}".format(self.prereleases)
if self._prereleases is not None
else ""
)
return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
def __str__(self):
return "{0}{1}".format(*self._spec)
def __hash__(self):
return hash(self._spec)
def __eq__(self, other):
if isinstance(other, string_types):
try:
other = self.__class__(other)
except InvalidSpecifier:
return NotImplemented
elif not isinstance(other, self.__class__):
return NotImplemented
return self._spec == other._spec
def __ne__(self, other):
if isinstance(other, string_types):
try:
other = self.__class__(other)
except InvalidSpecifier:
return NotImplemented
elif not isinstance(other, self.__class__):
return NotImplemented
return self._spec != other._spec
def _get_operator(self, op):
return getattr(self, "_compare_{0}".format(self._operators[op]))
def _coerce_version(self, version):
if not isinstance(version, (LegacyVersion, Version)):
version = parse(version)
return version
@property
def operator(self):
return self._spec[0]
@property
def version(self):
return self._spec[1]
@property
def prereleases(self):
return self._prereleases
@prereleases.setter
def prereleases(self, value):
self._prereleases = value
def __contains__(self, item):
return self.contains(item)
def contains(self, item, prereleases=None):
# Determine if prereleases are to be allowed or not.
if prereleases is None:
prereleases = self.prereleases
# Normalize item to a Version or LegacyVersion, this allows us to have
# a shortcut for ``"2.0" in Specifier(">=2")
item = self._coerce_version(item)
# Determine if we should be supporting prereleases in this specifier
# or not, if we do not support prereleases than we can short circuit
# logic if this version is a prereleases.
if item.is_prerelease and not prereleases:
return False
# Actually do the comparison to determine if this item is contained
# within this Specifier or not.
return self._get_operator(self.operator)(item, self.version)
def filter(self, iterable, prereleases=None):
yielded = False
found_prereleases = []
kw = {"prereleases": prereleases if prereleases is not None else True}
# Attempt to iterate over all the values in the iterable and if any of
# them match, yield them.
for version in iterable:
parsed_version = self._coerce_version(version)
if self.contains(parsed_version, **kw):
# If our version is a prerelease, and we were not set to allow
# prereleases, then we'll store it for later incase nothing
# else matches this specifier.
if parsed_version.is_prerelease and not (
prereleases or self.prereleases
):
found_prereleases.append(version)
# Either this is not a prerelease, or we should have been
# accepting prereleases from the beginning.
else:
yielded = True
yield version
# Now that we've iterated over everything, determine if we've yielded
# any values, and if we have not and we have any prereleases stored up
# then we will go ahead and yield the prereleases.
if not yielded and found_prereleases:
for version in found_prereleases:
yield version
class LegacySpecifier(_IndividualSpecifier):
_regex_str = r"""
(?P<operator>(==|!=|<=|>=|<|>))
\s*
(?P<version>
[^,;\s)]* # Since this is a "legacy" specifier, and the version
# string can be just about anything, we match everything
# except for whitespace, a semi-colon for marker support,
# a closing paren since versions can be enclosed in
# them, and a comma since it's a version separator.
)
"""
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"==": "equal",
"!=": "not_equal",
"<=": "less_than_equal",
">=": "greater_than_equal",
"<": "less_than",
">": "greater_than",
}
def _coerce_version(self, version):
if not isinstance(version, LegacyVersion):
version = LegacyVersion(str(version))
return version
def _compare_equal(self, prospective, spec):
return prospective == self._coerce_version(spec)
def _compare_not_equal(self, prospective, spec):
return prospective != self._coerce_version(spec)
def _compare_less_than_equal(self, prospective, spec):
return prospective <= self._coerce_version(spec)
def _compare_greater_than_equal(self, prospective, spec):
return prospective >= self._coerce_version(spec)
def _compare_less_than(self, prospective, spec):
return prospective < self._coerce_version(spec)
def _compare_greater_than(self, prospective, spec):
return prospective > self._coerce_version(spec)
def _require_version_compare(fn):
@functools.wraps(fn)
def wrapped(self, prospective, spec):
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
return wrapped
class Specifier(_IndividualSpecifier):
_regex_str = r"""
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
(?P<version>
(?:
# The identity operators allow for an escape hatch that will
# do an exact string match of the version you wish to install.
# This will not be parsed by PEP 440 and we cannot determine
# any semantic meaning from it. This operator is discouraged
# but included entirely as an escape hatch.
(?<====) # Only match for the identity operator
\s*
[^\s]* # We just match everything, except for whitespace
# since we are only testing for strict identity.
)
|
(?:
# The (non)equality operators allow for wild card and local
# versions to be specified so we have to define these two
# operators separately to enable that.
(?<===|!=) # Only match for equals and not equals
\s*
v?
(?:[0-9]+!)? # epoch
[0-9]+(?:\.[0-9]+)* # release
(?: # pre release
[-_\.]?
(a|b|c|rc|alpha|beta|pre|preview)
[-_\.]?
[0-9]*
)?
(?: # post release
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
)?
# You cannot use a wild card and a dev or local version
# together so group them with a | and make them optional.
(?:
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
|
\.\* # Wild card syntax of .*
)?
)
|
(?:
# The compatible operator requires at least two digits in the
# release segment.
(?<=~=) # Only match for the compatible operator
\s*
v?
(?:[0-9]+!)? # epoch
[0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
(?: # pre release
[-_\.]?
(a|b|c|rc|alpha|beta|pre|preview)
[-_\.]?
[0-9]*
)?
(?: # post release
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
)?
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
)
|
(?:
# All other operators only allow a sub set of what the
# (non)equality operators do. Specifically they do not allow
# local versions to be specified nor do they allow the prefix
# matching wild cards.
(?<!==|!=|~=) # We have special cases for these
# operators so we want to make sure they
# don't match here.
\s*
v?
(?:[0-9]+!)? # epoch
[0-9]+(?:\.[0-9]+)* # release
(?: # pre release
[-_\.]?
(a|b|c|rc|alpha|beta|pre|preview)
[-_\.]?
[0-9]*
)?
(?: # post release
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
)?
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
)
)
"""
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"~=": "compatible",
"==": "equal",
"!=": "not_equal",
"<=": "less_than_equal",
">=": "greater_than_equal",
"<": "less_than",
">": "greater_than",
"===": "arbitrary",
}
@_require_version_compare
def _compare_compatible(self, prospective, spec):
# Compatible releases have an equivalent combination of >= and ==. That
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
# implement this in terms of the other specifiers instead of
# implementing it ourselves. The only thing we need to do is construct
# the other specifiers.
# We want everything but the last item in the version, but we want to
# ignore post and dev releases and we want to treat the pre-release as
# it's own separate segment.
prefix = ".".join(
list(
itertools.takewhile(
lambda x: (not x.startswith("post") and not x.startswith("dev")),
_version_split(spec),
)
)[:-1]
)
# Add the prefix notation to the end of our string
prefix += ".*"
return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
prospective, prefix
)
@_require_version_compare
def _compare_equal(self, prospective, spec):
# We need special logic to handle prefix matching
if spec.endswith(".*"):
# In the case of prefix matching we want to ignore local segment.
prospective = Version(prospective.public)
# Split the spec out by dots, and pretend that there is an implicit
# dot in between a release segment and a pre-release segment.
spec = _version_split(spec[:-2]) # Remove the trailing .*
# Split the prospective version out by dots, and pretend that there
# is an implicit dot in between a release segment and a pre-release
# segment.
prospective = _version_split(str(prospective))
# Shorten the prospective version to be the same length as the spec
# so that we can determine if the specifier is a prefix of the
# prospective version or not.
prospective = prospective[: len(spec)]
# Pad out our two sides with zeros so that they both equal the same
# length.
spec, prospective = _pad_version(spec, prospective)
else:
# Convert our spec string into a Version
spec = Version(spec)
# If the specifier does not have a local segment, then we want to
# act as if the prospective version also does not have a local
# segment.
if not spec.local:
prospective = Version(prospective.public)
return prospective == spec
@_require_version_compare
def _compare_not_equal(self, prospective, spec):
return not self._compare_equal(prospective, spec)
@_require_version_compare
def _compare_less_than_equal(self, prospective, spec):
return prospective <= Version(spec)
@_require_version_compare
def _compare_greater_than_equal(self, prospective, spec):
return prospective >= Version(spec)
@_require_version_compare
def _compare_less_than(self, prospective, spec):
# Convert our spec to a Version instance, since we'll want to work with
# it as a version.
spec = Version(spec)
# Check to see if the prospective version is less than the spec
# version. If it's not we can short circuit and just return False now
# instead of doing extra unneeded work.
if not prospective < spec:
return False
# This special case is here so that, unless the specifier itself
# includes is a pre-release version, that we do not accept pre-release
# versions for the version mentioned in the specifier (e.g. <3.1 should
# not match 3.1.dev0, but should match 3.0.dev0).
if not spec.is_prerelease and prospective.is_prerelease:
if Version(prospective.base_version) == Version(spec.base_version):
return False
# If we've gotten to here, it means that prospective version is both
# less than the spec version *and* it's not a pre-release of the same
# version in the spec.
return True
@_require_version_compare
def _compare_greater_than(self, prospective, spec):
# Convert our spec to a Version instance, since we'll want to work with
# it as a version.
spec = Version(spec)
# Check to see if the prospective version is greater than the spec
# version. If it's not we can short circuit and just return False now
# instead of doing extra unneeded work.
if not prospective > spec:
return False
# This special case is here so that, unless the specifier itself
# includes is a post-release version, that we do not accept
# post-release versions for the version mentioned in the specifier
# (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
if not spec.is_postrelease and prospective.is_postrelease:
if Version(prospective.base_version) == Version(spec.base_version):
return False
# Ensure that we do not allow a local version of the version mentioned
# in the specifier, which is technically greater than, to match.
if prospective.local is not None:
if Version(prospective.base_version) == Version(spec.base_version):
return False
# If we've gotten to here, it means that prospective version is both
# greater than the spec version *and* it's not a pre-release of the
# same version in the spec.
return True
def _compare_arbitrary(self, prospective, spec):
return str(prospective).lower() == str(spec).lower()
@property
def prereleases(self):
# If there is an explicit prereleases set for this, then we'll just
# blindly use that.
if self._prereleases is not None:
return self._prereleases
# Look at all of our specifiers and determine if they are inclusive
# operators, and if they are if they are including an explicit
# prerelease.
operator, version = self._spec
if operator in ["==", ">=", "<=", "~=", "==="]:
# The == specifier can include a trailing .*, if it does we
# want to remove before parsing.
if operator == "==" and version.endswith(".*"):
version = version[:-2]
# Parse the version, and if it is a pre-release than this
# specifier allows pre-releases.
if parse(version).is_prerelease:
return True
return False
@prereleases.setter
def prereleases(self, value):
self._prereleases = value
_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
def _version_split(version):
result = []
for item in version.split("."):
match = _prefix_regex.search(item)
if match:
result.extend(match.groups())
else:
result.append(item)
return result
def _pad_version(left, right):
left_split, right_split = [], []
# Get the release segment of our versions
left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions
left_split.append(left[len(left_split[0]) :])
right_split.append(right[len(right_split[0]) :])
# Insert our padding
left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
class SpecifierSet(BaseSpecifier):
def __init__(self, specifiers="", prereleases=None):
# Split on , to break each indidivual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
# Parsed each individual specifier, attempting first to make it a
# Specifier and falling back to a LegacySpecifier.
parsed = set()
for specifier in specifiers:
try:
parsed.add(Specifier(specifier))
except InvalidSpecifier:
parsed.add(LegacySpecifier(specifier))
# Turn our parsed specifiers into a frozen set and save them for later.
self._specs = frozenset(parsed)
# Store our prereleases value so we can use it later to determine if
# we accept prereleases or not.
self._prereleases = prereleases
def __repr__(self):
pre = (
", prereleases={0!r}".format(self.prereleases)
if self._prereleases is not None
else ""
)
return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
def __str__(self):
return ",".join(sorted(str(s) for s in self._specs))
def __hash__(self):
return hash(self._specs)
def __and__(self, other):
if isinstance(other, string_types):
other = SpecifierSet(other)
elif not isinstance(other, SpecifierSet):
return NotImplemented
specifier = SpecifierSet()
specifier._specs = frozenset(self._specs | other._specs)
if self._prereleases is None and other._prereleases is not None:
specifier._prereleases = other._prereleases
elif self._prereleases is not None and other._prereleases is None:
specifier._prereleases = self._prereleases
elif self._prereleases == other._prereleases:
specifier._prereleases = self._prereleases
else:
raise ValueError(
"Cannot combine SpecifierSets with True and False prerelease "
"overrides."
)
return specifier
def __eq__(self, other):
if isinstance(other, string_types):
other = SpecifierSet(other)
elif isinstance(other, _IndividualSpecifier):
other = SpecifierSet(str(other))
elif not isinstance(other, SpecifierSet):
return NotImplemented
return self._specs == other._specs
def __ne__(self, other):
if isinstance(other, string_types):
other = SpecifierSet(other)
elif isinstance(other, _IndividualSpecifier):
other = SpecifierSet(str(other))
elif not isinstance(other, SpecifierSet):
return NotImplemented
return self._specs != other._specs
def __len__(self):
return len(self._specs)
def __iter__(self):
return iter(self._specs)
@property
def prereleases(self):
# If we have been given an explicit prerelease modifier, then we'll
# pass that through here.
if self._prereleases is not None:
return self._prereleases
# If we don't have any specifiers, and we don't have a forced value,
# then we'll just return None since we don't know if this should have
# pre-releases or not.
if not self._specs:
return None
# Otherwise we'll see if any of the given specifiers accept
# prereleases, if any of them do we'll return True, otherwise False.
return any(s.prereleases for s in self._specs)
@prereleases.setter
def prereleases(self, value):
self._prereleases = value
def __contains__(self, item):
return self.contains(item)
def contains(self, item, prereleases=None):
# Ensure that our item is a Version or LegacyVersion instance.
if not isinstance(item, (LegacyVersion, Version)):
item = parse(item)
# Determine if we're forcing a prerelease or not, if we're not forcing
# one for this particular filter call, then we'll use whatever the
# SpecifierSet thinks for whether or not we should support prereleases.
if prereleases is None:
prereleases = self.prereleases
# We can determine if we're going to allow pre-releases by looking to
# see if any of the underlying items supports them. If none of them do
# and this item is a pre-release then we do not allow it and we can
# short circuit that here.
# Note: This means that 1.0.dev1 would not be contained in something
# like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
if not prereleases and item.is_prerelease:
return False
# We simply dispatch to the underlying specs here to make sure that the
# given version is contained within all of them.
# Note: This use of all() here means that an empty set of specifiers
# will always return True, this is an explicit design decision.
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(self, iterable, prereleases=None):
# Determine if we're forcing a prerelease or not, if we're not forcing
# one for this particular filter call, then we'll use whatever the
# SpecifierSet thinks for whether or not we should support prereleases.
if prereleases is None:
prereleases = self.prereleases
# If we have any specifiers, then we want to wrap our iterable in the
# filter method for each one, this will act as a logical AND amongst
# each specifier.
if self._specs:
for spec in self._specs:
iterable = spec.filter(iterable, prereleases=bool(prereleases))
return iterable
# If we do not have any specifiers, then we need to have a rough filter
# which will filter out any pre-releases, unless there are no final
# releases, and which will filter out LegacyVersion in general.
else:
filtered = []
found_prereleases = []
for item in iterable:
# Ensure that we some kind of Version class for this item.
if not isinstance(item, (LegacyVersion, Version)):
parsed_version = parse(item)
else:
parsed_version = item
# Filter out any item which is parsed as a LegacyVersion
if isinstance(parsed_version, LegacyVersion):
continue
# Store any item which is a pre-release for later unless we've
# already found a final version or we are accepting prereleases
if parsed_version.is_prerelease and not prereleases:
if not filtered:
found_prereleases.append(item)
else:
filtered.append(item)
# If we've found no items except for pre-releases, then we'll go
# ahead and use the pre-releases
if not filtered and found_prereleases and prereleases is None:
return found_prereleases
return filtered
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,7 +13,6 @@ description = "" ...@@ -14,7 +13,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "C" name = "C"
...@@ -23,7 +21,6 @@ description = "" ...@@ -23,7 +21,6 @@ description = ""
category = "main" category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
D = "^1.0" D = "^1.0"
...@@ -35,14 +32,12 @@ description = "" ...@@ -35,14 +32,12 @@ description = ""
category = "main" category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
platform = "*"
[extras] [extras]
foo = ["C"] foo = ["C"]
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,7 +13,6 @@ description = "" ...@@ -14,7 +13,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "C" name = "C"
...@@ -23,7 +21,6 @@ description = "" ...@@ -23,7 +21,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "D" name = "D"
...@@ -32,14 +29,12 @@ description = "" ...@@ -32,14 +29,12 @@ description = ""
category = "main" category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
platform = "*"
[extras] [extras]
foo = ["D"] foo = ["D"]
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -2,7 +2,6 @@ package = [] ...@@ -2,7 +2,6 @@ package = []
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
...@@ -5,11 +5,9 @@ description = "" ...@@ -5,11 +5,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,11 +5,9 @@ description = "" ...@@ -5,11 +5,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
"B" = "^1.0" "B" = "^1.0"
...@@ -18,19 +17,15 @@ description = "" ...@@ -18,19 +17,15 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "C" name = "C"
version = "1.1" version = "1.1"
description = "" description = ""
category = "main" category = "main"
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.requirements]
python = ">=2.7,<2.8"
[[package]] [[package]]
name = "D" name = "D"
...@@ -39,11 +34,9 @@ description = "" ...@@ -39,11 +34,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,14 +13,12 @@ description = "" ...@@ -14,14 +13,12 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
A = "^1.0" A = "^1.0"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = ">=3.5"
[package.requirements]
python = ">=3.5,<4.0"
[[package]]
name = "A"
version = "1.0.1"
description = ""
category = "main"
optional = false
python-versions = ">=3.6"
[package.requirements]
python = ">=3.6,<4.0"
[metadata]
python-versions = "~2.7 || ^3.4"
content-hash = "123456789"
[metadata.hashes]
A = []
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,7 +13,6 @@ description = "" ...@@ -14,7 +13,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
C = "^1.0" C = "^1.0"
...@@ -26,11 +24,9 @@ description = "" ...@@ -26,11 +24,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,11 +13,9 @@ description = "" ...@@ -14,11 +13,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -3,7 +3,6 @@ description = "" ...@@ -3,7 +3,6 @@ description = ""
category = "main" category = "main"
name = "pendulum" name = "pendulum"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.4.4" version = "1.4.4"
...@@ -12,7 +11,6 @@ category = "main" ...@@ -12,7 +11,6 @@ category = "main"
description = "" description = ""
name = "project-with-extras" name = "project-with-extras"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.2.3" version = "1.2.3"
...@@ -26,7 +24,6 @@ url = "tests/fixtures/project_with_extras" ...@@ -26,7 +24,6 @@ url = "tests/fixtures/project_with_extras"
[metadata] [metadata]
content-hash = "123456789" content-hash = "123456789"
platform = "*"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "my-package" name = "my-package"
...@@ -14,7 +13,6 @@ description = "Demo project." ...@@ -14,7 +13,6 @@ description = "Demo project."
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.source] [package.source]
type = "directory" type = "directory"
...@@ -32,11 +30,9 @@ description = "" ...@@ -32,11 +30,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
B = "^2.0" B = "^2.0"
...@@ -17,7 +16,6 @@ description = "" ...@@ -17,7 +16,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
C = "1.5" C = "1.5"
...@@ -29,11 +27,9 @@ description = "" ...@@ -29,11 +27,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
B = [ B = [
...@@ -18,58 +17,45 @@ name = "B" ...@@ -18,58 +17,45 @@ name = "B"
version = "1.0" version = "1.0"
description = "" description = ""
category = "main" category = "main"
marker = "python_version < \"4.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
C = "1.2" C = "1.2"
[package.requirements]
python = "<4.0"
[[package]] [[package]]
name = "B" name = "B"
version = "2.0" version = "2.0"
description = "" description = ""
category = "main" category = "main"
marker = "python_version >= \"4.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
C = "1.5" C = "1.5"
[package.requirements]
python = ">=4.0"
[[package]] [[package]]
name = "C" name = "C"
version = "1.2" version = "1.2"
description = "" description = ""
category = "main" category = "main"
marker = "python_version < \"4.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.requirements]
python = "<4.0"
[[package]] [[package]]
name = "C" name = "C"
version = "1.5" version = "1.5"
description = "" description = ""
category = "main" category = "main"
marker = "python_version >= \"4.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.requirements]
python = ">=4.0"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "Description" ...@@ -5,7 +5,6 @@ description = "Description"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "*"
[package.source] [package.source]
type = "file" type = "file"
...@@ -22,11 +21,9 @@ description = "" ...@@ -22,11 +21,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,41 +5,33 @@ description = "" ...@@ -5,41 +5,33 @@ description = ""
category = "main" category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "C" name = "C"
version = "1.3" version = "1.3"
description = "" description = ""
category = "main" category = "main"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"4.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
D = "^1.2" D = "^1.2"
[package.requirements]
python = ">=2.7,<2.8 || >=3.4,<4.0"
[[package]] [[package]]
name = "D" name = "D"
version = "1.4" version = "1.4"
description = "" description = ""
category = "main" category = "main"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"4.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.requirements]
python = ">=2.7,<2.8 || >=3.4,<4.0"
[extras] [extras]
foo = ["A"] foo = ["A"]
[metadata] [metadata]
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,53 +5,42 @@ description = "" ...@@ -5,53 +5,42 @@ description = ""
category = "main" category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
version = "1.1" version = "1.1"
description = "" description = ""
category = "main" category = "main"
marker = "sys_platform == \"custom\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.requirements]
platform = "custom"
[[package]] [[package]]
name = "C" name = "C"
version = "1.3" version = "1.3"
description = "" description = ""
category = "main" category = "main"
marker = "sys_platform == \"darwin\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
D = "^1.2" D = "^1.2"
[package.requirements]
platform = "darwin"
[[package]] [[package]]
name = "D" name = "D"
version = "1.4" version = "1.4"
description = "" description = ""
category = "main" category = "main"
marker = "sys_platform == \"darwin\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.requirements]
platform = "darwin"
[extras] [extras]
foo = ["A"] foo = ["A"]
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,11 +13,9 @@ description = "" ...@@ -14,11 +13,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,31 +5,24 @@ description = "Classes Without Boilerplate" ...@@ -5,31 +5,24 @@ description = "Classes Without Boilerplate"
category = "dev" category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.3.9" version = "0.3.9"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "dev" category = "dev"
marker = "sys_platform == \"win32\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "UNKNOWN"
[package.requirements]
platform = "win32"
[[package]] [[package]]
name = "funcsigs" name = "funcsigs"
version = "1.0.2" version = "1.0.2"
description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
category = "dev" category = "dev"
marker = "python_version < \"3.0\""
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "UNKNOWN"
[package.requirements]
python = "<3.0"
[[package]] [[package]]
name = "more-itertools" name = "more-itertools"
...@@ -38,7 +31,6 @@ description = "More routines for operating on iterables, beyond itertools" ...@@ -38,7 +31,6 @@ description = "More routines for operating on iterables, beyond itertools"
category = "dev" category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
six = ">=1.0.0,<2.0.0" six = ">=1.0.0,<2.0.0"
...@@ -50,7 +42,6 @@ description = "plugin and hook calling mechanisms for python" ...@@ -50,7 +42,6 @@ description = "plugin and hook calling mechanisms for python"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "unix"
[[package]] [[package]]
name = "py" name = "py"
...@@ -59,7 +50,6 @@ description = "library with cross-python path, ini-parsing, io, code, log facili ...@@ -59,7 +50,6 @@ description = "library with cross-python path, ini-parsing, io, code, log facili
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "unix"
[[package]] [[package]]
name = "pytest" name = "pytest"
...@@ -68,7 +58,6 @@ description = "pytest: simple powerful testing with Python" ...@@ -68,7 +58,6 @@ description = "pytest: simple powerful testing with Python"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "unix"
[package.dependencies] [package.dependencies]
py = ">=1.5.0" py = ">=1.5.0"
...@@ -78,7 +67,7 @@ attrs = ">=17.4.0" ...@@ -78,7 +67,7 @@ attrs = ">=17.4.0"
more-itertools = ">=4.0.0" more-itertools = ">=4.0.0"
pluggy = ">=0.5,<0.7" pluggy = ">=0.5,<0.7"
funcsigs = {"version" = "*", "python" = "<3.0"} funcsigs = {"version" = "*", "python" = "<3.0"}
colorama = {"version" = "*", "platform" = "win32"} colorama = "*"
[[package]] [[package]]
name = "six" name = "six"
...@@ -87,11 +76,9 @@ description = "Python 2 and 3 compatibility utilities" ...@@ -87,11 +76,9 @@ description = "Python 2 and 3 compatibility utilities"
category = "dev" category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "B" name = "B"
...@@ -14,7 +13,6 @@ description = "" ...@@ -14,7 +13,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "C" name = "C"
...@@ -23,11 +21,9 @@ description = "" ...@@ -23,11 +21,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "~2.7 || ^3.3" python-versions = "~2.7 || ^3.3"
platform = "*"
[metadata] [metadata]
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -5,7 +5,6 @@ description = "" ...@@ -5,7 +5,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
D = "^1.0" D = "^1.0"
...@@ -17,7 +16,6 @@ description = "" ...@@ -17,7 +16,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[package.dependencies] [package.dependencies]
C = "~1.2" C = "~1.2"
...@@ -29,7 +27,6 @@ description = "" ...@@ -29,7 +27,6 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[[package]] [[package]]
name = "D" name = "D"
...@@ -38,11 +35,9 @@ description = "" ...@@ -38,11 +35,9 @@ description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
platform = "*"
[metadata] [metadata]
python-versions = "*" python-versions = "*"
platform = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
......
...@@ -63,10 +63,8 @@ class Locker(BaseLocker): ...@@ -63,10 +63,8 @@ class Locker(BaseLocker):
def _write_lock_data(self, data): def _write_lock_data(self, data):
for package in data["package"]: for package in data["package"]:
python_versions = str(package["python-versions"]) python_versions = str(package["python-versions"])
platform = str(package["platform"])
if PY2: if PY2:
python_versions = python_versions.decode() python_versions = python_versions.decode()
platform = platform.decode()
if "requirements" in package: if "requirements" in package:
requirements = {} requirements = {}
for key, value in package["requirements"].items(): for key, value in package["requirements"].items():
...@@ -75,7 +73,6 @@ class Locker(BaseLocker): ...@@ -75,7 +73,6 @@ class Locker(BaseLocker):
package["requirements"] = requirements package["requirements"] = requirements
package["python-versions"] = python_versions package["python-versions"] = python_versions
package["platform"] = platform
self._written_data = data self._written_data = data
...@@ -1143,3 +1140,34 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1143,3 +1140,34 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
assert len(updates) == 1 assert len(updates) == 1
removals = installer.installer.removals removals = installer.installer.removals
assert len(removals) == 0 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
...@@ -91,12 +91,12 @@ def test_convert_dependencies(): ...@@ -91,12 +91,12 @@ def test_convert_dependencies():
main = ["B>=1.0,<1.1"] main = ["B>=1.0,<1.1"]
extra_python = ( extra_python = (
':(python_version >= "2.7" and python_version < "2.8") ' ':python_version >= "2.7" and python_version < "2.8" '
'or (python_version >= "3.6" and python_version < "4.0")' 'or python_version >= "3.6" and python_version < "4.0"'
) )
extra_d_dependency = ( extra_d_dependency = (
'baz:(python_version >= "2.7" and python_version < "2.8") ' 'baz:python_version >= "2.7" and python_version < "2.8" '
'or (python_version >= "3.4" and python_version < "4.0")' 'or python_version >= "3.4" and python_version < "4.0"'
) )
extras = {extra_python: ["C==1.2.3"], extra_d_dependency: ["D==3.4.5"]} extras = {extra_python: ["C==1.2.3"], extra_d_dependency: ["D==3.4.5"]}
......
from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.empty_constraint import EmptyConstraint
from poetry.packages.constraints.multi_constraint import MultiConstraint
from poetry.packages.constraints.union_constraint import UnionConstraint
def test_allows():
c = Constraint("win32")
assert c.allows(Constraint("win32"))
assert not c.allows(Constraint("linux"))
c = Constraint("win32", "!=")
assert not c.allows(Constraint("win32"))
assert c.allows(Constraint("linux"))
def test_allows_any():
c = Constraint("win32")
assert c.allows_any(Constraint("win32"))
assert not c.allows_any(Constraint("linux"))
assert c.allows_any(UnionConstraint(Constraint("win32"), Constraint("linux")))
assert c.allows_any(Constraint("linux", "!="))
c = Constraint("win32", "!=")
assert not c.allows_any(Constraint("win32"))
assert c.allows_any(Constraint("linux"))
assert c.allows_any(UnionConstraint(Constraint("win32"), Constraint("linux")))
assert c.allows_any(Constraint("linux", "!="))
def test_allows_all():
c = Constraint("win32")
assert c.allows_all(Constraint("win32"))
assert not c.allows_all(Constraint("linux"))
assert not c.allows_all(Constraint("linux", "!="))
assert not c.allows_all(UnionConstraint(Constraint("win32"), Constraint("linux")))
def test_intersect():
c = Constraint("win32")
intersection = c.intersect(Constraint("linux"))
assert intersection == EmptyConstraint()
intersection = c.intersect(
UnionConstraint(Constraint("win32"), Constraint("linux"))
)
assert intersection == Constraint("win32")
intersection = c.intersect(
UnionConstraint(Constraint("linux"), Constraint("linux2"))
)
assert intersection == EmptyConstraint()
intersection = c.intersect(Constraint("linux", "!="))
assert intersection == c
c = Constraint("win32", "!=")
intersection = c.intersect(Constraint("linux", "!="))
assert intersection == MultiConstraint(
Constraint("win32", "!="), Constraint("linux", "!=")
)
def test_union():
c = Constraint("win32")
union = c.union(Constraint("linux"))
assert union == UnionConstraint(Constraint("win32"), Constraint("linux"))
union = c.union(UnionConstraint(Constraint("win32"), Constraint("linux")))
assert union == UnionConstraint(Constraint("win32"), Constraint("linux"))
union = c.union(UnionConstraint(Constraint("linux"), Constraint("linux2")))
assert union == UnionConstraint(
Constraint("win32"), Constraint("linux"), Constraint("linux2")
)
def test_difference():
c = Constraint("win32")
assert c.difference(Constraint("win32")).is_empty()
assert c.difference(Constraint("linux")) == c
import pytest
from poetry.packages.constraints import parse_constraint
from poetry.packages.constraints.any_constraint import AnyConstraint
from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.multi_constraint import MultiConstraint
from poetry.packages.constraints.union_constraint import UnionConstraint
@pytest.mark.parametrize(
"input,constraint",
[
("*", AnyConstraint()),
("win32", Constraint("win32", "=")),
("=win32", Constraint("win32", "=")),
("==win32", Constraint("win32", "=")),
("!=win32", Constraint("win32", "!=")),
("!= win32", Constraint("win32", "!=")),
],
)
def test_parse_constraint(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input,constraint",
[
(
"!=win32,!=linux",
MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")),
),
(
"!=win32,!=linux,!=linux2",
MultiConstraint(
Constraint("win32", "!="),
Constraint("linux", "!="),
Constraint("linux2", "!="),
),
),
],
)
def test_parse_constraint_multi(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input,constraint",
[
("win32 || linux", UnionConstraint(Constraint("win32"), Constraint("linux"))),
(
"win32 || !=linux2",
UnionConstraint(Constraint("win32"), Constraint("linux2", "!=")),
),
],
)
def test_parse_constraint_multi(input, constraint):
assert parse_constraint(input) == constraint
from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.multi_constraint import MultiConstraint
def test_allows():
c = MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!="))
assert not c.allows(Constraint("win32"))
assert not c.allows(Constraint("linux"))
assert c.allows(Constraint("darwin"))
def test_allows_any():
c = MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!="))
assert c.allows_any(Constraint("darwin"))
assert c.allows_any(Constraint("darwin", "!="))
assert not c.allows_any(Constraint("win32"))
assert c.allows_any(c)
assert c.allows_any(
MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!="))
)
def test_allows_all():
c = MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!="))
assert c.allows_all(Constraint("darwin"))
assert c.allows_all(Constraint("darwin", "!="))
assert not c.allows_all(Constraint("win32"))
assert c.allows_all(c)
assert not c.allows_all(
MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!="))
)
def test_intersect():
c = MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!="))
intersection = c.intersect(Constraint("win32", "!="))
assert intersection == Constraint("win32", "!=")
from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.union_constraint import UnionConstraint
def test_allows():
c = UnionConstraint(Constraint("win32"), Constraint("linux"))
assert c.allows(Constraint("win32"))
assert c.allows(Constraint("linux"))
assert not c.allows(Constraint("darwin"))
def test_allows_any():
c = UnionConstraint(Constraint("win32"), Constraint("linux"))
assert c.allows_any(c)
assert c.allows_any(UnionConstraint(Constraint("win32"), Constraint("darwin")))
assert not c.allows_any(UnionConstraint(Constraint("linux2"), Constraint("darwin")))
assert c.allows_any(Constraint("win32"))
assert not c.allows_any(Constraint("darwin"))
def test_allows_all():
c = UnionConstraint(Constraint("win32"), Constraint("linux"))
assert c.allows_all(c)
assert not c.allows_all(UnionConstraint(Constraint("win32"), Constraint("darwin")))
assert not c.allows_all(UnionConstraint(Constraint("linux2"), Constraint("darwin")))
assert c.allows_all(Constraint("win32"))
assert not c.allows_all(Constraint("darwin"))
...@@ -67,23 +67,8 @@ def test_to_pep_508(): ...@@ -67,23 +67,8 @@ def test_to_pep_508():
result = dependency.to_pep_508() result = dependency.to_pep_508()
assert ( assert (
result == "Django (>=1.23,<2.0); " result == "Django (>=1.23,<2.0); "
'(python_version >= "2.7" and python_version < "2.8") ' 'python_version >= "2.7" and python_version < "2.8" '
'or (python_version >= "3.6" and python_version < "4.0")' 'or python_version >= "3.6" and python_version < "4.0"'
)
def test_to_pep_508_with_platform():
dependency = Dependency("Django", "^1.23")
dependency.python_versions = "~2.7 || ^3.6"
dependency.platform = "linux || linux2"
result = dependency.to_pep_508()
assert result == (
"Django (>=1.23,<2.0); "
'((python_version >= "2.7" and python_version < "2.8") '
'or (python_version >= "3.6" and python_version < "4.0"))'
' and (sys_platform == "linux" or sys_platform == "linux2")'
) )
...@@ -112,8 +97,8 @@ def test_to_pep_508_in_extras(): ...@@ -112,8 +97,8 @@ def test_to_pep_508_in_extras():
assert result == ( assert result == (
"Django (>=1.23,<2.0); " "Django (>=1.23,<2.0); "
"(" "("
'(python_version >= "2.7" and python_version < "2.8") ' 'python_version >= "2.7" and python_version < "2.8" '
'or (python_version >= "3.6" and python_version < "4.0")' 'or python_version >= "3.6" and python_version < "4.0"'
") " ") "
'and (extra == "foo" or extra == "bar")' 'and (extra == "foo" or extra == "bar")'
) )
...@@ -13,7 +13,6 @@ def test_file_dependency_wheel(): ...@@ -13,7 +13,6 @@ def test_file_dependency_wheel():
assert dependency.name == "demo" assert dependency.name == "demo"
assert dependency.pretty_constraint == "0.1.0" assert dependency.pretty_constraint == "0.1.0"
assert dependency.python_versions == "*" assert dependency.python_versions == "*"
assert dependency.platform == "*"
meta = dependency.metadata meta = dependency.metadata
assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"] assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"]
...@@ -26,7 +25,6 @@ def test_file_dependency_sdist(): ...@@ -26,7 +25,6 @@ def test_file_dependency_sdist():
assert dependency.name == "demo" assert dependency.name == "demo"
assert dependency.pretty_constraint == "0.1.0" assert dependency.pretty_constraint == "0.1.0"
assert dependency.python_versions == "*" assert dependency.python_versions == "*"
assert dependency.platform == "*"
meta = dependency.metadata meta = dependency.metadata
assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"] assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"]
......
...@@ -37,7 +37,6 @@ category = "main" ...@@ -37,7 +37,6 @@ category = "main"
description = "" description = ""
name = "A" name = "A"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.0.0" version = "1.0.0"
...@@ -49,13 +48,11 @@ category = "main" ...@@ -49,13 +48,11 @@ category = "main"
description = "" description = ""
name = "B" name = "B"
optional = false optional = false
platform = "*"
python-versions = "*" python-versions = "*"
version = "1.2" version = "1.2"
[metadata] [metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
platform = "*"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.hashes]
......
...@@ -40,37 +40,40 @@ def test_dependency_from_pep_508_with_extras(): ...@@ -40,37 +40,40 @@ def test_dependency_from_pep_508_with_extras():
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.in_extras == ["foo", "bar"] assert dep.in_extras == ["foo", "bar"]
assert str(dep.marker) == 'extra == "foo" or extra == "bar"'
def test_dependency_from_pep_508_with_python_version(): def test_dependency_from_pep_508_with_python_version():
name = "requests (==2.18.0); " 'python_version == "2.7" or python_version == "2.6"' name = 'requests (==2.18.0); python_version == "2.7" or python_version == "2.6"'
dep = dependency_from_pep_508(name) dep = dependency_from_pep_508(name)
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.extras == [] assert dep.extras == []
assert dep.python_versions == "~2.7 || ~2.6" assert dep.python_versions == "~2.7 || ~2.6"
assert str(dep.marker) == 'python_version == "2.7" or python_version == "2.6"'
def test_dependency_from_pep_508_with_single_python_version(): def test_dependency_from_pep_508_with_single_python_version():
name = "requests (==2.18.0); " 'python_version == "2.7"' name = 'requests (==2.18.0); python_version == "2.7"'
dep = dependency_from_pep_508(name) dep = dependency_from_pep_508(name)
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.extras == [] assert dep.extras == []
assert dep.python_versions == "~2.7" assert dep.python_versions == "~2.7"
assert str(dep.marker) == 'python_version == "2.7"'
def test_dependency_from_pep_508_with_platform(): def test_dependency_from_pep_508_with_platform():
name = "requests (==2.18.0); " 'sys_platform == "win32" or sys_platform == "darwin"' name = 'requests (==2.18.0); sys_platform == "win32" or sys_platform == "darwin"'
dep = dependency_from_pep_508(name) dep = dependency_from_pep_508(name)
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.extras == [] assert dep.extras == []
assert dep.python_versions == "*" assert dep.python_versions == "*"
assert dep.platform == "win32 || darwin" assert str(dep.marker) == 'sys_platform == "win32" or sys_platform == "darwin"'
def test_dependency_from_pep_508_complex(): def test_dependency_from_pep_508_complex():
...@@ -86,34 +89,40 @@ def test_dependency_from_pep_508_complex(): ...@@ -86,34 +89,40 @@ def test_dependency_from_pep_508_complex():
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.in_extras == ["foo"] assert dep.in_extras == ["foo"]
assert dep.python_versions == ">=2.7 !=3.2.*" assert dep.python_versions == ">=2.7 !=3.2.*"
assert dep.platform == "win32 || darwin" assert str(dep.marker) == (
'python_version >= "2.7" and python_version != "3.2" '
'and (sys_platform == "win32" or sys_platform == "darwin") '
'and extra == "foo"'
)
def test_dependency_python_version_in(): def test_dependency_python_version_in():
name = "requests (==2.18.0); " "python_version in '3.3 3.4 3.5'" name = "requests (==2.18.0); python_version in '3.3 3.4 3.5'"
dep = dependency_from_pep_508(name) dep = dependency_from_pep_508(name)
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.python_versions == "3.3.* || 3.4.* || 3.5.*" assert dep.python_versions == "3.3.* || 3.4.* || 3.5.*"
assert str(dep.marker) == 'python_version in "3.3 3.4 3.5"'
def test_dependency_python_version_in_comma(): def test_dependency_python_version_in_comma():
name = "requests (==2.18.0); " "python_version in '3.3, 3.4, 3.5'" name = "requests (==2.18.0); python_version in '3.3, 3.4, 3.5'"
dep = dependency_from_pep_508(name) dep = dependency_from_pep_508(name)
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.python_versions == "3.3.* || 3.4.* || 3.5.*" assert dep.python_versions == "3.3.* || 3.4.* || 3.5.*"
assert str(dep.marker) == 'python_version in "3.3, 3.4, 3.5"'
def test_dependency_platform_in(): def test_dependency_platform_in():
name = "requests (==2.18.0); " "sys_platform in 'win32 darwin'" name = "requests (==2.18.0); sys_platform in 'win32 darwin'"
dep = dependency_from_pep_508(name) dep = dependency_from_pep_508(name)
assert dep.name == "requests" assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0" assert str(dep.constraint) == "2.18.0"
assert dep.platform == "win32 || darwin" assert str(dep.marker) == 'sys_platform in "win32 darwin"'
def test_dependency_with_extra(): def test_dependency_with_extra():
...@@ -125,3 +134,21 @@ def test_dependency_with_extra(): ...@@ -125,3 +134,21 @@ def test_dependency_with_extra():
assert len(dep.extras) == 1 assert len(dep.extras) == 1
assert dep.extras[0] == "security" assert dep.extras[0] == "security"
def test_dependency_from_pep_508_with_python_version_union_of_multi():
name = (
"requests (==2.18.0); "
'(python_version >= "2.7" and python_version < "2.8") '
'or (python_version >= "3.4" and python_version < "3.5")'
)
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.extras == []
assert dep.python_versions == ">=2.7 <2.8 || >=3.4 <3.5"
assert str(dep.marker) == (
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "3.5"'
)
...@@ -3,6 +3,7 @@ import pytest ...@@ -3,6 +3,7 @@ import pytest
from cleo.outputs.null_output import NullOutput from cleo.outputs.null_output import NullOutput
from cleo.styles import OutputStyle from cleo.styles import OutputStyle
from poetry.packages import dependency_from_pep_508
from poetry.packages import ProjectPackage from poetry.packages import ProjectPackage
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
...@@ -12,7 +13,6 @@ from poetry.puzzle.exceptions import SolverProblemError ...@@ -12,7 +13,6 @@ from poetry.puzzle.exceptions import SolverProblemError
from tests.helpers import get_dependency from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
from tests.repositories.test_pypi_repository import MockRepository
@pytest.fixture() @pytest.fixture()
...@@ -370,36 +370,6 @@ def test_solver_solves_optional_and_compatible_packages(solver, repo, package): ...@@ -370,36 +370,6 @@ def test_solver_solves_optional_and_compatible_packages(solver, repo, package):
) )
def test_solver_solves_while_respecting_root_platforms(solver, repo, package):
package.platform = "darwin"
package.add_dependency("A")
package.add_dependency("B")
package_a = get_package("A", "1.0")
package_b = get_package("B", "1.0")
package_c12 = get_package("C", "1.2")
package_c12.platform = "win32"
package_c10 = get_package("C", "1.0")
package_c10.platform = "darwin"
package_b.add_dependency("C", "^1.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c10)
repo.add_package(package_c12)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_c10},
{"job": "install", "package": package_a},
{"job": "install", "package": package_b},
],
)
def test_solver_does_not_return_extras_if_not_requested(solver, repo, package): def test_solver_does_not_return_extras_if_not_requested(solver, repo, package):
package.add_dependency("A") package.add_dependency("A")
package.add_dependency("B") package.add_dependency("B")
...@@ -537,7 +507,7 @@ def test_solver_sub_dependencies_with_requirements(solver, repo, package): ...@@ -537,7 +507,7 @@ def test_solver_sub_dependencies_with_requirements(solver, repo, package):
) )
op = ops[1] op = ops[1]
assert op.package.requirements == {} assert op.package.marker.is_any()
def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package): def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package):
...@@ -581,16 +551,19 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package ...@@ -581,16 +551,19 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package
) )
op = ops[3] # d op = ops[3] # d
assert op.package.requirements == {"python": "<4.0"} assert str(op.package.marker) == 'python_version < "4.0"'
op = ops[0] # e op = ops[0] # e
assert op.package.requirements == {"platform": "win32", "python": "<5.0"} assert str(op.package.marker) == (
'python_version < "4.0" and sys_platform == "win32" '
'or python_version < "5.0" and sys_platform == "win32"'
)
op = ops[1] # f op = ops[1] # f
assert op.package.requirements == {"python": "<5.0"} assert str(op.package.marker) == 'python_version < "5.0"'
op = ops[4] # a op = ops[4] # a
assert op.package.requirements == {"python": "<5.0"} assert str(op.package.marker) == 'python_version < "5.0"'
def test_solver_sub_dependencies_with_not_supported_python_version( def test_solver_sub_dependencies_with_not_supported_python_version(
...@@ -785,7 +758,9 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package): ...@@ -785,7 +758,9 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package):
) )
op = ops[0] op = ops[0]
assert op.package.requirements == {"python": "2.7 || >=3.4"} assert (
str(op.package.marker) == 'python_version == "2.7" or python_version >= "3.4"'
)
def test_solver_duplicate_dependencies_different_constraints(solver, repo, package): def test_solver_duplicate_dependencies_different_constraints(solver, repo, package):
...@@ -814,10 +789,10 @@ def test_solver_duplicate_dependencies_different_constraints(solver, repo, packa ...@@ -814,10 +789,10 @@ def test_solver_duplicate_dependencies_different_constraints(solver, repo, packa
) )
op = ops[0] op = ops[0]
assert op.package.requirements == {"python": "<3.4"} assert str(op.package.marker) == 'python_version < "3.4"'
op = ops[1] op = ops[1]
assert op.package.requirements == {"python": ">=3.4"} assert str(op.package.marker) == 'python_version >= "3.4"'
def test_solver_duplicate_dependencies_different_constraints_same_requirements( def test_solver_duplicate_dependencies_different_constraints_same_requirements(
...@@ -882,10 +857,10 @@ def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package): ...@@ -882,10 +857,10 @@ def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package):
) )
op = ops[2] op = ops[2]
assert op.package.requirements == {"python": "<3.4"} assert str(op.package.marker) == 'python_version < "3.4"'
op = ops[3] op = ops[3]
assert op.package.requirements == {"python": ">=3.4"} assert str(op.package.marker) == 'python_version >= "3.4"'
def test_solver_fails_if_dependency_name_does_not_match_package(solver, repo, package): def test_solver_fails_if_dependency_name_does_not_match_package(solver, repo, package):
...@@ -967,7 +942,7 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package): ...@@ -967,7 +942,7 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible( def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
solver, repo, package solver, repo, package
): ):
package.python_versions = "~2.7 || ^3.6" package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"}) package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package_a = get_package("A", "1.0.0") package_a = get_package("A", "1.0.0")
...@@ -978,3 +953,123 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir ...@@ -978,3 +953,123 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir
ops = solver.solve() ops = solver.solve()
check_solver_result(ops, [{"job": "install", "package": package_a}]) check_solver_result(ops, [{"job": "install", "package": package_a}])
assert (
str(ops[0].package.marker)
== 'python_version >= "3.6" and python_version < "4.0"'
)
def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})
package_a = get_package("A", "1.0.0")
package_a.python_versions = ">=3.6"
repo.add_package(package_a)
with pytest.raises(SolverProblemError):
solver.solve()
@pytest.mark.skip(
"This is not working at the moment due to limitations in the resolver"
)
def test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python(
solver, repo, package
):
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)
ops = solver.solve()
check_solver_result(ops, [{"job": "install", "package": package_a100}])
assert (
str(ops[0].package.marker)
== 'python_version >= "3.5" and python_version < "4.0"'
)
def test_solver_sets_appropriate_markers_when_solving(solver, repo, package):
dep = dependency_from_pep_508(
'B (>=1.0); python_version >= "3.6" and sys_platform != "win32"'
)
package.add_dependency("A", "^1.0")
package_a = get_package("A", "1.0.0")
package_a.requires.append(dep)
package_b = get_package("B", "1.0.0")
repo.add_package(package_a)
repo.add_package(package_b)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_b},
{"job": "install", "package": package_a},
],
)
assert (
str(ops[0].package.marker)
== 'python_version >= "3.6" and sys_platform != "win32"'
)
assert str(ops[1].package.marker) == ""
def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_only_extras(
solver, repo, package
):
dep1 = dependency_from_pep_508('B (>=1.0); extra == "foo"')
dep1.activate()
dep2 = dependency_from_pep_508('B (>=2.0); extra == "bar"')
dep2.activate()
package.add_dependency("A", {"version": "^1.0", "extras": ["foo", "bar"]})
package_a = get_package("A", "1.0.0")
package_a.extras = {"foo": [dep1], "bar": [dep2]}
package_a.requires.append(dep1)
package_a.requires.append(dep2)
package_b2 = get_package("B", "2.0.0")
package_b1 = get_package("B", "1.0.0")
repo.add_package(package_a)
repo.add_package(package_b1)
repo.add_package(package_b2)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_b2},
{"job": "install", "package": package_a},
],
)
assert str(ops[0].package.marker) in [
'extra == "foo" or extra == "bar"',
'extra == "bar" or extra == "foo"',
]
assert str(ops[1].package.marker) == ""
...@@ -68,7 +68,10 @@ def test_package(): ...@@ -68,7 +68,10 @@ def test_package():
win_inet = package.extras["socks"][0] win_inet = package.extras["socks"][0]
assert win_inet.name == "win-inet-pton" assert win_inet.name == "win-inet-pton"
assert win_inet.python_versions == "~2.7 || ~2.6" assert win_inet.python_versions == "~2.7 || ~2.6"
assert win_inet.platform == "win32" assert str(win_inet.marker) == (
'sys_platform == "win32" and (python_version == "2.7" '
'or python_version == "2.6") and extra == "socks"'
)
def test_fallback_on_downloading_packages(): def test_fallback_on_downloading_packages():
......
import os
import pytest
from poetry.version.markers import MarkerUnion
from poetry.version.markers import MultiMarker
from poetry.version.markers import SingleMarker
from poetry.version.markers import parse_marker
def test_single_marker():
m = parse_marker('sys_platform == "darwin"')
assert isinstance(m, SingleMarker)
assert m.name == "sys_platform"
assert m.constraint_string == "==darwin"
m = parse_marker('python_version in "2.7, 3.0, 3.1"')
assert isinstance(m, SingleMarker)
assert m.name == "python_version"
assert m.constraint_string == "in 2.7, 3.0, 3.1"
assert str(m.constraint) == ">=2.7.0,<2.8.0 || >=3.0.0,<3.2.0"
m = parse_marker('"2.7" in python_version')
assert isinstance(m, SingleMarker)
assert m.name == "python_version"
assert m.constraint_string == "in 2.7"
assert str(m.constraint) == ">=2.7.0,<2.8.0"
m = parse_marker('python_version not in "2.7, 3.0, 3.1"')
assert isinstance(m, SingleMarker)
assert m.name == "python_version"
assert m.constraint_string == "not in 2.7, 3.0, 3.1"
assert str(m.constraint) == "<2.7.0 || >=2.8.0,<3.0.0 || >=3.2.0"
def test_single_marker_intersect():
m = parse_marker('sys_platform == "darwin"')
intersection = m.intersect(parse_marker('implementation_name == "cpython"'))
assert (
str(intersection)
== 'sys_platform == "darwin" and implementation_name == "cpython"'
)
m = parse_marker('python_version >= "3.4"')
intersection = m.intersect(parse_marker('python_version < "3.6"'))
assert str(intersection) == 'python_version >= "3.4" and python_version < "3.6"'
def test_single_marker_intersect_compacts_constraints():
m = parse_marker('python_version < "3.6"')
intersection = m.intersect(parse_marker('python_version < "3.4"'))
assert str(intersection) == 'python_version < "3.4"'
def test_single_marker_intersect_with_multi():
m = parse_marker('sys_platform == "darwin"')
intersection = m.intersect(
parse_marker('implementation_name == "cpython" and python_version >= "3.6"')
)
assert (
str(intersection)
== 'implementation_name == "cpython" and python_version >= "3.6" and sys_platform == "darwin"'
)
def test_single_marker_intersect_with_multi_compacts_constraint():
m = parse_marker('python_version < "3.6"')
intersection = m.intersect(
parse_marker('implementation_name == "cpython" and python_version < "3.4"')
)
assert (
str(intersection)
== 'implementation_name == "cpython" and python_version < "3.4"'
)
def test_single_marker_not_in_python_intersection():
m = parse_marker('python_version not in "2.7, 3.0, 3.1"')
intersection = m.intersect(
parse_marker('python_version not in "2.7, 3.0, 3.1, 3.2"')
)
assert str(intersection) == 'python_version not in "2.7, 3.0, 3.1, 3.2"'
def test_single_marker_union():
m = parse_marker('sys_platform == "darwin"')
intersection = m.union(parse_marker('implementation_name == "cpython"'))
assert (
str(intersection)
== 'sys_platform == "darwin" or implementation_name == "cpython"'
)
m = parse_marker('python_version >= "3.4"')
intersection = m.union(parse_marker('python_version < "3.6"'))
assert str(intersection) == 'python_version >= "3.4" or python_version < "3.6"'
def test_single_marker_union_compacts_constraints():
m = parse_marker('python_version < "3.6"')
union = m.union(parse_marker('python_version < "3.4"'))
assert str(union) == 'python_version < "3.6"'
def test_single_marker_union_with_multi():
m = parse_marker('sys_platform == "darwin"')
union = m.union(
parse_marker('implementation_name == "cpython" and python_version >= "3.6"')
)
assert (
str(union)
== 'implementation_name == "cpython" and python_version >= "3.6" or sys_platform == "darwin"'
)
def test_single_marker_union_with_multi_duplicate():
m = parse_marker('sys_platform == "darwin" and python_version >= "3.6"')
union = m.union(
parse_marker('sys_platform == "darwin" and python_version >= "3.6"')
)
assert str(union) == 'sys_platform == "darwin" and python_version >= "3.6"'
def test_single_marker_union_with_union():
m = parse_marker('sys_platform == "darwin"')
union = m.union(
parse_marker('implementation_name == "cpython" or python_version >= "3.6"')
)
assert (
str(union)
== 'implementation_name == "cpython" or python_version >= "3.6" or sys_platform == "darwin"'
)
def test_single_marker_not_in_python_union():
m = parse_marker('python_version not in "2.7, 3.0, 3.1"')
union = m.union(parse_marker('python_version not in "2.7, 3.0, 3.1, 3.2"'))
assert str(union) == 'python_version not in "2.7, 3.0, 3.1"'
def test_single_marker_union_with_union_duplicate():
m = parse_marker('sys_platform == "darwin"')
union = m.union(parse_marker('sys_platform == "darwin" or python_version >= "3.6"'))
assert str(union) == 'sys_platform == "darwin" or python_version >= "3.6"'
m = parse_marker('python_version >= "3.7"')
union = m.union(parse_marker('sys_platform == "darwin" or python_version >= "3.6"'))
assert str(union) == 'sys_platform == "darwin" or python_version >= "3.6"'
m = parse_marker('python_version <= "3.6"')
union = m.union(parse_marker('sys_platform == "darwin" or python_version < "3.4"'))
assert str(union) == 'sys_platform == "darwin" or python_version <= "3.6"'
def test_multi_marker():
m = parse_marker('sys_platform == "darwin" and implementation_name == "cpython"')
assert isinstance(m, MultiMarker)
assert m.markers == [
parse_marker('sys_platform == "darwin"'),
parse_marker('implementation_name == "cpython"'),
]
def test_multi_marker_is_empty_is_contradictory():
m = parse_marker(
'sys_platform == "linux" and python_version >= "3.5" and python_version < "2.8"'
)
assert m.is_empty()
m = parse_marker('sys_platform == "linux" and sys_platform == "win32"')
assert m.is_empty()
def test_multi_marker_intersect_multi():
m = parse_marker('sys_platform == "darwin" and implementation_name == "cpython"')
intersection = m.intersect(
parse_marker('python_version >= "3.6" and os_name == "Windows"')
)
assert str(intersection) == (
'sys_platform == "darwin" and implementation_name == "cpython" '
'and python_version >= "3.6" and os_name == "Windows"'
)
def test_multi_marker_intersect_multi_with_overlapping_constraints():
m = parse_marker('sys_platform == "darwin" and python_version < "3.6"')
intersection = m.intersect(
parse_marker(
'python_version <= "3.4" and os_name == "Windows" and sys_platform == "darwin"'
)
)
assert str(intersection) == (
'sys_platform == "darwin" and python_version <= "3.4" and os_name == "Windows"'
)
def test_multi_marker_union_multi():
m = parse_marker('sys_platform == "darwin" and implementation_name == "cpython"')
intersection = m.union(
parse_marker('python_version >= "3.6" and os_name == "Windows"')
)
assert str(intersection) == (
'sys_platform == "darwin" and implementation_name == "cpython" '
'or python_version >= "3.6" and os_name == "Windows"'
)
def test_multi_marker_union_with_union():
m = parse_marker('sys_platform == "darwin" and implementation_name == "cpython"')
intersection = m.union(
parse_marker('python_version >= "3.6" or os_name == "Windows"')
)
assert str(intersection) == (
'python_version >= "3.6" or os_name == "Windows"'
' or sys_platform == "darwin" and implementation_name == "cpython"'
)
def test_marker_union():
m = parse_marker('sys_platform == "darwin" or implementation_name == "cpython"')
assert isinstance(m, MarkerUnion)
assert m.markers == [
parse_marker('sys_platform == "darwin"'),
parse_marker('implementation_name == "cpython"'),
]
def test_marker_union_deduplicate():
m = parse_marker(
'sys_platform == "darwin" or implementation_name == "cpython" or sys_platform == "darwin"'
)
assert str(m) == 'sys_platform == "darwin" or implementation_name == "cpython"'
def test_marker_union_intersect_single_marker():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
intersection = m.intersect(parse_marker('implementation_name == "cpython"'))
assert str(intersection) == (
'sys_platform == "darwin" and implementation_name == "cpython" '
'or python_version < "3.4" and implementation_name == "cpython"'
)
def test_marker_union_intersect_single_with_overlapping_constraints():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
intersection = m.intersect(parse_marker('python_version <= "3.6"'))
assert (
str(intersection)
== 'sys_platform == "darwin" and python_version <= "3.6" or python_version < "3.4"'
)
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
intersection = m.intersect(parse_marker('sys_platform == "darwin"'))
assert (
str(intersection)
== 'sys_platform == "darwin" or python_version < "3.4" and sys_platform == "darwin"'
)
def test_marker_union_intersect_marker_union():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
intersection = m.intersect(
parse_marker('implementation_name == "cpython" or os_name == "Windows"')
)
assert str(intersection) == (
'sys_platform == "darwin" and implementation_name == "cpython" '
'or sys_platform == "darwin" and os_name == "Windows" or '
'python_version < "3.4" and implementation_name == "cpython" or '
'python_version < "3.4" and os_name == "Windows"'
)
def test_marker_union_intersect_multi_marker():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
intersection = m.intersect(
parse_marker('implementation_name == "cpython" and os_name == "Windows"')
)
assert str(intersection) == (
'implementation_name == "cpython" and os_name == "Windows" and sys_platform == "darwin" '
'or implementation_name == "cpython" and os_name == "Windows" and python_version < "3.4"'
)
def test_marker_union_union_with_union():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
union = m.union(
parse_marker('implementation_name == "cpython" or os_name == "Windows"')
)
assert str(union) == (
'sys_platform == "darwin" or python_version < "3.4" '
'or implementation_name == "cpython" or os_name == "Windows"'
)
def test_marker_union_union_duplicates():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
union = m.union(parse_marker('sys_platform == "darwin" or os_name == "Windows"'))
assert str(union) == (
'sys_platform == "darwin" or python_version < "3.4" or os_name == "Windows"'
)
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
union = m.union(
parse_marker(
'sys_platform == "darwin" or os_name == "Windows" or python_version <= "3.6"'
)
)
assert str(union) == (
'sys_platform == "darwin" or python_version <= "3.6" or os_name == "Windows"'
)
def test_intersect_compacts_constraints():
m = parse_marker('python_version < "4.0"')
intersection = m.intersect(parse_marker('python_version < "5.0"'))
assert str(intersection) == 'python_version < "4.0"'
def test_multi_marker_removes_duplicates():
m = parse_marker('sys_platform == "win32" and sys_platform == "win32"')
assert 'sys_platform == "win32"' == str(m)
m = parse_marker(
'sys_platform == "darwin" and implementation_name == "cpython" '
'and sys_platform == "darwin" and implementation_name == "cpython"'
)
assert 'sys_platform == "darwin" and implementation_name == "cpython"' == str(m)
@pytest.mark.parametrize(
("marker_string", "environment", "expected"),
[
("os_name == '{0}'".format(os.name), None, True),
("os_name == 'foo'", {"os_name": "foo"}, True),
("os_name == 'foo'", {"os_name": "bar"}, False),
("'2.7' in python_version", {"python_version": "2.7.5"}, True),
("'2.7' not in python_version", {"python_version": "2.7"}, False),
(
"os_name == 'foo' and python_version ~= '2.7.0'",
{"os_name": "foo", "python_version": "2.7.6"},
True,
),
(
"python_version ~= '2.7.0' and (os_name == 'foo' or " "os_name == 'bar')",
{"os_name": "foo", "python_version": "2.7.4"},
True,
),
(
"python_version ~= '2.7.0' and (os_name == 'foo' or " "os_name == 'bar')",
{"os_name": "bar", "python_version": "2.7.4"},
True,
),
(
"python_version ~= '2.7.0' and (os_name == 'foo' or " "os_name == 'bar')",
{"os_name": "other", "python_version": "2.7.4"},
False,
),
("extra == 'security'", {"extra": "quux"}, False),
("extra == 'security'", {"extra": "security"}, True),
("os.name == '{0}'".format(os.name), None, True),
("sys.platform == 'win32'", {"sys_platform": "linux2"}, False),
("platform.version in 'Ubuntu'", {"platform_version": "#39"}, False),
("platform.machine=='x86_64'", {"platform_machine": "x86_64"}, True),
(
"platform.python_implementation=='Jython'",
{"platform_python_implementation": "CPython"},
False,
),
(
"python_version == '2.5' and platform.python_implementation" "!= 'Jython'",
{"python_version": "2.7"},
False,
),
],
)
def test_validate(marker_string, environment, expected):
m = parse_marker(marker_string)
assert m.validate(environment) is expected
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