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 @@
- Improved virtualenv detection and management.
- 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
......
......@@ -318,11 +318,11 @@ and installs them.
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.
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
the `--no-dev` option.
......@@ -346,14 +346,14 @@ poetry install -E mysql -E pgsql
### 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.
```bash
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:
......
[[package]]
category = "dev"
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"
optional = false
platform = "*"
python-versions = "*"
version = "1.4.3"
[package.requirements]
python = ">=3.6,<4.0"
[[package]]
category = "dev"
description = "A few extensions to pyyaml."
name = "aspy.yaml"
optional = false
platform = "all"
python-versions = "*"
version = "1.1.1"
......@@ -27,27 +23,26 @@ category = "dev"
description = "Atomic file writes."
name = "atomicwrites"
optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.2.1"
[[package]]
category = "dev"
description = "Classes Without Boilerplate"
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "attrs"
optional = false
platform = "*"
python-versions = "*"
version = "18.2.0"
[[package]]
category = "dev"
description = "The uncompromising code formatter."
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "black"
optional = false
platform = "*"
python-versions = ">=3.6"
version = "18.6b4"
version = "18.9b0"
[package.dependencies]
appdirs = "*"
......@@ -55,15 +50,11 @@ attrs = ">=17.4.0"
click = ">=6.5"
toml = ">=0.9.4"
[package.requirements]
python = ">=3.6,<4.0"
[[package]]
category = "main"
description = "httplib2 caching for requests"
name = "cachecontrol"
optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.12.5"
......@@ -77,7 +68,6 @@ category = "dev"
description = "A decorator for caching properties in classes."
name = "cached-property"
optional = false
platform = "*"
python-versions = "*"
version = "1.5.1"
......@@ -86,7 +76,6 @@ category = "main"
description = "Cachy provides a simple yet effective caching library."
name = "cachy"
optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.2.0"
......@@ -95,7 +84,6 @@ category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
optional = false
platform = "*"
python-versions = "*"
version = "2018.8.24"
......@@ -104,7 +92,6 @@ category = "dev"
description = "Validate configuration and produce human readable error messages."
name = "cfgv"
optional = false
platform = "*"
python-versions = "*"
version = "1.1.0"
......@@ -116,7 +103,6 @@ category = "main"
description = "Universal encoding detector for Python 2 and 3"
name = "chardet"
optional = false
platform = "*"
python-versions = "*"
version = "3.0.4"
......@@ -125,7 +111,6 @@ category = "main"
description = "Cleo allows you to create beautiful and testable command-line interfaces."
name = "cleo"
optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.6.8"
......@@ -135,79 +120,62 @@ pylev = ">=1.3,<2.0"
[[package]]
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"
optional = false
platform = "*"
python-versions = "*"
version = "6.7"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "7.0"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "0.3.9"
[package.requirements]
platform = "win32"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
version = "4.5.1"
[[package]]
category = "main"
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "enum34"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "1.1.6"
[package.requirements]
python = ">=2.7,<2.8"
[[package]]
category = "dev"
description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
marker = "python_version < \"3.0\""
name = "funcsigs"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "1.0.2"
[package.requirements]
python = "<3.0"
[[package]]
category = "main"
description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy."
marker = "python_version == \"2.7\""
name = "functools32"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "3.2.3-2"
[package.requirements]
python = ">=2.7,<2.8"
[[package]]
category = "main"
description = "HTML parser based on the WHATWG HTML specification"
name = "html5lib"
optional = false
platform = "*"
python-versions = "*"
version = "1.0.1"
......@@ -220,40 +188,34 @@ category = "dev"
description = "File identification library for Python"
name = "identify"
optional = false
platform = "*"
python-versions = "*"
version = "1.1.6"
version = "1.1.7"
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
platform = "*"
python-versions = "*"
version = "2.7"
[[package]]
category = "dev"
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"
optional = false
platform = "*"
python-versions = "*"
version = "2.10"
[package.dependencies]
MarkupSafe = ">=0.23"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]]
category = "main"
description = "An implementation of JSON Schema validation for Python"
name = "jsonschema"
optional = false
platform = "*"
python-versions = "*"
version = "2.6.0"
......@@ -265,9 +227,9 @@ version = "*"
[[package]]
category = "dev"
description = "Python LiveReload is an awesome tool for web developers"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "livereload"
optional = false
platform = "*"
python-versions = "*"
version = "2.5.2"
......@@ -275,45 +237,39 @@ version = "2.5.2"
six = "*"
tornado = "*"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]]
category = "main"
description = "Platform-independent file locking module"
marker = "extra == \"filecache\""
name = "lockfile"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "0.12.2"
[[package]]
category = "dev"
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"
optional = false
platform = "*"
python-versions = "*"
version = "2.6.11"
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "3.0.1"
[[package]]
category = "dev"
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"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "1.0"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]]
category = "dev"
description = "Project documentation with Markdown."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "mkdocs"
optional = false
platform = "*"
python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.0.4"
......@@ -325,15 +281,12 @@ click = ">=3.3"
livereload = ">=2.5.1"
tornado = ">=5.0"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]]
category = "dev"
description = "Rolling backport of unittest.mock for all Pythons"
marker = "python_version < \"3.0\""
name = "mock"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "2.0.0"
......@@ -345,15 +298,11 @@ six = ">=1.9"
python = "<3.3"
version = ">=1"
[package.requirements]
python = "<3.0"
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
platform = "*"
python-versions = "*"
version = "4.3.0"
......@@ -365,7 +314,6 @@ category = "main"
description = "MessagePack (de)serializer."
name = "msgpack"
optional = false
platform = "*"
python-versions = "*"
version = "0.5.6"
......@@ -374,7 +322,6 @@ category = "dev"
description = "Node.js virtual environment builder"
name = "nodeenv"
optional = false
platform = "any"
python-versions = "*"
version = "1.3.2"
......@@ -383,16 +330,15 @@ category = "main"
description = "Bring colors to your terminal."
name = "pastel"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "0.1.0"
[[package]]
category = "main"
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"
optional = false
platform = "*"
python-versions = "*"
version = "2.3.2"
......@@ -403,27 +349,20 @@ six = "*"
python = "<3.5"
version = "*"
[package.requirements]
python = "<3.6"
[[package]]
category = "dev"
description = "Python Build Reasonableness"
marker = "python_version < \"3.0\""
name = "pbr"
optional = false
platform = "*"
python-versions = "*"
version = "4.2.0"
[package.requirements]
python = "<3.0"
version = "4.3.0"
[[package]]
category = "main"
description = "Query metadatdata from sdists / bdists / installed packages."
name = "pkginfo"
optional = false
platform = "Unix"
python-versions = "*"
version = "1.4.2"
......@@ -432,7 +371,6 @@ category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.7.1"
......@@ -441,9 +379,8 @@ category = "dev"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
name = "pre-commit"
optional = false
platform = "*"
python-versions = "*"
version = "1.11.0"
version = "1.11.1"
[package.dependencies]
"aspy.yaml" = "*"
......@@ -461,7 +398,6 @@ category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.6.0"
......@@ -470,7 +406,6 @@ category = "dev"
description = "Pygments is a syntax highlighting package written in Python."
name = "pygments"
optional = false
platform = "any"
python-versions = "*"
version = "2.2.0"
......@@ -479,7 +414,6 @@ category = "dev"
description = "Pygments Github custom lexers."
name = "pygments-github-lexers"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "0.0.5"
......@@ -491,7 +425,6 @@ category = "main"
description = "A pure Python Levenshtein implementation that's not freaking GPL'd."
name = "pylev"
optional = false
platform = "UNKNOWN"
python-versions = "*"
version = "1.3.0"
......@@ -500,7 +433,6 @@ category = "dev"
description = "Extension pack for Python Markdown."
name = "pymdown-extensions"
optional = false
platform = "*"
python-versions = "*"
version = "4.12"
......@@ -512,16 +444,14 @@ category = "main"
description = "Python parsing module"
name = "pyparsing"
optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.2.1"
version = "2.2.2"
[[package]]
category = "main"
description = "Persistent/Functional/Immutable data structures"
name = "pyrsistent"
optional = false
platform = "*"
python-versions = "*"
version = "0.14.4"
......@@ -533,23 +463,19 @@ category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.8.0"
version = "3.8.2"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
pluggy = ">=0.7"
py = ">=1.5.0"
setuptools = "*"
six = ">=1.10.0"
[package.dependencies.colorama]
platform = "win32"
version = "*"
[package.dependencies.funcsigs]
python = "<3.0"
version = "*"
......@@ -563,7 +489,6 @@ category = "dev"
description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.6.0"
......@@ -576,7 +501,6 @@ category = "dev"
description = "Thin-wrapper around the mock package for easier use with py.test"
name = "pytest-mock"
optional = false
platform = "any"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.10.0"
......@@ -590,9 +514,9 @@ version = "*"
[[package]]
category = "dev"
description = "YAML parser and emitter for Python"
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "pyyaml"
optional = false
platform = "Any"
python-versions = "*"
version = "3.13"
......@@ -601,7 +525,6 @@ category = "main"
description = "Python HTTP for Humans."
name = "requests"
optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.19.1"
......@@ -616,7 +539,6 @@ category = "main"
description = "A utility belt for advanced users of python-requests"
name = "requests-toolbelt"
optional = false
platform = "*"
python-versions = "*"
version = "0.8.0"
......@@ -626,48 +548,43 @@ requests = ">=2.0.1,<3.0.0"
[[package]]
category = "main"
description = "scandir, a better directory iterator and faster os.walk()"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.5\""
name = "scandir"
optional = false
platform = "*"
python-versions = "*"
version = "1.9.0"
[package.requirements]
python = "<3.5"
[[package]]
category = "main"
description = "Tool to Detect Surrounding Shell"
name = "shellingham"
optional = false
platform = "*"
python-versions = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3"
version = "1.2.6"
[[package]]
category = "main"
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"
optional = false
platform = "*"
python-versions = "*"
version = "1.11.0"
[[package]]
category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language"
marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "toml"
optional = false
platform = "*"
python-versions = "*"
version = "0.9.6"
version = "0.10.0"
[[package]]
category = "main"
description = "Style preserving TOML library"
name = "tomlkit"
optional = false
platform = "*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.4"
......@@ -683,21 +600,17 @@ version = ">=3.6,<4.0"
[[package]]
category = "dev"
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "tornado"
optional = false
platform = "*"
python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*"
version = "5.1.1"
[package.requirements]
python = ">=2.7.9,<2.8.0 || >=3.4,<4.0"
[[package]]
category = "dev"
description = "virtualenv-based automation of test activities"
name = "tox"
optional = false
platform = "unix"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.4.0"
......@@ -712,30 +625,26 @@ virtualenv = ">=1.11.2"
[[package]]
category = "main"
description = "Type Hints for Python"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""
name = "typing"
optional = false
platform = "*"
python-versions = "*"
version = "3.6.6"
[package.requirements]
python = ">=2.7,<2.8 || >=3.4,<3.5"
[[package]]
category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
optional = false
platform = "*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.23"
[[package]]
category = "main"
description = "Virtual Python Environment builder"
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "virtualenv"
optional = false
platform = "*"
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
version = "16.0.0"
......@@ -744,13 +653,11 @@ category = "main"
description = "Character encoding aliases for legacy web content"
name = "webencodings"
optional = false
platform = "*"
python-versions = "*"
version = "0.5.1"
[metadata]
content-hash = "39b7f1660472b5b5d4db23bdf13c56cae616f260870026c753d762cfdb64281a"
platform = "*"
python-versions = "~2.7 || ^3.4"
[metadata.hashes]
......@@ -758,7 +665,7 @@ appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "
"aspy.yaml" = ["04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", "0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa"]
atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"]
attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"]
black = ["22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", "4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"]
black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"]
cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"]
cached-property = ["3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", "9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504"]
cachy = ["b71513e5a38ce90c1280c02b7d8d6bb3fdf64666c9cc0584f2479afea097d56c", "b71e8e7ddb5b386e23e81befdfac8a93885406139b8681bedc17b3444fcb8fca"]
......@@ -766,20 +673,20 @@ certifi = ["376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", "
cfgv = ["73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", "d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa"]
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
cleo = ["85a63076b72ca376fb06668be1fc7758dc16740b394783d5cc65200c4b32f71b", "9b7f79f1aa470a025c0d28c76aa225ee9e65028d32f80032e871aa3500df61b8"]
click = ["29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", "f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"]
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
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"]
enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"]
funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"]
functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"]
html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"]
identify = ["a425281e5578eb08651cd205e64b5c3660de5ba33860d16450dd5f6e478f50ff", "baa10ef6a82d45a29e4b179866dce7cf41a271d1e6c678c293922b50375d42be"]
identify = ["5e956558a9a1e3b3891d7c6609fc9709657a11878af288ace484d1a46a93922b", "623086059219cc7b86c77a3891f3700cb175d4ce02b8fb8802b047301d71e783"]
idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"]
jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"]
jsonschema = ["000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", "6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"]
livereload = ["583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", "dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"]
lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"]
markdown = ["9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", "a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81"]
markdown = ["c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa", "d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"]
markupsafe = ["a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"]
mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"]
mock = ["5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", "b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"]
......@@ -788,18 +695,18 @@ msgpack = ["0b3b1773d2693c70598585a34ca2715873ba899565f0a7c9a1545baef7e7fbdc", "
nodeenv = ["aa040ab5189bae17d272175609010be6c5b589ec4b8dbd832cc50c9e9cb7496f"]
pastel = ["3108af417ec0fa6d0a620e676ec4f02c839ca13e10611586e5d2174b46aa0bc3", "d1fee8079534f99f1805a044fef946d23eee6d6a7cd34292c30e6c16be9a80b9"]
pathlib2 = ["8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", "d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"]
pbr = ["1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", "b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"]
pbr = ["1be135151a0da949af8c5d0ee9013d9eafada71237eb80b3ba8896b4f12ec5dc", "cf36765bf2218654ae824ec8e14257259ba44e43b117fd573c8d07a9895adbdd"]
pkginfo = ["5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", "a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"]
pluggy = ["6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"]
pre-commit = ["18ef2892ff6b6518945bc7dcf423e3c969033a4ca132b6a1ae0d52eb2e51ea27", "88d59872610a7069d937b6868632ba534187bda58c4665de12b25c8c549ddd0e"]
pre-commit = ["3ce9dd5a912e1c0d0fa0c5991927429a62661ca7ed00ee3dc4ca2c743ec259ce", "e7efe81063b8cb2a72190db06109191aac67971fc7a31d9e9485907f6be76954"]
py = ["06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"]
pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"]
pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"]
pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"]
pymdown-extensions = ["20f2ae1067ab850cab92fcf57487267a7fd1365a7b1e7c5394e1e0778455eec1", "7d3fcbb4c5d70a78d1f4c2c7eef02dbe7e1ba08b06cb72e08b3d1027eb77458b"]
pyparsing = ["905d8090c335314568b5faee0025b1829f27bb974604a5762a6cdef3a7dfc3b7", "f493ee323be1e94929416b3585eefcc04943115cecbaaa35a8c86d1a2368af19"]
pyparsing = ["bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", "d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"]
pyrsistent = ["4024f838472cba9ea1ccbc638e0bcafec2efda28594a9905177ec365f1a95fea"]
pytest = ["453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", "a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d"]
pytest = ["7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e", "9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"]
pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"]
pytest-mock = ["53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928", "d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"]
pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"]
......@@ -808,7 +715,7 @@ requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4a
scandir = ["04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", "1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", "1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", "346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", "44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", "61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", "a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", "c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", "c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", "c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", "f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"]
shellingham = ["52e8047d76abf52edbdf1ffed8cd46cab4dbef4f71a1baa1085afdaa272cef83", "58e4a0f5fecd7b73e097f2ac5d924e81cc1161e2ce760d5aed07ee2f87923074"]
six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"]
toml = ["380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", "a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957"]
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
tomlkit = ["8ab16e93162fc44d3ad83d2aa29a7140b8f7d996ae1790a73b9a7aed6fb504ac", "ca181cee7aee805d455628f7c94eb8ae814763769a93e69157f250fe4ebe1926"]
tornado = ["0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", "4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", "732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", "8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", "8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", "d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", "e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444"]
tox = ["7f802b37fffd3b5ef2aab104943fa5dad24bf9564bb7e732e54b8d0cfec2fca0", "cc97859bd7f38aa5b3b8ba55ffe7ee9952e7050faad1aedc0829cd3db2fb61d6"]
......
......@@ -14,16 +14,17 @@ class DebugResolveCommand(Command):
{ --E|extras=* : Extras to activate for the dependency. }
{ --python= : Python version(s) to use for resolution. }
{ --tree : Displays the dependency tree. }
{ --install : Show what would be installed for the current system. }
"""
_loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
from poetry.packages import Dependency
from poetry.packages import ProjectPackage
from poetry.puzzle import Solver
from poetry.repositories.repository import Repository
from poetry.semver import parse_constraint
from poetry.utils.env import Env
packages = self.argument("package")
......@@ -35,7 +36,6 @@ class DebugResolveCommand(Command):
)
requirements = self._format_requirements(packages)
dependencies = []
for name, constraint in requirements.items():
dep = package.add_dependency(name, constraint)
extras = []
......@@ -48,13 +48,13 @@ class DebugResolveCommand(Command):
for ex in extras:
dep.extras.append(ex)
package.python_versions = (
self.option("python") or self.poetry.package.python_versions
package.python_versions = self.option("python") or (
self.poetry.package.python_versions
)
solver = Solver(
package, self.poetry.pool, Repository(), Repository(), self.output
)
pool = self.poetry.pool
solver = Solver(package, pool, Repository(), Repository(), self.output)
ops = solver.solve()
......@@ -79,16 +79,28 @@ class DebugResolveCommand(Command):
return 0
env = Env.get()
current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info)
)
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(
" - <info>{}</info> (<comment>{}</comment>)".format(
package.name, package.version
pkg.name, pkg.version
)
)
if package.requirements:
for req_name, req_value in package.requirements.items():
self.line(" - {}: {}".format(req_name, req_value))
if not pkg.python_constraint.is_any():
self.line(" - python: {}".format(pkg.python_versions))
if not pkg.marker.is_any():
self.line(" - marker: {}".format(pkg.marker))
def _determine_requirements(self, requires): # type: (List[str]) -> List[str]
from poetry.semver import parse_constraint
......@@ -97,7 +109,6 @@ class DebugResolveCommand(Command):
return []
requires = self._parse_name_version_pairs(requires)
result = []
for requirement in requires:
if "version" in requirement:
parse_constraint(requirement["version"])
......
......@@ -23,7 +23,9 @@ lists all packages available."""
colors = ["green", "yellow", "cyan", "magenta", "blue"]
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.semver import Version
from poetry.semver import parse_constraint
......@@ -99,17 +101,13 @@ lists all packages available."""
installed_repo = InstalledRepository.load(self.env)
skipped = []
platform = sys.platform
python = Version.parse(".".join([str(i) for i in self.env.version_info[:3]]))
# Computing widths
for locked in locked_packages:
python_constraint = parse_constraint(locked.requirements.get("python", "*"))
platform_constraint = GenericConstraint.parse(
locked.requirements.get("platform", "*")
)
if not python_constraint.allows(python) or not platform_constraint.matches(
GenericConstraint("=", platform)
python_constraint = locked.python_constraint
if not python_constraint.allows(python) or not self.env.is_valid_for_marker(
locked.marker
):
skipped.append(locked)
......
......@@ -7,7 +7,7 @@ from poetry.io import NullIO
from poetry.packages import Dependency
from poetry.packages import Locker
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.operations import Install
from poetry.puzzle.operations import Uninstall
......@@ -457,31 +457,15 @@ class Installer:
if op.skipped:
op.unskip()
python = Version.parse(
".".join([str(i) for i in self._env.version_info[:3]])
current_python = parse_constraint(
".".join(str(v) for v in self._env.version_info[:3])
)
if "python" in package.requirements:
python_constraint = parse_constraint(package.requirements["python"])
if not python_constraint.allows(python):
# Incompatible python versions
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")
if not package.python_constraint.allows(
current_python
) or not self._env.is_valid_for_marker(package.marker):
op.skip("Not needed for the current environment")
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
if self._update:
extras = {}
for extra, deps in self._package.extras.items():
......
......@@ -151,7 +151,7 @@ class Term(object):
return (
self.dependency.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):
......
......@@ -34,7 +34,7 @@ def dependency_from_pep_508(name):
req = Requirement(name)
if req.marker:
markers = convert_markers(req.marker.markers)
markers = convert_markers(req.marker)
else:
markers = {}
......@@ -128,28 +128,8 @@ def dependency_from_pep_508(name):
dep.python_versions = " || ".join(ors)
if "sys_platform" in markers:
ors = []
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)
if req.marker:
dep.marker = req.marker
# 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):
def matches(self, provider):
raise NotImplementedError()
def allows_all(self, other):
raise NotImplementedError()
......@@ -14,5 +11,17 @@ class BaseConstraint(object):
def intersect(self, other):
raise NotImplementedError()
def union(self, other):
raise NotImplementedError()
def is_any(self):
return False
def is_empty(self):
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):
def difference(self, other):
return
def __eq__(self, other):
return other.is_empty()
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 .constraint import Constraint
class MultiConstraint(BaseConstraint):
def __init__(self, constraints, conjunctive=True):
self._constraints = tuple(constraints)
self._conjunctive = conjunctive
def __init__(self, *constraints):
if any(c.operator == "==" for c in constraints):
raise ValueError(
"A multi-constraint can only be comprised of negative constraints"
)
self._constraints = constraints
@property
def constraints(self):
return self._constraints
def is_conjunctive(self):
return self._conjunctive
def is_disjunctive(self):
return not self._conjunctive
def allows(self, other):
for constraint in self._constraints:
if not constraint.allows(other):
return False
def matches(self, provider):
if self.is_disjunctive():
for constraint in self._constraints:
if constraint.matches(provider):
return True
return True
def allows_all(self, other):
if other.is_any():
return False
for constraint in self._constraints:
if not constraint.matches(provider):
return False
if other.is_empty():
return True
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 False
def intersect(self, other):
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 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((", " 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
from poetry.semver import VersionRange
from poetry.semver import VersionUnion
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.generic_constraint import GenericConstraint
from .constraints import parse_constraint as parse_generic_constraint
from .constraints.any_constraint import AnyConstraint
from .constraints.constraint import Constraint
from .constraints.multi_constraint import MultiConstraint
from .constraints.union_constraint import UnionConstraint
class Dependency(object):
......@@ -45,8 +49,6 @@ class Dependency(object):
self._python_versions = "*"
self._python_constraint = parse_constraint("*")
self._platform = "*"
self._platform_constraint = EmptyConstraint()
self._extras = []
self._in_extras = []
......@@ -54,6 +56,7 @@ class Dependency(object):
self._activated = not self._optional
self.is_root = False
self.marker = AnyMarker()
@property
def name(self):
......@@ -83,25 +86,20 @@ class Dependency(object):
def python_versions(self, value):
self._python_versions = 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
def python_constraint(self):
return self._python_constraint
@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
return self._extras
......@@ -152,28 +150,27 @@ class Dependency(object):
elif not self.constraint.is_any():
requirement += " ({})".format(str(self.constraint).replace(" ", ""))
# Markers
markers = []
if not self.marker.is_any():
marker = self.marker
if not with_extras:
marker = marker.without_extras()
# Python marker
if self.python_versions != "*":
python_constraint = self.python_constraint
markers.append(
self._create_nested_marker("python_version", python_constraint)
)
if not marker.is_empty():
markers.append(str(marker))
else:
# Python marker
if self.python_versions != "*":
python_constraint = self.python_constraint
if self.platform != "*":
platform_constraint = self.platform_constraint
markers.append(
self._create_nested_marker("sys_platform", platform_constraint)
)
markers.append(
self._create_nested_marker("python_version", python_constraint)
)
in_extras = " || ".join(self._in_extras)
if in_extras and with_extras:
markers.append(
self._create_nested_marker("extra", GenericConstraint.parse(in_extras))
self._create_nested_marker("extra", parse_generic_constraint(in_extras))
)
if markers:
......@@ -186,17 +183,17 @@ class Dependency(object):
return requirement
def _create_nested_marker(self, name, constraint):
if isinstance(constraint, MultiConstraint):
if isinstance(constraint, (MultiConstraint, UnionConstraint)):
parts = []
for c in constraint.constraints:
multi = False
if isinstance(c, MultiConstraint):
if isinstance(c, (MultiConstraint, UnionConstraint)):
multi = True
parts.append((multi, self._create_nested_marker(name, c)))
glue = " and "
if constraint.is_disjunctive():
if isinstance(constraint, UnionConstraint):
parts = [
"({})".format(part[1]) if part[0] else part[1] for part in parts
]
......@@ -205,10 +202,8 @@ class Dependency(object):
parts = [part[1] for part in parts]
marker = glue.join(parts)
elif isinstance(constraint, GenericConstraint):
marker = '{} {} "{}"'.format(
name, constraint.string_operator, constraint.version
)
elif isinstance(constraint, Constraint):
marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version)
elif isinstance(constraint, VersionUnion):
parts = []
for c in constraint.ranges:
......@@ -278,7 +273,6 @@ class Dependency(object):
new.is_root = self.is_root
new.python_versions = self.python_versions
new.platform = self.platform
for extra in self.extras:
new.extras.append(extra)
......
from .package import Package
class DependencyPackage:
class DependencyPackage(object):
def __init__(self, dependency, package):
self._dependency = dependency
self._package = package
......@@ -17,6 +14,12 @@ class DependencyPackage:
def __getattr__(self, 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):
return str(self._package)
......
......@@ -73,7 +73,6 @@ class DirectoryDependency(Dependency):
self._package.dev_requires += package.dev_requires
self._package.extras = package.extras
self._package.python_versions = package.python_versions
self._package.platform = package.platform
else:
# Execute egg_info
current_dir = os.getcwd()
......
......@@ -5,11 +5,11 @@ import poetry.repositories
from hashlib import sha256
from tomlkit import document
from tomlkit import inline_table
from typing import List
from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile
from poetry.version.markers import parse_marker
class Locker:
......@@ -86,6 +86,22 @@ class Locker:
package.hashes = lock_data["metadata"]["hashes"][info["name"]]
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():
if isinstance(constraint, list):
for c in constraint:
......@@ -95,9 +111,6 @@ class Locker:
package.add_dependency(dep_name, constraint)
if "requirements" in info:
package.requirements = info["requirements"]
if "source" in info:
package.source_type = info["source"]["type"]
package.source_url = info["source"]["url"]
......@@ -129,7 +142,6 @@ class Locker:
lock["metadata"] = {
"python-versions": root.python_versions,
"platform": root.platform,
"content-hash": self._content_hash,
"hashes": hashes,
}
......@@ -198,9 +210,6 @@ class Locker:
if not dependency.python_constraint.is_any():
constraint["python"] = str(dependency.python_constraint)
if dependency.platform != "*":
constraint["platform"] = dependency.platform
if len(constraint) == 1:
dependencies[dependency.pretty_name].append(constraint["version"])
else:
......@@ -217,9 +226,10 @@ class Locker:
"category": package.category,
"optional": package.optional,
"python-versions": package.python_versions,
"platform": package.platform,
"hashes": sorted(package.hashes),
}
if not package.marker.is_any():
data["marker"] = str(package.marker)
if dependencies:
for k, constraints in dependencies.items():
......@@ -235,7 +245,4 @@ class Locker:
"reference": package.source_reference,
}
if package.requirements:
data["requirements"] = package.requirements
return data
......@@ -11,13 +11,15 @@ from poetry.spdx import license_by_id
from poetry.spdx import License
from poetry.utils._compat import Path
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.generic_constraint import GenericConstraint
from .constraints import parse_constraint as parse_generic_constraint
from .dependency import Dependency
from .directory_dependency import DirectoryDependency
from .file_dependency import FileDependency
from .vcs_dependency import VCSDependency
from .utils.utils import create_nested_marker
AUTHOR_REGEX = re.compile("(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?)>)?$")
......@@ -63,15 +65,13 @@ class Package(object):
self.hashes = []
self.optional = False
# Requirements for making it mandatory
self.requirements = {}
self.classifiers = []
self._python_versions = "*"
self._python_constraint = parse_constraint("*")
self._platform = "*"
self._platform_constraint = EmptyConstraint()
self._python_marker = AnyMarker()
self.marker = AnyMarker()
self.root_dir = None
......@@ -153,19 +153,26 @@ class Package(object):
def python_versions(self, value):
self._python_versions = value
self._python_constraint = parse_constraint(value)
self._python_marker = parse_marker(
create_nested_marker("python_version", self._python_constraint)
)
@property
def python_constraint(self):
return self._python_constraint
@property
def python_marker(self):
return self._python_marker
@property
def platform(self): # type: () -> str
return self._platform
@platform.setter
def platform(self, value): # type: (str) -> None
self._platform = value
self._platform_constraint = GenericConstraint.parse(value)
self._platform_constraint = parse_generic_constraint(value)
@property
def platform_constraint(self):
......@@ -282,11 +289,28 @@ class Package(object):
allows_prereleases=allows_prereleases,
)
marker = AnyMarker()
if python_versions:
dependency.python_versions = python_versions
marker = marker.intersect(
parse_marker(
create_nested_marker(
"python_version", dependency.python_constraint
)
)
)
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:
for extra in constraint["extras"]:
......@@ -319,7 +343,6 @@ class Package(object):
clone.category = self.category
clone.optional = self.optional
clone.python_versions = self.python_versions
clone.platform = self.platform
clone.extras = self.extras
clone.source_type = self.source_type
clone.source_url = self.source_url
......
from poetry.semver import VersionRange
from poetry.semver import parse_constraint
from poetry.version.markers import parse_marker
from .package import Package
from .utils.utils import create_nested_marker
class ProjectPackage(Package):
......@@ -33,7 +35,11 @@ class ProjectPackage(Package):
@python_versions.setter
def python_versions(self, value):
self._python_versions = value
if value == "*" or value == VersionRange():
value = "~2.7 || >=3.4"
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
import posixpath
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:
import urllib.parse as urlparse
except ImportError:
......@@ -105,28 +114,27 @@ def splitext(path):
return base, ext
def group_markers(markers):
def group_markers(markers, or_=False):
groups = [[]]
for marker in markers:
assert isinstance(marker, (list, tuple, str))
if or_:
groups.append([])
if isinstance(marker, list):
groups[-1].append(group_markers(marker))
elif isinstance(marker, tuple):
lhs, op, rhs = marker
if isinstance(marker, (MultiMarker, MarkerUnion)):
groups[-1].append(
group_markers(marker.markers, isinstance(marker, MarkerUnion))
)
elif isinstance(marker, SingleMarker):
lhs, op, rhs = marker.name, marker.operator, marker.value
groups[-1].append((lhs.value, op, rhs.value))
else:
assert marker in ["and", "or"]
if marker == "or":
groups.append([])
groups[-1].append((lhs, op, rhs))
return groups
def convert_markers(markers):
groups = group_markers(markers)
def convert_markers(marker):
groups = group_markers([marker])
requirements = {}
......@@ -149,3 +157,70 @@ def convert_markers(markers):
_group(groups)
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):
def skip_reason(self): # type: () -> Union[str, None]
return self._skip_reason
@property
def package(self):
raise NotImplementedError()
def format_version(self, package): # type: (...) -> str
return package.full_pretty_version
......
......@@ -105,8 +105,27 @@ class Provider:
if dependency.is_root:
return PackageCollection(dependency, [self._package])
if dependency in self._search_for:
return PackageCollection(dependency, self._search_for[dependency])
for constraint in self._search_for.keys():
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():
packages = self.search_for_vcs(dependency)
......@@ -134,7 +153,7 @@ class Provider:
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]
"""
......@@ -316,66 +335,29 @@ class Provider:
if not package.python_constraint.allows_all(
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 (
not package.dependency.python_constraint.is_any()
and not package.python_constraint.intersect(
package.dependency.python_constraint.is_any()
or not self._package.python_constraint.allows_all(
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
)
raise CompatibilityError(
str(intersection),
str(self._package.python_constraint.difference(intersection)),
)
return [
Incompatibility(
[Term(package.to_dependency(), True)],
PythonCause(
package.python_versions, self._package.python_versions
),
)
]
if not self._package.platform_constraint.matches(
package.platform_constraint
):
return [
Incompatibility(
[Term(package.to_dependency(), True)],
PlatformCause(package.platform),
)
]
):
return [
Incompatibility(
[Term(package.to_dependency(), True)],
PythonCause(
package.python_versions, self._package.python_versions
),
)
]
dependencies = [
dep
for dep in dependencies
if dep.name not in self.UNSAFE_PACKAGES
and self._package.python_constraint.allows_any(dep.python_constraint)
and self._package.platform_constraint.matches(dep.platform_constraint)
]
return [
......@@ -403,7 +385,6 @@ class Provider:
for r in package.requires
if r.is_activated()
and self._package.python_constraint.allows_any(r.python_constraint)
and self._package.platform_constraint.matches(r.platform_constraint)
]
# Searching for duplicate dependencies
......@@ -454,7 +435,7 @@ class Provider:
for constraint, _deps in by_constraint.items():
new_markers = []
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:
continue
......@@ -473,7 +454,7 @@ class Provider:
dep = _deps[0]
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)
if dep.is_optional() and not dep.is_activated():
......@@ -501,7 +482,7 @@ class Provider:
_deps = [value[0] for value in by_constraint.values()]
seen = set()
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:
_requirements = ""
else:
......
import time
from typing import Any
from typing import Dict
from typing import List
from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage
from poetry.packages.constraints.generic_constraint import GenericConstraint
from poetry.semver import parse_constraint
from poetry.version.markers import AnyMarker
from .exceptions import CompatibilityError
from .exceptions import SolverProblemError
......@@ -26,12 +29,25 @@ class Solver:
self._locked = locked
self._io = io
self._provider = Provider(self._package, self._pool, self._io)
self._branches = []
def solve(self, use_latest=None): # type: (...) -> List[Operation]
with self._provider.progress():
start = time.time()
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 = []
for package in packages:
......@@ -79,6 +95,7 @@ class Solver:
# since it actually doesn't matter since removals are always on top.
-depths[packages.index(o.package)] if o.job_type != "uninstall" else 0,
o.package.name,
o.package.version,
),
)
......@@ -104,15 +121,21 @@ class Solver:
packages.append(package)
depths.append(_depths[index])
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:
if dep not in current_package.requires:
current_package.requires.append(dep)
for dep in package.requires:
if dep not in pkg.requires:
pkg.requires.append(dep)
return packages, depths
def _solve(self, use_latest=None):
self._branches.append(self._package.python_versions)
locked = {}
for package in self._locked.packages:
locked[package.name] = DependencyPackage(package.to_dependency(), package)
......@@ -134,58 +157,37 @@ class Solver:
depths = []
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
)
depths.append(depth)
package.category = category
package.optional = optional
# 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
package.marker = marker
return packages, depths
def _build_graph(
self, package, packages, previous=None, previous_dep=None, dep=None
):
): # type: (...) -> Dict[str, Any]
if not previous:
category = "dev"
optional = True
python_version = "*"
platform = "*"
marker = package.marker
else:
category = dep.category
optional = dep.is_optional() and not dep.is_activated()
python_version = str(
parse_constraint(previous["python_version"]).intersect(
previous_dep.python_constraint
)
)
platform = str(
previous_dep.platform
if GenericConstraint.parse(previous["platform"]).matches(
previous_dep.platform_constraint
)
and previous_dep.platform != "*"
else previous["platform"]
)
intersection = previous["marker"].intersect(previous_dep.marker)
marker = intersection
graph = {
"name": package.name,
"category": category,
"optional": optional,
"python_version": python_version,
"platform": platform,
"children": [],
"marker": marker,
"children": [], # type: List[Dict[str, Any]]
}
if previous_dep and previous_dep is not dep and previous_dep.name == dep.name:
......@@ -237,10 +239,8 @@ class Solver:
)
if existing:
existing["python_version"] = str(
parse_constraint(existing["python_version"]).union(
parse_constraint(child_graph["python_version"])
)
existing["marker"] = existing["marker"].union(
child_graph["marker"]
)
continue
......@@ -251,37 +251,27 @@ class Solver:
def _get_tags_for_package(self, package, graph, depth=0):
categories = ["dev"]
optionals = [True]
python_versions = []
platforms = []
markers = []
_depths = [0]
children = graph["children"]
found = False
for child in children:
if child["name"] == package.name:
category = child["category"]
optional = child["optional"]
python_version = child["python_version"]
platform = child["platform"]
marker = child["marker"]
_depths.append(depth)
else:
(
category,
optional,
python_version,
platform,
_depth,
) = self._get_tags_for_package(package, child, depth=depth + 1)
(category, optional, marker, _depth) = self._get_tags_for_package(
package, child, depth=depth + 1
)
_depths.append(_depth)
categories.append(category)
optionals.append(optional)
if python_version is not None:
python_versions.append(python_version)
if platform is not None:
platforms.append(platform)
if not marker.is_any():
markers.append(marker)
if "main" in categories:
category = "main"
......@@ -290,37 +280,13 @@ class Solver:
optional = all(optionals)
if not python_versions:
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
depth = max(*(_depths + [0]))
if not platforms:
platform = None
if not markers:
marker = AnyMarker()
else:
platform = platforms[0]
for constraint in platforms[1:]:
previous = GenericConstraint.parse(platform)
current = GenericConstraint.parse(constraint)
if platform == "*":
continue
elif constraint == "*":
platform = constraint
elif current.matches(previous):
platform = constraint
depth = max(*(_depths + [0]))
marker = markers[0]
for m in markers[1:]:
marker = marker.union(m)
return category, optional, python_version, platform, depth
return category, optional, marker, depth
......@@ -168,6 +168,9 @@ class VersionUnion(VersionConstraint):
return True
while True:
if state["their_range"] is None:
break
if state["their_range"].is_strictly_lower(state["current"]):
if not their_next_range():
break
......
import json
import os
import platform
import subprocess
......@@ -8,6 +9,7 @@ import warnings
from contextlib import contextmanager
from subprocess import CalledProcessError
from typing import Any
from typing import Dict
from typing import Optional
from typing import Tuple
......@@ -15,6 +17,7 @@ from poetry.config import Config
from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.version.markers import BaseMarker
class EnvError(Exception):
......@@ -47,8 +50,7 @@ class Env(object):
self._base = base or path
self._version_info = self.get_version_info()
self._python_implementation = self.get_python_implementation()
self._marker_env = None
@property
def path(self): # type: () -> Path
......@@ -60,11 +62,11 @@ class Env(object):
@property
def version_info(self): # type: () -> Tuple[int]
return self._version_info
return tuple(self.marker_env["version_info"])
@property
def python_implementation(self): # type: () -> str
return self._python_implementation
return self.marker_env["platform_python_implementation"]
@property
def python(self): # type: () -> str
......@@ -74,6 +76,13 @@ class Env(object):
return self._bin("python")
@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
"""
Path to current pip executable
......@@ -214,9 +223,15 @@ class Env(object):
def get_python_implementation(self): # type: () -> str
raise NotImplementedError()
def config_var(self, var): # type: () -> Any
def get_marker_env(self): # type: () -> Dict[str, Any]
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):
"""
Run a command inside the Python environment.
......@@ -262,7 +277,7 @@ class Env(object):
return str(bin_path)
def __repr__(self):
return '{}("{}")'.format(self.__class__.__name__, self._base)
return '{}("{}")'.format(self.__class__.__name__, self._path)
class SystemEnv(Env):
......@@ -276,6 +291,34 @@ class SystemEnv(Env):
def get_python_implementation(self): # type: () -> str
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
try:
return sysconfig.get_config_var(var)
......@@ -314,7 +357,7 @@ class VirtualEnv(Env):
" )"
')"',
shell=True,
)
).strip()
)
def get_version_info(self): # type: () -> Tuple[int]
......@@ -328,12 +371,35 @@ class VirtualEnv(Env):
return tuple([int(s) for s in output.strip().split(".")])
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",
"-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,
).strip()
)
return json.loads(output)
def config_var(self, var): # type: (str) -> Any
try:
......@@ -344,7 +410,7 @@ class VirtualEnv(Env):
"print(sysconfig.get_config_var('{}'))\"".format(var),
shell=True,
).strip()
except VenvCommandError as e:
except EnvCommandError as e:
warnings.warn("{0}".format(e), RuntimeWarning)
return None
......
......@@ -86,3 +86,8 @@ def get_http_basic_auth(repository_name): # type: (str) -> tuple
if repo_auth:
return repo_auth["username"], repo_auth["password"]
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 ZeroOrMore, Group, Forward, QuotedString
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):
"""
......@@ -111,6 +116,9 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
MARKER = stringStart + MARKER_EXPR + stringEnd
_undefined = object()
def _coerce_parse_result(results):
if isinstance(results, ParseResults):
return [_coerce_parse_result(i) for i in results]
......@@ -144,42 +152,504 @@ def _format_marker(marker, first=True):
return marker
_operators = {
"in": lambda lhs, rhs: lhs in rhs,
"not in": lambda lhs, rhs: lhs not in rhs,
"<": operator.lt,
"<=": operator.le,
"==": operator.eq,
"!=": operator.ne,
">=": operator.ge,
">": operator.gt,
}
class BaseMarker(object):
def intersect(self, other): # type: (BaseMarker) -> BaseMarker
raise NotImplementedError()
def union(self, other): # type: (BaseMarker) -> BaseMarker
raise NotImplementedError()
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
def is_any(self): # type: () -> bool
return False
def is_empty(self): # type: () -> bool
return False
class Marker(object):
def __init__(self, marker):
try:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc : e.loc + 8]
)
raise InvalidMarker(err_str)
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>"
class SingleMarker(BaseMarker):
_CONSTRAINT_RE = re.compile(r"(?i)^(~=|!=|>=?|<=?|==?|in|not in)?\s*(.+)$")
def __init__(self, name, constraint):
from poetry.packages.constraints import (
parse_constraint as parse_generic_constraint,
)
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
def markers(self):
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):
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)))
return " and ".join(elements)
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)
def __repr__(self):
return "<Marker({0!r})>".format(str(self))
@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
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"""
......@@ -53,7 +54,7 @@ REGEX = r"""
# 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
......@@ -67,7 +68,7 @@ REGEX = r"""
(?: # 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.
(?:
......@@ -82,7 +83,7 @@ REGEX = r"""
# The compatible operator requires at least two digits in the
# release segment.
(?<=~=) # Only match for the compatible operator
\s*
v?
(?:[0-9]+!)? # epoch
......@@ -107,7 +108,7 @@ REGEX = r"""
(?<!==|!=|~=) # We have special cases for these
# operators so we want to make sure they
# don't match here.
\s*
v?
(?:[0-9]+!)? # epoch
......@@ -171,7 +172,7 @@ VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
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 = 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 = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,7 +13,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "C"
......@@ -23,7 +21,6 @@ description = ""
category = "main"
optional = true
python-versions = "*"
platform = "*"
[package.dependencies]
D = "^1.0"
......@@ -35,14 +32,12 @@ description = ""
category = "main"
optional = true
python-versions = "*"
platform = "*"
[extras]
foo = ["C"]
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,7 +13,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "C"
......@@ -23,7 +21,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "D"
......@@ -32,14 +29,12 @@ description = ""
category = "main"
optional = true
python-versions = "*"
platform = "*"
[extras]
foo = ["D"]
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -2,7 +2,6 @@ package = []
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......@@ -5,11 +5,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,11 +5,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
"B" = "^1.0"
......@@ -18,19 +17,15 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "C"
version = "1.1"
description = ""
category = "main"
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
optional = false
python-versions = "*"
platform = "*"
[package.requirements]
python = ">=2.7,<2.8"
[[package]]
name = "D"
......@@ -39,11 +34,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,14 +13,12 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
A = "^1.0"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[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 = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,7 +13,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
C = "^1.0"
......@@ -26,11 +24,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,11 +13,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -3,7 +3,6 @@ description = ""
category = "main"
name = "pendulum"
optional = false
platform = "*"
python-versions = "*"
version = "1.4.4"
......@@ -12,7 +11,6 @@ category = "main"
description = ""
name = "project-with-extras"
optional = false
platform = "*"
python-versions = "*"
version = "1.2.3"
......@@ -26,7 +24,6 @@ url = "tests/fixtures/project_with_extras"
[metadata]
content-hash = "123456789"
platform = "*"
python-versions = "*"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "my-package"
......@@ -14,7 +13,6 @@ description = "Demo project."
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.source]
type = "directory"
......@@ -32,11 +30,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
B = "^2.0"
......@@ -17,7 +16,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
C = "1.5"
......@@ -29,11 +27,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
B = [
......@@ -18,58 +17,45 @@ name = "B"
version = "1.0"
description = ""
category = "main"
marker = "python_version < \"4.0\""
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
C = "1.2"
[package.requirements]
python = "<4.0"
[[package]]
name = "B"
version = "2.0"
description = ""
category = "main"
marker = "python_version >= \"4.0\""
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
C = "1.5"
[package.requirements]
python = ">=4.0"
[[package]]
name = "C"
version = "1.2"
description = ""
category = "main"
marker = "python_version < \"4.0\""
optional = false
python-versions = "*"
platform = "*"
[package.requirements]
python = "<4.0"
[[package]]
name = "C"
version = "1.5"
description = ""
category = "main"
marker = "python_version >= \"4.0\""
optional = false
python-versions = "*"
platform = "*"
[package.requirements]
python = ">=4.0"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = "Description"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "*"
[package.source]
type = "file"
......@@ -22,11 +21,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,41 +5,33 @@ description = ""
category = "main"
optional = true
python-versions = "*"
platform = "*"
[[package]]
name = "C"
version = "1.3"
description = ""
category = "main"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"4.0\""
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
D = "^1.2"
[package.requirements]
python = ">=2.7,<2.8 || >=3.4,<4.0"
[[package]]
name = "D"
version = "1.4"
description = ""
category = "main"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"4.0\""
optional = false
python-versions = "*"
platform = "*"
[package.requirements]
python = ">=2.7,<2.8 || >=3.4,<4.0"
[extras]
foo = ["A"]
[metadata]
python-versions = "~2.7 || ^3.4"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,53 +5,42 @@ description = ""
category = "main"
optional = true
python-versions = "*"
platform = "*"
[[package]]
name = "B"
version = "1.1"
description = ""
category = "main"
marker = "sys_platform == \"custom\""
optional = false
python-versions = "*"
platform = "*"
[package.requirements]
platform = "custom"
[[package]]
name = "C"
version = "1.3"
description = ""
category = "main"
marker = "sys_platform == \"darwin\""
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
D = "^1.2"
[package.requirements]
platform = "darwin"
[[package]]
name = "D"
version = "1.4"
description = ""
category = "main"
marker = "sys_platform == \"darwin\""
optional = false
python-versions = "*"
platform = "*"
[package.requirements]
platform = "darwin"
[extras]
foo = ["A"]
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,11 +13,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,31 +5,24 @@ description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "colorama"
version = "0.3.9"
description = "Cross-platform colored terminal text."
category = "dev"
marker = "sys_platform == \"win32\""
optional = false
python-versions = "*"
platform = "UNKNOWN"
[package.requirements]
platform = "win32"
[[package]]
name = "funcsigs"
version = "1.0.2"
description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
category = "dev"
marker = "python_version < \"3.0\""
optional = false
python-versions = "*"
platform = "UNKNOWN"
[package.requirements]
python = "<3.0"
[[package]]
name = "more-itertools"
......@@ -38,7 +31,6 @@ description = "More routines for operating on iterables, beyond itertools"
category = "dev"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
six = ">=1.0.0,<2.0.0"
......@@ -50,7 +42,6 @@ description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "unix"
[[package]]
name = "py"
......@@ -59,7 +50,6 @@ description = "library with cross-python path, ini-parsing, io, code, log facili
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "unix"
[[package]]
name = "pytest"
......@@ -68,7 +58,6 @@ description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
platform = "unix"
[package.dependencies]
py = ">=1.5.0"
......@@ -78,7 +67,7 @@ attrs = ">=17.4.0"
more-itertools = ">=4.0.0"
pluggy = ">=0.5,<0.7"
funcsigs = {"version" = "*", "python" = "<3.0"}
colorama = {"version" = "*", "platform" = "win32"}
colorama = "*"
[[package]]
name = "six"
......@@ -87,11 +76,9 @@ description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "B"
......@@ -14,7 +13,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "C"
......@@ -23,11 +21,9 @@ description = ""
category = "main"
optional = false
python-versions = "~2.7 || ^3.3"
platform = "*"
[metadata]
python-versions = "~2.7 || ^3.4"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -5,7 +5,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
D = "^1.0"
......@@ -17,7 +16,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[package.dependencies]
C = "~1.2"
......@@ -29,7 +27,6 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[[package]]
name = "D"
......@@ -38,11 +35,9 @@ description = ""
category = "main"
optional = false
python-versions = "*"
platform = "*"
[metadata]
python-versions = "*"
platform = "*"
content-hash = "123456789"
[metadata.hashes]
......
......@@ -63,10 +63,8 @@ class Locker(BaseLocker):
def _write_lock_data(self, data):
for package in data["package"]:
python_versions = str(package["python-versions"])
platform = str(package["platform"])
if PY2:
python_versions = python_versions.decode()
platform = platform.decode()
if "requirements" in package:
requirements = {}
for key, value in package["requirements"].items():
......@@ -75,7 +73,6 @@ class Locker(BaseLocker):
package["requirements"] = requirements
package["python-versions"] = python_versions
package["platform"] = platform
self._written_data = data
......@@ -1143,3 +1140,34 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
assert len(updates) == 1
removals = installer.installer.removals
assert len(removals) == 0
@pytest.mark.skip(
"This is not working at the moment due to limitations in the resolver"
)
def test_installer_test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python(
installer, locker, repo, package, installed
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})
package_a101 = get_package("A", "1.0.1")
package_a101.python_versions = ">=3.6"
package_a100 = get_package("A", "1.0.0")
package_a100.python_versions = ">=3.5"
repo.add_package(package_a100)
repo.add_package(package_a101)
installer.run()
expected = fixture("with-conditional-dependency")
assert locker.written_data == expected
installs = installer.installer.installs
if sys.version_info >= (3, 5, 0):
assert len(installs) == 1
else:
assert len(installs) == 0
......@@ -91,12 +91,12 @@ def test_convert_dependencies():
main = ["B>=1.0,<1.1"]
extra_python = (
':(python_version >= "2.7" and python_version < "2.8") '
'or (python_version >= "3.6" and python_version < "4.0")'
':python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.6" and python_version < "4.0"'
)
extra_d_dependency = (
'baz:(python_version >= "2.7" and python_version < "2.8") '
'or (python_version >= "3.4" and python_version < "4.0")'
'baz:python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
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():
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")'
)
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")'
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.6" and python_version < "4.0"'
)
......@@ -112,8 +97,8 @@ def test_to_pep_508_in_extras():
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")'
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.6" and python_version < "4.0"'
") "
'and (extra == "foo" or extra == "bar")'
)
......@@ -13,7 +13,6 @@ def test_file_dependency_wheel():
assert dependency.name == "demo"
assert dependency.pretty_constraint == "0.1.0"
assert dependency.python_versions == "*"
assert dependency.platform == "*"
meta = dependency.metadata
assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"]
......@@ -26,7 +25,6 @@ def test_file_dependency_sdist():
assert dependency.name == "demo"
assert dependency.pretty_constraint == "0.1.0"
assert dependency.python_versions == "*"
assert dependency.platform == "*"
meta = dependency.metadata
assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"]
......
......@@ -37,7 +37,6 @@ category = "main"
description = ""
name = "A"
optional = false
platform = "*"
python-versions = "*"
version = "1.0.0"
......@@ -49,13 +48,11 @@ category = "main"
description = ""
name = "B"
optional = false
platform = "*"
python-versions = "*"
version = "1.2"
[metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
platform = "*"
python-versions = "*"
[metadata.hashes]
......
......@@ -40,37 +40,40 @@ def test_dependency_from_pep_508_with_extras():
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.in_extras == ["foo", "bar"]
assert str(dep.marker) == 'extra == "foo" or extra == "bar"'
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)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.extras == []
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():
name = "requests (==2.18.0); " 'python_version == "2.7"'
name = 'requests (==2.18.0); python_version == "2.7"'
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"
assert str(dep.marker) == 'python_version == "2.7"'
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)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.extras == []
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():
......@@ -86,34 +89,40 @@ def test_dependency_from_pep_508_complex():
assert str(dep.constraint) == "2.18.0"
assert dep.in_extras == ["foo"]
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():
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)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
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():
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)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
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():
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)
assert dep.name == "requests"
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():
......@@ -125,3 +134,21 @@ def test_dependency_with_extra():
assert len(dep.extras) == 1
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
from cleo.outputs.null_output import NullOutput
from cleo.styles import OutputStyle
from poetry.packages import dependency_from_pep_508
from poetry.packages import ProjectPackage
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool
......@@ -12,7 +13,6 @@ from poetry.puzzle.exceptions import SolverProblemError
from tests.helpers import get_dependency
from tests.helpers import get_package
from tests.repositories.test_pypi_repository import MockRepository
@pytest.fixture()
......@@ -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):
package.add_dependency("A")
package.add_dependency("B")
......@@ -537,7 +507,7 @@ def test_solver_sub_dependencies_with_requirements(solver, repo, package):
)
op = ops[1]
assert op.package.requirements == {}
assert op.package.marker.is_any()
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
assert op.package.requirements == {"python": "<4.0"}
assert str(op.package.marker) == 'python_version < "4.0"'
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
assert op.package.requirements == {"python": "<5.0"}
assert str(op.package.marker) == 'python_version < "5.0"'
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(
......@@ -785,7 +758,9 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package):
)
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):
......@@ -814,10 +789,10 @@ def test_solver_duplicate_dependencies_different_constraints(solver, repo, packa
)
op = ops[0]
assert op.package.requirements == {"python": "<3.4"}
assert str(op.package.marker) == 'python_version < "3.4"'
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(
......@@ -882,10 +857,10 @@ def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package):
)
op = ops[2]
assert op.package.requirements == {"python": "<3.4"}
assert str(op.package.marker) == 'python_version < "3.4"'
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):
......@@ -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(
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_a = get_package("A", "1.0.0")
......@@ -978,3 +953,123 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir
ops = solver.solve()
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():
win_inet = package.extras["socks"][0]
assert win_inet.name == "win-inet-pton"
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():
......
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