Commit 3b30d2a1 by Sébastien Eustace Committed by GitHub

Use a lightweight core library (#2212)

* Use poetry-core

* Upgrade dependencies

* No longer use Poetry as a build backend but poetry-core instead

* Use the latest version of poetry-core
parent 8224b164
...@@ -42,8 +42,8 @@ So, in your `pyproject.toml` file, add this section if it does not already exist ...@@ -42,8 +42,8 @@ So, in your `pyproject.toml` file, add this section if it does not already exist
```toml ```toml
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.masonry.api" build-backend = "poetry.core.masonry.api"
``` ```
And use a `tox.ini` configuration file similar to this: And use a `tox.ini` configuration file similar to this:
......
...@@ -274,16 +274,22 @@ If you publish you package on PyPI, they will appear in the `Project Links` sect ...@@ -274,16 +274,22 @@ If you publish you package on PyPI, they will appear in the `Project Links` sect
[PEP-517](https://www.python.org/dev/peps/pep-0517/) introduces a standard way [PEP-517](https://www.python.org/dev/peps/pep-0517/) introduces a standard way
to define alternative build systems to build a Python project. to define alternative build systems to build a Python project.
Poetry is compliant with PEP-517 so if you use Poetry to manage your Python Poetry is compliant with PEP-517, by providing a lightweight core library,
project you should reference it in the `build-system` section of the `pyproject.toml` so if you use Poetry to manage your Python project you should reference
file like so: it in the `build-system` section of the `pyproject.toml` file like so:
```toml ```toml
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry_core>=1.0.0"]
build-backend = "poetry.masonry.api" build-backend = "poetry.core.masonry.api"
``` ```
!!!note !!!note
When using the `new` or `init` command this section will be automatically added. When using the `new` or `init` command this section will be automatically added.
!!!note
If your `pyproject.toml` file still references `poetry` directly as a build backend,
you should update it to reference `poetry_core` instead.
...@@ -204,8 +204,13 @@ import sys ...@@ -204,8 +204,13 @@ import sys
import os import os
lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib")) lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib"))
vendors = os.path.join(lib, "poetry", "_vendor")
current_vendors = os.path.join(
vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2]))
)
sys.path.insert(0, lib) sys.path.insert(0, lib)
sys.path.insert(0, current_vendors)
if __name__ == "__main__": if __name__ == "__main__":
from poetry.console import main from poetry.console import main
......
...@@ -26,7 +26,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" ...@@ -26,7 +26,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.0" version = "1.3.0"
[[package]] [[package]]
category = "main" category = "dev"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
name = "attrs" name = "attrs"
optional = false optional = false
...@@ -104,7 +104,7 @@ version = "2019.11.28" ...@@ -104,7 +104,7 @@ version = "2019.11.28"
[[package]] [[package]]
category = "main" category = "main"
description = "Foreign Function Interface for Python calling C code." description = "Foreign Function Interface for Python calling C code."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.4\" and python_version < \"3.5\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "cffi" name = "cffi"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -158,7 +158,7 @@ description = "CliKit is a group of utilities to build beautiful and testable co ...@@ -158,7 +158,7 @@ description = "CliKit is a group of utilities to build beautiful and testable co
name = "clikit" name = "clikit"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.2" version = "0.4.3"
[package.dependencies] [package.dependencies]
pastel = ">=0.2.0,<0.3.0" pastel = ">=0.2.0,<0.3.0"
...@@ -179,15 +179,6 @@ version = ">=3.6,<4.0" ...@@ -179,15 +179,6 @@ version = ">=3.6,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\" and python_version == \"3.4\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.1"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\" and python_version != \"3.4\" or platform_system == \"Windows\"" marker = "sys_platform == \"win32\" and python_version != \"3.4\" or platform_system == \"Windows\""
name = "colorama" name = "colorama"
optional = false optional = false
...@@ -221,14 +212,6 @@ category = "dev" ...@@ -221,14 +212,6 @@ category = "dev"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
name = "coverage" name = "coverage"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
version = "4.5.4"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "5.0.3" version = "5.0.3"
...@@ -238,7 +221,7 @@ toml = ["toml"] ...@@ -238,7 +221,7 @@ toml = ["toml"]
[[package]] [[package]]
category = "main" category = "main"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.4\" and python_version < \"3.5\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "cryptography" name = "cryptography"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
...@@ -274,7 +257,7 @@ version = "0.3.0" ...@@ -274,7 +257,7 @@ version = "0.3.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Discover and load entry points from installed packages." description = "Discover and load entry points from installed packages."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "entrypoints" name = "entrypoints"
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=2.7"
...@@ -314,7 +297,7 @@ version = "1.0.2" ...@@ -314,7 +297,7 @@ version = "1.0.2"
[[package]] [[package]]
category = "main" category = "main"
description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy." description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "functools32" name = "functools32"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -341,7 +324,7 @@ version = "3.3.0" ...@@ -341,7 +324,7 @@ version = "3.3.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Version of the glob module that can capture patterns and supports recursive wildcards" description = "Version of the glob module that can capture patterns and supports recursive wildcards"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "glob2" name = "glob2"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -394,19 +377,12 @@ description = "Internationalized Domain Names in Applications (IDNA)" ...@@ -394,19 +377,12 @@ description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.8"
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.9" version = "2.9"
[[package]] [[package]]
category = "main" category = "main"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\""
name = "importlib-metadata" name = "importlib-metadata"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
...@@ -472,21 +448,6 @@ description = "A very fast and expressive template engine." ...@@ -472,21 +448,6 @@ description = "A very fast and expressive template engine."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\"" marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "jinja2" name = "jinja2"
optional = false optional = false
python-versions = "*"
version = "2.10.3"
[package.dependencies]
MarkupSafe = ">=0.23"
[package.extras]
i18n = ["Babel (>=0.8)"]
[[package]]
category = "dev"
description = "A very fast and expressive template engine."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "jinja2"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.11.1" version = "2.11.1"
...@@ -498,34 +459,8 @@ i18n = ["Babel (>=0.8)"] ...@@ -498,34 +459,8 @@ i18n = ["Babel (>=0.8)"]
[[package]] [[package]]
category = "main" category = "main"
description = "An implementation of JSON Schema validation for Python"
name = "jsonschema"
optional = false
python-versions = "*"
version = "3.2.0"
[package.dependencies]
attrs = ">=17.4.0"
pyrsistent = ">=0.14.0"
setuptools = "*"
six = ">=1.11.0"
[package.dependencies.functools32]
python = "<3"
version = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.extras]
format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
[[package]]
category = "main"
description = "Store and access your passwords safely." description = "Store and access your passwords safely."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "keyring" name = "keyring"
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=2.7"
...@@ -610,14 +545,6 @@ category = "dev" ...@@ -610,14 +545,6 @@ category = "dev"
description = "Python implementation of Markdown." description = "Python implementation of Markdown."
name = "markdown" name = "markdown"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "3.0.1"
[[package]]
category = "dev"
description = "Python implementation of Markdown."
name = "markdown"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "3.1.1" version = "3.1.1"
...@@ -738,15 +665,6 @@ description = "More routines for operating on iterables, beyond itertools" ...@@ -738,15 +665,6 @@ description = "More routines for operating on iterables, beyond itertools"
marker = "python_version > \"2.7\"" marker = "python_version > \"2.7\""
name = "more-itertools" name = "more-itertools"
optional = false optional = false
python-versions = ">=3.4"
version = "7.2.0"
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
marker = "python_version > \"2.7\""
name = "more-itertools"
optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "8.2.0" version = "8.2.0"
...@@ -809,7 +727,7 @@ version = "0.2.0" ...@@ -809,7 +727,7 @@ version = "0.2.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Object-oriented filesystem paths" description = "Object-oriented filesystem paths"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version < \"3.6\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.6\""
name = "pathlib2" name = "pathlib2"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -878,27 +796,17 @@ version = ">=0.12" ...@@ -878,27 +796,17 @@ version = ">=0.12"
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
[[package]] [[package]]
category = "dev" category = "main"
description = "A framework for managing and maintaining multi-language pre-commit hooks." description = "Core utilities for Poetry"
name = "pre-commit" name = "poetry-core"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.18.3" version = "1.0.0a5"
[package.dependencies] [package.dependencies]
"aspy.yaml" = "*" [package.dependencies.typing]
cfgv = ">=2.0.0" python = ">=2.7,<2.8"
identify = ">=1.0.0" version = ">=3.7.4.1,<4.0.0.0"
importlib-metadata = "*"
nodeenv = ">=0.11.1"
pyyaml = "*"
six = "*"
toml = "*"
virtualenv = ">=15.2"
[package.dependencies.importlib-resources]
python = "<3.7"
version = "*"
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -949,7 +857,7 @@ version = "1.8.1" ...@@ -949,7 +857,7 @@ version = "1.8.1"
[[package]] [[package]]
category = "main" category = "main"
description = "C parser in Python" description = "C parser in Python"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.4\" and python_version < \"3.5\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\""
name = "pycparser" name = "pycparser"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
...@@ -960,14 +868,6 @@ category = "dev" ...@@ -960,14 +868,6 @@ category = "dev"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
name = "pygments" name = "pygments"
optional = false optional = false
python-versions = "*"
version = "2.3.1"
[[package]]
category = "dev"
description = "Pygments is a syntax highlighting package written in Python."
name = "pygments"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.5.2" version = "2.5.2"
...@@ -995,17 +895,6 @@ category = "dev" ...@@ -995,17 +895,6 @@ category = "dev"
description = "Extension pack for Python Markdown." description = "Extension pack for Python Markdown."
name = "pymdown-extensions" name = "pymdown-extensions"
optional = false optional = false
python-versions = "*"
version = "6.0"
[package.dependencies]
Markdown = ">=3.0.1"
[[package]]
category = "dev"
description = "Extension pack for Python Markdown."
name = "pymdown-extensions"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "6.2.1" version = "6.2.1"
...@@ -1025,7 +914,7 @@ version = "6.3" ...@@ -1025,7 +914,7 @@ version = "6.3"
Markdown = ">=3.2" Markdown = ">=3.2"
[[package]] [[package]]
category = "main" category = "dev"
description = "Python parsing module" description = "Python parsing module"
name = "pyparsing" name = "pyparsing"
optional = false optional = false
...@@ -1033,17 +922,6 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" ...@@ -1033,17 +922,6 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.6" version = "2.4.6"
[[package]] [[package]]
category = "main"
description = "Persistent/Functional/Immutable data structures"
name = "pyrsistent"
optional = false
python-versions = "*"
version = "0.14.11"
[package.dependencies]
six = "*"
[[package]]
category = "dev" category = "dev"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
name = "pytest" name = "pytest"
...@@ -1060,14 +938,6 @@ py = ">=1.5.0" ...@@ -1060,14 +938,6 @@ py = ">=1.5.0"
six = ">=1.10.0" six = ">=1.10.0"
wcwidth = "*" wcwidth = "*"
[[package.dependencies.colorama]]
python = "<3.4.0 || >=3.5.0"
version = "*"
[[package.dependencies.colorama]]
python = ">=3.4,<3.5"
version = "<=0.4.1"
[[package.dependencies.more-itertools]] [[package.dependencies.more-itertools]]
python = "<2.8" python = "<2.8"
version = ">=4.0.0,<6.0.0" version = ">=4.0.0,<6.0.0"
...@@ -1076,6 +946,10 @@ version = ">=4.0.0,<6.0.0" ...@@ -1076,6 +946,10 @@ version = ">=4.0.0,<6.0.0"
python = ">=2.8" python = ">=2.8"
version = ">=4.0.0" version = ">=4.0.0"
[package.dependencies.colorama]
python = "<3.4.0 || >=3.5.0"
version = "*"
[package.dependencies.funcsigs] [package.dependencies.funcsigs]
python = "<3.0" python = "<3.0"
version = ">=1.0" version = ">=1.0"
...@@ -1140,7 +1014,7 @@ termcolor = ">=1.1.0" ...@@ -1140,7 +1014,7 @@ termcolor = ">=1.1.0"
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform == \"win32\" or python_version >= \"3.4\" and python_version < \"3.5\" and sys_platform == \"win32\" or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"win32\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" and sys_platform == \"win32\" or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"win32\""
name = "pywin32-ctypes" name = "pywin32-ctypes"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -1151,14 +1025,6 @@ category = "dev" ...@@ -1151,14 +1025,6 @@ category = "dev"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
name = "pyyaml" name = "pyyaml"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.2"
[[package]]
category = "dev"
description = "YAML parser and emitter for Python"
name = "pyyaml"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "5.3" version = "5.3"
...@@ -1176,24 +1042,6 @@ category = "main" ...@@ -1176,24 +1042,6 @@ category = "main"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
name = "requests" name = "requests"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.21.0"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<3.1.0"
idna = ">=2.5,<2.9"
urllib3 = ">=1.21.1,<1.25"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]]
category = "main"
description = "Python HTTP for Humans."
name = "requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.23.0" version = "2.23.0"
...@@ -1221,7 +1069,7 @@ requests = ">=2.0.1,<3.0.0" ...@@ -1221,7 +1069,7 @@ requests = ">=2.0.1,<3.0.0"
[[package]] [[package]]
category = "main" category = "main"
description = "scandir, a better directory iterator and faster os.walk()" description = "scandir, a better directory iterator and faster os.walk()"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version < \"3.5\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.5\" or python_version >= \"2.7\" and python_version < \"2.8\" and (python_version >= \"2.7\" and python_version < \"2.8\" or python_version < \"3.5\")"
name = "scandir" name = "scandir"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -1230,7 +1078,7 @@ version = "1.10.0" ...@@ -1230,7 +1078,7 @@ version = "1.10.0"
[[package]] [[package]]
category = "main" category = "main"
description = "Python bindings to FreeDesktop.org Secret Service API" description = "Python bindings to FreeDesktop.org Secret Service API"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.4\" and python_version < \"3.5\" and (sys_platform == \"linux2\" or sys_platform == \"linux\")" marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\")"
name = "secretstorage" name = "secretstorage"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -1274,7 +1122,7 @@ version = "1.14.0" ...@@ -1274,7 +1122,7 @@ version = "1.14.0"
[[package]] [[package]]
category = "main" category = "main"
description = "A backport of the subprocess module from Python 3 for use on 2.x." description = "A backport of the subprocess module from Python 3 for use on 2.x."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\""
name = "subprocess32" name = "subprocess32"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
...@@ -1340,27 +1188,6 @@ category = "dev" ...@@ -1340,27 +1188,6 @@ category = "dev"
description = "tox is a generic virtualenv management and test command line tool" description = "tox is a generic virtualenv management and test command line tool"
name = "tox" name = "tox"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.12.1"
[package.dependencies]
filelock = ">=3.0.0,<4"
pluggy = ">=0.3.0,<1"
py = ">=1.4.17,<2"
setuptools = ">=30.0.0"
six = ">=1.0.0,<2"
toml = ">=0.9.4"
virtualenv = ">=14.0.0"
[package.extras]
docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"]
testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=3.0.0,<5)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.2.3,<2)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"]
[[package]]
category = "dev"
description = "tox is a generic virtualenv management and test command line tool"
name = "tox"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "3.14.5" version = "3.14.5"
...@@ -1414,18 +1241,6 @@ category = "main" ...@@ -1414,18 +1241,6 @@ category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3" name = "urllib3"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.24.3"
[package.extras]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
[[package]]
category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.25.8" version = "1.25.8"
...@@ -1491,6 +1306,7 @@ version = "0.5.1" ...@@ -1491,6 +1306,7 @@ version = "0.5.1"
[[package]] [[package]]
category = "main" category = "main"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\""
name = "zipp" name = "zipp"
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=2.7"
...@@ -1506,8 +1322,8 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] ...@@ -1506,8 +1322,8 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"]
[metadata] [metadata]
content-hash = "70609fddc0d3768b1003fc24207951ab7ad8bfad4c6cb326d6217c52f5a92e3d" content-hash = "35617c80a428d8f081214fcbe428540df288e67e5596531cba40415f19438d57"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.5"
[metadata.files] [metadata.files]
appdirs = [ appdirs = [
...@@ -1589,12 +1405,10 @@ click = [ ...@@ -1589,12 +1405,10 @@ click = [
{file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
] ]
clikit = [ clikit = [
{file = "clikit-0.4.2-py2.py3-none-any.whl", hash = "sha256:95394982cfa460a77ded2f173380a958e5f90c16972307c19d79b96f6e335326"}, {file = "clikit-0.4.3-py2.py3-none-any.whl", hash = "sha256:71e321b7795a2a6c4888629f43365d52db071737e668ab16861121d7dd3ada09"},
{file = "clikit-0.4.2.tar.gz", hash = "sha256:f67336462800078e0896cf6ecfa3b460dfea4dfa01de659388a4ff0d83c8d6ca"}, {file = "clikit-0.4.3.tar.gz", hash = "sha256:6e2d7e115e7c7b35bceb0209109935ab2f9ab50910e9ff2293f7fa0b7abf973e"},
] ]
colorama = [ colorama = [
{file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"},
{file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"},
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
] ]
...@@ -1607,38 +1421,6 @@ contextlib2 = [ ...@@ -1607,38 +1421,6 @@ contextlib2 = [
{file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"}, {file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"},
] ]
coverage = [ coverage = [
{file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"},
{file = "coverage-4.5.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c"},
{file = "coverage-4.5.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce"},
{file = "coverage-4.5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe"},
{file = "coverage-4.5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888"},
{file = "coverage-4.5.4-cp27-cp27m-win32.whl", hash = "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc"},
{file = "coverage-4.5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24"},
{file = "coverage-4.5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437"},
{file = "coverage-4.5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6"},
{file = "coverage-4.5.4-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5"},
{file = "coverage-4.5.4-cp34-cp34m-macosx_10_12_x86_64.whl", hash = "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef"},
{file = "coverage-4.5.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e"},
{file = "coverage-4.5.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca"},
{file = "coverage-4.5.4-cp34-cp34m-win32.whl", hash = "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0"},
{file = "coverage-4.5.4-cp34-cp34m-win_amd64.whl", hash = "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1"},
{file = "coverage-4.5.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7"},
{file = "coverage-4.5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47"},
{file = "coverage-4.5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"},
{file = "coverage-4.5.4-cp35-cp35m-win32.whl", hash = "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e"},
{file = "coverage-4.5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d"},
{file = "coverage-4.5.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9"},
{file = "coverage-4.5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755"},
{file = "coverage-4.5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9"},
{file = "coverage-4.5.4-cp36-cp36m-win32.whl", hash = "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f"},
{file = "coverage-4.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5"},
{file = "coverage-4.5.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca"},
{file = "coverage-4.5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650"},
{file = "coverage-4.5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2"},
{file = "coverage-4.5.4-cp37-cp37m-win32.whl", hash = "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5"},
{file = "coverage-4.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351"},
{file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"},
{file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"},
{file = "coverage-5.0.3-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f"}, {file = "coverage-5.0.3-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f"},
{file = "coverage-5.0.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc"}, {file = "coverage-5.0.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc"},
{file = "coverage-5.0.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a"}, {file = "coverage-5.0.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a"},
...@@ -1740,8 +1522,6 @@ identify = [ ...@@ -1740,8 +1522,6 @@ identify = [
{file = "identify-1.4.11.tar.gz", hash = "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96"}, {file = "identify-1.4.11.tar.gz", hash = "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96"},
] ]
idna = [ idna = [
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
] ]
...@@ -1762,15 +1542,9 @@ jeepney = [ ...@@ -1762,15 +1542,9 @@ jeepney = [
{file = "jeepney-0.4.2.tar.gz", hash = "sha256:0ba6d8c597e9bef1ebd18aaec595f942a264e25c1a48f164d46120eacaa2e9bb"}, {file = "jeepney-0.4.2.tar.gz", hash = "sha256:0ba6d8c597e9bef1ebd18aaec595f942a264e25c1a48f164d46120eacaa2e9bb"},
] ]
jinja2 = [ jinja2 = [
{file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"},
{file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"},
{file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"},
{file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"},
] ]
jsonschema = [
{file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
{file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
]
keyring = [ keyring = [
{file = "keyring-18.0.1-py2.py3-none-any.whl", hash = "sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6"}, {file = "keyring-18.0.1-py2.py3-none-any.whl", hash = "sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6"},
{file = "keyring-18.0.1.tar.gz", hash = "sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838"}, {file = "keyring-18.0.1.tar.gz", hash = "sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838"},
...@@ -1790,8 +1564,6 @@ lunr = [ ...@@ -1790,8 +1564,6 @@ lunr = [
{file = "lunr-0.5.6.tar.gz", hash = "sha256:7be69d7186f65784a4f2adf81e5c58efd6a9921aa95966babcb1f2f2ada75c20"}, {file = "lunr-0.5.6.tar.gz", hash = "sha256:7be69d7186f65784a4f2adf81e5c58efd6a9921aa95966babcb1f2f2ada75c20"},
] ]
markdown = [ markdown = [
{file = "Markdown-3.0.1-py2.py3-none-any.whl", hash = "sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa"},
{file = "Markdown-3.0.1.tar.gz", hash = "sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"},
{file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"},
{file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"},
{file = "Markdown-3.2.1-py2.py3-none-any.whl", hash = "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d"}, {file = "Markdown-3.2.1-py2.py3-none-any.whl", hash = "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d"},
...@@ -1828,11 +1600,6 @@ markupsafe = [ ...@@ -1828,11 +1600,6 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
] ]
mkdocs = [ mkdocs = [
...@@ -1849,8 +1616,6 @@ more-itertools = [ ...@@ -1849,8 +1616,6 @@ more-itertools = [
{file = "more-itertools-5.0.0.tar.gz", hash = "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4"}, {file = "more-itertools-5.0.0.tar.gz", hash = "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4"},
{file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"}, {file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"},
{file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"}, {file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"},
{file = "more-itertools-7.2.0.tar.gz", hash = "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832"},
{file = "more_itertools-7.2.0-py3-none-any.whl", hash = "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"},
{file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
{file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
] ]
...@@ -1913,9 +1678,11 @@ pluggy = [ ...@@ -1913,9 +1678,11 @@ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
] ]
poetry-core = [
{file = "poetry-core-1.0.0a5.tar.gz", hash = "sha256:afac65874baf2030328f43df840cec2b5a17bcdad0a04c55b0f830e444ef1120"},
{file = "poetry_core-1.0.0a5-py2.py3-none-any.whl", hash = "sha256:ffbd0512d80affce937ce53db2d35bf28ea52d287bae61da5b5d63058618d6b9"},
]
pre-commit = [ pre-commit = [
{file = "pre_commit-1.18.3-py2.py3-none-any.whl", hash = "sha256:fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a"},
{file = "pre_commit-1.18.3.tar.gz", hash = "sha256:1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f"},
{file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"}, {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"},
{file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"}, {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"},
] ]
...@@ -1931,8 +1698,6 @@ pycparser = [ ...@@ -1931,8 +1698,6 @@ pycparser = [
{file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"}, {file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"},
] ]
pygments = [ pygments = [
{file = "Pygments-2.3.1-py2.py3-none-any.whl", hash = "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"},
{file = "Pygments-2.3.1.tar.gz", hash = "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a"},
{file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
{file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
] ]
...@@ -1945,8 +1710,6 @@ pylev = [ ...@@ -1945,8 +1710,6 @@ pylev = [
{file = "pylev-1.3.0.tar.gz", hash = "sha256:063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3"}, {file = "pylev-1.3.0.tar.gz", hash = "sha256:063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3"},
] ]
pymdown-extensions = [ pymdown-extensions = [
{file = "pymdown-extensions-6.0.tar.gz", hash = "sha256:6cf0cf36b5a03b291ace22dc2f320f4789ce56fbdb6635a3be5fadbf5d7694dd"},
{file = "pymdown_extensions-6.0-py2.py3-none-any.whl", hash = "sha256:25b0a7967fa697b5035e23340a48594e3e93acb10b06d74574218ace3347d1df"},
{file = "pymdown-extensions-6.2.1.tar.gz", hash = "sha256:3bbe6048275f8a0d13a0fe44e0ea201e67268aa7bb40c2544eef16abbf168f7b"}, {file = "pymdown-extensions-6.2.1.tar.gz", hash = "sha256:3bbe6048275f8a0d13a0fe44e0ea201e67268aa7bb40c2544eef16abbf168f7b"},
{file = "pymdown_extensions-6.2.1-py2.py3-none-any.whl", hash = "sha256:dce5e17b93be0572322b7d06c9a13c13a9d98694d6468277911d50ca87d26f29"}, {file = "pymdown_extensions-6.2.1-py2.py3-none-any.whl", hash = "sha256:dce5e17b93be0572322b7d06c9a13c13a9d98694d6468277911d50ca87d26f29"},
{file = "pymdown-extensions-6.3.tar.gz", hash = "sha256:cb879686a586b22292899771f5e5bc3382808e92aa938f71b550ecdea709419f"}, {file = "pymdown-extensions-6.3.tar.gz", hash = "sha256:cb879686a586b22292899771f5e5bc3382808e92aa938f71b550ecdea709419f"},
...@@ -1956,9 +1719,6 @@ pyparsing = [ ...@@ -1956,9 +1719,6 @@ pyparsing = [
{file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
{file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
] ]
pyrsistent = [
{file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"},
]
pytest = [ pytest = [
{file = "pytest-4.6.9-py2.py3-none-any.whl", hash = "sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"}, {file = "pytest-4.6.9-py2.py3-none-any.whl", hash = "sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"},
{file = "pytest-4.6.9.tar.gz", hash = "sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339"}, {file = "pytest-4.6.9.tar.gz", hash = "sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339"},
...@@ -1980,17 +1740,6 @@ pywin32-ctypes = [ ...@@ -1980,17 +1740,6 @@ pywin32-ctypes = [
{file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
] ]
pyyaml = [ pyyaml = [
{file = "PyYAML-5.2-cp27-cp27m-win32.whl", hash = "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc"},
{file = "PyYAML-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"},
{file = "PyYAML-5.2-cp35-cp35m-win32.whl", hash = "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15"},
{file = "PyYAML-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075"},
{file = "PyYAML-5.2-cp36-cp36m-win32.whl", hash = "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31"},
{file = "PyYAML-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc"},
{file = "PyYAML-5.2-cp37-cp37m-win32.whl", hash = "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04"},
{file = "PyYAML-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd"},
{file = "PyYAML-5.2-cp38-cp38-win32.whl", hash = "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f"},
{file = "PyYAML-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803"},
{file = "PyYAML-5.2.tar.gz", hash = "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c"},
{file = "PyYAML-5.3-cp27-cp27m-win32.whl", hash = "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d"}, {file = "PyYAML-5.3-cp27-cp27m-win32.whl", hash = "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d"},
{file = "PyYAML-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6"}, {file = "PyYAML-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6"},
{file = "PyYAML-5.3-cp35-cp35m-win32.whl", hash = "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e"}, {file = "PyYAML-5.3-cp35-cp35m-win32.whl", hash = "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e"},
...@@ -2027,8 +1776,6 @@ regex = [ ...@@ -2027,8 +1776,6 @@ regex = [
{file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"},
] ]
requests = [ requests = [
{file = "requests-2.21.0-py2.py3-none-any.whl", hash = "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"},
{file = "requests-2.21.0.tar.gz", hash = "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e"},
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
] ]
...@@ -2095,8 +1842,6 @@ tornado = [ ...@@ -2095,8 +1842,6 @@ tornado = [
{file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"}, {file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"},
] ]
tox = [ tox = [
{file = "tox-3.12.1-py2.py3-none-any.whl", hash = "sha256:f5c8e446b51edd2ea97df31d4ded8c8b72e7d6c619519da6bb6084b9dd5770f9"},
{file = "tox-3.12.1.tar.gz", hash = "sha256:f87fd33892a2df0950e5e034def9468988b8d008c7e9416be665fcc0dd45b14f"},
{file = "tox-3.14.5-py2.py3-none-any.whl", hash = "sha256:0cbe98369081fa16bd6f1163d3d0b2a62afa29d402ccfad2bd09fb2668be0956"}, {file = "tox-3.14.5-py2.py3-none-any.whl", hash = "sha256:0cbe98369081fa16bd6f1163d3d0b2a62afa29d402ccfad2bd09fb2668be0956"},
{file = "tox-3.14.5.tar.gz", hash = "sha256:676f1e3e7de245ad870f956436b84ea226210587d1f72c8dfb8cd5ac7b6f0e70"}, {file = "tox-3.14.5.tar.gz", hash = "sha256:676f1e3e7de245ad870f956436b84ea226210587d1f72c8dfb8cd5ac7b6f0e70"},
] ]
...@@ -2134,8 +1879,6 @@ typing-extensions = [ ...@@ -2134,8 +1879,6 @@ typing-extensions = [
{file = "typing_extensions-3.7.4.1.tar.gz", hash = "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2"}, {file = "typing_extensions-3.7.4.1.tar.gz", hash = "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2"},
] ]
urllib3 = [ urllib3 = [
{file = "urllib3-1.24.3-py2.py3-none-any.whl", hash = "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"},
{file = "urllib3-1.24.3.tar.gz", hash = "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4"},
{file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
{file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
] ]
......
import os from pkgutil import extend_path
import sys
from .__version__ import __version__ # noqa
__path__ = extend_path(__path__, __name__)
_ROOT = os.path.dirname(os.path.realpath(__file__))
_VENDOR = os.path.join(_ROOT, "_vendor")
_CURRENT_VENDOR = os.path.join(
_VENDOR, "py{}".format(".".join(str(v) for v in sys.version_info[:2]))
)
# Add vendored dependencies to path.
sys.path.insert(0, _CURRENT_VENDOR)
from cleo import Application as BaseApplication from cleo import Application as BaseApplication
from poetry import __version__ from poetry.__version__ import __version__
from .commands.about import AboutCommand from .commands.about import AboutCommand
from .commands.add import AddCommand from .commands.add import AddCommand
......
...@@ -56,7 +56,7 @@ If you do not specify a version constraint, poetry will choose a suitable one ba ...@@ -56,7 +56,7 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
def handle(self): def handle(self):
from poetry.installation.installer import Installer from poetry.installation.installer import Installer
from poetry.semver import parse_constraint from poetry.core.semver import parse_constraint
from tomlkit import inline_table from tomlkit import inline_table
packages = self.argument("name") packages = self.argument("name")
......
...@@ -13,7 +13,7 @@ class BuildCommand(EnvCommand): ...@@ -13,7 +13,7 @@ class BuildCommand(EnvCommand):
] ]
def handle(self): def handle(self):
from poetry.masonry import Builder from poetry.core.masonry import Builder
fmt = "all" fmt = "all"
if self.option("format"): if self.option("format"):
...@@ -26,5 +26,5 @@ class BuildCommand(EnvCommand): ...@@ -26,5 +26,5 @@ class BuildCommand(EnvCommand):
) )
) )
builder = Builder(self.poetry, self.env, self.io) builder = Builder(self.poetry)
builder.build(fmt) builder.build(fmt)
...@@ -29,7 +29,7 @@ class DebugResolveCommand(InitCommand): ...@@ -29,7 +29,7 @@ class DebugResolveCommand(InitCommand):
def handle(self): def handle(self):
from poetry.io.null_io import NullIO from poetry.io.null_io import NullIO
from poetry.packages import ProjectPackage from poetry.core.packages import ProjectPackage
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
......
...@@ -64,7 +64,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -64,7 +64,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
from poetry.layouts import layout from poetry.layouts import layout
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import SystemEnv from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig from poetry.core.vcs.git import GitConfig
if (Path.cwd() / "pyproject.toml").exists(): if (Path.cwd() / "pyproject.toml").exists():
self.line("<error>A pyproject.toml file already exists.</error>") self.line("<error>A pyproject.toml file already exists.</error>")
...@@ -367,8 +367,8 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -367,8 +367,8 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
if url_parsed.scheme and url_parsed.netloc: if url_parsed.scheme and url_parsed.netloc:
# Url # Url
if url_parsed.scheme in ["git+https", "git+ssh"]: if url_parsed.scheme in ["git+https", "git+ssh"]:
from poetry.vcs.git import Git from poetry.core.vcs.git import Git
from poetry.vcs.git import ParsedUrl from poetry.core.vcs.git import ParsedUrl
parsed = ParsedUrl.parse(requirement) parsed = ParsedUrl.parse(requirement)
url = Git.normalize_url(requirement) url = Git.normalize_url(requirement)
...@@ -481,7 +481,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -481,7 +481,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
return requires return requires
def _validate_author(self, author, default): def _validate_author(self, author, default):
from poetry.packages.package import AUTHOR_REGEX from poetry.core.packages.package import AUTHOR_REGEX
author = author or default author = author or default
...@@ -498,7 +498,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -498,7 +498,7 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
return author return author
def _validate_license(self, license): def _validate_license(self, license):
from poetry.spdx import license_by_id from poetry.core.spdx import license_by_id
if license: if license:
license_by_id(license) license_by_id(license)
......
...@@ -42,7 +42,7 @@ exist it will look for <comment>pyproject.toml</> and do the same. ...@@ -42,7 +42,7 @@ exist it will look for <comment>pyproject.toml</> and do the same.
from clikit.io import NullIO from clikit.io import NullIO
from poetry.installation.installer import Installer from poetry.installation.installer import Installer
from poetry.masonry.builders import EditableBuilder from poetry.masonry.builders import EditableBuilder
from poetry.masonry.utils.module import ModuleOrPackageNotFound from poetry.core.masonry.utils.module import ModuleOrPackageNotFound
installer = Installer( installer = Installer(
self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool self.io, self.env, self.poetry.package, self.poetry.locker, self.poetry.pool
......
...@@ -21,10 +21,10 @@ class NewCommand(Command): ...@@ -21,10 +21,10 @@ class NewCommand(Command):
def handle(self): def handle(self):
from poetry.layouts import layout from poetry.layouts import layout
from poetry.semver import parse_constraint from poetry.core.semver import parse_constraint
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import SystemEnv from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig from poetry.core.vcs.git import GitConfig
if self.option("src"): if self.option("src"):
layout_ = layout("src") layout_ = layout("src")
......
...@@ -40,7 +40,7 @@ the config command. ...@@ -40,7 +40,7 @@ the config command.
loggers = ["poetry.masonry.publishing.publisher"] loggers = ["poetry.masonry.publishing.publisher"]
def handle(self): def handle(self):
from poetry.masonry.publishing.publisher import Publisher from poetry.publishing.publisher import Publisher
publisher = Publisher(self.poetry, self.io) publisher = Publisher(self.poetry, self.io)
......
...@@ -54,7 +54,7 @@ class SelfUpdateCommand(Command): ...@@ -54,7 +54,7 @@ class SelfUpdateCommand(Command):
def handle(self): def handle(self):
from poetry.__version__ import __version__ from poetry.__version__ import __version__
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
from poetry.semver import Version from poetry.core.semver import Version
from poetry.utils._compat import Path from poetry.utils._compat import Path
current = Path(__file__) current = Path(__file__)
......
...@@ -35,7 +35,7 @@ lists all packages available.""" ...@@ -35,7 +35,7 @@ lists all packages available."""
def handle(self): def handle(self):
from clikit.utils.terminal import Terminal from clikit.utils.terminal import Terminal
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.semver import Version from poetry.core.semver import Version
package = self.argument("package") package = self.argument("package")
...@@ -347,7 +347,7 @@ lists all packages available.""" ...@@ -347,7 +347,7 @@ lists all packages available."""
return selector.find_best_candidate(name, ">={}".format(package.pretty_version)) return selector.find_best_candidate(name, ">={}".format(package.pretty_version))
def get_update_status(self, latest, package): def get_update_status(self, latest, package):
from poetry.semver import parse_constraint from poetry.core.semver import parse_constraint
if latest.full_pretty_version == package.full_pretty_version: if latest.full_pretty_version == package.full_pretty_version:
return "up-to-date" return "up-to-date"
......
...@@ -65,7 +65,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -65,7 +65,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
) )
def increment_version(self, version, rule): def increment_version(self, version, rule):
from poetry.semver import Version from poetry.core.semver import Version
try: try:
version = Version.parse(version) version = Version.parse(version)
......
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import shutil
from typing import Dict from typing import Dict
from typing import List
from typing import Optional from typing import Optional
from clikit.api.io.io import IO from clikit.api.io.io import IO
from poetry.core.factory import Factory as BaseFactory
from poetry.core.utils.toml_file import TomlFile
from .config.config import Config from .config.config import Config
from .config.file_config_source import FileConfigSource from .config.file_config_source import FileConfigSource
from .io.null_io import NullIO from .io.null_io import NullIO
from .json import validate_object
from .locations import CONFIG_DIR from .locations import CONFIG_DIR
from .packages.dependency import Dependency
from .packages.locker import Locker from .packages.locker import Locker
from .packages.project_package import ProjectPackage
from .poetry import Poetry from .poetry import Poetry
from .repositories.pypi_repository import PyPiRepository from .repositories.pypi_repository import PyPiRepository
from .spdx import license_by_id
from .utils._compat import Path from .utils._compat import Path
from .utils.toml_file import TomlFile
class Factory: class Factory(BaseFactory):
""" """
Factory class to create various elements needed by Poetry. Factory class to create various elements needed by Poetry.
""" """
...@@ -35,125 +30,17 @@ class Factory: ...@@ -35,125 +30,17 @@ class Factory:
if io is None: if io is None:
io = NullIO() io = NullIO()
poetry_file = self.locate(cwd) base_poetry = super(Factory, self).create_poetry(cwd)
local_config = TomlFile(poetry_file.as_posix()).read()
if "tool" not in local_config or "poetry" not in local_config["tool"]:
raise RuntimeError(
"[tool.poetry] section not found in {}".format(poetry_file.name)
)
local_config = local_config["tool"]["poetry"]
# Checking validity
check_result = self.validate(local_config)
if check_result["errors"]:
message = ""
for error in check_result["errors"]:
message += " - {}\n".format(error)
raise RuntimeError("The Poetry configuration is invalid:\n" + message)
# Load package
name = local_config["name"]
version = local_config["version"]
package = ProjectPackage(name, version, version)
package.root_dir = poetry_file.parent
for author in local_config["authors"]:
package.authors.append(author)
for maintainer in local_config.get("maintainers", []):
package.maintainers.append(maintainer)
package.description = local_config.get("description", "")
package.homepage = local_config.get("homepage")
package.repository_url = local_config.get("repository")
package.documentation_url = local_config.get("documentation")
try:
license_ = license_by_id(local_config.get("license", ""))
except ValueError:
license_ = None
package.license = license_
package.keywords = local_config.get("keywords", [])
package.classifiers = local_config.get("classifiers", [])
if "readme" in local_config:
package.readme = Path(poetry_file.parent) / local_config["readme"]
if "platform" in local_config:
package.platform = local_config["platform"]
if "dependencies" in local_config:
for name, constraint in local_config["dependencies"].items():
if name.lower() == "python":
package.python_versions = constraint
continue
if isinstance(constraint, list):
for _constraint in constraint:
package.add_dependency(name, _constraint)
continue
package.add_dependency(name, constraint)
if "dev-dependencies" in local_config:
for name, constraint in local_config["dev-dependencies"].items():
if isinstance(constraint, list):
for _constraint in constraint:
package.add_dependency(name, _constraint, category="dev")
continue
package.add_dependency(name, constraint, category="dev")
extras = local_config.get("extras", {})
for extra_name, requirements in extras.items():
package.extras[extra_name] = []
# Checking for dependency
for req in requirements:
req = Dependency(req, "*")
for dep in package.requires:
if dep.name == req.name:
dep.in_extras.append(extra_name)
package.extras[extra_name].append(dep)
break
if "build" in local_config:
package.build = local_config["build"]
if "include" in local_config:
package.include = local_config["include"]
if "exclude" in local_config: locker = Locker(
package.exclude = local_config["exclude"] base_poetry.file.parent / "poetry.lock", base_poetry.local_config
)
if "packages" in local_config:
package.packages = local_config["packages"]
# Custom urls
if "urls" in local_config:
package.custom_urls = local_config["urls"]
# Moving lock if necessary (pyproject.lock -> poetry.lock)
lock = poetry_file.parent / "poetry.lock"
if not lock.exists():
# Checking for pyproject.lock
old_lock = poetry_file.with_suffix(".lock")
if old_lock.exists():
shutil.move(str(old_lock), str(lock))
locker = Locker(poetry_file.parent / "poetry.lock", local_config)
# Loading global configuration # Loading global configuration
config = self.create_config(io) config = self.create_config(io)
# Loading local configuration # Loading local configuration
local_config_file = TomlFile(poetry_file.parent / "poetry.toml") local_config_file = TomlFile(base_poetry.file.parent / "poetry.toml")
if local_config_file.exists(): if local_config_file.exists():
if io.is_debug(): if io.is_debug():
io.write_line( io.write_line(
...@@ -162,10 +49,16 @@ class Factory: ...@@ -162,10 +49,16 @@ class Factory:
config.merge(local_config_file.read()) config.merge(local_config_file.read())
poetry = Poetry(poetry_file, local_config, package, locker, config) poetry = Poetry(
base_poetry.file.path,
base_poetry.local_config,
base_poetry.package,
locker,
config,
)
# Configuring sources # Configuring sources
for source in local_config.get("source", []): for source in poetry.local_config.get("source", []):
repository = self.create_legacy_repository(source, config) repository = self.create_legacy_repository(source, config)
is_default = source.get("default", False) is_default = source.get("default", False)
is_secondary = source.get("secondary", False) is_secondary = source.get("secondary", False)
...@@ -259,82 +152,3 @@ class Factory: ...@@ -259,82 +152,3 @@ class Factory:
cert=get_cert(auth_config, name), cert=get_cert(auth_config, name),
client_cert=get_client_cert(auth_config, name), client_cert=get_client_cert(auth_config, name),
) )
@classmethod
def validate(
cls, config, strict=False
): # type: (dict, bool) -> Dict[str, List[str]]
"""
Checks the validity of a configuration
"""
result = {"errors": [], "warnings": []}
# Schema validation errors
validation_errors = validate_object(config, "poetry-schema")
result["errors"] += validation_errors
if strict:
# If strict, check the file more thoroughly
# Checking license
license = config.get("license")
if license:
try:
license_by_id(license)
except ValueError:
result["errors"].append("{} is not a valid license".format(license))
if "dependencies" in config:
python_versions = config["dependencies"]["python"]
if python_versions == "*":
result["warnings"].append(
"A wildcard Python dependency is ambiguous. "
"Consider specifying a more explicit one."
)
for name, constraint in config["dependencies"].items():
if not isinstance(constraint, dict):
continue
if "allows-prereleases" in constraint:
result["warnings"].append(
'The "{}" dependency specifies '
'the "allows-prereleases" property, which is deprecated. '
'Use "allow-prereleases" instead.'.format(name)
)
# Checking for scripts with extras
if "scripts" in config:
scripts = config["scripts"]
for name, script in scripts.items():
if not isinstance(script, dict):
continue
extras = script["extras"]
for extra in extras:
if extra not in config["extras"]:
result["errors"].append(
'Script "{}" requires extra "{}" which is not defined.'.format(
name, extra
)
)
return result
@classmethod
def locate(cls, cwd): # type: (Path) -> Path
candidates = [Path(cwd)]
candidates.extend(Path(cwd).parents)
for path in candidates:
poetry_file = path / "pyproject.toml"
if poetry_file.exists():
return poetry_file
else:
raise RuntimeError(
"Poetry could not find a pyproject.toml file in {} or its parents".format(
cwd
)
)
...@@ -4,8 +4,9 @@ from typing import Union ...@@ -4,8 +4,9 @@ from typing import Union
from clikit.api.io import IO from clikit.api.io import IO
from clikit.io import NullIO from clikit.io import NullIO
from poetry.core.packages.package import Package
from poetry.core.semver import parse_constraint
from poetry.packages import Locker from poetry.packages import Locker
from poetry.packages import Package
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.puzzle.operations import Install from poetry.puzzle.operations import Install
from poetry.puzzle.operations import Uninstall from poetry.puzzle.operations import Uninstall
...@@ -14,7 +15,6 @@ from poetry.puzzle.operations.operation import Operation ...@@ -14,7 +15,6 @@ from poetry.puzzle.operations.operation import Operation
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.semver import parse_constraint
from poetry.utils.extras import get_extra_package_names from poetry.utils.extras import get_extra_package_names
from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import canonicalize_name
......
...@@ -176,7 +176,7 @@ class PipInstaller(BaseInstaller): ...@@ -176,7 +176,7 @@ class PipInstaller(BaseInstaller):
return name return name
def install_directory(self, package): def install_directory(self, package):
from poetry.masonry.builder import SdistBuilder from poetry.core.masonry.builder import SdistBuilder
from poetry.factory import Factory from poetry.factory import Factory
from poetry.utils._compat import decode from poetry.utils._compat import decode
from poetry.utils.env import NullEnv from poetry.utils.env import NullEnv
...@@ -229,8 +229,8 @@ class PipInstaller(BaseInstaller): ...@@ -229,8 +229,8 @@ class PipInstaller(BaseInstaller):
os.remove(setup) os.remove(setup)
def install_git(self, package): def install_git(self, package):
from poetry.packages import Package from poetry.core.packages import Package
from poetry.vcs import Git from poetry.core.vcs import Git
src_dir = self._env.path / "src" / package.name src_dir = self._env.path / "src" / package.name
if src_dir.exists(): if src_dir.exists():
......
...@@ -38,7 +38,7 @@ license = "" ...@@ -38,7 +38,7 @@ license = ""
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
""" """
BUILD_SYSTEM_MIN_VERSION = "0.12" BUILD_SYSTEM_MIN_VERSION = "1.0.0a5"
BUILD_SYSTEM_MAX_VERSION = None BUILD_SYSTEM_MAX_VERSION = None
...@@ -109,8 +109,8 @@ class Layout(object): ...@@ -109,8 +109,8 @@ class Layout(object):
if BUILD_SYSTEM_MAX_VERSION is not None: if BUILD_SYSTEM_MAX_VERSION is not None:
build_system_version += ",<" + BUILD_SYSTEM_MAX_VERSION build_system_version += ",<" + BUILD_SYSTEM_MAX_VERSION
build_system.add("requires", ["poetry" + build_system_version]) build_system.add("requires", ["poetry-core" + build_system_version])
build_system.add("build-backend", "poetry.masonry.api") build_system.add("build-backend", "poetry.core.masonry.api")
content.add("build-system", build_system) content.add("build-system", build_system)
......
"""
This module handles the packaging and publishing
of python projects.
A lot of the code used here has been taken from
`flit <https://github.com/takluyver/flit>`__ and adapted
to work with the poetry codebase, so kudos to them for showing the way.
"""
from .builder import Builder
""" from poetry.core.masonry.api import build_sdist
PEP-517 compliant buildsystem API from poetry.core.masonry.api import build_wheel
""" from poetry.core.masonry.api import get_requires_for_build_sdist
import logging from poetry.core.masonry.api import get_requires_for_build_wheel
import sys from poetry.core.masonry.api import prepare_metadata_for_build_wheel
from clikit.io import NullIO
__all__ = [
from poetry.factory import Factory "build_sdist",
from poetry.utils._compat import Path "build_wheel",
from poetry.utils._compat import unicode "get_requires_for_build_sdist",
from poetry.utils.env import SystemEnv "get_requires_for_build_wheel",
"prepare_metadata_for_build_wheel",
from .builders.sdist import SdistBuilder ]
from .builders.wheel import WheelBuilder
log = logging.getLogger(__name__)
def get_requires_for_build_wheel(config_settings=None):
"""
Returns an additional list of requirements for building, as PEP508 strings,
above and beyond those specified in the pyproject.toml file.
This implementation is optional. At the moment it only returns an empty list, which would be the same as if
not define. So this is just for completeness for future implementation.
"""
return []
# For now, we require all dependencies to build either a wheel or an sdist.
get_requires_for_build_sdist = get_requires_for_build_wheel
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
poetry = Factory().create_poetry(Path("."))
builder = WheelBuilder(poetry, SystemEnv(Path(sys.prefix)), NullIO())
dist_info = Path(metadata_directory, builder.dist_info)
dist_info.mkdir(parents=True, exist_ok=True)
if "scripts" in poetry.local_config or "plugins" in poetry.local_config:
with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f:
builder._write_entry_points(f)
with (dist_info / "WHEEL").open("w", encoding="utf-8") as f:
builder._write_wheel_file(f)
with (dist_info / "METADATA").open("w", encoding="utf-8") as f:
builder._write_metadata_file(f)
return dist_info.name
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
"""Builds a wheel, places it in wheel_directory"""
poetry = Factory().create_poetry(Path("."))
return unicode(
WheelBuilder.make_in(
poetry, SystemEnv(Path(sys.prefix)), NullIO(), Path(wheel_directory)
)
)
def build_sdist(sdist_directory, config_settings=None):
"""Builds an sdist, places it in sdist_directory"""
poetry = Factory().create_poetry(Path("."))
path = SdistBuilder(poetry, SystemEnv(Path(sys.prefix)), NullIO()).build(
Path(sdist_directory)
)
return unicode(path.name)
from .builders.complete import CompleteBuilder
from .builders.sdist import SdistBuilder
from .builders.wheel import WheelBuilder
class Builder:
_FORMATS = {"sdist": SdistBuilder, "wheel": WheelBuilder, "all": CompleteBuilder}
def __init__(self, poetry, env, io):
self._poetry = poetry
self._env = env
self._io = io
def build(self, fmt):
if fmt not in self._FORMATS:
raise ValueError("Invalid format: {}".format(fmt))
builder = self._FORMATS[fmt](self._poetry, self._env, self._io)
return builder.build()
from .complete import CompleteBuilder
from .editable import EditableBuilder from .editable import EditableBuilder
from .sdist import SdistBuilder
from .wheel import WheelBuilder
# -*- coding: utf-8 -*-
import re
import shutil
import tempfile
from collections import defaultdict
from contextlib import contextmanager
from typing import Set
from typing import Union
from clikit.api.io.flags import VERY_VERBOSE
from poetry.utils._compat import Path
from poetry.utils._compat import glob
from poetry.utils._compat import lru_cache
from poetry.utils._compat import to_str
from poetry.vcs import get_vcs
from ..metadata import Metadata
from ..utils.module import Module
from ..utils.package_include import PackageInclude
AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+) <(?P<email>.+?)>$")
METADATA_BASE = """\
Metadata-Version: 2.1
Name: {name}
Version: {version}
Summary: {summary}
"""
class Builder(object):
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"}
format = None
def __init__(
self, poetry, env, io, ignore_packages_formats=False
): # type: ("Poetry", "Env", "IO", bool) -> None
self._poetry = poetry
self._env = env
self._io = io
self._package = poetry.package
self._path = poetry.file.parent
self._original_path = self._path
packages = []
for p in self._package.packages:
formats = p.get("format", [])
if not isinstance(formats, list):
formats = [formats]
if (
formats
and self.format
and self.format not in formats
and not ignore_packages_formats
):
continue
packages.append(p)
self._module = Module(
self._package.name,
self._path.as_posix(),
packages=packages,
includes=self._package.include,
)
self._meta = Metadata.from_package(self._package)
def build(self):
raise NotImplementedError()
@lru_cache(maxsize=None)
def find_excluded_files(self): # type: () -> Set[str]
# Checking VCS
vcs = get_vcs(self._original_path)
if not vcs:
vcs_ignored_files = set()
else:
vcs_ignored_files = set(vcs.get_ignored_files())
explicitely_excluded = set()
for excluded_glob in self._package.exclude:
for excluded in glob(
Path(self._path, excluded_glob).as_posix(), recursive=True
):
explicitely_excluded.add(
Path(excluded).relative_to(self._path).as_posix()
)
ignored = vcs_ignored_files | explicitely_excluded
result = set()
for file in ignored:
result.add(file)
# The list of excluded files might be big and we will do a lot
# containment check (x in excluded).
# Returning a set make those tests much much faster.
return result
def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool
exclude_path = Path(filepath)
while True:
if exclude_path.as_posix() in self.find_excluded_files():
return True
if len(exclude_path.parts) > 1:
exclude_path = exclude_path.parent
else:
break
return False
def find_files_to_add(self, exclude_build=True): # type: (bool) -> list
"""
Finds all files to add to the tarball
"""
to_add = []
for include in self._module.includes:
for file in include.elements:
if "__pycache__" in str(file):
continue
if file.is_dir():
continue
file = file.relative_to(self._path)
if self.is_excluded(file) and isinstance(include, PackageInclude):
continue
if file.suffix == ".pyc":
continue
if file in to_add:
# Skip duplicates
continue
self._io.write_line(
" - Adding: <comment>{}</comment>".format(str(file)), VERY_VERBOSE
)
to_add.append(file)
# Include project files
self._io.write_line(
" - Adding: <comment>pyproject.toml</comment>", VERY_VERBOSE
)
to_add.append(Path("pyproject.toml"))
# If a license file exists, add it
for license_file in self._path.glob("LICENSE*"):
self._io.write_line(
" - Adding: <comment>{}</comment>".format(
license_file.relative_to(self._path)
),
VERY_VERBOSE,
)
to_add.append(license_file.relative_to(self._path))
# If a README is specified we need to include it
# to avoid errors
if "readme" in self._poetry.local_config:
readme = self._path / self._poetry.local_config["readme"]
if readme.exists():
self._io.write_line(
" - Adding: <comment>{}</comment>".format(
readme.relative_to(self._path)
),
VERY_VERBOSE,
)
to_add.append(readme.relative_to(self._path))
# If a build script is specified and explicitely required
# we add it to the list of files
if self._package.build and not exclude_build:
to_add.append(Path(self._package.build))
return sorted(to_add)
def get_metadata_content(self): # type: () -> bytes
content = METADATA_BASE.format(
name=self._meta.name,
version=self._meta.version,
summary=to_str(self._meta.summary),
)
# Optional fields
if self._meta.home_page:
content += "Home-page: {}\n".format(self._meta.home_page)
if self._meta.license:
content += "License: {}\n".format(self._meta.license)
if self._meta.keywords:
content += "Keywords: {}\n".format(self._meta.keywords)
if self._meta.author:
content += "Author: {}\n".format(to_str(self._meta.author))
if self._meta.author_email:
content += "Author-email: {}\n".format(to_str(self._meta.author_email))
if self._meta.maintainer:
content += "Maintainer: {}\n".format(to_str(self._meta.maintainer))
if self._meta.maintainer_email:
content += "Maintainer-email: {}\n".format(
to_str(self._meta.maintainer_email)
)
if self._meta.requires_python:
content += "Requires-Python: {}\n".format(self._meta.requires_python)
for classifier in self._meta.classifiers:
content += "Classifier: {}\n".format(classifier)
for extra in sorted(self._meta.provides_extra):
content += "Provides-Extra: {}\n".format(extra)
for dep in sorted(self._meta.requires_dist):
content += "Requires-Dist: {}\n".format(dep)
for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
content += "Project-URL: {}\n".format(to_str(url))
if self._meta.description_content_type:
content += "Description-Content-Type: {}\n".format(
self._meta.description_content_type
)
if self._meta.description is not None:
content += "\n" + to_str(self._meta.description) + "\n"
return content
def convert_entry_points(self): # type: () -> dict
result = defaultdict(list)
# Scripts -> Entry points
for name, ep in self._poetry.local_config.get("scripts", {}).items():
extras = ""
if isinstance(ep, dict):
extras = "[{}]".format(", ".join(ep["extras"]))
ep = ep["callable"]
result["console_scripts"].append("{} = {}{}".format(name, ep, extras))
# Plugins -> entry points
plugins = self._poetry.local_config.get("plugins", {})
for groupname, group in plugins.items():
for name, ep in sorted(group.items()):
result[groupname].append("{} = {}".format(name, ep))
for groupname in result:
result[groupname] = sorted(result[groupname])
return dict(result)
@classmethod
def convert_author(cls, author): # type: (...) -> dict
m = AUTHOR_REGEX.match(author)
name = m.group("name")
email = m.group("email")
return {"name": name, "email": email}
@classmethod
@contextmanager
def temporary_directory(cls, *args, **kwargs):
try:
from tempfile import TemporaryDirectory
with TemporaryDirectory(*args, **kwargs) as name:
yield name
except ImportError:
name = tempfile.mkdtemp(*args, **kwargs)
yield name
shutil.rmtree(name)
import os
import tarfile
from contextlib import contextmanager
from poetry.factory import Factory
from poetry.io.null_io import NullIO
from poetry.utils._compat import Path
from poetry.utils.helpers import temporary_directory
from .builder import Builder
from .sdist import SdistBuilder
from .wheel import WheelBuilder
class CompleteBuilder(Builder):
def build(self):
# We start by building the tarball
# We will use it to build the wheel
sdist_builder = SdistBuilder(self._poetry, self._env, self._io)
build_for_all_formats = False
for p in self._package.packages:
formats = p.get("format", [])
if not isinstance(formats, list):
formats = [formats]
if formats and sdist_builder.format not in formats:
build_for_all_formats = True
break
sdist_file = sdist_builder.build()
self._io.write_line("")
dist_dir = self._path / "dist"
if build_for_all_formats:
sdist_builder = SdistBuilder(
self._poetry, self._env, NullIO(), ignore_packages_formats=True
)
with temporary_directory() as tmp_dir:
sdist_file = sdist_builder.build(Path(tmp_dir))
with self.unpacked_tarball(sdist_file) as tmpdir:
WheelBuilder.make_in(
Factory().create_poetry(tmpdir),
self._env,
self._io,
dist_dir,
original=self._poetry,
)
else:
with self.unpacked_tarball(sdist_file) as tmpdir:
WheelBuilder.make_in(
Factory().create_poetry(tmpdir),
self._env,
self._io,
dist_dir,
original=self._poetry,
)
@classmethod
@contextmanager
def unpacked_tarball(cls, path):
tf = tarfile.open(str(path))
with cls.temporary_directory() as tmpdir:
tf.extractall(tmpdir)
files = os.listdir(tmpdir)
assert len(files) == 1, files
yield Path(tmpdir) / files[0]
...@@ -5,19 +5,24 @@ import shutil ...@@ -5,19 +5,24 @@ import shutil
from collections import defaultdict from collections import defaultdict
from poetry.semver.version import Version from poetry.core.masonry.builders.builder import Builder
from poetry.core.masonry.builders.sdist import SdistBuilder
from poetry.core.semver.version import Version
from poetry.utils._compat import decode from poetry.utils._compat import decode
from .builder import Builder
from .sdist import SdistBuilder
class EditableBuilder(Builder): class EditableBuilder(Builder):
def __init__(self, poetry, env, io):
super(EditableBuilder, self).__init__(poetry)
self._env = env
self._io = io
def build(self): def build(self):
return self._setup_build() return self._setup_build()
def _setup_build(self): def _setup_build(self):
builder = SdistBuilder(self._poetry, self._env, self._io) builder = SdistBuilder(self._poetry)
setup = self._path / "setup.py" setup = self._path / "setup.py"
has_setup = setup.exists() has_setup = setup.exists()
......
# -*- coding: utf-8 -*-
import os
import re
import tarfile
import time
from collections import defaultdict
from copy import copy
from gzip import GzipFile
from io import BytesIO
from posixpath import join as pjoin
from pprint import pformat
from poetry.utils._compat import Path
from poetry.utils._compat import encode
from poetry.utils._compat import to_str
from ..utils.helpers import normalize_file_permissions
from ..utils.package_include import PackageInclude
from .builder import Builder
SETUP = """\
# -*- coding: utf-8 -*-
from setuptools import setup
{before}
setup_kwargs = {{
'name': {name!r},
'version': {version!r},
'description': {description!r},
'long_description': {long_description!r},
'author': {author!r},
'author_email': {author_email!r},
'maintainer': {maintainer!r},
'maintainer_email': {maintainer_email!r},
'url': {url!r},
{extra}
}}
{after}
setup(**setup_kwargs)
"""
class SdistBuilder(Builder):
format = "sdist"
def build(self, target_dir=None): # type: (Path) -> Path
self._io.write_line(" - Building <info>sdist</info>")
if target_dir is None:
target_dir = self._path / "dist"
if not target_dir.exists():
target_dir.mkdir(parents=True)
target = target_dir / "{}-{}.tar.gz".format(
self._package.pretty_name, self._meta.version
)
gz = GzipFile(target.as_posix(), mode="wb")
tar = tarfile.TarFile(
target.as_posix(), mode="w", fileobj=gz, format=tarfile.PAX_FORMAT
)
try:
tar_dir = "{}-{}".format(self._package.pretty_name, self._meta.version)
files_to_add = self.find_files_to_add(exclude_build=False)
for relpath in files_to_add:
path = self._path / relpath
tar_info = tar.gettarinfo(
str(path), arcname=pjoin(tar_dir, str(relpath))
)
tar_info = self.clean_tarinfo(tar_info)
if tar_info.isreg():
with path.open("rb") as f:
tar.addfile(tar_info, f)
else:
tar.addfile(tar_info) # Symlinks & ?
setup = self.build_setup()
tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py"))
tar_info.size = len(setup)
tar_info.mtime = time.time()
tar.addfile(tar_info, BytesIO(setup))
pkg_info = self.build_pkg_info()
tar_info = tarfile.TarInfo(pjoin(tar_dir, "PKG-INFO"))
tar_info.size = len(pkg_info)
tar_info.mtime = time.time()
tar.addfile(tar_info, BytesIO(pkg_info))
finally:
tar.close()
gz.close()
self._io.write_line(" - Built <comment>{}</comment>".format(target.name))
return target
def build_setup(self): # type: () -> bytes
before, extra, after = [], [], []
package_dir = {}
# If we have a build script, use it
if self._package.build:
after += [
"from {} import *".format(self._package.build.split(".")[0]),
"build(setup_kwargs)",
]
modules = []
packages = []
package_data = {}
for include in self._module.includes:
if include.formats and "sdist" not in include.formats:
continue
if isinstance(include, PackageInclude):
if include.is_package():
pkg_dir, _packages, _package_data = self.find_packages(include)
if pkg_dir is not None:
package_dir[""] = os.path.relpath(pkg_dir, str(self._path))
packages += [p for p in _packages if p not in packages]
package_data.update(_package_data)
else:
module = include.elements[0].relative_to(include.base).stem
if include.source is not None:
package_dir[""] = str(include.base.relative_to(self._path))
if module not in modules:
modules.append(module)
else:
pass
if package_dir:
before.append("package_dir = \\\n{}\n".format(pformat(package_dir)))
extra.append("'package_dir': package_dir,")
if packages:
before.append("packages = \\\n{}\n".format(pformat(sorted(packages))))
extra.append("'packages': packages,")
if package_data:
before.append("package_data = \\\n{}\n".format(pformat(package_data)))
extra.append("'package_data': package_data,")
if modules:
before.append("modules = \\\n{}".format(pformat(modules)))
extra.append("'py_modules': modules,".format())
dependencies, extras = self.convert_dependencies(
self._package, self._package.requires
)
if dependencies:
before.append(
"install_requires = \\\n{}\n".format(pformat(sorted(dependencies)))
)
extra.append("'install_requires': install_requires,")
if extras:
before.append("extras_require = \\\n{}\n".format(pformat(extras)))
extra.append("'extras_require': extras_require,")
entry_points = self.convert_entry_points()
if entry_points:
before.append("entry_points = \\\n{}\n".format(pformat(entry_points)))
extra.append("'entry_points': entry_points,")
if self._package.python_versions != "*":
python_requires = self._meta.requires_python
extra.append("'python_requires': {!r},".format(python_requires))
return encode(
SETUP.format(
before="\n".join(before),
name=to_str(self._meta.name),
version=to_str(self._meta.version),
description=to_str(self._meta.summary),
long_description=to_str(self._meta.description),
author=to_str(self._meta.author),
author_email=to_str(self._meta.author_email),
maintainer=to_str(self._meta.maintainer),
maintainer_email=to_str(self._meta.maintainer_email),
url=to_str(self._meta.home_page),
extra="\n ".join(extra),
after="\n".join(after),
)
)
def build_pkg_info(self):
return encode(self.get_metadata_content())
def find_packages(self, include):
"""
Discover subpackages and data.
It also retrieves necessary files.
"""
pkgdir = None
if include.source is not None:
pkgdir = str(include.base)
base = str(include.elements[0].parent)
pkg_name = include.package
pkg_data = defaultdict(list)
# Undocumented distutils feature:
# the empty string matches all package names
pkg_data[""].append("*")
packages = [pkg_name]
subpkg_paths = set()
def find_nearest_pkg(rel_path):
parts = rel_path.split(os.sep)
for i in reversed(range(1, len(parts))):
ancestor = "/".join(parts[:i])
if ancestor in subpkg_paths:
pkg = ".".join([pkg_name] + parts[:i])
return pkg, "/".join(parts[i:])
# Relative to the top-level package
return pkg_name, Path(rel_path).as_posix()
for path, dirnames, filenames in os.walk(str(base), topdown=True):
if os.path.basename(path) == "__pycache__":
continue
from_top_level = os.path.relpath(path, base)
if from_top_level == ".":
continue
is_subpkg = any(
[filename.endswith(".py") for filename in filenames]
) and not all(
[
self.is_excluded(Path(path, filename).relative_to(self._path))
for filename in filenames
if filename.endswith(".py")
]
)
if is_subpkg:
subpkg_paths.add(from_top_level)
parts = from_top_level.split(os.sep)
packages.append(".".join([pkg_name] + parts))
else:
pkg, from_nearest_pkg = find_nearest_pkg(from_top_level)
data_elements = [
f.relative_to(self._path)
for f in Path(path).glob("*")
if not f.is_dir()
]
data = [e for e in data_elements if not self.is_excluded(e)]
if not data:
continue
if len(data) == len(data_elements):
pkg_data[pkg].append(pjoin(from_nearest_pkg, "*"))
else:
for d in data:
if d.is_dir():
continue
pkg_data[pkg] += [pjoin(from_nearest_pkg, d.name) for d in data]
# Sort values in pkg_data
pkg_data = {k: sorted(v) for (k, v) in pkg_data.items() if v}
return pkgdir, sorted(packages), pkg_data
@classmethod
def convert_dependencies(cls, package, dependencies):
main = []
extras = defaultdict(list)
req_regex = re.compile(r"^(.+) \((.+)\)$")
for dependency in dependencies:
if dependency.is_optional():
for extra_name, reqs in package.extras.items():
for req in reqs:
if req.name == dependency.name:
requirement = to_str(
dependency.to_pep_508(with_extras=False)
)
if ";" in requirement:
requirement, conditions = requirement.split(";")
requirement = requirement.strip()
if req_regex.match(requirement):
requirement = req_regex.sub(
"\\1\\2", requirement.strip()
)
extras[extra_name + ":" + conditions.strip()].append(
requirement
)
continue
requirement = requirement.strip()
if req_regex.match(requirement):
requirement = req_regex.sub(
"\\1\\2", requirement.strip()
)
extras[extra_name].append(requirement)
continue
requirement = to_str(dependency.to_pep_508())
if ";" in requirement:
requirement, conditions = requirement.split(";")
requirement = requirement.strip()
if req_regex.match(requirement):
requirement = req_regex.sub("\\1\\2", requirement.strip())
extras[":" + conditions.strip()].append(requirement)
continue
requirement = requirement.strip()
if req_regex.match(requirement):
requirement = req_regex.sub("\\1\\2", requirement.strip())
main.append(requirement)
return main, dict(extras)
@classmethod
def clean_tarinfo(cls, tar_info):
"""
Clean metadata from a TarInfo object to make it more reproducible.
- Set uid & gid to 0
- Set uname and gname to ""
- Normalise permissions to 644 or 755
- Set mtime if not None
"""
ti = copy(tar_info)
ti.uid = 0
ti.gid = 0
ti.uname = ""
ti.gname = ""
ti.mode = normalize_file_permissions(ti.mode)
return ti
from __future__ import unicode_literals
import contextlib
import hashlib
import os
import shutil
import stat
import tempfile
import zipfile
from base64 import urlsafe_b64encode
from io import StringIO
from clikit.api.io.flags import VERY_VERBOSE
from poetry.__version__ import __version__
from poetry.semver import parse_constraint
from poetry.utils._compat import decode
from ..utils.helpers import escape_name
from ..utils.helpers import escape_version
from ..utils.helpers import normalize_file_permissions
from ..utils.package_include import PackageInclude
from ..utils.tags import get_abbr_impl
from ..utils.tags import get_abi_tag
from ..utils.tags import get_impl_ver
from ..utils.tags import get_platform
from .builder import Builder
wheel_file_template = """\
Wheel-Version: 1.0
Generator: poetry {version}
Root-Is-Purelib: {pure_lib}
Tag: {tag}
"""
class WheelBuilder(Builder):
format = "wheel"
def __init__(self, poetry, env, io, target_dir=None, original=None):
super(WheelBuilder, self).__init__(poetry, env, io)
self._records = []
self._original_path = self._path
self._target_dir = target_dir or (self._poetry.file.parent / "dist")
if original:
self._original_path = original.file.parent
@classmethod
def make_in(cls, poetry, env, io, directory=None, original=None):
wb = WheelBuilder(poetry, env, io, target_dir=directory, original=original)
wb.build()
return wb.wheel_filename
@classmethod
def make(cls, poetry, env, io):
"""Build a wheel in the dist/ directory, and optionally upload it."""
cls.make_in(poetry, env, io)
def build(self):
self._io.write_line(" - Building <info>wheel</info>")
dist_dir = self._target_dir
if not dist_dir.exists():
dist_dir.mkdir()
(fd, temp_path) = tempfile.mkstemp(suffix=".whl")
st_mode = os.stat(temp_path).st_mode
new_mode = normalize_file_permissions(st_mode)
os.chmod(temp_path, new_mode)
with zipfile.ZipFile(
os.fdopen(fd, "w+b"), mode="w", compression=zipfile.ZIP_DEFLATED
) as zip_file:
self._copy_module(zip_file)
self._build(zip_file)
self._write_metadata(zip_file)
self._write_record(zip_file)
wheel_path = dist_dir / self.wheel_filename
if wheel_path.exists():
wheel_path.unlink()
shutil.move(temp_path, str(wheel_path))
self._io.write_line(
" - Built <comment>{}</comment>".format(self.wheel_filename)
)
def _build(self, wheel):
if self._package.build:
setup = self._path / "setup.py"
# We need to place ourselves in the temporary
# directory in order to build the package
current_path = os.getcwd()
try:
os.chdir(str(self._path))
self._env.run(
"python", str(setup), "build", "-b", str(self._path / "build")
)
finally:
os.chdir(current_path)
build_dir = self._path / "build"
lib = list(build_dir.glob("lib.*"))
if not lib:
# The result of building the extensions
# does not exist, this may due to conditional
# builds, so we assume that it's okay
return
lib = lib[0]
for pkg in lib.glob("**/*"):
if pkg.is_dir() or self.is_excluded(pkg):
continue
rel_path = str(pkg.relative_to(lib))
if rel_path in wheel.namelist():
continue
self._io.write_line(
" - Adding: <comment>{}</comment>".format(rel_path), VERY_VERBOSE
)
self._add_file(wheel, pkg, rel_path)
def _copy_module(self, wheel):
to_add = []
for include in self._module.includes:
if include.formats and "wheel" not in include.formats:
continue
include.refresh()
for file in include.elements:
if "__pycache__" in str(file):
continue
if file.is_dir():
continue
if isinstance(include, PackageInclude) and include.source:
rel_file = file.relative_to(include.base)
else:
rel_file = file.relative_to(self._path)
if self.is_excluded(rel_file.as_posix()) and isinstance(
include, PackageInclude
):
continue
if file.suffix == ".pyc":
continue
if (file, rel_file) in to_add:
# Skip duplicates
continue
self._io.write_line(
" - Adding: <comment>{}</comment>".format(str(file)), VERY_VERBOSE
)
to_add.append((file, rel_file))
# Walk the files and compress them,
# sorting everything so the order is stable.
for full_path, rel_path in sorted(to_add, key=lambda x: x[1]):
self._add_file(wheel, full_path, rel_path)
def _write_metadata(self, wheel):
if (
"scripts" in self._poetry.local_config
or "plugins" in self._poetry.local_config
):
with self._write_to_zip(wheel, self.dist_info + "/entry_points.txt") as f:
self._write_entry_points(f)
for base in ("COPYING", "LICENSE"):
for path in sorted(self._path.glob(base + "*")):
self._add_file(wheel, path, "%s/%s" % (self.dist_info, path.name))
with self._write_to_zip(wheel, self.dist_info + "/WHEEL") as f:
self._write_wheel_file(f)
with self._write_to_zip(wheel, self.dist_info + "/METADATA") as f:
self._write_metadata_file(f)
def _write_record(self, wheel):
# Write a record of the files in the wheel
with self._write_to_zip(wheel, self.dist_info + "/RECORD") as f:
for path, hash, size in self._records:
f.write("{},sha256={},{}\n".format(path, hash, size))
# RECORD itself is recorded with no hash or size
f.write(self.dist_info + "/RECORD,,\n")
@property
def dist_info(self): # type: () -> str
return self.dist_info_name(self._package.name, self._meta.version)
@property
def wheel_filename(self): # type: () -> str
return "{}-{}-{}.whl".format(
escape_name(self._package.pretty_name),
escape_version(self._meta.version),
self.tag,
)
def supports_python2(self):
return self._package.python_constraint.allows_any(
parse_constraint(">=2.0.0 <3.0.0")
)
def dist_info_name(self, distribution, version): # type: (...) -> str
escaped_name = escape_name(distribution)
escaped_version = escape_version(version)
return "{}-{}.dist-info".format(escaped_name, escaped_version)
@property
def tag(self):
if self._package.build:
platform = get_platform().replace(".", "_").replace("-", "_")
impl_name = get_abbr_impl(self._env)
impl_ver = get_impl_ver(self._env)
impl = impl_name + impl_ver
abi_tag = str(get_abi_tag(self._env)).lower()
tag = (impl, abi_tag, platform)
else:
platform = "any"
if self.supports_python2():
impl = "py2.py3"
else:
impl = "py3"
tag = (impl, "none", platform)
return "-".join(tag)
def _add_file(self, wheel, full_path, rel_path):
full_path, rel_path = str(full_path), str(rel_path)
if os.sep != "/":
# We always want to have /-separated paths in the zip file and in
# RECORD
rel_path = rel_path.replace(os.sep, "/")
zinfo = zipfile.ZipInfo(rel_path)
# Normalize permission bits to either 755 (executable) or 644
st_mode = os.stat(full_path).st_mode
new_mode = normalize_file_permissions(st_mode)
zinfo.external_attr = (new_mode & 0xFFFF) << 16 # Unix attributes
if stat.S_ISDIR(st_mode):
zinfo.external_attr |= 0x10 # MS-DOS directory flag
hashsum = hashlib.sha256()
with open(full_path, "rb") as src:
while True:
buf = src.read(1024 * 8)
if not buf:
break
hashsum.update(buf)
src.seek(0)
wheel.writestr(zinfo, src.read(), compress_type=zipfile.ZIP_DEFLATED)
size = os.stat(full_path).st_size
hash_digest = urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
self._records.append((rel_path, hash_digest, size))
@contextlib.contextmanager
def _write_to_zip(self, wheel, rel_path):
sio = StringIO()
yield sio
# The default is a fixed timestamp rather than the current time, so
# that building a wheel twice on the same computer can automatically
# give you the exact same result.
date_time = (2016, 1, 1, 0, 0, 0)
zi = zipfile.ZipInfo(rel_path, date_time)
zi.external_attr = (0o644 & 0xFFFF) << 16 # Unix attributes
b = sio.getvalue().encode("utf-8")
hashsum = hashlib.sha256(b)
hash_digest = urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
wheel.writestr(zi, b, compress_type=zipfile.ZIP_DEFLATED)
self._records.append((rel_path, hash_digest, len(b)))
def _write_entry_points(self, fp):
"""
Write entry_points.txt.
"""
entry_points = self.convert_entry_points()
for group_name in sorted(entry_points):
fp.write("[{}]\n".format(group_name))
for ep in sorted(entry_points[group_name]):
fp.write(ep.replace(" ", "") + "\n")
fp.write("\n")
def _write_wheel_file(self, fp):
fp.write(
wheel_file_template.format(
version=__version__,
pure_lib="true" if self._package.build is None else "false",
tag=self.tag,
)
)
def _write_metadata_file(self, fp):
"""
Write out metadata in the 2.x format (email like)
"""
fp.write(decode(self.get_metadata_content()))
from poetry.utils.helpers import canonicalize_name
from poetry.utils.helpers import normalize_version
from poetry.version.helpers import format_python_constraint
class Metadata:
metadata_version = "2.1"
# version 1.0
name = None
version = None
platforms = ()
supported_platforms = ()
summary = None
description = None
keywords = None
home_page = None
download_url = None
author = None
author_email = None
license = None
# version 1.1
classifiers = ()
requires = ()
provides = ()
obsoletes = ()
# version 1.2
maintainer = None
maintainer_email = None
requires_python = None
requires_external = ()
requires_dist = []
provides_dist = ()
obsoletes_dist = ()
project_urls = ()
# Version 2.1
description_content_type = None
provides_extra = []
@classmethod
def from_package(cls, package): # type: (...) -> Metadata
meta = cls()
meta.name = canonicalize_name(package.name)
meta.version = normalize_version(package.version.text)
meta.summary = package.description
if package.readme:
with package.readme.open(encoding="utf-8") as f:
meta.description = f.read()
meta.keywords = ",".join(package.keywords)
meta.home_page = package.homepage or package.repository_url
meta.author = package.author_name
meta.author_email = package.author_email
if package.license:
meta.license = package.license.id
meta.classifiers = package.all_classifiers
# Version 1.2
meta.maintainer = package.maintainer_name
meta.maintainer_email = package.maintainer_email
# Requires python
if package.python_versions != "*":
meta.requires_python = format_python_constraint(package.python_constraint)
meta.requires_dist = [d.to_pep_508() for d in package.requires]
# Version 2.1
if package.readme:
if package.readme.suffix == ".rst":
meta.description_content_type = "text/x-rst"
elif package.readme.suffix in [".md", ".markdown"]:
meta.description_content_type = "text/markdown"
else:
meta.description_content_type = "text/plain"
meta.provides_extra = [e for e in package.extras]
if package.urls:
for name, url in package.urls.items():
if name == "Homepage" and meta.home_page == url:
continue
meta.project_urls += ("{}, {}".format(name, url),)
return meta
import re
def normalize_file_permissions(st_mode):
"""
Normalizes the permission bits in the st_mode field from stat to 644/755
Popular VCSs only track whether a file is executable or not. The exact
permissions can vary on systems with different umasks. Normalising
to 644 (non executable) or 755 (executable) makes builds more reproducible.
"""
# Set 644 permissions, leaving higher bits of st_mode unchanged
new_mode = (st_mode | 0o644) & ~0o133
if st_mode & 0o100:
new_mode |= 0o111 # Executable: 644 -> 755
return new_mode
def escape_version(version):
"""
Escaped version in wheel filename. Doesn't exactly follow
the escaping specification in :pep:`427#escaping-and-unicode`
because this conflicts with :pep:`440#local-version-identifiers`.
"""
return re.sub(r"[^\w\d.+]+", "_", version, flags=re.UNICODE)
def escape_name(name):
"""Escaped wheel name as specified in :pep:`427#escaping-and-unicode`."""
return re.sub(r"[^\w\d.]+", "_", name, flags=re.UNICODE)
from typing import List
from typing import Optional
from poetry.utils._compat import Path
class Include(object):
"""
Represents an "include" entry.
It can be a glob string, a single file or a directory.
This class will then detect the type of this include:
- a package
- a module
- a file
- a directory
"""
def __init__(
self, base, include, formats=None
): # type: (Path, str, Optional[List[str]]) -> None
self._base = base
self._include = str(include)
self._formats = formats
self._elements = sorted(list(self._base.glob(str(self._include))))
@property
def base(self): # type: () -> Path
return self._base
@property
def elements(self): # type: () -> List[Path]
return self._elements
@property
def formats(self): # type: () -> Optional[List[str]]
return self._formats
def is_empty(self): # type: () -> bool
return len(self._elements) == 0
def refresh(self): # type: () -> Include
self._elements = sorted(list(self._base.glob(self._include)))
return self
from typing import List
from poetry.utils._compat import Path
from poetry.utils.helpers import module_name
from .include import Include
from .package_include import PackageInclude
class ModuleOrPackageNotFound(ValueError):
pass
class Module:
def __init__(self, name, directory=".", packages=None, includes=None):
self._name = module_name(name)
self._in_src = False
self._is_package = False
self._path = Path(directory)
self._includes = []
packages = packages or []
includes = includes or []
if not packages:
# It must exist either as a .py file or a directory, but not both
pkg_dir = Path(directory, self._name)
py_file = Path(directory, self._name + ".py")
if pkg_dir.is_dir() and py_file.is_file():
raise ValueError("Both {} and {} exist".format(pkg_dir, py_file))
elif pkg_dir.is_dir():
packages = [{"include": str(pkg_dir.relative_to(self._path))}]
elif py_file.is_file():
packages = [{"include": str(py_file.relative_to(self._path))}]
else:
# Searching for a src module
src = Path(directory, "src")
src_pkg_dir = src / self._name
src_py_file = src / (self._name + ".py")
if src_pkg_dir.is_dir() and src_py_file.is_file():
raise ValueError("Both {} and {} exist".format(pkg_dir, py_file))
elif src_pkg_dir.is_dir():
packages = [
{
"include": str(src_pkg_dir.relative_to(src)),
"from": str(src.relative_to(self._path)),
}
]
elif src_py_file.is_file():
packages = [
{
"include": str(src_py_file.relative_to(src)),
"from": str(src.relative_to(self._path)),
}
]
else:
raise ModuleOrPackageNotFound(
"No file/folder found for package {}".format(name)
)
for package in packages:
formats = package.get("format")
if formats and not isinstance(formats, list):
formats = [formats]
self._includes.append(
PackageInclude(
self._path,
package["include"],
formats=formats,
source=package.get("from"),
)
)
for include in includes:
self._includes.append(Include(self._path, include))
@property
def name(self): # type: () -> str
return self._name
@property
def path(self): # type: () -> Path
return self._path
@property
def file(self): # type: () -> Path
if self._is_package:
return self._path / "__init__.py"
else:
return self._path
@property
def includes(self): # type: () -> List
return self._includes
def is_package(self): # type: () -> bool
return self._is_package
def is_in_src(self): # type: () -> bool
return self._in_src
from .include import Include
class PackageInclude(Include):
def __init__(self, base, include, formats=None, source=None):
self._package = None
self._is_package = False
self._is_module = False
self._source = source
if source is not None:
base = base / source
super(PackageInclude, self).__init__(base, include, formats=formats)
self.check_elements()
@property
def package(self): # type: () -> str
return self._package
@property
def source(self): # type: () -> str
return self._source
def is_package(self): # type: () -> bool
return self._is_package
def is_module(self): # type: () -> bool
return self._is_module
def refresh(self): # type: () -> PackageInclude
super(PackageInclude, self).refresh()
return self.check_elements()
def check_elements(self): # type: () -> PackageInclude
root = self._elements[0]
if not self._elements:
raise ValueError(
"{} does not contain any element".format(self._base / self._include)
)
if len(self._elements) > 1:
# Probably glob
self._is_package = True
# Packages no longer need an __init__.py in python3, but there must
# at least be one .py file for it to be considered a package
if not any([element.suffix == ".py" for element in self._elements]):
raise ValueError("{} is not a package.".format(root.name))
self._package = root.parent.name
else:
if root.is_dir():
# If it's a directory, we include everything inside it
self._package = root.name
self._elements = sorted(list(root.glob("**/*")))
if not any([element.suffix == ".py" for element in self._elements]):
raise ValueError("{} is not a package.".format(root.name))
self._is_package = True
else:
self._package = root.stem
self._is_module = True
return self
"""
Generate and work with PEP 425 Compatibility Tags.
Base implementation taken from
https://github.com/pypa/wheel/blob/master/wheel/pep425tags.py
and adapted to work with poetry's env util.
"""
from __future__ import unicode_literals
import distutils.util
import sys
import warnings
def get_abbr_impl(env):
"""Return abbreviated implementation name."""
impl = env.python_implementation
if impl == "PyPy":
return "pp"
elif impl == "Jython":
return "jy"
elif impl == "IronPython":
return "ip"
elif impl == "CPython":
return "cp"
raise LookupError("Unknown Python implementation: " + impl)
def get_impl_ver(env):
"""Return implementation version."""
impl_ver = env.config_var("py_version_nodot")
if not impl_ver or get_abbr_impl(env) == "pp":
impl_ver = "".join(map(str, get_impl_version_info(env)))
return impl_ver
def get_impl_version_info(env):
"""Return sys.version_info-like tuple for use in decrementing the minor
version."""
if get_abbr_impl(env) == "pp":
# as per https://github.com/pypa/pip/issues/2882
return env.version_info[:3]
else:
return env.version_info[:2]
def get_flag(env, var, fallback, expected=True, warn=True):
"""Use a fallback method for determining SOABI flags if the needed config
var is unset or unavailable."""
val = env.config_var(var)
if val is None:
if warn:
warnings.warn(
"Config variable '{0}' is unset, Python ABI tag may "
"be incorrect".format(var),
RuntimeWarning,
2,
)
return fallback()
return val == expected
def get_abi_tag(env):
"""Return the ABI tag based on SOABI (if available) or emulate SOABI
(CPython 2, PyPy)."""
soabi = env.config_var("SOABI")
impl = get_abbr_impl(env)
if not soabi and impl in ("cp", "pp") and hasattr(sys, "maxunicode"):
d = ""
m = ""
u = ""
if get_flag(
env,
"Py_DEBUG",
lambda: hasattr(sys, "gettotalrefcount"),
warn=(impl == "cp"),
):
d = "d"
if get_flag(env, "WITH_PYMALLOC", lambda: impl == "cp", warn=(impl == "cp")):
m = "m"
if get_flag(
env,
"Py_UNICODE_SIZE",
lambda: sys.maxunicode == 0x10FFFF,
expected=4,
warn=(impl == "cp" and env.version_info < (3, 3)),
) and env.version_info < (3, 3):
u = "u"
abi = "%s%s%s%s%s" % (impl, get_impl_ver(env), d, m, u)
elif soabi and soabi.startswith("cpython-"):
abi = "cp" + soabi.split("-")[1]
elif soabi:
abi = soabi.replace(".", "_").replace("-", "_")
else:
abi = None
return abi
def get_platform():
"""Return our platform name 'win32', 'linux_x86_64'"""
# XXX remove distutils dependency
result = distutils.util.get_platform().replace(".", "_").replace("-", "_")
if result == "linux_x86_64" and sys.maxsize == 2147483647:
# pip pull request #3497
result = "linux_i686"
return result
def get_supported(env, versions=None, supplied_platform=None):
"""Return a list of supported tags for each version specified in
`versions`.
:param versions: a list of string versions, of the form ["33", "32"],
or None. The first version will be assumed to support our ABI.
"""
supported = []
# Versions must be given with respect to the preference
if versions is None:
versions = []
version_info = get_impl_version_info(env)
major = version_info[:-1]
# Support all previous minor Python versions.
for minor in range(version_info[-1], -1, -1):
versions.append("".join(map(str, major + (minor,))))
impl = get_abbr_impl(env)
abis = []
abi = get_abi_tag(env)
if abi:
abis[0:0] = [abi]
abi3s = set()
import imp
for suffix in imp.get_suffixes():
if suffix[0].startswith(".abi"):
abi3s.add(suffix[0].split(".", 2)[1])
abis.extend(sorted(list(abi3s)))
abis.append("none")
platforms = []
if supplied_platform:
platforms.append(supplied_platform)
platforms.append(get_platform())
# Current version, current API (built specifically for our Python):
for abi in abis:
for arch in platforms:
supported.append(("%s%s" % (impl, versions[0]), abi, arch))
# abi3 modules compatible with older version of Python
for version in versions[1:]:
# abi3 was introduced in Python 3.2
if version in ("31", "30"):
break
for abi in abi3s: # empty set if not Python 3
for arch in platforms:
supported.append(("%s%s" % (impl, version), abi, arch))
# No abi / arch, but requires our implementation:
for i, version in enumerate(versions):
supported.append(("%s%s" % (impl, version), "none", "any"))
if i == 0:
# Tagged specifically as being cross-version compatible
# (with just the major version specified)
supported.append(("%s%s" % (impl, versions[0][0]), "none", "any"))
# Major Python version + platform; e.g. binaries not using the Python API
supported.append(("py%s" % (versions[0][0]), "none", arch))
# No abi / arch, generic Python
for i, version in enumerate(versions):
supported.append(("py%s" % (version,), "none", "any"))
if i == 0:
supported.append(("py%s" % (version[0]), "none", "any"))
return supported
...@@ -2,8 +2,8 @@ from collections import OrderedDict ...@@ -2,8 +2,8 @@ from collections import OrderedDict
from typing import Dict from typing import Dict
from typing import List from typing import List
from poetry.packages import Dependency from poetry.core.packages import Dependency
from poetry.packages import Package from poetry.core.packages import Package
from .assignment import Assignment from .assignment import Assignment
from .incompatibility import Incompatibility from .incompatibility import Incompatibility
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Union from typing import Union
from poetry.packages import Dependency from poetry.core.packages import Dependency
from .set_relation import SetRelation from .set_relation import SetRelation
......
...@@ -6,12 +6,12 @@ from typing import Dict ...@@ -6,12 +6,12 @@ from typing import Dict
from typing import List from typing import List
from typing import Union from typing import Union
from poetry.packages import Dependency from poetry.core.packages import Dependency
from poetry.packages import Package from poetry.core.packages import Package
from poetry.packages import ProjectPackage from poetry.core.packages import ProjectPackage
from poetry.core.semver import Version
from poetry.core.semver import VersionRange
from poetry.puzzle.provider import Provider from poetry.puzzle.provider import Provider
from poetry.semver import Version
from poetry.semver import VersionRange
from .failure import SolveFailure from .failure import SolveFailure
from .incompatibility import Incompatibility from .incompatibility import Incompatibility
......
import os
import re
from poetry.semver import Version
from poetry.utils.patterns import wheel_file_re
from poetry.version.requirements import Requirement
from .dependency import Dependency
from .dependency_package import DependencyPackage from .dependency_package import DependencyPackage
from .directory_dependency import DirectoryDependency
from .file_dependency import FileDependency
from .locker import Locker from .locker import Locker
from .package import Package
from .package_collection import PackageCollection from .package_collection import PackageCollection
from .project_package import ProjectPackage
from .url_dependency import URLDependency
from .utils.link import Link
from .utils.utils import convert_markers
from .utils.utils import group_markers
from .utils.utils import is_archive_file
from .utils.utils import is_installable_dir
from .utils.utils import is_url
from .utils.utils import path_to_url
from .utils.utils import strip_extras
from .vcs_dependency import VCSDependency
def dependency_from_pep_508(name):
from poetry.vcs.git import ParsedUrl
# Removing comments
parts = name.split("#", 1)
name = parts[0].strip()
if len(parts) > 1:
rest = parts[1]
if ";" in rest:
name += ";" + rest.split(";", 1)[1]
req = Requirement(name)
if req.marker:
markers = convert_markers(req.marker)
else:
markers = {}
name = req.name
path = os.path.normpath(os.path.abspath(name))
link = None
if is_url(name):
link = Link(name)
elif req.url:
link = Link(req.url)
else:
p, extras = strip_extras(path)
if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")):
if not is_installable_dir(p):
raise ValueError(
"Directory {!r} is not installable. File 'setup.py' "
"not found.".format(name)
)
link = Link(path_to_url(p))
elif is_archive_file(p):
link = Link(path_to_url(p))
# it's a local file, dir, or url
if link:
# Handle relative file URLs
if link.scheme == "file" and re.search(r"\.\./", link.url):
link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
m = wheel_file_re.match(link.filename)
if not m:
raise ValueError("Invalid wheel name: {}".format(link.filename))
name = m.group("name")
version = m.group("ver")
dep = Dependency(name, version)
else:
name = req.name or link.egg_fragment
if link.scheme.startswith("git+"):
url = ParsedUrl.parse(link.url)
dep = VCSDependency(name, "git", url.url, rev=url.rev)
elif link.scheme == "git":
dep = VCSDependency(name, "git", link.url_without_fragment)
elif link.scheme in ["http", "https"]:
dep = URLDependency(name, link.url_without_fragment)
else:
dep = Dependency(name, "*")
else:
if req.pretty_constraint:
constraint = req.constraint
else:
constraint = "*"
dep = Dependency(name, constraint)
if "extra" in markers:
# If we have extras, the dependency is optional
dep.deactivate()
for or_ in markers["extra"]:
for _, extra in or_:
dep.in_extras.append(extra)
if "python_version" in markers:
ors = []
for or_ in markers["python_version"]:
ands = []
for op, version in or_:
# Expand python version
if op == "==":
version = "~" + version
op = ""
elif op == "!=":
version += ".*"
elif op in ("<=", ">"):
parsed_version = Version.parse(version)
if parsed_version.precision == 1:
if op == "<=":
op = "<"
version = parsed_version.next_major.text
elif op == ">":
op = ">="
version = parsed_version.next_major.text
elif parsed_version.precision == 2:
if op == "<=":
op = "<"
version = parsed_version.next_minor.text
elif op == ">":
op = ">="
version = parsed_version.next_minor.text
elif op in ("in", "not in"):
versions = []
for v in re.split("[ ,]+", version):
split = v.split(".")
if len(split) in [1, 2]:
split.append("*")
op_ = "" if op == "in" else "!="
else:
op_ = "==" if op == "in" else "!="
versions.append(op_ + ".".join(split))
glue = " || " if op == "in" else ", "
if versions:
ands.append(glue.join(versions))
continue
ands.append("{}{}".format(op, version))
ors.append(" ".join(ands))
dep.python_versions = " || ".join(ors)
if req.marker:
dep.marker = req.marker
# Extras
for extra in req.extras:
dep.extras.append(extra)
return dep
import re
from .any_constraint import AnyConstraint
from .base_constraint import BaseConstraint
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 allows_all(self, other):
raise NotImplementedError()
def allows_any(self, other):
raise NotImplementedError()
def difference(self, other):
raise NotImplementedError()
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
)
from .base_constraint import BaseConstraint
class EmptyConstraint(BaseConstraint):
pretty_string = None
def matches(self, _):
return True
def is_empty(self):
return True
def allows_all(self, other):
return True
def allows_any(self, other):
return True
def intersect(self, other):
return other
def difference(self, other):
return
def __eq__(self, other):
return other.is_empty()
def __str__(self):
return ""
from .base_constraint import BaseConstraint
from .constraint import Constraint
class MultiConstraint(BaseConstraint):
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 allows(self, other):
for constraint in self._constraints:
if not constraint.allows(other):
return False
return True
def allows_all(self, other):
if other.is_any():
return False
if other.is_empty():
return True
if isinstance(other, Constraint):
return self.allows(other)
our_constraints = iter(self._constraints)
their_constraints = iter(other.constraints)
our_constraint = next(our_constraints, None)
their_constraint = next(their_constraints, None)
while our_constraint and their_constraint:
if our_constraint.allows_all(their_constraint):
their_constraint = next(their_constraints, None)
else:
our_constraint = next(our_constraints, None)
return their_constraint is None
def allows_any(self, other):
if other.is_any():
return True
if other.is_empty():
return True
if isinstance(other, Constraint):
return self.allows(other)
if isinstance(other, MultiConstraint):
for c1 in self.constraints:
for c2 in other.constraints:
if c1.allows(c2):
return True
return False
def intersect(self, other):
if isinstance(other, Constraint):
constraints = self._constraints
if other not in constraints:
constraints += (other,)
else:
constraints = (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(", ").join(constraints)
from .base_constraint import BaseConstraint
from .constraint import Constraint
from .empty_constraint import EmptyConstraint
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)
import re
from .constraint import Constraint
class WilcardConstraint(Constraint):
def __init__(self, constraint): # type: (str) -> None
m = re.match(
r"^(!= ?|==)?v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$", constraint
)
if not m:
raise ValueError("Invalid value for wildcard constraint")
if not m.group(1):
operator = "=="
else:
operator = m.group(1).strip()
super(WilcardConstraint, self).__init__(
operator, ".".join([g if g else "*" for g in m.groups()[1:]])
)
if m.group(4):
position = 2
elif m.group(3):
position = 1
else:
position = 0
from ..version_parser import VersionParser
parser = VersionParser()
groups = m.groups()[1:]
low_version = parser._manipulate_version_string(groups, position)
high_version = parser._manipulate_version_string(groups, position, 1)
if operator == "!=":
if low_version == "0.0.0.0":
self._constraint = Constraint(">=", high_version)
else:
self._constraint = parser.parse_constraints(
"<{} || >={}".format(low_version, high_version)
)
else:
if low_version == "0.0.0.0":
self._constraint = Constraint("<", high_version)
else:
self._constraint = parser.parse_constraints(
">={},<{}".format(low_version, high_version)
)
@property
def supported_operators(self):
return ["!=", "=="]
@property
def constraint(self):
return self._constraint
def matches(self, provider): # type: (Constraint) -> bool
if isinstance(provider, self.__class__):
return self._constraint.matches(provider.constraint)
return provider.matches(self._constraint)
def __str__(self):
op = ""
if self.string_operator == "!=":
op = "!= "
return "{}{}".format(op, self._version)
from typing import Optional
import poetry.packages
from poetry.semver import Version
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver import VersionUnion
from poetry.semver import parse_constraint
from poetry.utils.helpers import canonicalize_name
from poetry.version.markers import AnyMarker
from poetry.version.markers import parse_marker
from .constraints import parse_constraint as parse_generic_constraint
from .constraints.constraint import Constraint
from .constraints.multi_constraint import MultiConstraint
from .constraints.union_constraint import UnionConstraint
from .utils.utils import convert_markers
class Dependency(object):
def __init__(
self,
name, # type: str
constraint, # type: str
optional=False, # type: bool
category="main", # type: str
allows_prereleases=False, # type: bool
source_name=None, # type: Optional[str]
):
self._name = canonicalize_name(name)
self._pretty_name = name
try:
if not isinstance(constraint, VersionConstraint):
self._constraint = parse_constraint(constraint)
else:
self._constraint = constraint
except ValueError:
self._constraint = parse_constraint("*")
self._pretty_constraint = str(constraint)
self._optional = optional
self._category = category
if isinstance(self._constraint, VersionRange) and self._constraint.min:
allows_prereleases = (
allows_prereleases or self._constraint.min.is_prerelease()
)
self._allows_prereleases = allows_prereleases
self._source_name = source_name
self._python_versions = "*"
self._python_constraint = parse_constraint("*")
self._transitive_python_versions = None
self._transitive_python_constraint = None
self._transitive_marker = None
self._extras = []
self._in_extras = []
self._activated = not self._optional
self.is_root = False
self.marker = AnyMarker()
@property
def name(self):
return self._name
@property
def constraint(self):
return self._constraint
@property
def pretty_constraint(self):
return self._pretty_constraint
@property
def pretty_name(self):
return self._pretty_name
@property
def category(self):
return self._category
@property
def source_name(self):
return self._source_name
@property
def python_versions(self):
return self._python_versions
@python_versions.setter
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 transitive_python_versions(self):
if self._transitive_python_versions is None:
return self._python_versions
return self._transitive_python_versions
@transitive_python_versions.setter
def transitive_python_versions(self, value):
self._transitive_python_versions = value
self._transitive_python_constraint = parse_constraint(value)
@property
def transitive_marker(self):
if self._transitive_marker is None:
return self.marker
return self._transitive_marker
@transitive_marker.setter
def transitive_marker(self, value):
self._transitive_marker = value
@property
def python_constraint(self):
return self._python_constraint
@property
def transitive_python_constraint(self):
if self._transitive_python_constraint is None:
return self._python_constraint
return self._transitive_python_constraint
@property
def extras(self): # type: () -> list
return self._extras
@property
def in_extras(self): # type: () -> list
return self._in_extras
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
if isinstance(self.constraint, VersionUnion):
if self.constraint.excludes_single_version():
requirement += " ({})".format(str(self.constraint))
else:
requirement += " ({})".format(self.pretty_constraint)
elif isinstance(self.constraint, Version):
requirement += " (=={})".format(self.constraint.text)
elif not self.constraint.is_any():
requirement += " ({})".format(str(self.constraint).replace(" ", ""))
return requirement
def allows_prereleases(self):
return self._allows_prereleases
def is_optional(self):
return self._optional
def is_activated(self):
return self._activated
def is_vcs(self):
return False
def is_file(self):
return False
def is_directory(self):
return False
def is_url(self):
return False
def accepts(self, package): # type: (poetry.packages.Package) -> bool
"""
Determines if the given package matches this dependency.
"""
return (
self._name == package.name
and self._constraint.allows(package.version)
and (not package.is_prerelease() or self.allows_prereleases())
)
def to_pep_508(self, with_extras=True): # type: (bool) -> str
requirement = self.base_pep_508_name
markers = []
has_extras = False
if not self.marker.is_any():
marker = self.marker
if not with_extras:
marker = marker.without_extras()
if not marker.is_empty():
markers.append(str(marker))
has_extras = "extra" in convert_markers(marker)
else:
# Python marker
if self.python_versions != "*":
python_constraint = self.python_constraint
markers.append(
self._create_nested_marker("python_version", python_constraint)
)
in_extras = " || ".join(self._in_extras)
if in_extras and with_extras and not has_extras:
markers.append(
self._create_nested_marker("extra", parse_generic_constraint(in_extras))
)
if markers:
if self.is_vcs():
requirement += " "
if len(markers) > 1:
markers = ["({})".format(m) for m in markers]
requirement += "; {}".format(" and ".join(markers))
else:
requirement += "; {}".format(markers[0])
return requirement
def _create_nested_marker(self, name, constraint):
if isinstance(constraint, (MultiConstraint, UnionConstraint)):
parts = []
for c in constraint.constraints:
multi = False
if isinstance(c, (MultiConstraint, UnionConstraint)):
multi = True
parts.append((multi, self._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(self._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
def activate(self):
"""
Set the dependency as mandatory.
"""
self._activated = True
def deactivate(self):
"""
Set the dependency as optional.
"""
if not self._optional:
self._optional = True
self._activated = False
def with_constraint(self, constraint):
new = Dependency(
self.pretty_name,
constraint,
optional=self.is_optional(),
category=self.category,
allows_prereleases=self.allows_prereleases(),
)
new.is_root = self.is_root
new.python_versions = self.python_versions
for extra in self.extras:
new.extras.append(extra)
for in_extra in self.in_extras:
new.in_extras.append(in_extra)
return new
def __eq__(self, other):
if not isinstance(other, Dependency):
return NotImplemented
return self._name == other.name and self._constraint == other.constraint
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash((self._name, self._pretty_constraint))
def __str__(self):
if self.is_root:
return self._pretty_name
return "{} ({})".format(self._pretty_name, self._pretty_constraint)
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self))
from pkginfo.distribution import HEADER_ATTRS
from pkginfo.distribution import HEADER_ATTRS_2_0
from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile
from .dependency import Dependency
# Patching pkginfo to support Metadata version 2.1 (PEP 566)
HEADER_ATTRS.update(
{"2.1": HEADER_ATTRS_2_0 + (("Provides-Extra", "provides_extra", True),)}
)
class DirectoryDependency(Dependency):
def __init__(
self,
name,
path, # type: Path
category="main", # type: str
optional=False, # type: bool
base=None, # type: Path
develop=True, # type: bool
):
self._path = path
self._base = base
self._full_path = path
self._develop = develop
self._supports_poetry = False
if self._base and not self._path.is_absolute():
self._full_path = self._base / self._path
if not self._full_path.exists():
raise ValueError("Directory {} does not exist".format(self._path))
if self._full_path.is_file():
raise ValueError("{} is a file, expected a directory".format(self._path))
# Checking content to determine actions
setup = self._full_path / "setup.py"
pyproject = TomlFile(self._full_path / "pyproject.toml")
if pyproject.exists():
pyproject_content = pyproject.read()
self._supports_poetry = (
"tool" in pyproject_content and "poetry" in pyproject_content["tool"]
)
if not setup.exists() and not self._supports_poetry:
raise ValueError(
"Directory {} does not seem to be a Python package".format(
self._full_path
)
)
super(DirectoryDependency, self).__init__(
name, "*", category=category, optional=optional, allows_prereleases=True
)
@property
def path(self):
return self._path
@property
def full_path(self):
return self._full_path.resolve()
@property
def base(self):
return self._base
@property
def develop(self):
return self._develop
def supports_poetry(self):
return self._supports_poetry
def is_directory(self):
return True
import hashlib
import io
from pkginfo.distribution import HEADER_ATTRS
from pkginfo.distribution import HEADER_ATTRS_2_0
from poetry.utils._compat import Path
from .dependency import Dependency
# Patching pkginfo to support Metadata version 2.1 (PEP 566)
HEADER_ATTRS.update(
{"2.1": HEADER_ATTRS_2_0 + (("Provides-Extra", "provides_extra", True),)}
)
class FileDependency(Dependency):
def __init__(
self,
name,
path, # type: Path
category="main", # type: str
optional=False, # type: bool
base=None, # type: Path
):
self._path = path
self._base = base
self._full_path = path
if self._base and not self._path.is_absolute():
self._full_path = self._base / self._path
if not self._full_path.exists():
raise ValueError("File {} does not exist".format(self._path))
if self._full_path.is_dir():
raise ValueError("{} is a directory, expected a file".format(self._path))
super(FileDependency, self).__init__(
name, "*", category=category, optional=optional, allows_prereleases=True
)
@property
def path(self):
return self._path
@property
def full_path(self):
return self._full_path.resolve()
def is_file(self):
return True
def hash(self):
h = hashlib.sha256()
with self._full_path.open("rb") as fp:
for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""):
h.update(content)
return h.hexdigest()
...@@ -10,12 +10,13 @@ from tomlkit import item ...@@ -10,12 +10,13 @@ from tomlkit import item
from tomlkit import table from tomlkit import table
from tomlkit.exceptions import TOMLKitError from tomlkit.exceptions import TOMLKitError
import poetry.packages
import poetry.repositories import poetry.repositories
from poetry.core.packages.package import Dependency
from poetry.core.packages.package import Package
from poetry.core.version.markers import parse_marker
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from poetry.version.markers import parse_marker
class Locker(object): class Locker(object):
...@@ -83,9 +84,7 @@ class Locker(object): ...@@ -83,9 +84,7 @@ class Locker(object):
return packages return packages
for info in locked_packages: for info in locked_packages:
package = poetry.packages.Package( package = Package(info["name"], info["version"], info["version"])
info["name"], info["version"], info["version"]
)
package.description = info.get("description", "") package.description = info.get("description", "")
package.category = info["category"] package.category = info["category"]
package.optional = info["optional"] package.optional = info["optional"]
...@@ -109,16 +108,14 @@ class Locker(object): ...@@ -109,16 +108,14 @@ class Locker(object):
dep_name = m.group(1) dep_name = m.group(1)
constraint = m.group(2) or "*" constraint = m.group(2) or "*"
package.extras[name].append( package.extras[name].append(Dependency(dep_name, constraint))
poetry.packages.Dependency(dep_name, constraint)
)
if "marker" in info: if "marker" in info:
package.marker = parse_marker(info["marker"]) package.marker = parse_marker(info["marker"])
else: else:
# Compatibility for old locks # Compatibility for old locks
if "requirements" in info: if "requirements" in info:
dep = poetry.packages.Dependency("foo", "0.0.0") dep = Dependency("foo", "0.0.0")
for name, value in info["requirements"].items(): for name, value in info["requirements"].items():
if name == "python": if name == "python":
dep.python_versions = value dep.python_versions = value
...@@ -238,7 +235,7 @@ class Locker(object): ...@@ -238,7 +235,7 @@ class Locker(object):
return locked return locked
def _dump_package(self, package): # type: (poetry.packages.Package) -> dict def _dump_package(self, package): # type: (Package) -> dict
dependencies = {} dependencies = {}
for dependency in sorted(package.requires, key=lambda d: d.name): for dependency in sorted(package.requires, key=lambda d: d.name):
if dependency.is_optional() and not dependency.is_activated(): if dependency.is_optional() and not dependency.is_activated():
......
# -*- coding: utf-8 -*-
import copy
import logging
import re
from contextlib import contextmanager
from typing import Union
from unicodedata import normalize
from warnings import warn
from poetry.semver import Version
from poetry.semver import parse_constraint
from poetry.spdx import License
from poetry.spdx import license_by_id
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 import parse_constraint as parse_generic_constraint
from .dependency import Dependency
from .directory_dependency import DirectoryDependency
from .file_dependency import FileDependency
from .url_dependency import URLDependency
from .utils.utils import create_nested_marker
from .vcs_dependency import VCSDependency
AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?)>)?$")
logger = logging.getLogger(__name__)
class Package(object):
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8"}
def __init__(self, name, version, pretty_version=None):
"""
Creates a new in memory package.
"""
self._pretty_name = name
self._name = canonicalize_name(name)
if not isinstance(version, Version):
self._version = Version.parse(version)
self._pretty_version = pretty_version or version
else:
self._version = version
self._pretty_version = pretty_version or self._version.text
self.description = ""
self._authors = []
self._maintainers = []
self.homepage = None
self.repository_url = None
self.documentation_url = None
self.keywords = []
self._license = None
self.readme = None
self.source_name = ""
self.source_type = ""
self.source_reference = ""
self.source_url = ""
self.requires = []
self.dev_requires = []
self.extras = {}
self.requires_extras = []
self.category = "main"
self.files = []
self.optional = False
self.classifiers = []
self._python_versions = "*"
self._python_constraint = parse_constraint("*")
self._python_marker = AnyMarker()
self.platform = None
self.marker = AnyMarker()
self.root_dir = None
self.develop = True
@property
def name(self):
return self._name
@property
def pretty_name(self):
return self._pretty_name
@property
def version(self):
return self._version
@property
def pretty_version(self):
return self._pretty_version
@property
def unique_name(self):
if self.is_root():
return self._name
return self.name + "-" + self._version.text
@property
def pretty_string(self):
return self.pretty_name + " " + self.pretty_version
@property
def full_pretty_version(self):
if self.source_type in ["file", "directory", "url"]:
return "{} {}".format(self._pretty_version, self.source_url)
if self.source_type not in ["hg", "git"]:
return self._pretty_version
# if source reference is a sha1 hash -- truncate
if len(self.source_reference) == 40:
return "{} {}".format(self._pretty_version, self.source_reference[0:7])
return "{} {}".format(self._pretty_version, self.source_reference)
@property
def authors(self): # type: () -> list
return self._authors
@property
def author_name(self): # type: () -> str
return self._get_author()["name"]
@property
def author_email(self): # type: () -> str
return self._get_author()["email"]
@property
def maintainers(self): # type: () -> list
return self._maintainers
@property
def maintainer_name(self): # type: () -> str
return self._get_maintainer()["name"]
@property
def maintainer_email(self): # type: () -> str
return self._get_maintainer()["email"]
@property
def all_requires(self):
return self.requires + self.dev_requires
def _get_author(self): # type: () -> dict
if not self._authors:
return {"name": None, "email": None}
m = AUTHOR_REGEX.match(normalize("NFC", self._authors[0]))
name = m.group("name")
email = m.group("email")
return {"name": name, "email": email}
def _get_maintainer(self): # type: () -> dict
if not self._maintainers:
return {"name": None, "email": None}
m = AUTHOR_REGEX.match(normalize("NFC", self._maintainers[0]))
name = m.group("name")
email = m.group("email")
return {"name": name, "email": email}
@property
def python_versions(self):
return self._python_versions
@python_versions.setter
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 license(self):
return self._license
@license.setter
def license(self, value):
if value is None:
self._license = value
elif isinstance(value, License):
self._license = value
else:
self._license = license_by_id(value)
@property
def all_classifiers(self):
classifiers = copy.copy(self.classifiers)
# Automatically set python classifiers
if self.python_versions == "*":
python_constraint = parse_constraint("~2.7 || ^3.4")
else:
python_constraint = self.python_constraint
for version in sorted(self.AVAILABLE_PYTHONS):
if len(version) == 1:
constraint = parse_constraint(version + ".*")
else:
constraint = Version.parse(version)
if python_constraint.allows_any(constraint):
classifiers.append(
"Programming Language :: Python :: {}".format(version)
)
# Automatically set license classifiers
if self.license:
classifiers.append(self.license.classifier)
classifiers = set(classifiers)
return sorted(classifiers)
@property
def urls(self):
urls = {}
if self.homepage:
urls["Homepage"] = self.homepage
if self.repository_url:
urls["Repository"] = self.repository_url
if self.documentation_url:
urls["Documentation"] = self.documentation_url
return urls
def is_prerelease(self):
return self._version.is_prerelease()
def is_root(self):
return False
def add_dependency(
self,
name, # type: str
constraint=None, # type: Union[str, dict, None]
category="main", # type: str
): # type: (...) -> Dependency
if constraint is None:
constraint = "*"
if isinstance(constraint, dict):
optional = constraint.get("optional", False)
python_versions = constraint.get("python")
platform = constraint.get("platform")
markers = constraint.get("markers")
if "allows-prereleases" in constraint:
message = (
'The "{}" dependency specifies '
'the "allows-prereleases" property, which is deprecated. '
'Use "allow-prereleases" instead.'.format(name)
)
warn(message, DeprecationWarning)
logger.warning(message)
allows_prereleases = constraint.get(
"allow-prereleases", constraint.get("allows-prereleases", False)
)
if "git" in constraint:
# VCS dependency
dependency = VCSDependency(
name,
"git",
constraint["git"],
branch=constraint.get("branch", None),
tag=constraint.get("tag", None),
rev=constraint.get("rev", None),
category=category,
optional=optional,
)
elif "file" in constraint:
file_path = Path(constraint["file"])
dependency = FileDependency(
name, file_path, category=category, base=self.root_dir
)
elif "path" in constraint:
path = Path(constraint["path"])
if self.root_dir:
is_file = (self.root_dir / path).is_file()
else:
is_file = path.is_file()
if is_file:
dependency = FileDependency(
name,
path,
category=category,
optional=optional,
base=self.root_dir,
)
else:
dependency = DirectoryDependency(
name,
path,
category=category,
optional=optional,
base=self.root_dir,
develop=constraint.get("develop", True),
)
elif "url" in constraint:
dependency = URLDependency(name, constraint["url"], category=category)
else:
version = constraint["version"]
dependency = Dependency(
name,
version,
optional=optional,
category=category,
allows_prereleases=allows_prereleases,
source_name=constraint.get("source"),
)
if not markers:
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:
marker = marker.intersect(
parse_marker(
create_nested_marker(
"sys_platform", parse_generic_constraint(platform)
)
)
)
else:
marker = parse_marker(markers)
if not marker.is_any():
dependency.marker = marker
if "extras" in constraint:
for extra in constraint["extras"]:
dependency.extras.append(extra)
else:
dependency = Dependency(name, constraint, category=category)
if category == "dev":
self.dev_requires.append(dependency)
else:
self.requires.append(dependency)
return dependency
def to_dependency(self):
from . import dependency_from_pep_508
name = "{} (=={})".format(self._name, self._version)
if not self.marker.is_any():
name += " ; {}".format(str(self.marker))
return dependency_from_pep_508(name)
@contextmanager
def with_python_versions(self, python_versions):
original_python_versions = self.python_versions
self.python_versions = python_versions
yield
self.python_versions = original_python_versions
def clone(self): # type: () -> Package
clone = self.__class__(self.pretty_name, self.version)
clone.category = self.category
clone.optional = self.optional
clone.python_versions = self.python_versions
clone.marker = self.marker
clone.extras = self.extras
clone.source_type = self.source_type
clone.source_url = self.source_url
clone.source_reference = self.source_reference
for dep in self.requires:
clone.requires.append(dep)
for dep in self.dev_requires:
clone.dev_requires.append(dep)
return clone
def __hash__(self):
return hash((self._name, self._version))
def __eq__(self, other):
if not isinstance(other, Package):
return NotImplemented
return self._name == other.name and self._version == other.version
def __str__(self):
return self.unique_name
def __repr__(self):
return "<Package {}>".format(self.unique_name)
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):
def __init__(self, name, version, pretty_version=None):
super(ProjectPackage, self).__init__(name, version, pretty_version)
self.build = None
self.packages = []
self.include = []
self.exclude = []
self.custom_urls = {}
if self._python_versions == "*":
self._python_constraint = parse_constraint("~2.7 || >=3.4")
def is_root(self):
return True
def to_dependency(self):
dependency = super(ProjectPackage, self).to_dependency()
dependency.is_root = True
return dependency
@property
def python_versions(self):
return self._python_versions
@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)
)
@property
def urls(self):
urls = super(ProjectPackage, self).urls
urls.update(self.custom_urls)
return urls
def clone(self): # type: () -> ProjectPackage
package = super(ProjectPackage, self).clone()
package.build = self.build
package.packages = self.packages[:]
package.include = self.include[:]
package.exclude = self.exclude[:]
return package
from poetry.utils._compat import urlparse
from .dependency import Dependency
class URLDependency(Dependency):
def __init__(
self,
name,
url, # type: str
category="main", # type: str
optional=False, # type: bool
):
self._url = url
parsed = urlparse.urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError("{} does not seem like a valid url".format(url))
super(URLDependency, self).__init__(
name, "*", category=category, optional=optional, allows_prereleases=True
)
@property
def url(self):
return self._url
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
requirement += " @ {}".format(self._url)
return requirement
def is_url(self): # type: () -> bool
return True
import posixpath
import re
from .utils import path_to_url
from .utils import splitext
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
class Link:
def __init__(self, url, comes_from=None, requires_python=None):
"""
Object representing a parsed link from https://pypi.python.org/simple/*
url:
url of the resource pointed to (href of the link)
comes_from:
instance of HTMLPage where the link was found, or string.
requires_python:
String containing the `Requires-Python` metadata field, specified
in PEP 345. This may be specified by a data-requires-python
attribute in the HTML link tag, as described in PEP 503.
"""
# url can be a UNC windows share
if url.startswith("\\\\"):
url = path_to_url(url)
self.url = url
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
def __str__(self):
if self.requires_python:
rp = " (requires-python:%s)" % self.requires_python
else:
rp = ""
if self.comes_from:
return "%s (from %s)%s" % (self.url, self.comes_from, rp)
else:
return str(self.url)
def __repr__(self):
return "<Link %s>" % self
def __eq__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url == other.url
def __ne__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url != other.url
def __lt__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url < other.url
def __le__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url <= other.url
def __gt__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url > other.url
def __ge__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url >= other.url
def __hash__(self):
return hash(self.url)
@property
def filename(self):
_, netloc, path, _, _ = urlparse.urlsplit(self.url)
name = posixpath.basename(path.rstrip("/")) or netloc
name = urlparse.unquote(name)
assert name, "URL %r produced no filename" % self.url
return name
@property
def scheme(self):
return urlparse.urlsplit(self.url)[0]
@property
def netloc(self):
return urlparse.urlsplit(self.url)[1]
@property
def path(self):
return urlparse.unquote(urlparse.urlsplit(self.url)[2])
def splitext(self):
return splitext(posixpath.basename(self.path.rstrip("/")))
@property
def ext(self):
return self.splitext()[1]
@property
def url_without_fragment(self):
scheme, netloc, path, query, fragment = urlparse.urlsplit(self.url)
return urlparse.urlunsplit((scheme, netloc, path, query, None))
_egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
@property
def egg_fragment(self):
match = self._egg_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
@property
def subdirectory_fragment(self):
match = self._subdirectory_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_hash_re = re.compile(r"(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)")
@property
def hash(self):
match = self._hash_re.search(self.url)
if match:
return match.group(2)
return None
@property
def hash_name(self):
match = self._hash_re.search(self.url)
if match:
return match.group(1)
return None
@property
def show_url(self):
return posixpath.basename(self.url.split("#", 1)[0].split("?", 1)[0])
@property
def is_wheel(self):
return self.ext == ".whl"
@property
def is_artifact(self):
"""
Determines if this points to an actual artifact (e.g. a tarball) or if
it points to an "abstract" thing like a path or a VCS location.
"""
if self.scheme in ["ssh", "git", "hg", "bzr", "sftp", "svn"]:
return False
return True
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 EmptyConstraint
from poetry.semver import Version
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver import VersionUnion
from poetry.semver import parse_constraint
from poetry.version.markers import BaseMarker
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:
import urlparse
try:
import urllib.request as urllib2
except ImportError:
import urllib2
BZ2_EXTENSIONS = (".tar.bz2", ".tbz")
XZ_EXTENSIONS = (".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma")
ZIP_EXTENSIONS = (".zip", ".whl")
TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar")
ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS
try:
import bz2 # noqa
SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
except ImportError:
pass
try:
# Only for Python 3.3+
import lzma # noqa
SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
except ImportError:
pass
def path_to_url(path):
"""
Convert a path to a file: URL. The path will be made absolute and have
quoted path parts.
"""
path = os.path.normpath(os.path.abspath(path))
url = urlparse.urljoin("file:", urllib2.pathname2url(path))
return url
def is_url(name):
if ":" not in name:
return False
scheme = name.split(":", 1)[0].lower()
return scheme in [
"http",
"https",
"file",
"ftp",
"ssh",
"git",
"hg",
"bzr",
"sftp",
"svn" "ssh",
]
def strip_extras(path):
m = re.match(r"^(.+)(\[[^\]]+\])$", path)
extras = None
if m:
path_no_extras = m.group(1)
extras = m.group(2)
else:
path_no_extras = path
return path_no_extras, extras
def is_installable_dir(path):
"""Return True if `path` is a directory containing a setup.py file."""
if not os.path.isdir(path):
return False
setup_py = os.path.join(path, "setup.py")
if os.path.isfile(setup_py):
return True
return False
def is_archive_file(name):
"""Return True if `name` is a considered as an archive file."""
ext = splitext(name)[1].lower()
if ext in ARCHIVE_EXTENSIONS:
return True
return False
def splitext(path):
"""Like os.path.splitext, but take off .tar too"""
base, ext = posixpath.splitext(path)
if base.lower().endswith(".tar"):
ext = base[-4:] + ext
base = base[:-4]
return base, ext
def group_markers(markers, or_=False):
groups = [[]]
for marker in markers:
if or_:
groups.append([])
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, op, rhs))
return groups
def convert_markers(marker):
groups = group_markers([marker])
requirements = {}
def _group(_groups, or_=False):
ors = {}
for group in _groups:
if isinstance(group, list):
_group(group, or_=True)
else:
variable, op, value = group
group_name = str(variable)
# python_full_version is equivalent to python_version
# for Poetry so we merge them
if group_name == "python_full_version":
group_name = "python_version"
if group_name not in requirements:
requirements[group_name] = []
if group_name not in ors:
ors[group_name] = or_
if ors[group_name] or not requirements[group_name]:
requirements[group_name].append([])
requirements[group_name][-1].append((str(op), str(value)))
ors[group_name] = False
_group(groups, or_=True)
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
def get_python_constraint_from_marker(
marker,
): # type: (BaseMarker) -> VersionConstraint
python_marker = marker.only("python_version")
if python_marker.is_any():
return VersionRange()
if python_marker.is_empty():
return EmptyConstraint()
markers = convert_markers(marker)
ors = []
for or_ in markers["python_version"]:
ands = []
for op, version in or_:
# Expand python version
if op == "==":
version = "~" + version
op = ""
elif op == "!=":
version += ".*"
elif op in ("<=", ">"):
parsed_version = Version.parse(version)
if parsed_version.precision == 1:
if op == "<=":
op = "<"
version = parsed_version.next_major.text
elif op == ">":
op = ">="
version = parsed_version.next_major.text
elif parsed_version.precision == 2:
if op == "<=":
op = "<"
version = parsed_version.next_minor.text
elif op == ">":
op = ">="
version = parsed_version.next_minor.text
elif op in ("in", "not in"):
versions = []
for v in re.split("[ ,]+", version):
split = v.split(".")
if len(split) in [1, 2]:
split.append("*")
op_ = "" if op == "in" else "!="
else:
op_ = "==" if op == "in" else "!="
versions.append(op_ + ".".join(split))
glue = " || " if op == "in" else ", "
if versions:
ands.append(glue.join(versions))
continue
ands.append("{}{}".format(op, version))
ors.append(" ".join(ands))
return parse_constraint(" || ".join(ors))
from poetry.vcs import git
from .dependency import Dependency
class VCSDependency(Dependency):
"""
Represents a VCS dependency
"""
def __init__(
self,
name,
vcs,
source,
branch=None,
tag=None,
rev=None,
category="main",
optional=False,
):
self._vcs = vcs
self._source = source
if not any([branch, tag, rev]):
# If nothing has been specified, we assume master
branch = "master"
self._branch = branch
self._tag = tag
self._rev = rev
super(VCSDependency, self).__init__(
name, "*", category=category, optional=optional, allows_prereleases=True
)
@property
def vcs(self):
return self._vcs
@property
def source(self):
return self._source
@property
def branch(self):
return self._branch
@property
def tag(self):
return self._tag
@property
def rev(self):
return self._rev
@property
def reference(self): # type: () -> str
return self._branch or self._tag or self._rev
@property
def pretty_constraint(self): # type: () -> str
if self._branch:
what = "branch"
version = self._branch
elif self._tag:
what = "tag"
version = self._tag
else:
what = "rev"
version = self._rev
return "{} {}".format(what, version)
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
parsed_url = git.ParsedUrl.parse(self._source)
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
if parsed_url.protocol is not None:
requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference)
else:
requirement += " @ {}+ssh://{}@{}".format(
self._vcs, parsed_url.format(), self.reference
)
return requirement
def is_vcs(self): # type: () -> bool
return True
def accepts_prereleases(self): # type: () -> bool
return True
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from poetry.core.packages import ProjectPackage
from poetry.core.poetry import Poetry as BasePoetry
from .__version__ import __version__ from .__version__ import __version__
from .config.config import Config from .config.config import Config
from .packages import Locker from .packages import Locker
from .packages import ProjectPackage
from .repositories.pool import Pool from .repositories.pool import Pool
from .utils._compat import Path from .utils._compat import Path
from .utils.toml_file import TomlFile
class Poetry: class Poetry(BasePoetry):
VERSION = __version__ VERSION = __version__
...@@ -22,26 +23,13 @@ class Poetry: ...@@ -22,26 +23,13 @@ class Poetry:
locker, # type: Locker locker, # type: Locker
config, # type: Config config, # type: Config
): ):
self._file = TomlFile(file) super(Poetry, self).__init__(file, local_config, package)
self._package = package
self._local_config = local_config
self._locker = locker self._locker = locker
self._config = config self._config = config
self._pool = Pool() self._pool = Pool()
@property @property
def file(self):
return self._file
@property
def package(self): # type: () -> ProjectPackage
return self._package
@property
def local_config(self): # type: () -> dict
return self._local_config
@property
def locker(self): # type: () -> Locker def locker(self): # type: () -> Locker
return self._locker return self._locker
......
...@@ -15,14 +15,13 @@ from requests_toolbelt.multipart import MultipartEncoder ...@@ -15,14 +15,13 @@ from requests_toolbelt.multipart import MultipartEncoder
from requests_toolbelt.multipart import MultipartEncoderMonitor from requests_toolbelt.multipart import MultipartEncoderMonitor
from poetry.__version__ import __version__ from poetry.__version__ import __version__
from poetry.core.masonry.metadata import Metadata
from poetry.core.masonry.utils.helpers import escape_name
from poetry.core.masonry.utils.helpers import escape_version
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.helpers import normalize_version from poetry.utils.helpers import normalize_version
from poetry.utils.patterns import wheel_file_re from poetry.utils.patterns import wheel_file_re
from ..metadata import Metadata
from ..utils.helpers import escape_name
from ..utils.helpers import escape_version
_has_blake2 = hasattr(hashlib, "blake2b") _has_blake2 = hasattr(hashlib, "blake2b")
......
class Dependencies:
"""
Proxy to package dependencies to only require them when needed.
"""
def __init__(self, package, provider):
self._package = package
self._provider = provider
self._dependencies = None
@property
def dependencies(self):
if self._dependencies is None:
self._dependencies = self._get_dependencies()
return self._dependencies
def _get_dependencies(self):
self._provider.debug("Getting dependencies for {}".format(self._package), 0)
dependencies = self._provider._dependencies_for(self._package)
if dependencies is None:
dependencies = []
return dependencies
def __len__(self):
return len(self.dependencies)
def __iter__(self):
return self.dependencies.__iter__()
def __add__(self, other):
return self.dependencies + other
__radd__ = __add__
...@@ -14,21 +14,23 @@ import pkginfo ...@@ -14,21 +14,23 @@ import pkginfo
from clikit.ui.components import ProgressIndicator from clikit.ui.components import ProgressIndicator
from poetry.core.packages import Dependency
from poetry.core.packages import DirectoryDependency
from poetry.core.packages import FileDependency
from poetry.core.packages import Package
from poetry.core.packages import URLDependency
from poetry.core.packages import VCSDependency
from poetry.core.packages import dependency_from_pep_508
from poetry.core.packages.utils.utils import get_python_constraint_from_marker
from poetry.core.vcs.git import Git
from poetry.core.version.markers import MarkerUnion
from poetry.factory import Factory from poetry.factory import Factory
from poetry.mixology.incompatibility import Incompatibility from poetry.mixology.incompatibility import Incompatibility
from poetry.mixology.incompatibility_cause import DependencyCause from poetry.mixology.incompatibility_cause import DependencyCause
from poetry.mixology.incompatibility_cause import PythonCause from poetry.mixology.incompatibility_cause import PythonCause
from poetry.mixology.term import Term from poetry.mixology.term import Term
from poetry.packages import Dependency
from poetry.packages import DependencyPackage from poetry.packages import DependencyPackage
from poetry.packages import DirectoryDependency from poetry.packages.package_collection import PackageCollection
from poetry.packages import FileDependency
from poetry.packages import Package
from poetry.packages import PackageCollection
from poetry.packages import URLDependency
from poetry.packages import VCSDependency
from poetry.packages import dependency_from_pep_508
from poetry.packages.utils.utils import get_python_constraint_from_marker
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.utils._compat import PY35 from poetry.utils._compat import PY35
from poetry.utils._compat import OrderedDict from poetry.utils._compat import OrderedDict
...@@ -43,8 +45,6 @@ from poetry.utils.helpers import temporary_directory ...@@ -43,8 +45,6 @@ from poetry.utils.helpers import temporary_directory
from poetry.utils.inspector import Inspector from poetry.utils.inspector import Inspector
from poetry.utils.setup_reader import SetupReader from poetry.utils.setup_reader import SetupReader
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from poetry.vcs.git import Git
from poetry.version.markers import MarkerUnion
from .exceptions import CompatibilityError from .exceptions import CompatibilityError
......
...@@ -4,12 +4,12 @@ from typing import Any ...@@ -4,12 +4,12 @@ from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
from poetry.core.packages import Package
from poetry.core.semver import parse_constraint
from poetry.core.version.markers import AnyMarker
from poetry.mixology import resolve_version from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage from poetry.packages import DependencyPackage
from poetry.packages import Package
from poetry.semver import parse_constraint
from poetry.version.markers import AnyMarker
from .exceptions import CompatibilityError from .exceptions import CompatibilityError
from .exceptions import SolverProblemError from .exceptions import SolverProblemError
...@@ -56,7 +56,7 @@ class Solver: ...@@ -56,7 +56,7 @@ class Solver:
installed = True installed = True
if pkg.source_type == "git" and package.source_type == "git": if pkg.source_type == "git" and package.source_type == "git":
from poetry.vcs.git import Git from poetry.core.vcs.git import Git
# Trying to find the currently installed version # Trying to find the currently installed version
pkg_source_url = Git.normalize_url(pkg.source_url) pkg_source_url = Git.normalize_url(pkg.source_url)
......
from poetry import _CURRENT_VENDOR from poetry.core.packages import Package
from poetry.packages import Package
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import metadata from poetry.utils._compat import metadata
from poetry.utils.env import Env from poetry.utils.env import Env
...@@ -7,6 +6,9 @@ from poetry.utils.env import Env ...@@ -7,6 +6,9 @@ from poetry.utils.env import Env
from .repository import Repository from .repository import Repository
_VENDORS = Path(__file__).parent.parent.joinpath("_vendor")
class InstalledRepository(Repository): class InstalledRepository(Repository):
@classmethod @classmethod
def load(cls, env): # type: (Env) -> InstalledRepository def load(cls, env): # type: (Env) -> InstalledRepository
...@@ -32,7 +34,7 @@ class InstalledRepository(Repository): ...@@ -32,7 +34,7 @@ class InstalledRepository(Repository):
continue continue
try: try:
path.relative_to(_CURRENT_VENDOR) path.relative_to(_VENDORS)
except ValueError: except ValueError:
pass pass
else: else:
...@@ -58,7 +60,7 @@ class InstalledRepository(Repository): ...@@ -58,7 +60,7 @@ class InstalledRepository(Repository):
try: try:
path.relative_to(src_path) path.relative_to(src_path)
from poetry.vcs.git import Git from poetry.core.vcs.git import Git
git = Git() git = Git()
revision = git.rev_parse("HEAD", src_path / package.name).strip() revision = git.rev_parse("HEAD", src_path / package.name).strip()
......
...@@ -13,21 +13,19 @@ from cachecontrol import CacheControl ...@@ -13,21 +13,19 @@ from cachecontrol import CacheControl
from cachecontrol.caches.file_cache import FileCache from cachecontrol.caches.file_cache import FileCache
from cachy import CacheManager from cachy import CacheManager
import poetry.packages from poetry.core.packages import Package
from poetry.core.packages import dependency_from_pep_508
from poetry.core.packages.utils.link import Link
from poetry.core.semver import Version
from poetry.core.semver import VersionConstraint
from poetry.core.semver import VersionRange
from poetry.core.semver import parse_constraint
from poetry.core.version.markers import InvalidMarker
from poetry.locations import REPOSITORY_CACHE_DIR from poetry.locations import REPOSITORY_CACHE_DIR
from poetry.packages import Package
from poetry.packages import dependency_from_pep_508
from poetry.packages.utils.link import Link
from poetry.semver import Version
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver import parse_constraint
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import canonicalize_name
from poetry.utils.inspector import Inspector from poetry.utils.inspector import Inspector
from poetry.utils.patterns import wheel_file_re from poetry.utils.patterns import wheel_file_re
from poetry.version.markers import InvalidMarker
from .auth import Auth from .auth import Auth
from .exceptions import PackageNotFound from .exceptions import PackageNotFound
...@@ -283,9 +281,7 @@ class LegacyRepository(PyPiRepository): ...@@ -283,9 +281,7 @@ class LegacyRepository(PyPiRepository):
return packages return packages
def package( def package(self, name, version, extras=None): # type: (...) -> Package
self, name, version, extras=None
): # type: (...) -> poetry.packages.Package
""" """
Retrieve the release information. Retrieve the release information.
...@@ -298,9 +294,7 @@ class LegacyRepository(PyPiRepository): ...@@ -298,9 +294,7 @@ class LegacyRepository(PyPiRepository):
should be much faster. should be much faster.
""" """
try: try:
index = self._packages.index( index = self._packages.index(Package(name, version, version))
poetry.packages.Package(name, version, version)
)
return self._packages[index] return self._packages[index]
except ValueError: except ValueError:
...@@ -309,7 +303,7 @@ class LegacyRepository(PyPiRepository): ...@@ -309,7 +303,7 @@ class LegacyRepository(PyPiRepository):
release_info = self.get_release_info(name, version) release_info = self.get_release_info(name, version)
package = poetry.packages.Package(name, version, version) package = Package(name, version, version)
if release_info["requires_python"]: if release_info["requires_python"]:
package.python_versions = release_info["requires_python"] package.python_versions = release_info["requires_python"]
......
...@@ -15,21 +15,21 @@ from requests import get ...@@ -15,21 +15,21 @@ from requests import get
from requests import session from requests import session
from requests.exceptions import TooManyRedirects from requests.exceptions import TooManyRedirects
from poetry.core.packages import Package
from poetry.core.packages import dependency_from_pep_508
from poetry.core.packages.utils.link import Link
from poetry.core.semver import VersionConstraint
from poetry.core.semver import VersionRange
from poetry.core.semver import parse_constraint
from poetry.core.semver.exceptions import ParseVersionError
from poetry.core.version.markers import InvalidMarker
from poetry.core.version.markers import parse_marker
from poetry.locations import REPOSITORY_CACHE_DIR from poetry.locations import REPOSITORY_CACHE_DIR
from poetry.packages import Package
from poetry.packages import dependency_from_pep_508
from poetry.packages.utils.link import Link
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver import parse_constraint
from poetry.semver.exceptions import ParseVersionError
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import to_str from poetry.utils._compat import to_str
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.utils.inspector import Inspector from poetry.utils.inspector import Inspector
from poetry.utils.patterns import wheel_file_re from poetry.utils.patterns import wheel_file_re
from poetry.version.markers import InvalidMarker
from poetry.version.markers import parse_marker
from .exceptions import PackageNotFound from .exceptions import PackageNotFound
from .remote_repository import RemoteRepository from .remote_repository import RemoteRepository
......
from poetry.semver import VersionConstraint from poetry.core.semver import VersionConstraint
from poetry.semver import VersionRange from poetry.core.semver import VersionRange
from poetry.semver import parse_constraint from poetry.core.semver import parse_constraint
from .base_repository import BaseRepository from .base_repository import BaseRepository
......
import re
from .empty_constraint import EmptyConstraint
from .patterns import BASIC_CONSTRAINT
from .patterns import CARET_CONSTRAINT
from .patterns import TILDE_CONSTRAINT
from .patterns import TILDE_PEP440_CONSTRAINT
from .patterns import X_CONSTRAINT
from .version import Version
from .version_constraint import VersionConstraint
from .version_range import VersionRange
from .version_union import VersionUnion
def parse_constraint(constraints): # type: (str) -> VersionConstraint
if constraints == "*":
return VersionRange()
or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip())
or_groups = []
for constraints in or_constraints:
and_constraints = re.split(
"(?<!^)(?<![=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", 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 VersionUnion.of(*or_groups)
def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint)
if m:
return VersionRange()
# Tilde range
m = TILDE_CONSTRAINT.match(constraint)
if m:
version = Version.parse(m.group(1))
high = version.stable.next_minor
if len(m.group(1).split(".")) == 1:
high = version.stable.next_major
return VersionRange(
version, high, include_min=True, always_include_max_prerelease=True
)
# PEP 440 Tilde range (~=)
m = TILDE_PEP440_CONSTRAINT.match(constraint)
if m:
precision = 1
if m.group(3):
precision += 1
if m.group(4):
precision += 1
version = Version.parse(m.group(1))
if precision == 2:
high = version.stable.next_major
else:
high = version.stable.next_minor
return VersionRange(
version, high, include_min=True, always_include_max_prerelease=True
)
# Caret range
m = CARET_CONSTRAINT.match(constraint)
if m:
version = Version.parse(m.group(1))
return VersionRange(
version,
version.next_breaking,
include_min=True,
always_include_max_prerelease=True,
)
# X Range
m = X_CONSTRAINT.match(constraint)
if m:
op = m.group(1)
major = int(m.group(2))
minor = m.group(3)
if minor is not None:
version = Version(major, int(minor), 0)
result = VersionRange(
version,
version.next_minor,
include_min=True,
always_include_max_prerelease=True,
)
else:
if major == 0:
result = VersionRange(max=Version(1, 0, 0))
else:
version = Version(major, 0, 0)
result = VersionRange(
version,
version.next_major,
include_min=True,
always_include_max_prerelease=True,
)
if op == "!=":
result = VersionRange().difference(result)
return result
# Basic comparator
m = BASIC_CONSTRAINT.match(constraint)
if m:
op = m.group(1)
version = m.group(2)
if version == "dev":
version = "0.0-dev"
try:
version = Version.parse(version)
except ValueError:
raise ValueError(
"Could not parse version constraint: {}".format(constraint)
)
if op == "<":
return VersionRange(max=version)
elif op == "<=":
return VersionRange(max=version, include_max=True)
elif op == ">":
return VersionRange(min=version)
elif op == ">=":
return VersionRange(min=version, include_min=True)
elif op == "!=":
return VersionUnion(VersionRange(max=version), VersionRange(min=version))
else:
return version
raise ValueError("Could not parse version constraint: {}".format(constraint))
from .version_constraint import VersionConstraint
class EmptyConstraint(VersionConstraint):
def is_empty(self):
return True
def is_any(self):
return False
def allows(self, version):
return False
def allows_all(self, other):
return other.is_empty()
def allows_any(self, other):
return False
def intersect(self, other):
return self
def union(self, other):
return other
def difference(self, other):
return self
def __str__(self):
return "<empty>"
class ParseVersionError(ValueError):
pass
import re
MODIFIERS = (
"[._-]?"
r"((?!post)(?:beta|b|c|pre|RC|alpha|a|patch|pl|p|dev)(?:(?:[.-]?\d+)*)?)?"
r"([+-]?([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?"
)
_COMPLETE_VERSION = r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?{}(?:\+[^\s]+)?".format(
MODIFIERS
)
COMPLETE_VERSION = re.compile("(?i)" + _COMPLETE_VERSION)
CARET_CONSTRAINT = re.compile(r"(?i)^\^({})$".format(_COMPLETE_VERSION))
TILDE_CONSTRAINT = re.compile("(?i)^~(?!=)({})$".format(_COMPLETE_VERSION))
TILDE_PEP440_CONSTRAINT = re.compile("(?i)^~=({})$".format(_COMPLETE_VERSION))
X_CONSTRAINT = re.compile(r"^(!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$")
BASIC_CONSTRAINT = re.compile(
r"(?i)^(<>|!=|>=?|<=?|==?)?\s*({}|dev)".format(_COMPLETE_VERSION)
)
import re
from typing import List
from typing import Optional
from typing import Union
from .empty_constraint import EmptyConstraint
from .exceptions import ParseVersionError
from .patterns import COMPLETE_VERSION
from .version_constraint import VersionConstraint
from .version_range import VersionRange
from .version_union import VersionUnion
class Version(VersionRange):
"""
A parsed semantic version number.
"""
def __init__(
self,
major, # type: int
minor=None, # type: Optional[int]
patch=None, # type: Optional[int]
rest=None, # type: Optional[int]
pre=None, # type: Optional[str]
build=None, # type: Optional[str]
text=None, # type: Optional[str]
precision=None, # type: Optional[int]
): # type: (...) -> None
self._major = int(major)
self._precision = None
if precision is None:
self._precision = 1
if minor is None:
minor = 0
else:
if self._precision is not None:
self._precision += 1
self._minor = int(minor)
if patch is None:
patch = 0
else:
if self._precision is not None:
self._precision += 1
if rest is None:
rest = 0
else:
if self._precision is not None:
self._precision += 1
if precision is not None:
self._precision = precision
self._patch = int(patch)
self._rest = int(rest)
if text is None:
parts = [str(major)]
if self._precision >= 2 or minor != 0:
parts.append(str(minor))
if self._precision >= 3 or patch != 0:
parts.append(str(patch))
if self._precision >= 4 or rest != 0:
parts.append(str(rest))
text = ".".join(parts)
if pre:
text += "-{}".format(pre)
if build:
text += "+{}".format(build)
self._text = text
pre = self._normalize_prerelease(pre)
self._prerelease = []
if pre is not None:
self._prerelease = self._split_parts(pre)
build = self._normalize_build(build)
self._build = []
if build is not None:
if build.startswith(("-", "+")):
build = build[1:]
self._build = self._split_parts(build)
@property
def major(self): # type: () -> int
return self._major
@property
def minor(self): # type: () -> int
return self._minor
@property
def patch(self): # type: () -> int
return self._patch
@property
def rest(self): # type: () -> int
return self._rest
@property
def prerelease(self): # type: () -> List[str]
return self._prerelease
@property
def build(self): # type: () -> List[str]
return self._build
@property
def text(self):
return self._text
@property
def precision(self): # type: () -> int
return self._precision
@property
def stable(self):
if not self.is_prerelease():
return self
return self.next_patch
@property
def next_major(self): # type: () -> Version
if self.is_prerelease() and self.minor == 0 and self.patch == 0:
return Version(self.major, self.minor, self.patch)
return self._increment_major()
@property
def next_minor(self): # type: () -> Version
if self.is_prerelease() and self.patch == 0:
return Version(self.major, self.minor, self.patch)
return self._increment_minor()
@property
def next_patch(self): # type: () -> Version
if self.is_prerelease():
return Version(self.major, self.minor, self.patch)
return self._increment_patch()
@property
def next_breaking(self): # type: () -> Version
if self.major == 0:
if self.minor != 0:
return self._increment_minor()
if self._precision == 1:
return self._increment_major()
elif self._precision == 2:
return self._increment_minor()
return self._increment_patch()
return self._increment_major()
@property
def first_prerelease(self): # type: () -> Version
return Version.parse(
"{}.{}.{}-alpha.0".format(self.major, self.minor, self.patch)
)
@property
def min(self):
return self
@property
def max(self):
return self
@property
def full_max(self):
return self
@property
def include_min(self):
return True
@property
def include_max(self):
return True
@classmethod
def parse(cls, text): # type: (str) -> Version
try:
match = COMPLETE_VERSION.match(text)
except TypeError:
match = None
if match is None:
raise ParseVersionError('Unable to parse "{}".'.format(text))
text = text.rstrip(".")
major = int(match.group(1))
minor = int(match.group(2)) if match.group(2) else None
patch = int(match.group(3)) if match.group(3) else None
rest = int(match.group(4)) if match.group(4) else None
pre = match.group(5)
build = match.group(6)
if build:
build = build.lstrip("+")
return Version(major, minor, patch, rest, pre, build, text)
def is_any(self):
return False
def is_empty(self):
return False
def is_prerelease(self): # type: () -> bool
return len(self._prerelease) > 0
def allows(self, version): # type: (Version) -> bool
return self == version
def allows_all(self, other): # type: (VersionConstraint) -> bool
return other.is_empty() or other == self
def allows_any(self, other): # type: (VersionConstraint) -> bool
return other.allows(self)
def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
if other.allows(self):
return self
return EmptyConstraint()
def union(self, other): # type: (VersionConstraint) -> VersionConstraint
from .version_range import VersionRange
if other.allows(self):
return other
if isinstance(other, VersionRange):
if other.min == self:
return VersionRange(
other.min,
other.max,
include_min=True,
include_max=other.include_max,
)
if other.max == self:
return VersionRange(
other.min,
other.max,
include_min=other.include_min,
include_max=True,
)
return VersionUnion.of(self, other)
def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
if other.allows(self):
return EmptyConstraint()
return self
def equals_without_prerelease(self, other): # type: (Version) -> bool
return (
self.major == other.major
and self.minor == other.minor
and self.patch == other.patch
)
def _increment_major(self): # type: () -> Version
return Version(self.major + 1, 0, 0, precision=self._precision)
def _increment_minor(self): # type: () -> Version
return Version(self.major, self.minor + 1, 0, precision=self._precision)
def _increment_patch(self): # type: () -> Version
return Version(
self.major, self.minor, self.patch + 1, precision=self._precision
)
def _normalize_prerelease(self, pre): # type: (str) -> str
if not pre:
return
m = re.match(r"(?i)^(a|alpha|b|beta|c|pre|rc|dev)[-.]?(\d+)?$", pre)
if not m:
return
modifier = m.group(1)
number = m.group(2)
if number is None:
number = 0
if modifier == "a":
modifier = "alpha"
elif modifier == "b":
modifier = "beta"
elif modifier in {"c", "pre"}:
modifier = "rc"
elif modifier == "dev":
modifier = "alpha"
return "{}.{}".format(modifier, number)
def _normalize_build(self, build): # type: (str) -> str
if not build:
return
if build.startswith("post"):
build = build.lstrip("post")
if not build:
return
return build
def _split_parts(self, text): # type: (str) -> List[Union[str, int]]
parts = text.split(".")
for i, part in enumerate(parts):
try:
parts[i] = int(part)
except (TypeError, ValueError):
continue
return parts
def __lt__(self, other):
return self._cmp(other) < 0
def __le__(self, other):
return self._cmp(other) <= 0
def __gt__(self, other):
return self._cmp(other) > 0
def __ge__(self, other):
return self._cmp(other) >= 0
def _cmp(self, other):
if not isinstance(other, VersionConstraint):
return NotImplemented
if not isinstance(other, Version):
return -other._cmp(self)
if self.major != other.major:
return self._cmp_parts(self.major, other.major)
if self.minor != other.minor:
return self._cmp_parts(self.minor, other.minor)
if self.patch != other.patch:
return self._cmp_parts(self.patch, other.patch)
if self.rest != other.rest:
return self._cmp_parts(self.rest, other.rest)
# Pre-releases always come before no pre-release string.
if not self.is_prerelease() and other.is_prerelease():
return 1
if not other.is_prerelease() and self.is_prerelease():
return -1
comparison = self._cmp_lists(self.prerelease, other.prerelease)
if comparison != 0:
return comparison
# Builds always come after no build string.
if not self.build and other.build:
return -1
if not other.build and self.build:
return 1
return self._cmp_lists(self.build, other.build)
def _cmp_parts(self, a, b):
if a < b:
return -1
elif a > b:
return 1
return 0
def _cmp_lists(self, a, b): # type: (List, List) -> int
for i in range(max(len(a), len(b))):
a_part = None
if i < len(a):
a_part = a[i]
b_part = None
if i < len(b):
b_part = b[i]
if a_part == b_part:
continue
# Missing parts come after present ones.
if a_part is None:
return -1
if b_part is None:
return 1
if isinstance(a_part, int):
if isinstance(b_part, int):
return self._cmp_parts(a_part, b_part)
return -1
else:
if isinstance(b_part, int):
return 1
return self._cmp_parts(a_part, b_part)
return 0
def __eq__(self, other): # type: (Version) -> bool
if not isinstance(other, Version):
return NotImplemented
return (
self._major == other.major
and self._minor == other.minor
and self._patch == other.patch
and self._rest == other.rest
and self._prerelease == other.prerelease
and self._build == other.build
)
def __ne__(self, other):
return not self == other
def __str__(self):
return self._text
def __repr__(self):
return "<Version {}>".format(str(self))
def __hash__(self):
return hash(
(
self.major,
self.minor,
self.patch,
".".join(str(p) for p in self.prerelease),
".".join(str(p) for p in self.build),
)
)
class VersionConstraint:
def is_empty(self): # type: () -> bool
raise NotImplementedError()
def is_any(self): # type: () -> bool
raise NotImplementedError()
def allows(self, version): # type: ("Version") -> bool
raise NotImplementedError()
def allows_all(self, other): # type: (VersionConstraint) -> bool
raise NotImplementedError()
def allows_any(self, other): # type: (VersionConstraint) -> bool
raise NotImplementedError()
def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
raise NotImplementedError()
def union(self, other): # type: (VersionConstraint) -> VersionConstraint
raise NotImplementedError()
def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
raise NotImplementedError()
from typing import List
from .empty_constraint import EmptyConstraint
from .version_constraint import VersionConstraint
from .version_union import VersionUnion
class VersionRange(VersionConstraint):
def __init__(
self,
min=None,
max=None,
include_min=False,
include_max=False,
always_include_max_prerelease=False,
):
full_max = max
if (
always_include_max_prerelease
and not include_max
and not full_max.is_prerelease()
and not full_max.build
and (
min is None
or not min.is_prerelease()
or not min.equals_without_prerelease(full_max)
)
):
full_max = full_max.first_prerelease
self._min = min
self._max = max
self._full_max = full_max
self._include_min = include_min
self._include_max = include_max
@property
def min(self):
return self._min
@property
def max(self):
return self._max
@property
def full_max(self):
return self._full_max
@property
def include_min(self):
return self._include_min
@property
def include_max(self):
return self._include_max
def is_empty(self):
return False
def is_any(self):
return self._min is None and self._max is None
def allows(self, other): # type: ("Version") -> bool
if self._min is not None:
if other < self._min:
return False
if not self._include_min and other == self._min:
return False
if self._max is not None:
if other > self._max:
return False
if not self._include_max and other == self._max:
return False
return True
def allows_all(self, other): # type: (VersionConstraint) -> bool
from .version import Version
if other.is_empty():
return True
if isinstance(other, Version):
return self.allows(other)
if isinstance(other, VersionUnion):
return all([self.allows_all(constraint) for constraint in other.ranges])
if isinstance(other, VersionRange):
return not other.allows_lower(self) and not other.allows_higher(self)
raise ValueError("Unknown VersionConstraint type {}.".format(other))
def allows_any(self, other): # type: (VersionConstraint) -> bool
from .version import Version
if other.is_empty():
return False
if isinstance(other, Version):
return self.allows(other)
if isinstance(other, VersionUnion):
return any([self.allows_any(constraint) for constraint in other.ranges])
if isinstance(other, VersionRange):
return not other.is_strictly_lower(self) and not other.is_strictly_higher(
self
)
raise ValueError("Unknown VersionConstraint type {}.".format(other))
def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
from .version import Version
if other.is_empty():
return other
if isinstance(other, VersionUnion):
return other.intersect(self)
# A range and a Version just yields the version if it's in the range.
if isinstance(other, Version):
if self.allows(other):
return other
return EmptyConstraint()
if not isinstance(other, VersionRange):
raise ValueError("Unknown VersionConstraint type {}.".format(other))
if self.allows_lower(other):
if self.is_strictly_lower(other):
return EmptyConstraint()
intersect_min = other.min
intersect_include_min = other.include_min
else:
if other.is_strictly_lower(self):
return EmptyConstraint()
intersect_min = self._min
intersect_include_min = self._include_min
if self.allows_higher(other):
intersect_max = other.max
intersect_include_max = other.include_max
else:
intersect_max = self._max
intersect_include_max = self._include_max
if intersect_min is None and intersect_max is None:
return VersionRange()
# If the range is just a single version.
if intersect_min == intersect_max:
# Because we already verified that the lower range isn't strictly
# lower, there must be some overlap.
assert intersect_include_min and intersect_include_max
return intersect_min
# If we got here, there is an actual range.
return VersionRange(
intersect_min, intersect_max, intersect_include_min, intersect_include_max
)
def union(self, other): # type: (VersionConstraint) -> VersionConstraint
from .version import Version
if isinstance(other, Version):
if self.allows(other):
return self
if other == self.min:
return VersionRange(
self.min, self.max, include_min=True, include_max=self.include_max
)
if other == self.max:
return VersionRange(
self.min, self.max, include_min=self.include_min, include_max=True
)
return VersionUnion.of(self, other)
if isinstance(other, VersionRange):
# If the two ranges don't overlap, we won't be able to create a single
# VersionRange for both of them.
edges_touch = (
self.max == other.min and (self.include_max or other.include_min)
) or (self.min == other.max and (self.include_min or other.include_max))
if not edges_touch and not self.allows_any(other):
return VersionUnion.of(self, other)
if self.allows_lower(other):
union_min = self.min
union_include_min = self.include_min
else:
union_min = other.min
union_include_min = other.include_min
if self.allows_higher(other):
union_max = self.max
union_include_max = self.include_max
else:
union_max = other.max
union_include_max = other.include_max
return VersionRange(
union_min,
union_max,
include_min=union_include_min,
include_max=union_include_max,
)
return VersionUnion.of(self, other)
def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
from .version import Version
if other.is_empty():
return self
if isinstance(other, Version):
if not self.allows(other):
return self
if other == self.min:
if not self.include_min:
return self
return VersionRange(self.min, self.max, False, self.include_max)
if other == self.max:
if not self.include_max:
return self
return VersionRange(self.min, self.max, self.include_min, False)
return VersionUnion.of(
VersionRange(self.min, other, self.include_min, False),
VersionRange(other, self.max, False, self.include_max),
)
elif isinstance(other, VersionRange):
if not self.allows_any(other):
return self
if not self.allows_lower(other):
before = None
elif self.min == other.min:
before = self.min
else:
before = VersionRange(
self.min, other.min, self.include_min, not other.include_min
)
if not self.allows_higher(other):
after = None
elif self.max == other.max:
after = self.max
else:
after = VersionRange(
other.max, self.max, not other.include_max, self.include_max
)
if before is None and after is None:
return EmptyConstraint()
if before is None:
return after
if after is None:
return before
return VersionUnion.of(before, after)
elif isinstance(other, VersionUnion):
ranges = [] # type: List[VersionRange]
current = self
for range in other.ranges:
# Skip any ranges that are strictly lower than [current].
if range.is_strictly_lower(current):
continue
# If we reach a range strictly higher than [current], no more ranges
# will be relevant so we can bail early.
if range.is_strictly_higher(current):
break
difference = current.difference(range)
if difference.is_empty():
return EmptyConstraint()
elif isinstance(difference, VersionUnion):
# If [range] split [current] in half, we only need to continue
# checking future ranges against the latter half.
ranges.append(difference.ranges[0])
current = difference.ranges[-1]
else:
current = difference
if not ranges:
return current
return VersionUnion.of(*(ranges + [current]))
raise ValueError("Unknown VersionConstraint type {}.".format(other))
def allows_lower(self, other): # type: (VersionRange) -> bool
if self.min is None:
return other.min is not None
if other.min is None:
return False
if self.min < other.min:
return True
if self.min > other.min:
return False
return self.include_min and not other.include_min
def allows_higher(self, other): # type: (VersionRange) -> bool
if self.max is None:
return other.max is not None
if other.max is None:
return False
if self.max < other.max:
return False
if self.max > other.max:
return True
return self.include_max and not other.include_max
def is_strictly_lower(self, other): # type: (VersionRange) -> bool
if self.max is None or other.min is None:
return False
if self.full_max < other.min:
return True
if self.full_max > other.min:
return False
return not self.include_max or not other.include_min
def is_strictly_higher(self, other): # type: (VersionRange) -> bool
return other.is_strictly_lower(self)
def is_adjacent_to(self, other): # type: (VersionRange) -> bool
if self.max != other.min:
return False
return (
self.include_max
and not other.include_min
or not self.include_max
and other.include_min
)
def __eq__(self, other):
if not isinstance(other, VersionRange):
return False
return (
self._min == other.min
and self._max == other.max
and self._include_min == other.include_min
and self._include_max == other.include_max
)
def __lt__(self, other):
return self._cmp(other) < 0
def __le__(self, other):
return self._cmp(other) <= 0
def __gt__(self, other):
return self._cmp(other) > 0
def __ge__(self, other):
return self._cmp(other) >= 0
def _cmp(self, other): # type: (VersionRange) -> int
if self.min is None:
if other.min is None:
return self._compare_max(other)
return -1
elif other.min is None:
return 1
result = self.min._cmp(other.min)
if result != 0:
return result
if self.include_min != other.include_min:
return -1 if self.include_min else 1
return self._compare_max(other)
def _compare_max(self, other): # type: (VersionRange) -> int
if self.max is None:
if other.max is None:
return 0
return 1
elif other.max is None:
return -1
result = self.max._cmp(other.max)
if result != 0:
return result
if self.include_max != other.include_max:
return 1 if self.include_max else -1
return 0
def __str__(self):
text = ""
if self.min is not None:
text += ">=" if self.include_min else ">"
text += self.min.text
if self.max is not None:
if self.min is not None:
text += ","
text += "{}{}".format("<=" if self.include_max else "<", self.max.text)
if self.min is None and self.max is None:
return "*"
return text
def __repr__(self):
return "<VersionRange ({})>".format(str(self))
def __hash__(self):
return hash((self.min, self.max, self.include_min, self.include_max))
from typing import List
from .empty_constraint import EmptyConstraint
from .version_constraint import VersionConstraint
class VersionUnion(VersionConstraint):
"""
A version constraint representing a union of multiple disjoint version
ranges.
An instance of this will only be created if the version can't be represented
as a non-compound value.
"""
def __init__(self, *ranges):
self._ranges = list(ranges)
@property
def ranges(self):
return self._ranges
@classmethod
def of(cls, *ranges):
from .version_range import VersionRange
flattened = []
for constraint in ranges:
if constraint.is_empty():
continue
if isinstance(constraint, VersionUnion):
flattened += constraint.ranges
continue
flattened.append(constraint)
if not flattened:
return EmptyConstraint()
if any([constraint.is_any() for constraint in flattened]):
return VersionRange()
# Only allow Versions and VersionRanges here so we can more easily reason
# about everything in flattened. _EmptyVersions and VersionUnions are
# filtered out above.
for constraint in flattened:
if isinstance(constraint, VersionRange):
continue
raise ValueError("Unknown VersionConstraint type {}.".format(constraint))
flattened.sort()
merged = []
for constraint in flattened:
# Merge this constraint with the previous one, but only if they touch.
if not merged or (
not merged[-1].allows_any(constraint)
and not merged[-1].is_adjacent_to(constraint)
):
merged.append(constraint)
else:
merged[-1] = merged[-1].union(constraint)
if len(merged) == 1:
return merged[0]
return VersionUnion(*merged)
def is_empty(self):
return False
def is_any(self):
return False
def allows(self, version): # type: ("Version") -> bool
return any([constraint.allows(version) for constraint in self._ranges])
def allows_all(self, other): # type: (VersionConstraint) -> bool
our_ranges = iter(self._ranges)
their_ranges = iter(self._ranges_for(other))
our_current_range = next(our_ranges, None)
their_current_range = next(their_ranges, None)
while our_current_range and their_current_range:
if our_current_range.allows_all(their_current_range):
their_current_range = next(their_ranges, None)
else:
our_current_range = next(our_ranges, None)
return their_current_range is None
def allows_any(self, other): # type: (VersionConstraint) -> bool
our_ranges = iter(self._ranges)
their_ranges = iter(self._ranges_for(other))
our_current_range = next(our_ranges, None)
their_current_range = next(their_ranges, None)
while our_current_range and their_current_range:
if our_current_range.allows_any(their_current_range):
return True
if their_current_range.allows_higher(our_current_range):
our_current_range = next(our_ranges, None)
else:
their_current_range = next(their_ranges, None)
return False
def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint
our_ranges = iter(self._ranges)
their_ranges = iter(self._ranges_for(other))
new_ranges = []
our_current_range = next(our_ranges, None)
their_current_range = next(their_ranges, None)
while our_current_range and their_current_range:
intersection = our_current_range.intersect(their_current_range)
if not intersection.is_empty():
new_ranges.append(intersection)
if their_current_range.allows_higher(our_current_range):
our_current_range = next(our_ranges, None)
else:
their_current_range = next(their_ranges, None)
return VersionUnion.of(*new_ranges)
def union(self, other): # type: (VersionConstraint) -> VersionConstraint
return VersionUnion.of(self, other)
def difference(self, other): # type: (VersionConstraint) -> VersionConstraint
our_ranges = iter(self._ranges)
their_ranges = iter(self._ranges_for(other))
new_ranges = []
state = {
"current": next(our_ranges, None),
"their_range": next(their_ranges, None),
}
def their_next_range():
state["their_range"] = next(their_ranges, None)
if state["their_range"]:
return True
new_ranges.append(state["current"])
our_current = next(our_ranges, None)
while our_current:
new_ranges.append(our_current)
our_current = next(our_ranges, None)
return False
def our_next_range(include_current=True):
if include_current:
new_ranges.append(state["current"])
our_current = next(our_ranges, None)
if not our_current:
return False
state["current"] = our_current
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
continue
if state["their_range"].is_strictly_higher(state["current"]):
if not our_next_range():
break
continue
difference = state["current"].difference(state["their_range"])
if isinstance(difference, VersionUnion):
assert len(difference.ranges) == 2
new_ranges.append(difference.ranges[0])
state["current"] = difference.ranges[-1]
if not their_next_range():
break
elif difference.is_empty():
if not our_next_range(False):
break
else:
state["current"] = difference
if state["current"].allows_higher(state["their_range"]):
if not their_next_range():
break
else:
if not our_next_range():
break
if not new_ranges:
return EmptyConstraint()
if len(new_ranges) == 1:
return new_ranges[0]
return VersionUnion.of(*new_ranges)
def _ranges_for(
self, constraint
): # type: (VersionConstraint) -> List["VersionRange"]
from .version_range import VersionRange
if constraint.is_empty():
return []
if isinstance(constraint, VersionUnion):
return constraint.ranges
if isinstance(constraint, VersionRange):
return [constraint]
raise ValueError("Unknown VersionConstraint type {}".format(constraint))
def excludes_single_version(self): # type: () -> bool
from .version import Version
from .version_range import VersionRange
return isinstance(VersionRange().difference(self), Version)
def __eq__(self, other):
if not isinstance(other, VersionUnion):
return False
return self._ranges == other.ranges
def __str__(self):
from .version_range import VersionRange
if self.excludes_single_version():
return "!={}".format(VersionRange().difference(self))
return " || ".join([str(r) for r in self._ranges])
def __repr__(self):
return "<VersionUnion {}>".format(str(self))
import json
import os
from io import open
from .license import License
from .updater import Updater
_licenses = None
def license_by_id(identifier):
if _licenses is None:
load_licenses()
id = identifier.lower()
if id not in _licenses:
raise ValueError("Invalid license id: {}".format(identifier))
return _licenses[id]
def load_licenses():
global _licenses
_licenses = {}
licenses_file = os.path.join(os.path.dirname(__file__), "data", "licenses.json")
with open(licenses_file, encoding="utf-8") as f:
data = json.loads(f.read())
for name, license_info in data.items():
license = License(name, license_info[0], license_info[1], license_info[2])
_licenses[name.lower()] = license
full_name = license_info[0].lower()
if full_name in _licenses:
existing_license = _licenses[full_name]
if not existing_license.is_deprecated:
continue
_licenses[full_name] = license
# Add a Proprietary license for non-standard licenses
_licenses["proprietary"] = License("Proprietary", "Proprietary", False, False)
if __name__ == "__main__":
updater = Updater()
updater.dump()
{
"0BSD": [
"BSD Zero Clause License",
false,
false
],
"AAL": [
"Attribution Assurance License",
true,
false
],
"ADSL": [
"Amazon Digital Services License",
false,
false
],
"AFL-1.1": [
"Academic Free License v1.1",
true,
false
],
"AFL-1.2": [
"Academic Free License v1.2",
true,
false
],
"AFL-2.0": [
"Academic Free License v2.0",
true,
false
],
"AFL-2.1": [
"Academic Free License v2.1",
true,
false
],
"AFL-3.0": [
"Academic Free License v3.0",
true,
false
],
"AGPL-1.0": [
"Affero General Public License v1.0",
false,
false
],
"AGPL-3.0": [
"GNU Affero General Public License v3.0",
true,
true
],
"AGPL-3.0-only": [
"GNU Affero General Public License v3.0 only",
true,
false
],
"AGPL-3.0-or-later": [
"GNU Affero General Public License v3.0 or later",
true,
false
],
"AMDPLPA": [
"AMD's plpa_map.c License",
false,
false
],
"AML": [
"Apple MIT License",
false,
false
],
"AMPAS": [
"Academy of Motion Picture Arts and Sciences BSD",
false,
false
],
"ANTLR-PD": [
"ANTLR Software Rights Notice",
false,
false
],
"APAFML": [
"Adobe Postscript AFM License",
false,
false
],
"APL-1.0": [
"Adaptive Public License 1.0",
true,
false
],
"APSL-1.0": [
"Apple Public Source License 1.0",
true,
false
],
"APSL-1.1": [
"Apple Public Source License 1.1",
true,
false
],
"APSL-1.2": [
"Apple Public Source License 1.2",
true,
false
],
"APSL-2.0": [
"Apple Public Source License 2.0",
true,
false
],
"Abstyles": [
"Abstyles License",
false,
false
],
"Adobe-2006": [
"Adobe Systems Incorporated Source Code License Agreement",
false,
false
],
"Adobe-Glyph": [
"Adobe Glyph List License",
false,
false
],
"Afmparse": [
"Afmparse License",
false,
false
],
"Aladdin": [
"Aladdin Free Public License",
false,
false
],
"Apache-1.0": [
"Apache License 1.0",
false,
false
],
"Apache-1.1": [
"Apache License 1.1",
true,
false
],
"Apache-2.0": [
"Apache License 2.0",
true,
false
],
"Artistic-1.0": [
"Artistic License 1.0",
true,
false
],
"Artistic-1.0-Perl": [
"Artistic License 1.0 (Perl)",
true,
false
],
"Artistic-1.0-cl8": [
"Artistic License 1.0 w/clause 8",
true,
false
],
"Artistic-2.0": [
"Artistic License 2.0",
true,
false
],
"BSD-1-Clause": [
"BSD 1-Clause License",
false,
false
],
"BSD-2-Clause": [
"BSD 2-Clause \"Simplified\" License",
true,
false
],
"BSD-2-Clause-FreeBSD": [
"BSD 2-Clause FreeBSD License",
false,
false
],
"BSD-2-Clause-NetBSD": [
"BSD 2-Clause NetBSD License",
false,
false
],
"BSD-2-Clause-Patent": [
"BSD-2-Clause Plus Patent License",
true,
false
],
"BSD-3-Clause": [
"BSD 3-Clause \"New\" or \"Revised\" License",
true,
false
],
"BSD-3-Clause-Attribution": [
"BSD with attribution",
false,
false
],
"BSD-3-Clause-Clear": [
"BSD 3-Clause Clear License",
false,
false
],
"BSD-3-Clause-LBNL": [
"Lawrence Berkeley National Labs BSD variant license",
false,
false
],
"BSD-3-Clause-No-Nuclear-License": [
"BSD 3-Clause No Nuclear License",
false,
false
],
"BSD-3-Clause-No-Nuclear-License-2014": [
"BSD 3-Clause No Nuclear License 2014",
false,
false
],
"BSD-3-Clause-No-Nuclear-Warranty": [
"BSD 3-Clause No Nuclear Warranty",
false,
false
],
"BSD-4-Clause": [
"BSD 4-Clause \"Original\" or \"Old\" License",
false,
false
],
"BSD-4-Clause-UC": [
"BSD-4-Clause (University of California-Specific)",
false,
false
],
"BSD-Protection": [
"BSD Protection License",
false,
false
],
"BSD-Source-Code": [
"BSD Source Code Attribution",
false,
false
],
"BSL-1.0": [
"Boost Software License 1.0",
true,
false
],
"Bahyph": [
"Bahyph License",
false,
false
],
"Barr": [
"Barr License",
false,
false
],
"Beerware": [
"Beerware License",
false,
false
],
"BitTorrent-1.0": [
"BitTorrent Open Source License v1.0",
false,
false
],
"BitTorrent-1.1": [
"BitTorrent Open Source License v1.1",
false,
false
],
"Borceux": [
"Borceux license",
false,
false
],
"CATOSL-1.1": [
"Computer Associates Trusted Open Source License 1.1",
true,
false
],
"CC-BY-1.0": [
"Creative Commons Attribution 1.0",
false,
false
],
"CC-BY-2.0": [
"Creative Commons Attribution 2.0",
false,
false
],
"CC-BY-2.5": [
"Creative Commons Attribution 2.5",
false,
false
],
"CC-BY-3.0": [
"Creative Commons Attribution 3.0",
false,
false
],
"CC-BY-4.0": [
"Creative Commons Attribution 4.0",
false,
false
],
"CC-BY-NC-1.0": [
"Creative Commons Attribution Non Commercial 1.0",
false,
false
],
"CC-BY-NC-2.0": [
"Creative Commons Attribution Non Commercial 2.0",
false,
false
],
"CC-BY-NC-2.5": [
"Creative Commons Attribution Non Commercial 2.5",
false,
false
],
"CC-BY-NC-3.0": [
"Creative Commons Attribution Non Commercial 3.0",
false,
false
],
"CC-BY-NC-4.0": [
"Creative Commons Attribution Non Commercial 4.0",
false,
false
],
"CC-BY-NC-ND-1.0": [
"Creative Commons Attribution Non Commercial No Derivatives 1.0",
false,
false
],
"CC-BY-NC-ND-2.0": [
"Creative Commons Attribution Non Commercial No Derivatives 2.0",
false,
false
],
"CC-BY-NC-ND-2.5": [
"Creative Commons Attribution Non Commercial No Derivatives 2.5",
false,
false
],
"CC-BY-NC-ND-3.0": [
"Creative Commons Attribution Non Commercial No Derivatives 3.0",
false,
false
],
"CC-BY-NC-ND-4.0": [
"Creative Commons Attribution Non Commercial No Derivatives 4.0",
false,
false
],
"CC-BY-NC-SA-1.0": [
"Creative Commons Attribution Non Commercial Share Alike 1.0",
false,
false
],
"CC-BY-NC-SA-2.0": [
"Creative Commons Attribution Non Commercial Share Alike 2.0",
false,
false
],
"CC-BY-NC-SA-2.5": [
"Creative Commons Attribution Non Commercial Share Alike 2.5",
false,
false
],
"CC-BY-NC-SA-3.0": [
"Creative Commons Attribution Non Commercial Share Alike 3.0",
false,
false
],
"CC-BY-NC-SA-4.0": [
"Creative Commons Attribution Non Commercial Share Alike 4.0",
false,
false
],
"CC-BY-ND-1.0": [
"Creative Commons Attribution No Derivatives 1.0",
false,
false
],
"CC-BY-ND-2.0": [
"Creative Commons Attribution No Derivatives 2.0",
false,
false
],
"CC-BY-ND-2.5": [
"Creative Commons Attribution No Derivatives 2.5",
false,
false
],
"CC-BY-ND-3.0": [
"Creative Commons Attribution No Derivatives 3.0",
false,
false
],
"CC-BY-ND-4.0": [
"Creative Commons Attribution No Derivatives 4.0",
false,
false
],
"CC-BY-SA-1.0": [
"Creative Commons Attribution Share Alike 1.0",
false,
false
],
"CC-BY-SA-2.0": [
"Creative Commons Attribution Share Alike 2.0",
false,
false
],
"CC-BY-SA-2.5": [
"Creative Commons Attribution Share Alike 2.5",
false,
false
],
"CC-BY-SA-3.0": [
"Creative Commons Attribution Share Alike 3.0",
false,
false
],
"CC-BY-SA-4.0": [
"Creative Commons Attribution Share Alike 4.0",
false,
false
],
"CC0-1.0": [
"Creative Commons Zero v1.0 Universal",
false,
false
],
"CDDL-1.0": [
"Common Development and Distribution License 1.0",
true,
false
],
"CDDL-1.1": [
"Common Development and Distribution License 1.1",
false,
false
],
"CDLA-Permissive-1.0": [
"Community Data License Agreement Permissive 1.0",
false,
false
],
"CDLA-Sharing-1.0": [
"Community Data License Agreement Sharing 1.0",
false,
false
],
"CECILL-1.0": [
"CeCILL Free Software License Agreement v1.0",
false,
false
],
"CECILL-1.1": [
"CeCILL Free Software License Agreement v1.1",
false,
false
],
"CECILL-2.0": [
"CeCILL Free Software License Agreement v2.0",
false,
false
],
"CECILL-2.1": [
"CeCILL Free Software License Agreement v2.1",
true,
false
],
"CECILL-B": [
"CeCILL-B Free Software License Agreement",
false,
false
],
"CECILL-C": [
"CeCILL-C Free Software License Agreement",
false,
false
],
"CNRI-Jython": [
"CNRI Jython License",
false,
false
],
"CNRI-Python": [
"CNRI Python License",
true,
false
],
"CNRI-Python-GPL-Compatible": [
"CNRI Python Open Source GPL Compatible License Agreement",
false,
false
],
"CPAL-1.0": [
"Common Public Attribution License 1.0",
true,
false
],
"CPL-1.0": [
"Common Public License 1.0",
true,
false
],
"CPOL-1.02": [
"Code Project Open License 1.02",
false,
false
],
"CUA-OPL-1.0": [
"CUA Office Public License v1.0",
true,
false
],
"Caldera": [
"Caldera License",
false,
false
],
"ClArtistic": [
"Clarified Artistic License",
false,
false
],
"Condor-1.1": [
"Condor Public License v1.1",
false,
false
],
"Crossword": [
"Crossword License",
false,
false
],
"CrystalStacker": [
"CrystalStacker License",
false,
false
],
"Cube": [
"Cube License",
false,
false
],
"D-FSL-1.0": [
"Deutsche Freie Software Lizenz",
false,
false
],
"DOC": [
"DOC License",
false,
false
],
"DSDP": [
"DSDP License",
false,
false
],
"Dotseqn": [
"Dotseqn License",
false,
false
],
"ECL-1.0": [
"Educational Community License v1.0",
true,
false
],
"ECL-2.0": [
"Educational Community License v2.0",
true,
false
],
"EFL-1.0": [
"Eiffel Forum License v1.0",
true,
false
],
"EFL-2.0": [
"Eiffel Forum License v2.0",
true,
false
],
"EPL-1.0": [
"Eclipse Public License 1.0",
true,
false
],
"EPL-2.0": [
"Eclipse Public License 2.0",
true,
false
],
"EUDatagrid": [
"EU DataGrid Software License",
true,
false
],
"EUPL-1.0": [
"European Union Public License 1.0",
false,
false
],
"EUPL-1.1": [
"European Union Public License 1.1",
true,
false
],
"EUPL-1.2": [
"European Union Public License 1.2",
true,
false
],
"Entessa": [
"Entessa Public License v1.0",
true,
false
],
"ErlPL-1.1": [
"Erlang Public License v1.1",
false,
false
],
"Eurosym": [
"Eurosym License",
false,
false
],
"FSFAP": [
"FSF All Permissive License",
false,
false
],
"FSFUL": [
"FSF Unlimited License",
false,
false
],
"FSFULLR": [
"FSF Unlimited License (with License Retention)",
false,
false
],
"FTL": [
"Freetype Project License",
false,
false
],
"Fair": [
"Fair License",
true,
false
],
"Frameworx-1.0": [
"Frameworx Open License 1.0",
true,
false
],
"FreeImage": [
"FreeImage Public License v1.0",
false,
false
],
"GFDL-1.1": [
"GNU Free Documentation License v1.1",
false,
true
],
"GFDL-1.1-only": [
"GNU Free Documentation License v1.1 only",
false,
false
],
"GFDL-1.1-or-later": [
"GNU Free Documentation License v1.1 or later",
false,
false
],
"GFDL-1.2": [
"GNU Free Documentation License v1.2",
false,
true
],
"GFDL-1.2-only": [
"GNU Free Documentation License v1.2 only",
false,
false
],
"GFDL-1.2-or-later": [
"GNU Free Documentation License v1.2 or later",
false,
false
],
"GFDL-1.3": [
"GNU Free Documentation License v1.3",
false,
true
],
"GFDL-1.3-only": [
"GNU Free Documentation License v1.3 only",
false,
false
],
"GFDL-1.3-or-later": [
"GNU Free Documentation License v1.3 or later",
false,
false
],
"GL2PS": [
"GL2PS License",
false,
false
],
"GPL-1.0": [
"GNU General Public License v1.0 only",
false,
true
],
"GPL-1.0+": [
"GNU General Public License v1.0 or later",
false,
true
],
"GPL-1.0-only": [
"GNU General Public License v1.0 only",
false,
false
],
"GPL-1.0-or-later": [
"GNU General Public License v1.0 or later",
false,
false
],
"GPL-2.0": [
"GNU General Public License v2.0 only",
true,
true
],
"GPL-2.0+": [
"GNU General Public License v2.0 or later",
true,
true
],
"GPL-2.0-only": [
"GNU General Public License v2.0 only",
true,
false
],
"GPL-2.0-or-later": [
"GNU General Public License v2.0 or later",
true,
false
],
"GPL-2.0-with-GCC-exception": [
"GNU General Public License v2.0 w/GCC Runtime Library exception",
false,
true
],
"GPL-2.0-with-autoconf-exception": [
"GNU General Public License v2.0 w/Autoconf exception",
false,
true
],
"GPL-2.0-with-bison-exception": [
"GNU General Public License v2.0 w/Bison exception",
false,
true
],
"GPL-2.0-with-classpath-exception": [
"GNU General Public License v2.0 w/Classpath exception",
false,
true
],
"GPL-2.0-with-font-exception": [
"GNU General Public License v2.0 w/Font exception",
false,
true
],
"GPL-3.0": [
"GNU General Public License v3.0 only",
true,
true
],
"GPL-3.0+": [
"GNU General Public License v3.0 or later",
true,
true
],
"GPL-3.0-only": [
"GNU General Public License v3.0 only",
true,
false
],
"GPL-3.0-or-later": [
"GNU General Public License v3.0 or later",
true,
false
],
"GPL-3.0-with-GCC-exception": [
"GNU General Public License v3.0 w/GCC Runtime Library exception",
true,
true
],
"GPL-3.0-with-autoconf-exception": [
"GNU General Public License v3.0 w/Autoconf exception",
false,
true
],
"Giftware": [
"Giftware License",
false,
false
],
"Glide": [
"3dfx Glide License",
false,
false
],
"Glulxe": [
"Glulxe License",
false,
false
],
"HPND": [
"Historical Permission Notice and Disclaimer",
true,
false
],
"HaskellReport": [
"Haskell Language Report License",
false,
false
],
"IBM-pibs": [
"IBM PowerPC Initialization and Boot Software",
false,
false
],
"ICU": [
"ICU License",
false,
false
],
"IJG": [
"Independent JPEG Group License",
false,
false
],
"IPA": [
"IPA Font License",
true,
false
],
"IPL-1.0": [
"IBM Public License v1.0",
true,
false
],
"ISC": [
"ISC License",
true,
false
],
"ImageMagick": [
"ImageMagick License",
false,
false
],
"Imlib2": [
"Imlib2 License",
false,
false
],
"Info-ZIP": [
"Info-ZIP License",
false,
false
],
"Intel": [
"Intel Open Source License",
true,
false
],
"Intel-ACPI": [
"Intel ACPI Software License Agreement",
false,
false
],
"Interbase-1.0": [
"Interbase Public License v1.0",
false,
false
],
"JSON": [
"JSON License",
false,
false
],
"JasPer-2.0": [
"JasPer License",
false,
false
],
"LAL-1.2": [
"Licence Art Libre 1.2",
false,
false
],
"LAL-1.3": [
"Licence Art Libre 1.3",
false,
false
],
"LGPL-2.0": [
"GNU Library General Public License v2 only",
true,
true
],
"LGPL-2.0+": [
"GNU Library General Public License v2 or later",
true,
true
],
"LGPL-2.0-only": [
"GNU Library General Public License v2 only",
true,
false
],
"LGPL-2.0-or-later": [
"GNU Library General Public License v2 or later",
true,
false
],
"LGPL-2.1": [
"GNU Lesser General Public License v2.1 only",
true,
true
],
"LGPL-2.1+": [
"GNU Library General Public License v2 or later",
true,
true
],
"LGPL-2.1-only": [
"GNU Lesser General Public License v2.1 only",
true,
false
],
"LGPL-2.1-or-later": [
"GNU Lesser General Public License v2.1 or later",
true,
false
],
"LGPL-3.0": [
"GNU Lesser General Public License v3.0 only",
true,
true
],
"LGPL-3.0+": [
"GNU Lesser General Public License v3.0 or later",
true,
true
],
"LGPL-3.0-only": [
"GNU Lesser General Public License v3.0 only",
true,
false
],
"LGPL-3.0-or-later": [
"GNU Lesser General Public License v3.0 or later",
true,
false
],
"LGPLLR": [
"Lesser General Public License For Linguistic Resources",
false,
false
],
"LPL-1.0": [
"Lucent Public License Version 1.0",
true,
false
],
"LPL-1.02": [
"Lucent Public License v1.02",
true,
false
],
"LPPL-1.0": [
"LaTeX Project Public License v1.0",
false,
false
],
"LPPL-1.1": [
"LaTeX Project Public License v1.1",
false,
false
],
"LPPL-1.2": [
"LaTeX Project Public License v1.2",
false,
false
],
"LPPL-1.3a": [
"LaTeX Project Public License v1.3a",
false,
false
],
"LPPL-1.3c": [
"LaTeX Project Public License v1.3c",
true,
false
],
"Latex2e": [
"Latex2e License",
false,
false
],
"Leptonica": [
"Leptonica License",
false,
false
],
"LiLiQ-P-1.1": [
"Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1",
true,
false
],
"LiLiQ-R-1.1": [
"Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1",
true,
false
],
"LiLiQ-Rplus-1.1": [
"Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1",
true,
false
],
"Libpng": [
"libpng License",
false,
false
],
"MIT": [
"MIT License",
true,
false
],
"MIT-CMU": [
"CMU License",
false,
false
],
"MIT-advertising": [
"Enlightenment License (e16)",
false,
false
],
"MIT-enna": [
"enna License",
false,
false
],
"MIT-feh": [
"feh License",
false,
false
],
"MITNFA": [
"MIT +no-false-attribs license",
false,
false
],
"MPL-1.0": [
"Mozilla Public License 1.0",
true,
false
],
"MPL-1.1": [
"Mozilla Public License 1.1",
true,
false
],
"MPL-2.0": [
"Mozilla Public License 2.0",
true,
false
],
"MPL-2.0-no-copyleft-exception": [
"Mozilla Public License 2.0 (no copyleft exception)",
true,
false
],
"MS-PL": [
"Microsoft Public License",
true,
false
],
"MS-RL": [
"Microsoft Reciprocal License",
true,
false
],
"MTLL": [
"Matrix Template Library License",
false,
false
],
"MakeIndex": [
"MakeIndex License",
false,
false
],
"MirOS": [
"MirOS License",
true,
false
],
"Motosoto": [
"Motosoto License",
true,
false
],
"Multics": [
"Multics License",
true,
false
],
"Mup": [
"Mup License",
false,
false
],
"NASA-1.3": [
"NASA Open Source Agreement 1.3",
true,
false
],
"NBPL-1.0": [
"Net Boolean Public License v1",
false,
false
],
"NCSA": [
"University of Illinois/NCSA Open Source License",
true,
false
],
"NGPL": [
"Nethack General Public License",
true,
false
],
"NLOD-1.0": [
"Norwegian Licence for Open Government Data",
false,
false
],
"NLPL": [
"No Limit Public License",
false,
false
],
"NOSL": [
"Netizen Open Source License",
false,
false
],
"NPL-1.0": [
"Netscape Public License v1.0",
false,
false
],
"NPL-1.1": [
"Netscape Public License v1.1",
false,
false
],
"NPOSL-3.0": [
"Non-Profit Open Software License 3.0",
true,
false
],
"NRL": [
"NRL License",
false,
false
],
"NTP": [
"NTP License",
true,
false
],
"Naumen": [
"Naumen Public License",
true,
false
],
"Net-SNMP": [
"Net-SNMP License",
false,
false
],
"NetCDF": [
"NetCDF license",
false,
false
],
"Newsletr": [
"Newsletr License",
false,
false
],
"Nokia": [
"Nokia Open Source License",
true,
false
],
"Noweb": [
"Noweb License",
false,
false
],
"Nunit": [
"Nunit License",
false,
true
],
"OCCT-PL": [
"Open CASCADE Technology Public License",
false,
false
],
"OCLC-2.0": [
"OCLC Research Public License 2.0",
true,
false
],
"ODbL-1.0": [
"ODC Open Database License v1.0",
false,
false
],
"OFL-1.0": [
"SIL Open Font License 1.0",
false,
false
],
"OFL-1.1": [
"SIL Open Font License 1.1",
true,
false
],
"OGTSL": [
"Open Group Test Suite License",
true,
false
],
"OLDAP-1.1": [
"Open LDAP Public License v1.1",
false,
false
],
"OLDAP-1.2": [
"Open LDAP Public License v1.2",
false,
false
],
"OLDAP-1.3": [
"Open LDAP Public License v1.3",
false,
false
],
"OLDAP-1.4": [
"Open LDAP Public License v1.4",
false,
false
],
"OLDAP-2.0": [
"Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)",
false,
false
],
"OLDAP-2.0.1": [
"Open LDAP Public License v2.0.1",
false,
false
],
"OLDAP-2.1": [
"Open LDAP Public License v2.1",
false,
false
],
"OLDAP-2.2": [
"Open LDAP Public License v2.2",
false,
false
],
"OLDAP-2.2.1": [
"Open LDAP Public License v2.2.1",
false,
false
],
"OLDAP-2.2.2": [
"Open LDAP Public License 2.2.2",
false,
false
],
"OLDAP-2.3": [
"Open LDAP Public License v2.3",
false,
false
],
"OLDAP-2.4": [
"Open LDAP Public License v2.4",
false,
false
],
"OLDAP-2.5": [
"Open LDAP Public License v2.5",
false,
false
],
"OLDAP-2.6": [
"Open LDAP Public License v2.6",
false,
false
],
"OLDAP-2.7": [
"Open LDAP Public License v2.7",
false,
false
],
"OLDAP-2.8": [
"Open LDAP Public License v2.8",
false,
false
],
"OML": [
"Open Market License",
false,
false
],
"OPL-1.0": [
"Open Public License v1.0",
false,
false
],
"OSET-PL-2.1": [
"OSET Public License version 2.1",
true,
false
],
"OSL-1.0": [
"Open Software License 1.0",
true,
false
],
"OSL-1.1": [
"Open Software License 1.1",
false,
false
],
"OSL-2.0": [
"Open Software License 2.0",
true,
false
],
"OSL-2.1": [
"Open Software License 2.1",
true,
false
],
"OSL-3.0": [
"Open Software License 3.0",
true,
false
],
"OpenSSL": [
"OpenSSL License",
false,
false
],
"PDDL-1.0": [
"ODC Public Domain Dedication & License 1.0",
false,
false
],
"PHP-3.0": [
"PHP License v3.0",
true,
false
],
"PHP-3.01": [
"PHP License v3.01",
false,
false
],
"Plexus": [
"Plexus Classworlds License",
false,
false
],
"PostgreSQL": [
"PostgreSQL License",
true,
false
],
"Python-2.0": [
"Python License 2.0",
true,
false
],
"QPL-1.0": [
"Q Public License 1.0",
true,
false
],
"Qhull": [
"Qhull License",
false,
false
],
"RHeCos-1.1": [
"Red Hat eCos Public License v1.1",
false,
false
],
"RPL-1.1": [
"Reciprocal Public License 1.1",
true,
false
],
"RPL-1.5": [
"Reciprocal Public License 1.5",
true,
false
],
"RPSL-1.0": [
"RealNetworks Public Source License v1.0",
true,
false
],
"RSA-MD": [
"RSA Message-Digest License ",
false,
false
],
"RSCPL": [
"Ricoh Source Code Public License",
true,
false
],
"Rdisc": [
"Rdisc License",
false,
false
],
"Ruby": [
"Ruby License",
false,
false
],
"SAX-PD": [
"Sax Public Domain Notice",
false,
false
],
"SCEA": [
"SCEA Shared Source License",
false,
false
],
"SGI-B-1.0": [
"SGI Free Software License B v1.0",
false,
false
],
"SGI-B-1.1": [
"SGI Free Software License B v1.1",
false,
false
],
"SGI-B-2.0": [
"SGI Free Software License B v2.0",
false,
false
],
"SISSL": [
"Sun Industry Standards Source License v1.1",
true,
false
],
"SISSL-1.2": [
"Sun Industry Standards Source License v1.2",
false,
false
],
"SMLNJ": [
"Standard ML of New Jersey License",
false,
false
],
"SMPPL": [
"Secure Messaging Protocol Public License",
false,
false
],
"SNIA": [
"SNIA Public License 1.1",
false,
false
],
"SPL-1.0": [
"Sun Public License v1.0",
true,
false
],
"SWL": [
"Scheme Widget Library (SWL) Software License Agreement",
false,
false
],
"Saxpath": [
"Saxpath License",
false,
false
],
"Sendmail": [
"Sendmail License",
false,
false
],
"SimPL-2.0": [
"Simple Public License 2.0",
true,
false
],
"Sleepycat": [
"Sleepycat License",
true,
false
],
"Spencer-86": [
"Spencer License 86",
false,
false
],
"Spencer-94": [
"Spencer License 94",
false,
false
],
"Spencer-99": [
"Spencer License 99",
false,
false
],
"StandardML-NJ": [
"Standard ML of New Jersey License",
false,
true
],
"SugarCRM-1.1.3": [
"SugarCRM Public License v1.1.3",
false,
false
],
"TCL": [
"TCL/TK License",
false,
false
],
"TCP-wrappers": [
"TCP Wrappers License",
false,
false
],
"TMate": [
"TMate Open Source License",
false,
false
],
"TORQUE-1.1": [
"TORQUE v2.5+ Software License v1.1",
false,
false
],
"TOSL": [
"Trusster Open Source License",
false,
false
],
"UPL-1.0": [
"Universal Permissive License v1.0",
true,
false
],
"Unicode-DFS-2015": [
"Unicode License Agreement - Data Files and Software (2015)",
false,
false
],
"Unicode-DFS-2016": [
"Unicode License Agreement - Data Files and Software (2016)",
false,
false
],
"Unicode-TOU": [
"Unicode Terms of Use",
false,
false
],
"Unlicense": [
"The Unlicense",
false,
false
],
"VOSTROM": [
"VOSTROM Public License for Open Source",
false,
false
],
"VSL-1.0": [
"Vovida Software License v1.0",
true,
false
],
"Vim": [
"Vim License",
false,
false
],
"W3C": [
"W3C Software Notice and License (2002-12-31)",
true,
false
],
"W3C-19980720": [
"W3C Software Notice and License (1998-07-20)",
false,
false
],
"W3C-20150513": [
"W3C Software Notice and Document License (2015-05-13)",
false,
false
],
"WTFPL": [
"Do What The F*ck You Want To Public License",
false,
false
],
"Watcom-1.0": [
"Sybase Open Watcom Public License 1.0",
true,
false
],
"Wsuipa": [
"Wsuipa License",
false,
false
],
"X11": [
"X11 License",
false,
false
],
"XFree86-1.1": [
"XFree86 License 1.1",
false,
false
],
"XSkat": [
"XSkat License",
false,
false
],
"Xerox": [
"Xerox License",
false,
false
],
"Xnet": [
"X.Net License",
true,
false
],
"YPL-1.0": [
"Yahoo! Public License v1.0",
false,
false
],
"YPL-1.1": [
"Yahoo! Public License v1.1",
false,
false
],
"ZPL-1.1": [
"Zope Public License 1.1",
false,
false
],
"ZPL-2.0": [
"Zope Public License 2.0",
true,
false
],
"ZPL-2.1": [
"Zope Public License 2.1",
false,
false
],
"Zed": [
"Zed License",
false,
false
],
"Zend-2.0": [
"Zend License v2.0",
false,
false
],
"Zimbra-1.3": [
"Zimbra Public License v1.3",
false,
false
],
"Zimbra-1.4": [
"Zimbra Public License v1.4",
false,
false
],
"Zlib": [
"zlib License",
true,
false
],
"bzip2-1.0.5": [
"bzip2 and libbzip2 License v1.0.5",
false,
false
],
"bzip2-1.0.6": [
"bzip2 and libbzip2 License v1.0.6",
false,
false
],
"curl": [
"curl License",
false,
false
],
"diffmark": [
"diffmark license",
false,
false
],
"dvipdfm": [
"dvipdfm License",
false,
false
],
"eCos-2.0": [
"eCos license version 2.0",
false,
true
],
"eGenix": [
"eGenix.com Public License 1.1.0",
false,
false
],
"gSOAP-1.3b": [
"gSOAP Public License v1.3b",
false,
false
],
"gnuplot": [
"gnuplot License",
false,
false
],
"iMatix": [
"iMatix Standard Function Library Agreement",
false,
false
],
"libtiff": [
"libtiff License",
false,
false
],
"mpich2": [
"mpich2 License",
false,
false
],
"psfrag": [
"psfrag License",
false,
false
],
"psutils": [
"psutils License",
false,
false
],
"wxWindows": [
"wxWindows Library License",
false,
true
],
"xinetd": [
"xinetd License",
false,
false
],
"xpp": [
"XPP License",
false,
false
],
"zlib-acknowledgement": [
"zlib/libpng License with Acknowledgement",
false,
false
]
}
from collections import namedtuple
class License(namedtuple("License", "id name is_osi_approved is_deprecated")):
CLASSIFIER_SUPPORTED = {
# Not OSI Approved
"Aladdin",
"CC0-1.0",
"CECILL-B",
"CECILL-C",
"NPL-1.0",
"NPL-1.1",
# OSI Approved
"AFPL",
"AFL-1.1",
"AFL-1.2",
"AFL-2.0",
"AFL-2.1",
"AFL-3.0",
"Apache-1.1",
"Apache-2.0",
"APSL-1.1",
"APSL-1.2",
"APSL-2.0",
"Artistic-1.0",
"Artistic-2.0",
"AAL",
"AGPL-3.0",
"AGPL-3.0-only",
"AGPL-3.0-or-later",
"BSL-1.0",
"BSD-2-Clause",
"BSD-3-Clause",
"CDDL-1.0",
"CECILL-2.1",
"CPL-1.0",
"EFL-1.0",
"EFL-2.0",
"EPL-1.0",
"EPL-2.0",
"EUPL-1.1",
"EUPL-1.2",
"GPL-2.0",
"GPL-2.0+",
"GPL-2.0-only",
"GPL-2.0-or-later",
"GPL-3.0",
"GPL-3.0+",
"GPL-3.0-only",
"GPL-3.0-or-later",
"LGPL-2.0",
"LGPL-2.0+",
"LGPL-2.0-only",
"LGPL-2.0-or-later",
"LGPL-3.0",
"LGPL-3.0+",
"LGPL-3.0-only",
"LGPL-3.0-or-later",
"MIT",
"MPL-1.0",
"MPL-1.1",
"MPL-1.2",
"Nokia",
"W3C",
"ZPL-1.0",
"ZPL-2.0",
"ZPL-2.1",
}
CLASSIFIER_NAMES = {
# Not OSI Approved
"AFPL": "Aladdin Free Public License (AFPL)",
"CC0-1.0": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
"CECILL-B": "CeCILL-B Free Software License Agreement (CECILL-B)",
"CECILL-C": "CeCILL-C Free Software License Agreement (CECILL-C)",
"NPL-1.0": "Netscape Public License (NPL)",
"NPL-1.1": "Netscape Public License (NPL)",
# OSI Approved
"AFL-1.1": "Academic Free License (AFL)",
"AFL-1.2": "Academic Free License (AFL)",
"AFL-2.0": "Academic Free License (AFL)",
"AFL-2.1": "Academic Free License (AFL)",
"AFL-3.0": "Academic Free License (AFL)",
"Apache-1.1": "Apache Software License",
"Apache-2.0": "Apache Software License",
"APSL-1.1": "Apple Public Source License",
"APSL-1.2": "Apple Public Source License",
"APSL-2.0": "Apple Public Source License",
"Artistic-1.0": "Artistic License",
"Artistic-2.0": "Artistic License",
"AAL": "Attribution Assurance License",
"AGPL-3.0": "GNU Affero General Public License v3",
"AGPL-3.0-only": "GNU Affero General Public License v3",
"AGPL-3.0-or-later": "GNU Affero General Public License v3 or later (AGPLv3+)",
"BSL-1.0": "Boost Software License 1.0 (BSL-1.0)",
"BSD-2-Clause": "BSD License",
"BSD-3-Clause": "BSD License",
"CDDL-1.0": "Common Development and Distribution License 1.0 (CDDL-1.0)",
"CECILL-2.1": "CEA CNRS Inria Logiciel Libre License, version 2.1 (CeCILL-2.1)",
"CPL-1.0": "Common Public License",
"EPL-1.0": "Eclipse Public License 1.0 (EPL-1.0)",
"EFL-1.0": "Eiffel Forum License",
"EFL-2.0": "Eiffel Forum License",
"EUPL-1.1": "European Union Public Licence 1.1 (EUPL 1.1)",
"EUPL-1.2": "European Union Public Licence 1.2 (EUPL 1.2)",
"GPL-2.0": "GNU General Public License v2 (GPLv2)",
"GPL-2.0-only": "GNU General Public License v2 (GPLv2)",
"GPL-2.0+": "GNU General Public License v2 or later (GPLv2+)",
"GPL-2.0-or-later": "GNU General Public License v2 or later (GPLv2+)",
"GPL-3.0": "GNU General Public License v3 (GPLv3)",
"GPL-3.0-only": "GNU General Public License v3 (GPLv3)",
"GPL-3.0+": "GNU General Public License v3 or later (GPLv3+)",
"GPL-3.0-or-later": "GNU General Public License v3 or later (GPLv3+)",
"LGPL-2.0": "GNU Lesser General Public License v2 (LGPLv2)",
"LGPL-2.0-only": "GNU Lesser General Public License v2 (LGPLv2)",
"LGPL-2.0+": "GNU Lesser General Public License v2 or later (LGPLv2+)",
"LGPL-2.0-or-later": "GNU Lesser General Public License v2 or later (LGPLv2+)",
"LGPL-3.0": "GNU Lesser General Public License v3 (LGPLv3)",
"LGPL-3.0-only": "GNU Lesser General Public License v3 (LGPLv3)",
"LGPL-3.0+": "GNU Lesser General Public License v3 or later (LGPLv3+)",
"LGPL-3.0-or-later": "GNU Lesser General Public License v3 or later (LGPLv3+)",
"MPL-1.0": "Mozilla Public License 1.0 (MPL)",
"MPL-1.1": "Mozilla Public License 1.1 (MPL 1.1)",
"MPL-2.0": "Mozilla Public License 2.0 (MPL 2.0)",
"W3C": "W3C License",
"ZPL-1.1": "Zope Public License",
"ZPL-2.0": "Zope Public License",
"ZPL-2.1": "Zope Public License",
}
@property
def classifier(self):
parts = ["License"]
if self.is_osi_approved:
parts.append("OSI Approved")
name = self.classifier_name
if name is not None:
parts.append(name)
return " :: ".join(parts)
@property
def classifier_name(self):
if self.id not in self.CLASSIFIER_SUPPORTED:
if self.is_osi_approved:
return None
return "Other/Proprietary License"
if self.id in self.CLASSIFIER_NAMES:
return self.CLASSIFIER_NAMES[self.id]
return self.name
import json
import os
from io import open
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
class Updater:
BASE_URL = "https://raw.githubusercontent.com/spdx/license-list-data/master/json/"
def __init__(self, base_url=BASE_URL):
self._base_url = base_url
def dump(self, file=None):
if file is None:
file = os.path.join(os.path.dirname(__file__), "data", "licenses.json")
licenses_url = self._base_url + "licenses.json"
with open(file, "w", encoding="utf-8") as f:
f.write(
json.dumps(self.get_licenses(licenses_url), indent=2, sort_keys=True)
)
def get_licenses(self, url):
licenses = {}
with urlopen(url) as r:
data = json.loads(r.read().decode())
for info in data["licenses"]:
licenses[info["licenseId"]] = [
info["name"],
info["isOsiApproved"],
info["isDeprecatedLicenseId"],
]
return licenses
...@@ -20,10 +20,11 @@ import tomlkit ...@@ -20,10 +20,11 @@ import tomlkit
from clikit.api.io import IO from clikit.api.io import IO
from poetry.core.semver import parse_constraint
from poetry.core.semver.version import Version
from poetry.core.version.markers import BaseMarker
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.poetry import Poetry from poetry.poetry import Poetry
from poetry.semver import parse_constraint
from poetry.semver.version import Version
from poetry.utils._compat import CalledProcessError from poetry.utils._compat import CalledProcessError
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import decode from poetry.utils._compat import decode
...@@ -31,7 +32,6 @@ from poetry.utils._compat import encode ...@@ -31,7 +32,6 @@ from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command from poetry.utils._compat import list_to_shell_command
from poetry.utils._compat import subprocess from poetry.utils._compat import subprocess
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from poetry.version.markers import BaseMarker
GET_ENVIRONMENT_INFO = """\ GET_ENVIRONMENT_INFO = """\
......
...@@ -2,10 +2,10 @@ from typing import Union ...@@ -2,10 +2,10 @@ from typing import Union
from clikit.api.io import IO from clikit.api.io import IO
from poetry.packages.directory_dependency import DirectoryDependency from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.packages.file_dependency import FileDependency from poetry.core.packages.file_dependency import FileDependency
from poetry.packages.url_dependency import URLDependency from poetry.core.packages.url_dependency import URLDependency
from poetry.packages.vcs_dependency import VCSDependency from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.poetry import Poetry from poetry.poetry import Poetry
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import decode from poetry.utils._compat import decode
......
...@@ -3,7 +3,7 @@ from typing import List ...@@ -3,7 +3,7 @@ from typing import List
from typing import Mapping from typing import Mapping
from typing import Sequence from typing import Sequence
from poetry.packages import Package from poetry.core.packages import Package
from poetry.utils.helpers import canonicalize_name from poetry.utils.helpers import canonicalize_name
......
...@@ -9,8 +9,8 @@ from typing import List ...@@ -9,8 +9,8 @@ from typing import List
from typing import Optional from typing import Optional
from poetry.config.config import Config from poetry.config.config import Config
from poetry.core.version import Version
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.version import Version
try: try:
......
import os
import subprocess
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from .git import Git
def get_vcs(directory): # type: (Path) -> Git
working_dir = Path.cwd()
os.chdir(str(directory.resolve()))
try:
git_dir = decode(
subprocess.check_output(
["git", "rev-parse", "--show-toplevel"], stderr=subprocess.STDOUT
)
).strip()
vcs = Git(Path(git_dir))
except (subprocess.CalledProcessError, OSError):
vcs = None
finally:
os.chdir(str(working_dir))
return vcs
# -*- coding: utf-8 -*-
import re
import subprocess
from collections import namedtuple
from poetry.utils._compat import decode
pattern_formats = {
"protocol": r"\w+",
"user": r"[a-zA-Z0-9_.-]+",
"resource": r"[a-zA-Z0-9_.-]+",
"port": r"\d+",
"path": r"[\w~.\-/\\]+",
"name": r"[\w~.\-]+",
"rev": r"[^@#]+",
}
PATTERNS = [
re.compile(
r"^(git\+)?"
r"(?P<protocol>https?|git|ssh|rsync|file)://"
r"(?:(?P<user>{user})@)?"
r"(?P<resource>{resource})?"
r"(:(?P<port>{port}))?"
r"(?P<pathname>[:/\\]({path}[/\\])?"
r"((?P<name>{name}?)(\.git|[/\\])?)?)"
r"([@#](?P<rev>{rev}))?"
r"$".format(
user=pattern_formats["user"],
resource=pattern_formats["resource"],
port=pattern_formats["port"],
path=pattern_formats["path"],
name=pattern_formats["name"],
rev=pattern_formats["rev"],
)
),
re.compile(
r"(git\+)?"
r"((?P<protocol>{protocol})://)"
r"(?:(?P<user>{user})@)?"
r"(?P<resource>{resource}:?)"
r"(:(?P<port>{port}))?"
r"(?P<pathname>({path})"
r"(?P<name>{name})(\.git|/)?)"
r"([@#](?P<rev>{rev}))?"
r"$".format(
protocol=pattern_formats["protocol"],
user=pattern_formats["user"],
resource=pattern_formats["resource"],
port=pattern_formats["port"],
path=pattern_formats["path"],
name=pattern_formats["name"],
rev=pattern_formats["rev"],
)
),
re.compile(
r"^(?:(?P<user>{user})@)?"
r"(?P<resource>{resource})"
r"(:(?P<port>{port}))?"
r"(?P<pathname>([:/]{path}/)"
r"(?P<name>{name})(\.git|/)?)"
r"([@#](?P<rev>{rev}))?"
r"$".format(
user=pattern_formats["user"],
resource=pattern_formats["resource"],
port=pattern_formats["port"],
path=pattern_formats["path"],
name=pattern_formats["name"],
rev=pattern_formats["rev"],
)
),
re.compile(
r"((?P<user>{user})@)?"
r"(?P<resource>{resource})"
r"[:/]{{1,2}}"
r"(?P<pathname>({path})"
r"(?P<name>{name})(\.git|/)?)"
r"([@#](?P<rev>{rev}))?"
r"$".format(
user=pattern_formats["user"],
resource=pattern_formats["resource"],
path=pattern_formats["path"],
name=pattern_formats["name"],
rev=pattern_formats["rev"],
)
),
]
class ParsedUrl:
def __init__(self, protocol, resource, pathname, user, port, name, rev):
self.protocol = protocol
self.resource = resource
self.pathname = pathname
self.user = user
self.port = port
self.name = name
self.rev = rev
@classmethod
def parse(cls, url): # type: () -> ParsedUrl
for pattern in PATTERNS:
m = pattern.match(url)
if m:
groups = m.groupdict()
return ParsedUrl(
groups.get("protocol"),
groups.get("resource"),
groups.get("pathname"),
groups.get("user"),
groups.get("port"),
groups.get("name"),
groups.get("rev"),
)
raise ValueError('Invalid git url "{}"'.format(url))
@property
def url(self): # type: () -> str
return "{}{}{}{}{}".format(
"{}://".format(self.protocol) if self.protocol else "",
"{}@".format(self.user) if self.user else "",
self.resource,
":{}".format(self.port) if self.port else "",
"/" + self.pathname.lstrip(":/"),
)
def format(self):
return "{}".format(self.url, "#{}".format(self.rev) if self.rev else "",)
def __str__(self): # type: () -> str
return self.format()
GitUrl = namedtuple("GitUrl", ["url", "revision"])
class GitConfig:
def __init__(self, requires_git_presence=False):
self._config = {}
try:
config_list = decode(
subprocess.check_output(
["git", "config", "-l"], stderr=subprocess.STDOUT
)
)
m = re.findall("(?ms)^([^=]+)=(.*?)$", config_list)
if m:
for group in m:
self._config[group[0]] = group[1]
except (subprocess.CalledProcessError, OSError):
if requires_git_presence:
raise
def get(self, key, default=None):
return self._config.get(key, default)
def __getitem__(self, item):
return self._config[item]
class Git:
def __init__(self, work_dir=None):
self._config = GitConfig(requires_git_presence=True)
self._work_dir = work_dir
@classmethod
def normalize_url(cls, url): # type: (str) -> GitUrl
parsed = ParsedUrl.parse(url)
formatted = re.sub(r"^git\+", "", url)
if parsed.rev:
formatted = re.sub(r"[#@]{}$".format(parsed.rev), "", formatted)
altered = parsed.format() != formatted
if altered:
if re.match(r"^git\+https?", url) and re.match(
r"^/?:[^0-9]", parsed.pathname
):
normalized = re.sub(r"git\+(.*:[^:]+):(.*)", "\\1/\\2", url)
elif re.match(r"^git\+file", url):
normalized = re.sub(r"git\+", "", url)
else:
normalized = re.sub(r"^(?:git\+)?ssh://", "", url)
else:
normalized = parsed.format()
return GitUrl(re.sub(r"#[^#]*$", "", normalized), parsed.rev)
@property
def config(self): # type: () -> GitConfig
return self._config
def clone(self, repository, dest): # type: (...) -> str
return self.run("clone", repository, str(dest))
def checkout(self, rev, folder=None): # type: (...) -> str
args = []
if folder is None and self._work_dir:
folder = self._work_dir
if folder:
args += [
"--git-dir",
(folder / ".git").as_posix(),
"--work-tree",
folder.as_posix(),
]
args += ["checkout", rev]
return self.run(*args)
def rev_parse(self, rev, folder=None): # type: (...) -> str
args = []
if folder is None and self._work_dir:
folder = self._work_dir
if folder:
args += [
"--git-dir",
(folder / ".git").as_posix(),
"--work-tree",
folder.as_posix(),
]
# We need "^{commit}" to ensure that the commit SHA of the commit the
# tag points to is returned, even in the case of annotated tags.
args += ["rev-parse", rev + "^{commit}"]
return self.run(*args)
def get_ignored_files(self, folder=None): # type: (...) -> list
args = []
if folder is None and self._work_dir:
folder = self._work_dir
if folder:
args += [
"--git-dir",
(folder / ".git").as_posix(),
"--work-tree",
folder.as_posix(),
]
args += ["ls-files", "--others", "-i", "--exclude-standard"]
output = self.run(*args)
return output.strip().split("\n")
def remote_urls(self, folder=None): # type: (...) -> dict
output = self.run(
"config", "--get-regexp", r"remote\..*\.url", folder=folder
).strip()
urls = {}
for url in output.splitlines():
name, url = url.split(" ", 1)
urls[name.strip()] = url.strip()
return urls
def remote_url(self, folder=None): # type: (...) -> str
urls = self.remote_urls(folder=folder)
return urls.get("remote.origin.url", urls[list(urls.keys())[0]])
def run(self, *args, **kwargs): # type: (...) -> str
folder = kwargs.pop("folder", None)
if folder:
args = (
"--git-dir",
(folder / ".git").as_posix(),
"--work-tree",
folder.as_posix(),
) + args
return decode(
subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT)
).strip()
import operator
from typing import Union
from .exceptions import InvalidVersion
from .legacy_version import LegacyVersion
from .version import Version
OP_EQ = operator.eq
OP_LT = operator.lt
OP_LE = operator.le
OP_GT = operator.gt
OP_GE = operator.ge
OP_NE = operator.ne
_trans_op = {
"=": OP_EQ,
"==": OP_EQ,
"<": OP_LT,
"<=": OP_LE,
">": OP_GT,
">=": OP_GE,
"!=": OP_NE,
}
def parse(
version, # type: str
strict=False, # type: bool
): # type:(...) -> Union[Version, LegacyVersion]
"""
Parse the given version string and return either a :class:`Version` object
or a LegacyVersion object depending on if the given version is
a valid PEP 440 version or a legacy version.
If strict=True only PEP 440 versions will be accepted.
"""
try:
return Version(version)
except InvalidVersion:
if strict:
raise
return LegacyVersion(version)
class BaseVersion:
def __hash__(self):
return hash(self._key)
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
def _compare(self, other, method):
if not isinstance(other, BaseVersion):
return NotImplemented
return method(self._key, other._key)
class InvalidVersion(ValueError):
pass
from poetry.semver import Version
from poetry.semver import VersionUnion
from poetry.semver import parse_constraint
PYTHON_VERSION = [
"2.7.*",
"3.0.*",
"3.1.*",
"3.2.*",
"3.3.*",
"3.4.*",
"3.5.*",
"3.6.*",
"3.7.*",
"3.8.*",
]
def format_python_constraint(constraint):
"""
This helper will help in transforming
disjunctive constraint into proper constraint.
"""
if isinstance(constraint, Version):
if constraint.precision >= 3:
return "=={}".format(str(constraint))
# Transform 3.6 or 3
if constraint.precision == 2:
# 3.6
constraint = parse_constraint(
"~{}.{}".format(constraint.major, constraint.minor)
)
else:
constraint = parse_constraint("^{}.0".format(constraint.major))
if not isinstance(constraint, VersionUnion):
return str(constraint)
formatted = []
accepted = []
for version in PYTHON_VERSION:
version_constraint = parse_constraint(version)
matches = constraint.allows_any(version_constraint)
if not matches:
formatted.append("!=" + version)
else:
accepted.append(version)
# Checking lower bound
low = accepted[0]
formatted.insert(0, ">=" + ".".join(low.split(".")[:2]))
return ", ".join(formatted)
import re
from .base import BaseVersion
class LegacyVersion(BaseVersion):
def __init__(self, version):
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
def __str__(self):
return self._version
def __repr__(self):
return "<LegacyVersion({0})>".format(repr(str(self)))
@property
def public(self):
return self._version
@property
def base_version(self):
return self._version
@property
def local(self):
return None
@property
def is_prerelease(self):
return False
@property
def is_postrelease(self):
return False
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
_legacy_version_replacement_map = {
"pre": "c",
"preview": "c",
"-": "final-",
"rc": "c",
"dev": "@",
}
def _parse_version_parts(s):
for part in _legacy_version_component_re.split(s):
part = _legacy_version_replacement_map.get(part, part)
if not part or part == ".":
continue
if part[:1] in "0123456789":
# pad for numeric comparison
yield part.zfill(8)
else:
yield "*" + part
# ensure that alpha/beta/candidate are before final
yield "*final"
def _legacy_cmpkey(version):
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
# greater than or equal to 0. This will effectively put the LegacyVersion,
# which uses the defacto standard originally implemented by setuptools,
# as before all PEP 440 versions.
epoch = -1
# This scheme is taken from pkg_resources.parse_version setuptools prior to
# it's adoption of the packaging library.
parts = []
for part in _parse_version_parts(version.lower()):
if part.startswith("*"):
# remove "-" before a prerelease tag
if part < "*final":
while parts and parts[-1] == "*final-":
parts.pop()
# remove trailing zeros from each series of numeric parts
while parts and parts[-1] == "00000000":
parts.pop()
parts.append(part)
parts = tuple(parts)
return epoch, parts
import re
from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
from pyparsing import Forward
from pyparsing import Group
from pyparsing import Literal as L # noqa
from pyparsing import ParseResults
from pyparsing import QuotedString
from pyparsing import ZeroOrMore
from pyparsing import stringEnd
from pyparsing import stringStart
class InvalidMarker(ValueError):
"""
An invalid marker was found, users should refer to PEP 508.
"""
class UndefinedComparison(ValueError):
"""
An invalid operation was attempted on a value that doesn't support it.
"""
class UndefinedEnvironmentName(ValueError):
"""
A name was attempted to be used that does not exist inside of the
environment.
"""
class Node(object):
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
def __repr__(self):
return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
def serialize(self):
raise NotImplementedError
class Variable(Node):
def serialize(self):
return str(self)
class Value(Node):
def serialize(self):
return '"{0}"'.format(self)
class Op(Node):
def serialize(self):
return str(self)
VARIABLE = (
L("implementation_version")
| L("platform_python_implementation")
| L("implementation_name")
| L("python_full_version")
| L("platform_release")
| L("platform_version")
| L("platform_machine")
| L("platform_system")
| L("python_version")
| L("sys_platform")
| L("os_name")
| L("os.name")
| L("sys.platform") # PEP-345
| L("platform.version") # PEP-345
| L("platform.machine") # PEP-345
| L("platform.python_implementation") # PEP-345
| L("python_implementation") # PEP-345
| L("extra") # undocumented setuptools legacy
)
ALIASES = {
"os.name": "os_name",
"sys.platform": "sys_platform",
"platform.version": "platform_version",
"platform.machine": "platform_machine",
"platform.python_implementation": "platform_python_implementation",
"python_implementation": "platform_python_implementation",
}
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
)
MARKER_OP = VERSION_CMP | L("not in") | L("in")
MARKER_OP.setParseAction(lambda s, l, t: Op(t[0]))
MARKER_VALUE = QuotedString("'") | QuotedString('"')
MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0]))
BOOLOP = L("and") | L("or")
MARKER_VAR = VARIABLE | MARKER_VALUE
MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR)
MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0]))
LPAREN = L("(").suppress()
RPAREN = L(")").suppress()
MARKER_EXPR = Forward()
MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN)
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]
else:
return results
def _format_marker(marker, first=True):
assert isinstance(marker, (list, tuple, str))
# Sometimes we have a structure like [[...]] which is a single item list
# where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the
# outside.
if (
isinstance(marker, list)
and len(marker) == 1
and isinstance(marker[0], (list, tuple))
):
return _format_marker(marker[0])
if isinstance(marker, list):
inner = (_format_marker(m, first=False) for m in marker)
if first:
return " ".join(inner)
else:
return "(" + " ".join(inner) + ")"
elif isinstance(marker, tuple):
return " ".join([m.serialize() for m in marker])
else:
return marker
class BaseMarker(object):
def intersect(self, other): # type: (BaseMarker) -> BaseMarker
raise NotImplementedError()
def union(self, other): # type: (BaseMarker) -> BaseMarker
raise NotImplementedError()
def is_any(self): # type: () -> bool
return False
def is_empty(self): # type: () -> bool
return False
def validate(self, environment): # type: (Dict[str, Any]) -> bool
raise NotImplementedError()
def without_extras(self): # type: () -> BaseMarker
raise NotImplementedError()
def exclude(self, marker_name): # type: (str) -> BaseMarker
raise NotImplementedError()
def only(self, marker_name): # type: (str) -> 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 exclude(self, marker_name): # type: (str) -> AnyMarker
return self
def only(self, marker_name): # type: (str) -> AnyMarker
return self
def __str__(self):
return ""
def __repr__(self):
return "<AnyMarker>"
def __hash__(self):
return hash(("<any>", "<any>"))
def __eq__(self, other):
if not isinstance(other, BaseMarker):
return NotImplemented
return isinstance(other, 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 exclude(self, marker_name): # type: (str) -> EmptyMarker
return self
def only(self, marker_name): # type: (str) -> EmptyMarker
return self
def __str__(self):
return "<empty>"
def __repr__(self):
return "<EmptyMarker>"
def __hash__(self):
return hash(("<empty>", "<empty>"))
def __eq__(self, other):
if not isinstance(other, BaseMarker):
return NotImplemented
return isinstance(other, EmptyMarker)
class SingleMarker(BaseMarker):
_CONSTRAINT_RE = re.compile(r"(?i)^(~=|!=|>=?|<=?|==?|in|not in)?\s*(.+)$")
_VERSION_LIKE_MARKER_NAME = {"python_version", "platform_release"}
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 name in self._VERSION_LIKE_MARKER_NAME:
self._parser = parse_constraint
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.of(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):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
if self.name == marker_name:
return AnyMarker()
return self
def only(self, marker_name): # type: (str) -> BaseMarker
if self.name != marker_name:
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:
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
elif intersection == marker.constraint:
new_markers[i] = marker
intersected = True
elif intersection.is_empty():
return EmptyMarker()
if intersected:
continue
new_markers.append(marker)
if any(m.is_empty() for m in new_markers) or not new_markers:
return EmptyMarker()
if len(new_markers) == 1 and new_markers[0].is_any():
return AnyMarker()
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
new_markers = self._markers + [other]
return MultiMarker.of(*new_markers)
def union(self, other):
if isinstance(other, (SingleMarker, MultiMarker)):
return MarkerUnion.of(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):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name == marker_name:
# The marker is not relevant since it must be excluded
continue
marker = m.exclude(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def only(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name != marker_name:
# The marker is not relevant since it's not one we want
continue
marker = m.only(marker_name)
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):
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 = list(markers)
@property
def markers(self):
return self._markers
@classmethod
def of(cls, *markers): # type: (tuple) -> MarkerUnion
flattened_markers = _flatten_markers(markers, MarkerUnion)
markers = []
for marker in flattened_markers:
if marker in markers:
continue
if isinstance(marker, SingleMarker) and marker.name == "python_version":
intersected = False
for i, mark in enumerate(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:
markers[i] = marker
intersected = True
break
if intersected:
continue
markers.append(marker)
if any(m.is_any() for m in markers):
return AnyMarker()
return MarkerUnion(*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.of(*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.of(*new_markers)
def validate(self, environment):
for m in self._markers:
if m.validate(environment):
return True
return False
def without_extras(self):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name == marker_name:
# The marker is not relevant since it must be excluded
continue
marker = m.exclude(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def only(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name != marker_name:
# The marker is not relevant since it's not one we want
continue
marker = m.only(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*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 if not m.is_any() and not m.is_empty()
)
def is_any(self):
return any(m.is_any() for m in self._markers)
def is_empty(self):
return all(m.is_empty() 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.of(*groups)
# 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
from __future__ import division
from __future__ import print_function
import re
import string
from pyparsing import Combine
from pyparsing import Literal as L # noqa
from pyparsing import Optional
from pyparsing import ParseException
from pyparsing import Regex
from pyparsing import Word
from pyparsing import ZeroOrMore
from pyparsing import originalTextFor
from pyparsing import stringEnd
from pyparsing import stringStart
from poetry.semver import parse_constraint
from .markers import MARKER_EXPR
from .markers import parse_marker
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
LEGACY_REGEX = 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 = 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
)
)
"""
class InvalidRequirement(ValueError):
"""
An invalid requirement was found, users should refer to PEP 508.
"""
ALPHANUM = Word(string.ascii_letters + string.digits)
LBRACKET = L("[").suppress()
RBRACKET = L("]").suppress()
LPAREN = L("(").suppress()
RPAREN = L(")").suppress()
COMMA = L(",").suppress()
SEMICOLON = L(";").suppress()
AT = L("@").suppress()
PUNCTUATION = Word("-_.")
IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM)
IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name")
EXTRA = IDENTIFIER
URI = Regex(r"[^ ]+")("url")
URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
VERSION_PEP440 = Regex(REGEX, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LEGACY_REGEX, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
VERSION_MANY = Combine(
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
)("_raw_spec")
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction(
lambda s, l, t: parse_marker(s[t._original_start : t._original_end])
)
MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER)
NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
class Requirement(object):
"""Parse a requirement.
Parse a given requirement string into its parts, such as name, specifier,
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
string.
"""
def __init__(self, requirement_string):
try:
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement(
'Invalid requirement, parse error at "{0!r}"'.format(
requirement_string[e.loc : e.loc + 8]
)
)
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc
):
raise InvalidRequirement("Invalid URL given")
self.url = req.url
else:
self.url = None
self.extras = set(req.extras.asList() if req.extras else [])
constraint = req.specifier
if not constraint:
constraint = "*"
self.constraint = parse_constraint(constraint)
self.pretty_constraint = constraint
self.marker = req.marker if req.marker else None
def __str__(self):
parts = [self.name]
if self.extras:
parts.append("[{0}]".format(",".join(sorted(self.extras))))
if self.pretty_constraint:
parts.append(self.pretty_constraint)
if self.url:
parts.append("@ {0}".format(self.url))
if self.marker:
parts.append("; {0}".format(self.marker))
return "".join(parts)
def __repr__(self):
return "<Requirement({0!r})>".format(str(self))
# 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
from __future__ import division
from __future__ import 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
class Infinity(object):
def __repr__(self):
return "Infinity"
def __hash__(self):
return hash(repr(self))
def __lt__(self, other):
return False
def __le__(self, other):
return False
def __eq__(self, other):
return isinstance(other, self.__class__)
def __ne__(self, other):
return not isinstance(other, self.__class__)
def __gt__(self, other):
return True
def __ge__(self, other):
return True
def __neg__(self):
return NegativeInfinity
Infinity = Infinity() # type: ignore
class NegativeInfinity(object):
def __repr__(self):
return "-Infinity"
def __hash__(self):
return hash(repr(self))
def __lt__(self, other):
return True
def __le__(self, other):
return True
def __eq__(self, other):
return isinstance(other, self.__class__)
def __ne__(self, other):
return not isinstance(other, self.__class__)
def __gt__(self, other):
return False
def __ge__(self, other):
return False
def __neg__(self):
return Infinity
NegativeInfinity = NegativeInfinity() # type: ignore
import re
from collections import namedtuple
from itertools import dropwhile
from .base import BaseVersion
from .exceptions import InvalidVersion
from .utils import Infinity
_Version = namedtuple("_Version", ["epoch", "release", "dev", "pre", "post", "local"])
VERSION_PATTERN = re.compile(
r"""
^
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
[-_.]?
(?P<pre_n>[0-9]+)?
)?
(?P<post> # post release
(?:-(?P<post_n1>[0-9]+))
|
(?:
[-_.]?
(?P<post_l>post|rev|r)
[-_.]?
(?P<post_n2>[0-9]+)?
)
)?
(?P<dev> # dev release
[-_.]?
(?P<dev_l>dev)
[-_.]?
(?P<dev_n>[0-9]+)?
)?
)
(?:\+(?P<local>[a-z0-9]+(?:[-_.][a-z0-9]+)*))? # local version
$
""",
re.IGNORECASE | re.VERBOSE,
)
class Version(BaseVersion):
def __init__(self, version):
# Validate the version and parse it into pieces
match = VERSION_PATTERN.match(version)
if not match:
raise InvalidVersion("Invalid version: '{0}'".format(version))
# Store the parsed out pieces of the version
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
post=_parse_letter_version(
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
),
dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
)
# Generate a key which will be used for sorting
self._key = _cmpkey(
self._version.epoch,
self._version.release,
self._version.pre,
self._version.post,
self._version.dev,
self._version.local,
)
def __repr__(self):
return "<Version({0})>".format(repr(str(self)))
def __str__(self):
parts = []
# Epoch
if self._version.epoch != 0:
parts.append("{0}!".format(self._version.epoch))
# Release segment
parts.append(".".join(str(x) for x in self._version.release))
# Pre-release
if self._version.pre is not None:
parts.append("".join(str(x) for x in self._version.pre))
# Post-release
if self._version.post is not None:
parts.append(".post{0}".format(self._version.post[1]))
# Development release
if self._version.dev is not None:
parts.append(".dev{0}".format(self._version.dev[1]))
# Local version segment
if self._version.local is not None:
parts.append("+{0}".format(".".join(str(x) for x in self._version.local)))
return "".join(parts)
@property
def public(self):
return str(self).split("+", 1)[0]
@property
def base_version(self):
parts = []
# Epoch
if self._version.epoch != 0:
parts.append("{0}!".format(self._version.epoch))
# Release segment
parts.append(".".join(str(x) for x in self._version.release))
return "".join(parts)
@property
def local(self):
version_string = str(self)
if "+" in version_string:
return version_string.split("+", 1)[1]
@property
def is_prerelease(self):
return bool(self._version.dev or self._version.pre)
@property
def is_postrelease(self):
return bool(self._version.post)
def _parse_letter_version(letter, number):
if letter:
# We consider there to be an implicit 0 in a pre-release if there is
# not a numeral associated with it.
if number is None:
number = 0
# We normalize any letters to their lower case form
letter = letter.lower()
# We consider some words to be alternate spellings of other words and
# in those cases we want to normalize the spellings to our preferred
# spelling.
if letter == "alpha":
letter = "a"
elif letter == "beta":
letter = "b"
elif letter in ["c", "pre", "preview"]:
letter = "rc"
elif letter in ["rev", "r"]:
letter = "post"
return letter, int(number)
if not letter and number:
# We assume if we are given a number, but we are not given a letter
# then this is using the implicit post release syntax (e.g. 1.0-1)
letter = "post"
return letter, int(number)
_local_version_seperators = re.compile(r"[._-]")
def _parse_local_version(local):
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
if local is not None:
return tuple(
part.lower() if not part.isdigit() else int(part)
for part in _local_version_seperators.split(local)
)
def _cmpkey(epoch, release, pre, post, dev, local):
# When we compare a release version, we want to compare it with all of the
# trailing zeros removed. So we'll use a reverse the list, drop all the now
# leading zeros until we come to something non zero, then take the rest
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
release = tuple(reversed(list(dropwhile(lambda x: x == 0, reversed(release)))))
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
# We'll do this by abusing the pre segment, but we _only_ want to do this
# if there is not a pre or a post segment. If we have one of those then
# the normal sorting rules will handle this case correctly.
if pre is None and post is None and dev is not None:
pre = -Infinity
# Versions without a pre-release (except as noted above) should sort after
# those with one.
elif pre is None:
pre = Infinity
# Versions without a post segment should sort before those with one.
if post is None:
post = -Infinity
# Versions without a development segment should sort after those with one.
if dev is None:
dev = Infinity
if local is None:
# Versions without a local segment should sort before those with one.
local = -Infinity
else:
# Versions with a local segment need that segment parsed to implement
# the sorting rules in PEP440.
# - Alpha numeric segments sort before numeric segments
# - Alpha numeric segments sort lexicographically
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
return epoch, release, pre, post, dev, local
from typing import Union from typing import Union
from poetry.packages import Dependency from poetry.core.packages import Dependency
from poetry.packages import Package from poetry.core.packages import Package
from poetry.semver import Version from poetry.core.semver import Version
from poetry.semver import parse_constraint from poetry.core.semver import parse_constraint
class VersionSelector(object): class VersionSelector(object):
......
...@@ -22,15 +22,13 @@ classifiers = [ ...@@ -22,15 +22,13 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.4" python = "~2.7 || ^3.5"
poetry-core = "^1.0.0a5"
cleo = "^0.7.6" cleo = "^0.7.6"
clikit = "^0.4.2" clikit = "^0.4.3"
requests = "^2.18" requests = "^2.18"
cachy = "^0.3.0" cachy = "^0.3.0"
requests-toolbelt = "^0.8.0" requests-toolbelt = "^0.8.0"
jsonschema = "^3.1"
pyrsistent = "^0.14.2"
pyparsing = "^2.2"
cachecontrol = { version = "^0.12.4", extras = ["filecache"] } cachecontrol = { version = "^0.12.4", extras = ["filecache"] }
pkginfo = "^1.4" pkginfo = "^1.4"
html5lib = "^1.0" html5lib = "^1.0"
...@@ -38,23 +36,23 @@ shellingham = "^1.1" ...@@ -38,23 +36,23 @@ shellingham = "^1.1"
tomlkit = "^0.5.11" tomlkit = "^0.5.11"
pexpect = "^4.7.0" pexpect = "^4.7.0"
# The typing module is not in the stdlib in Python 2.7 and 3.4 # The typing module is not in the stdlib in Python 2.7
typing = { version = "^3.6", python = "~2.7 || ~3.4" } typing = { version = "^3.6", python = "~2.7" }
# Use pathlib2 for Python 2.7 and 3.4 # Use pathlib2 for Python 2.7
pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" } pathlib2 = { version = "^2.3", python = "~2.7" }
# Use glob2 for Python 2.7 and 3.4 # Use glob2 for Python 2.7 and 3.4
glob2 = { version = "^0.6", python = "~2.7 || ~3.4" } glob2 = { version = "^0.6", python = "~2.7" }
# Use virtualenv for Python 2.7 since venv does not exist # Use virtualenv for Python 2.7 since venv does not exist
virtualenv = { version = "^16.7.9", python = "~2.7" } virtualenv = { version = "^16.7.9", python = "~2.7" }
# functools32 is needed for Python 2.7 # functools32 is needed for Python 2.7
functools32 = { version = "^3.2.3", python = "~2.7" } functools32 = { version = "^3.2.3", python = "~2.7" }
keyring = [ keyring = [
{ version = "^18.0.1", python = "~2.7 || ~3.4" }, { version = "^18.0.1", python = "~2.7" },
{ version = "^20.0.1", python = "^3.5" } { version = "^20.0.1", python = "^3.5" }
] ]
# Use subprocess32 for Python 2.7 and 3.4 # Use subprocess32 for Python 2.7
subprocess32 = { version = "^3.5", python = "~2.7 || ~3.4" } subprocess32 = { version = "^3.5", python = "~2.7" }
importlib-metadata = {version = "~1.1.3", python = "<3.8"} importlib-metadata = {version = "~1.1.3", python = "<3.8"}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
...@@ -77,11 +75,8 @@ poetry = "poetry.console:main" ...@@ -77,11 +75,8 @@ poetry = "poetry.console:main"
[build-system] [build-system]
requires = ["intreehooks"] requires = ["poetry-core>=1.0.0a3"]
build-backend = "intreehooks:loader" build-backend = "poetry.core.masonry.api"
[tool.intreehooks]
build-backend = "poetry.masonry.api"
[tool.isort] [tool.isort]
...@@ -109,6 +104,7 @@ known_third_party = [ ...@@ -109,6 +104,7 @@ known_third_party = [
"keyring", "keyring",
"pexpect", "pexpect",
"pkginfo", "pkginfo",
"poetry_core",
"pyparsing", "pyparsing",
"pytest", "pytest",
"requests", "requests",
......
...@@ -25,7 +25,6 @@ class MakeReleaseCommand(Command): ...@@ -25,7 +25,6 @@ class MakeReleaseCommand(Command):
PYTHON = { PYTHON = {
"2.7": "python2.7", "2.7": "python2.7",
"3.4": "python3.4",
"3.5": "python3.5", "3.5": "python3.5",
"3.6": "python3.6", "3.6": "python3.6",
"3.7": "python3.7", "3.7": "python3.7",
...@@ -47,7 +46,8 @@ class MakeReleaseCommand(Command): ...@@ -47,7 +46,8 @@ class MakeReleaseCommand(Command):
self.check_system(pythons) self.check_system(pythons)
from poetry import __version__ from poetry.__version__ import __version__
from poetry.core.vcs import get_vcs
from poetry.factory import Factory from poetry.factory import Factory
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
...@@ -59,7 +59,6 @@ class MakeReleaseCommand(Command): ...@@ -59,7 +59,6 @@ class MakeReleaseCommand(Command):
from poetry.utils.env import GET_BASE_PREFIX from poetry.utils.env import GET_BASE_PREFIX
from poetry.utils.env import VirtualEnv from poetry.utils.env import VirtualEnv
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.vcs import get_vcs
project = Factory().create_poetry(Path.cwd()) project = Factory().create_poetry(Path.cwd())
package = project.package package = project.package
......
...@@ -87,9 +87,9 @@ def environ(): ...@@ -87,9 +87,9 @@ def environ():
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def git_mock(mocker): def git_mock(mocker):
# Patch git module to not actually clone projects # Patch git module to not actually clone projects
mocker.patch("poetry.vcs.git.Git.clone", new=mock_clone) mocker.patch("poetry.core.vcs.git.Git.clone", new=mock_clone)
mocker.patch("poetry.vcs.git.Git.checkout", new=lambda *_: None) mocker.patch("poetry.core.vcs.git.Git.checkout", new=lambda *_: None)
p = mocker.patch("poetry.vcs.git.Git.rev_parse") p = mocker.patch("poetry.core.vcs.git.Git.rev_parse")
p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
......
...@@ -6,7 +6,7 @@ import tomlkit ...@@ -6,7 +6,7 @@ import tomlkit
from cleo.testers import CommandTester from cleo.testers import CommandTester
from poetry.semver import Version from poetry.core.semver import Version
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
from poetry.utils.env import MockEnv from poetry.utils.env import MockEnv
......
...@@ -4,8 +4,8 @@ import pytest ...@@ -4,8 +4,8 @@ import pytest
from cleo.testers import CommandTester from cleo.testers import CommandTester
from poetry.core.semver import Version
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.semver import Version
from poetry.utils._compat import Path from poetry.utils._compat import Path
from tests.helpers import get_dependency from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
......
...@@ -22,7 +22,7 @@ HTTP Error 400: Bad Request ...@@ -22,7 +22,7 @@ HTTP Error 400: Bad Request
def test_publish_with_cert(app_tester, mocker): def test_publish_with_cert(app_tester, mocker):
publisher_publish = mocker.patch("poetry.masonry.publishing.Publisher.publish") publisher_publish = mocker.patch("poetry.publishing.Publisher.publish")
app_tester.execute("publish --cert path/to/ca.pem") app_tester.execute("publish --cert path/to/ca.pem")
...@@ -32,7 +32,7 @@ def test_publish_with_cert(app_tester, mocker): ...@@ -32,7 +32,7 @@ def test_publish_with_cert(app_tester, mocker):
def test_publish_with_client_cert(app_tester, mocker): def test_publish_with_client_cert(app_tester, mocker):
publisher_publish = mocker.patch("poetry.masonry.publishing.Publisher.publish") publisher_publish = mocker.patch("poetry.publishing.Publisher.publish")
app_tester.execute("publish --client-cert path/to/client.pem") app_tester.execute("publish --client-cert path/to/client.pem")
assert [ assert [
......
...@@ -49,9 +49,9 @@ def setup(mocker, installer, installed, config, env): ...@@ -49,9 +49,9 @@ def setup(mocker, installer, installed, config, env):
p.return_value = installed p.return_value = installed
# Patch git module to not actually clone projects # Patch git module to not actually clone projects
mocker.patch("poetry.vcs.git.Git.clone", new=mock_clone) mocker.patch("poetry.core.vcs.git.Git.clone", new=mock_clone)
mocker.patch("poetry.vcs.git.Git.checkout", new=lambda *_: None) mocker.patch("poetry.core.vcs.git.Git.checkout", new=lambda *_: None)
p = mocker.patch("poetry.vcs.git.Git.rev_parse") p = mocker.patch("poetry.core.vcs.git.Git.rev_parse")
p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
# Patch download to not download anything but to just copy from fixtures # Patch download to not download anything but to just copy from fixtures
......
import os import os
import shutil import shutil
from poetry.packages import Dependency from poetry.core.packages import Dependency
from poetry.packages import Package from poetry.core.packages import Package
from poetry.core.vcs.git import ParsedUrl
from poetry.utils._compat import PY2 from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import urlparse from poetry.utils._compat import urlparse
from poetry.vcs.git import ParsedUrl
FIXTURE_PATH = Path(__file__).parent / "fixtures" FIXTURE_PATH = Path(__file__).parent / "fixtures"
......
...@@ -6,10 +6,10 @@ import pytest ...@@ -6,10 +6,10 @@ import pytest
from clikit.io import NullIO from clikit.io import NullIO
from poetry.core.packages import ProjectPackage
from poetry.installation import Installer as BaseInstaller from poetry.installation import Installer as BaseInstaller
from poetry.installation.noop_installer import NoopInstaller from poetry.installation.noop_installer import NoopInstaller
from poetry.packages import Locker as BaseLocker from poetry.packages import Locker as BaseLocker
from poetry.packages import ProjectPackage
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
......
import pytest import pytest
from poetry.core.packages.package import Package
from poetry.installation.pip_installer import PipInstaller from poetry.installation.pip_installer import PipInstaller
from poetry.io.null_io import NullIO from poetry.io.null_io import NullIO
from poetry.packages.package import Package
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.utils._compat import Path from poetry.utils._compat import Path
......
import pytest
from poetry.json import validate_object
@pytest.fixture
def base_object():
return {
"name": "myapp",
"version": "1.0.0",
"description": "Some description.",
"dependencies": {"python": "^3.6"},
"dev-dependencies": {},
}
@pytest.fixture
def multi_url_object():
return {
"name": "myapp",
"version": "1.0.0",
"description": "Some description.",
"dependencies": {
"python": [
{
"url": "https://download.pytorch.org/whl/cpu/torch-1.4.0%2Bcpu-cp37-cp37m-linux_x86_64.whl",
"platform": "linux",
},
{"path": "../foo", "platform": "darwin"},
]
},
"dev-dependencies": {},
}
def test_path_dependencies(base_object):
base_object["dependencies"].update({"foo": {"path": "../foo"}})
base_object["dev-dependencies"].update({"foo": {"path": "../foo"}})
assert len(validate_object(base_object, "poetry-schema")) == 0
def test_multi_url_dependencies(multi_url_object):
assert len(validate_object(multi_url_object, "poetry-schema")) == 0
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
exclude = [
"**/SecondBar.py",
"my_package/FooBar/*",
"my_package/Foo/Bar.py",
"my_package/Foo/lowercasebar.py",
"my_package/bar/foo.py",
"my_package/bar/CapitalFoo.py"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
extra-script = {callable = "my_package.extra:main", extras = ["time"]}
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
maintainers = [
"People Everywhere <people@everywhere.com>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
exclude = [
"**/*.xml"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
[tool.poetry.dependencies.pendulum]
version = "^1.4"
markers= 'python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5"'
optional = true
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
extra-script = {callable = "my_package.extra:main", extras = ["time"]}
[tool.poetry.urls]
"Issue Tracker" = "https://github.com/python-poetry/poetry/issues"
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
exclude = ["my_package/data/data1.txt"]
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
exclude = ["**/data/", "**/*/item*"]
include = ["my_package/data/data2.txt"]
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
My Package
==========
\ No newline at end of file
from distutils.core import Extension
extensions = [Extension("extended.extended", ["extended/extended.c"])]
def build(setup_kwargs):
setup_kwargs.update({"ext_modules": extensions})
#include <Python.h>
static PyObject *hello(PyObject *self) {
return PyUnicode_FromString("Hello");
}
static PyMethodDef module_methods[] = {
{
"hello",
(PyCFunction) hello,
NULL,
PyDoc_STR("Say hello.")
},
{NULL}
};
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"extended",
NULL,
-1,
module_methods,
NULL,
NULL,
NULL,
NULL,
};
#endif
PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
PyInit_extended(void)
#else
init_extended(void)
#endif
{
PyObject *module;
#if PY_MAJOR_VERSION >= 3
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule3("extended", module_methods, NULL);
#endif
if (module == NULL)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
[tool.poetry]
name = "extended"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
build = "build.py"
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
exclude = [
"my_package/Bar/*/bar/*.py"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
extra-script = {callable = "my_package.extra:main", extras = ["time"]}
"""Test fixture for https://github.com/python-poetry/poetry/issues/756"""
[tool.poetry]
name = "localversionlabel"
description = "Local Version Label"
version = "0.1-beta.1+gitbranch-buildno-1"
authors = []
"""Example module"""
__version__ = "0.1"
[tool.poetry]
name = "module1"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
[tool.poetry.dependencies]
python = "*"
[tool.poetry]
name = "prerelease"
version = "0.1-beta.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
[tool.poetry]
name = "simple-version"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
readme = "README.rst"
[tool.poetry.dependencies]
python = "3.6"
[tool.poetry]
name = "single-python"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
[tool.poetry.dependencies]
python = "2.7.15"
[tool.poetry]
name = "module-src"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
[tool.poetry.dependencies]
python = "*"
[tool.poetry]
name = "package-src"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
[tool.poetry.dependencies]
python = "*"
from distutils.core import Extension
extensions = [Extension("extended.extended", ["src/extended/extended.c"])]
def build(setup_kwargs):
setup_kwargs.update({"ext_modules": extensions})
[tool.poetry]
name = "extended"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://python-poetry.org/"
build = "build.py"
#include <Python.h>
static PyObject *hello(PyObject *self) {
return PyUnicode_FromString("Hello");
}
static PyMethodDef module_methods[] = {
{
"hello",
(PyCFunction) hello,
NULL,
PyDoc_STR("Say hello.")
},
{NULL}
};
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"extended",
NULL,
-1,
module_methods,
NULL,
NULL,
NULL,
NULL,
};
#endif
PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
PyInit_extended(void)
#else
init_extended(void)
#endif
{
PyObject *module;
#if PY_MAJOR_VERSION >= 3
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule3("extended", module_methods, NULL);
#endif
if (module == NULL)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "with-url-dependency"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
demo = { url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl" }
[tool.poetry]
name = "with-vcs-dependency"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" }
# -*- coding: utf-8 -*-
from email.parser import Parser
from clikit.io import NullIO
from poetry.factory import Factory
from poetry.masonry.builders.builder import Builder
from poetry.utils._compat import Path
from poetry.utils.env import NullEnv
def test_builder_find_excluded_files(mocker):
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = []
builder = Builder(
Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete"),
NullEnv(),
NullIO(),
)
assert builder.find_excluded_files() == {"my_package/sub_pkg1/extra_file.xml"}
def test_builder_find_case_sensitive_excluded_files(mocker):
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = []
builder = Builder(
Factory().create_poetry(
Path(__file__).parent / "fixtures" / "case_sensitive_exclusions"
),
NullEnv(),
NullIO(),
)
assert builder.find_excluded_files() == {
"my_package/FooBar/Bar.py",
"my_package/FooBar/lowercasebar.py",
"my_package/Foo/SecondBar.py",
"my_package/Foo/Bar.py",
"my_package/Foo/lowercasebar.py",
"my_package/bar/foo.py",
"my_package/bar/CapitalFoo.py",
}
def test_builder_find_invalid_case_sensitive_excluded_files(mocker):
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = []
builder = Builder(
Factory().create_poetry(
Path(__file__).parent / "fixtures" / "invalid_case_sensitive_exclusions"
),
NullEnv(),
NullIO(),
)
assert {"my_package/Bar/foo/bar/Foo.py"} == builder.find_excluded_files()
def test_get_metadata_content():
builder = Builder(
Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete"),
NullEnv(),
NullIO(),
)
metadata = builder.get_metadata_content()
p = Parser()
parsed = p.parsestr(metadata)
assert parsed["Metadata-Version"] == "2.1"
assert parsed["Name"] == "my-package"
assert parsed["Version"] == "1.2.3"
assert parsed["Summary"] == "Some description."
assert parsed["Author"] == "Sébastien Eustace"
assert parsed["Author-email"] == "sebastien@eustace.io"
assert parsed["Keywords"] == "packaging,dependency,poetry"
assert parsed["Requires-Python"] == ">=3.6,<4.0"
assert parsed["License"] == "MIT"
assert parsed["Home-page"] == "https://python-poetry.org/"
classifiers = parsed.get_all("Classifier")
assert classifiers == [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
extras = parsed.get_all("Provides-Extra")
assert extras == ["time"]
requires = parsed.get_all("Requires-Dist")
assert requires == [
"cachy[msgpack] (>=0.2.0,<0.3.0)",
"cleo (>=0.6,<0.7)",
'pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")',
]
urls = parsed.get_all("Project-URL")
assert urls == [
"Documentation, https://python-poetry.org/docs",
"Issue Tracker, https://github.com/python-poetry/poetry/issues",
"Repository, https://github.com/python-poetry/poetry",
]
def test_metadata_homepage_default():
builder = Builder(
Factory().create_poetry(Path(__file__).parent / "fixtures" / "simple_version"),
NullEnv(),
NullIO(),
)
metadata = Parser().parsestr(builder.get_metadata_content())
assert metadata["Home-page"] is None
def test_metadata_with_vcs_dependencies():
builder = Builder(
Factory().create_poetry(
Path(__file__).parent / "fixtures" / "with_vcs_dependency"
),
NullEnv(),
NullIO(),
)
metadata = Parser().parsestr(builder.get_metadata_content())
requires_dist = metadata["Requires-Dist"]
assert "cleo @ git+https://github.com/sdispater/cleo.git@master" == requires_dist
def test_metadata_with_url_dependencies():
builder = Builder(
Factory().create_poetry(
Path(__file__).parent / "fixtures" / "with_url_dependency"
),
NullEnv(),
NullIO(),
)
metadata = Parser().parsestr(builder.get_metadata_content())
requires_dist = metadata["Requires-Dist"]
assert (
"demo @ https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
== requires_dist
)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import ast
import os
import re
import shutil
import sys
import tarfile
import tempfile
import zipfile
import pytest
from clikit.io import NullIO
from poetry import __version__
from poetry.factory import Factory
from poetry.masonry.builders import CompleteBuilder
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils.env import NullEnv
fixtures_dir = Path(__file__).parent / "fixtures"
@pytest.fixture(autouse=True)
def setup():
clear_samples_dist()
yield
clear_samples_dist()
def clear_samples_dist():
for dist in fixtures_dir.glob("**/dist"):
if dist.is_dir():
shutil.rmtree(str(dist))
@pytest.mark.skipif(
sys.platform == "win32" and sys.version_info <= (3, 6),
reason="Disable test on Windows for Python <=3.6",
)
def test_wheel_c_extension():
module_path = fixtures_dir / "extended"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()
sdist = fixtures_dir / "extended" / "dist" / "extended-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "extended-0.1/build.py" in tar.getnames()
assert "extended-0.1/extended/extended.c" in tar.getnames()
whl = list((module_path / "dist").glob("extended-0.1-cp*-cp*-*.whl"))[0]
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
has_compiled_extension = False
for name in zip.namelist():
if name.startswith("extended/extended") and name.endswith((".so", ".pyd")):
has_compiled_extension = True
assert has_compiled_extension
try:
wheel_data = decode(zip.read("extended-0.1.dist-info/WHEEL"))
assert (
re.match(
"""(?m)^\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dm?u?-.+
$""".format(
__version__
),
wheel_data,
)
is not None
)
records = decode(zip.read("extended-0.1.dist-info/RECORD"))
assert re.search(r"\s+extended/extended.*\.(so|pyd)", records) is not None
finally:
zip.close()
@pytest.mark.skipif(
sys.platform == "win32" and sys.version_info <= (3, 6),
reason="Disable test on Windows for Python <=3.6",
)
def test_wheel_c_extension_src_layout():
module_path = fixtures_dir / "src_extended"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()
sdist = fixtures_dir / "src_extended" / "dist" / "extended-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "extended-0.1/build.py" in tar.getnames()
assert "extended-0.1/src/extended/extended.c" in tar.getnames()
whl = list((module_path / "dist").glob("extended-0.1-cp*-cp*-*.whl"))[0]
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
has_compiled_extension = False
for name in zip.namelist():
if name.startswith("extended/extended") and name.endswith((".so", ".pyd")):
has_compiled_extension = True
assert has_compiled_extension
try:
wheel_data = decode(zip.read("extended-0.1.dist-info/WHEEL"))
assert (
re.match(
"""(?m)^\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dm?u?-.+
$""".format(
__version__
),
wheel_data,
)
is not None
)
records = decode(zip.read("extended-0.1.dist-info/RECORD"))
assert re.search(r"\s+extended/extended.*\.(so|pyd)", records) is not None
finally:
zip.close()
def test_complete():
module_path = fixtures_dir / "complete"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()
whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl"
assert whl.exists()
if sys.platform != "win32":
assert (os.stat(str(whl)).st_mode & 0o777) == 0o644
zip = zipfile.ZipFile(str(whl))
try:
assert "my_package/sub_pgk1/extra_file.xml" not in zip.namelist()
entry_points = zip.read("my_package-1.2.3.dist-info/entry_points.txt")
assert (
decode(entry_points.decode())
== """\
[console_scripts]
extra-script=my_package.extra:main[time]
my-2nd-script=my_package:main2
my-script=my_package:main
"""
)
wheel_data = decode(zip.read("my_package-1.2.3.dist-info/WHEEL"))
assert (
wheel_data
== """\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: true
Tag: py3-none-any
""".format(
__version__
)
)
wheel_data = decode(zip.read("my_package-1.2.3.dist-info/METADATA"))
assert (
wheel_data
== """\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://python-poetry.org/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Maintainer: People Everywhere
Maintainer-email: people@everywhere.com
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
)
finally:
zip.close()
def test_complete_no_vcs():
# Copy the complete fixtures dir to a temporary directory
module_path = fixtures_dir / "complete"
temporary_dir = Path(tempfile.mkdtemp()) / "complete"
shutil.copytree(module_path.as_posix(), temporary_dir.as_posix())
builder = CompleteBuilder(
Factory().create_poetry(temporary_dir), NullEnv(execute=True), NullIO()
)
builder.build()
whl = temporary_dir / "dist" / "my_package-1.2.3-py3-none-any.whl"
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
# Check the zipped file to be sure that included and excluded files are
# correctly taken account of without vcs
expected_name_list = [
"my_package/__init__.py",
"my_package/data1/test.json",
"my_package/sub_pkg1/__init__.py",
"my_package/sub_pkg2/__init__.py",
"my_package/sub_pkg2/data2/data.json",
"my_package/sub_pkg3/foo.py",
"my_package-1.2.3.dist-info/entry_points.txt",
"my_package-1.2.3.dist-info/LICENSE",
"my_package-1.2.3.dist-info/WHEEL",
"my_package-1.2.3.dist-info/METADATA",
"my_package-1.2.3.dist-info/RECORD",
]
assert sorted(zip.namelist()) == sorted(expected_name_list)
try:
entry_points = zip.read("my_package-1.2.3.dist-info/entry_points.txt")
assert (
decode(entry_points.decode())
== """\
[console_scripts]
extra-script=my_package.extra:main[time]
my-2nd-script=my_package:main2
my-script=my_package:main
"""
)
wheel_data = decode(zip.read("my_package-1.2.3.dist-info/WHEEL"))
assert (
wheel_data
== """\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: true
Tag: py3-none-any
""".format(
__version__
)
)
wheel_data = decode(zip.read("my_package-1.2.3.dist-info/METADATA"))
assert (
wheel_data
== """\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://python-poetry.org/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Maintainer: People Everywhere
Maintainer-email: people@everywhere.com
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
)
finally:
zip.close()
def test_module_src():
module_path = fixtures_dir / "source_file"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()
sdist = module_path / "dist" / "module-src-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "module-src-0.1/src/module_src.py" in tar.getnames()
whl = module_path / "dist" / "module_src-0.1-py2.py3-none-any.whl"
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
try:
assert "module_src.py" in zip.namelist()
finally:
zip.close()
def test_package_src():
module_path = fixtures_dir / "source_package"
builder = CompleteBuilder(
Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()
)
builder.build()
sdist = module_path / "dist" / "package-src-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "package-src-0.1/src/package_src/module.py" in tar.getnames()
whl = module_path / "dist" / "package_src-0.1-py2.py3-none-any.whl"
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
try:
assert "package_src/__init__.py" in zip.namelist()
assert "package_src/module.py" in zip.namelist()
finally:
zip.close()
def test_package_with_include(mocker):
module_path = fixtures_dir / "with-include"
# Patch git module to return specific excluded files
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = [
str(
Path(__file__).parent
/ "fixtures"
/ "with-include"
/ "extra_dir"
/ "vcs_excluded.txt"
),
str(
Path(__file__).parent
/ "fixtures"
/ "with-include"
/ "extra_dir"
/ "sub_pkg"
/ "vcs_excluded.txt"
),
]
builder = CompleteBuilder(Factory().create_poetry(module_path), NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "with-include" / "dist" / "with-include-1.2.3.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
names = tar.getnames()
assert len(names) == len(set(names))
assert "with-include-1.2.3/LICENSE" in names
assert "with-include-1.2.3/README.rst" in names
assert "with-include-1.2.3/extra_dir/__init__.py" in names
assert "with-include-1.2.3/extra_dir/vcs_excluded.txt" in names
assert "with-include-1.2.3/extra_dir/sub_pkg/__init__.py" in names
assert "with-include-1.2.3/extra_dir/sub_pkg/vcs_excluded.txt" not in names
assert "with-include-1.2.3/my_module.py" in names
assert "with-include-1.2.3/notes.txt" in names
assert "with-include-1.2.3/package_with_include/__init__.py" in names
assert "with-include-1.2.3/tests/__init__.py" in names
assert "with-include-1.2.3/pyproject.toml" in names
assert "with-include-1.2.3/setup.py" in names
assert "with-include-1.2.3/PKG-INFO" in names
assert "with-include-1.2.3/for_wheel_only/__init__.py" not in names
assert "with-include-1.2.3/src/src_package/__init__.py" in names
setup = tar.extractfile("with-include-1.2.3/setup.py").read()
setup_ast = ast.parse(setup)
setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
ns = {}
exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
assert ns["package_dir"] == {"": "src"}
assert ns["packages"] == [
"extra_dir",
"extra_dir.sub_pkg",
"package_with_include",
"src_package",
"tests",
]
assert ns["package_data"] == {"": ["*"]}
assert ns["modules"] == ["my_module"]
whl = module_path / "dist" / "with_include-1.2.3-py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
names = z.namelist()
assert len(names) == len(set(names))
assert "with_include-1.2.3.dist-info/LICENSE" in names
assert "extra_dir/__init__.py" in names
assert "extra_dir/vcs_excluded.txt" in names
assert "extra_dir/sub_pkg/__init__.py" in names
assert "extra_dir/sub_pkg/vcs_excluded.txt" not in names
assert "for_wheel_only/__init__.py" in names
assert "my_module.py" in names
assert "notes.txt" in names
assert "package_with_include/__init__.py" in names
assert "tests/__init__.py" not in names
assert "src_package/__init__.py" in names
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys
from clikit.io import NullIO
from poetry.factory import Factory
from poetry.masonry.builders import EditableBuilder
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
fixtures_dir = Path(__file__).parent / "fixtures"
def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mocker):
move = mocker.patch("shutil.move")
tmp_dir = Path(tmp_dir)
env = MockEnv(path=tmp_dir, pip_version="18.1", execute=False, sys_path=[])
module_path = fixtures_dir / "extended"
builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO())
builder.build()
expected = [[sys.executable, "-m", "pip", "install", "-e", str(module_path)]]
assert expected == env.executed
assert 0 == move.call_count
def test_build_should_temporarily_remove_the_pyproject_file(tmp_dir, mocker):
move = mocker.patch("shutil.move")
tmp_dir = Path(tmp_dir)
env = MockEnv(path=tmp_dir, pip_version="19.1", execute=False, sys_path=[])
module_path = fixtures_dir / "extended"
builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO())
builder.build()
expected = [[sys.executable, "-m", "pip", "install", "-e", str(module_path)]]
assert expected == env.executed
assert 2 == move.call_count
expected_calls = [
mocker.call(
str(module_path / "pyproject.toml"), str(module_path / "pyproject.tmp")
),
mocker.call(
str(module_path / "pyproject.tmp"), str(module_path / "pyproject.toml")
),
]
assert expected_calls == move.call_args_list
# -*- coding: utf-8 -*-
import ast
import shutil
import tarfile
from email.parser import Parser
import pytest
from clikit.io import NullIO
from poetry.factory import Factory
from poetry.masonry.builders.sdist import SdistBuilder
from poetry.masonry.utils.package_include import PackageInclude
from poetry.packages import Package
from poetry.packages.vcs_dependency import VCSDependency
from poetry.utils._compat import Path
from poetry.utils._compat import to_str
from poetry.utils.env import NullEnv
from tests.helpers import get_dependency
fixtures_dir = Path(__file__).parent / "fixtures"
@pytest.fixture(autouse=True)
def setup():
clear_samples_dist()
yield
clear_samples_dist()
def clear_samples_dist():
for dist in fixtures_dir.glob("**/dist"):
if dist.is_dir():
shutil.rmtree(str(dist))
def project(name):
return Path(__file__).parent / "fixtures" / name
def test_convert_dependencies():
package = Package("foo", "1.2.3")
result = SdistBuilder.convert_dependencies(
package,
[
get_dependency("A", "^1.0"),
get_dependency("B", "~1.0"),
get_dependency("C", "1.2.3"),
VCSDependency("D", "git", "https://github.com/sdispater/d.git"),
],
)
main = [
"A>=1.0,<2.0",
"B>=1.0,<1.1",
"C==1.2.3",
"D @ git+https://github.com/sdispater/d.git@master",
]
extras = {}
assert result == (main, extras)
package = Package("foo", "1.2.3")
package.extras = {"bar": [get_dependency("A")]}
result = SdistBuilder.convert_dependencies(
package,
[
get_dependency("A", ">=1.2", optional=True),
get_dependency("B", "~1.0"),
get_dependency("C", "1.2.3"),
],
)
main = ["B>=1.0,<1.1", "C==1.2.3"]
extras = {"bar": ["A>=1.2"]}
assert result == (main, extras)
c = get_dependency("C", "1.2.3")
c.python_versions = "~2.7 || ^3.6"
d = get_dependency("D", "3.4.5", optional=True)
d.python_versions = "~2.7 || ^3.4"
package.extras = {"baz": [get_dependency("D")]}
result = SdistBuilder.convert_dependencies(
package,
[
get_dependency("A", ">=1.2", optional=True),
get_dependency("B", "~1.0"),
c,
d,
],
)
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"'
)
extra_d_dependency = (
'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"]}
assert result == (main, extras)
def test_make_setup():
poetry = Factory().create_poetry(project("complete"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
setup = builder.build_setup()
setup_ast = ast.parse(setup)
setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
ns = {}
exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
assert ns["packages"] == [
"my_package",
"my_package.sub_pkg1",
"my_package.sub_pkg2",
"my_package.sub_pkg3",
]
assert ns["install_requires"] == ["cachy[msgpack]>=0.2.0,<0.3.0", "cleo>=0.6,<0.7"]
assert ns["entry_points"] == {
"console_scripts": [
"extra-script = my_package.extra:main[time]",
"my-2nd-script = my_package:main2",
"my-script = my_package:main",
]
}
assert ns["extras_require"] == {
'time:python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5"': [
"pendulum>=1.4,<2.0"
]
}
def test_make_pkg_info(mocker):
get_metadata_content = mocker.patch(
"poetry.masonry.builders.builder.Builder.get_metadata_content"
)
poetry = Factory().create_poetry(project("complete"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build_pkg_info()
assert get_metadata_content.called
def test_make_pkg_info_any_python():
poetry = Factory().create_poetry(project("module1"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))
assert "Requires-Python" not in parsed
def test_find_files_to_add():
poetry = Factory().create_poetry(project("complete"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
result = builder.find_files_to_add()
assert sorted(result) == sorted(
[
Path("LICENSE"),
Path("README.rst"),
Path("my_package/__init__.py"),
Path("my_package/data1/test.json"),
Path("my_package/sub_pkg1/__init__.py"),
Path("my_package/sub_pkg2/__init__.py"),
Path("my_package/sub_pkg2/data2/data.json"),
Path("my_package/sub_pkg3/foo.py"),
Path("pyproject.toml"),
]
)
def test_make_pkg_info_multi_constraints_dependency():
poetry = Factory().create_poetry(
Path(__file__).parent.parent.parent
/ "fixtures"
/ "project_with_multi_constraints_dependency"
)
builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))
requires = parsed.get_all("Requires-Dist")
assert requires == [
'pendulum (>=1.5,<2.0); python_version < "3.4"',
'pendulum (>=2.0,<3.0); python_version >= "3.4" and python_version < "4.0"',
]
def test_find_packages():
poetry = Factory().create_poetry(project("complete"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
base = project("complete")
include = PackageInclude(base, "my_package")
pkg_dir, packages, pkg_data = builder.find_packages(include)
assert pkg_dir is None
assert packages == [
"my_package",
"my_package.sub_pkg1",
"my_package.sub_pkg2",
"my_package.sub_pkg3",
]
assert pkg_data == {
"": ["*"],
"my_package": ["data1/*"],
"my_package.sub_pkg2": ["data2/*"],
}
poetry = Factory().create_poetry(project("source_package"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
base = project("source_package")
include = PackageInclude(base, "package_src", source="src")
pkg_dir, packages, pkg_data = builder.find_packages(include)
assert pkg_dir == str(base / "src")
assert packages == ["package_src"]
assert pkg_data == {"": ["*"]}
def test_package():
poetry = Factory().create_poetry(project("complete"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "complete" / "dist" / "my-package-1.2.3.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "my-package-1.2.3/LICENSE" in tar.getnames()
def test_module():
poetry = Factory().create_poetry(project("module1"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "module1" / "dist" / "module1-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "module1-0.1/module1.py" in tar.getnames()
def test_prelease():
poetry = Factory().create_poetry(project("prerelease"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "prerelease" / "dist" / "prerelease-0.1b1.tar.gz"
assert sdist.exists()
def test_with_c_extensions():
poetry = Factory().create_poetry(project("extended"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "extended" / "dist" / "extended-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "extended-0.1/build.py" in tar.getnames()
assert "extended-0.1/extended/extended.c" in tar.getnames()
def test_with_c_extensions_src_layout():
poetry = Factory().create_poetry(project("src_extended"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "src_extended" / "dist" / "extended-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "extended-0.1/build.py" in tar.getnames()
assert "extended-0.1/src/extended/extended.c" in tar.getnames()
def test_with_src_module_file():
poetry = Factory().create_poetry(project("source_file"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
# Check setup.py
setup = builder.build_setup()
setup_ast = ast.parse(setup)
setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
ns = {}
exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
assert ns["package_dir"] == {"": "src"}
assert ns["modules"] == ["module_src"]
builder.build()
sdist = fixtures_dir / "source_file" / "dist" / "module-src-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "module-src-0.1/src/module_src.py" in tar.getnames()
def test_with_src_module_dir():
poetry = Factory().create_poetry(project("source_package"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
# Check setup.py
setup = builder.build_setup()
setup_ast = ast.parse(setup)
setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
ns = {}
exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
assert ns["package_dir"] == {"": "src"}
assert ns["packages"] == ["package_src"]
builder.build()
sdist = fixtures_dir / "source_package" / "dist" / "package-src-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "package-src-0.1/src/package_src/__init__.py" in tar.getnames()
assert "package-src-0.1/src/package_src/module.py" in tar.getnames()
def test_default_with_excluded_data(mocker):
# Patch git module to return specific excluded files
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = [
(
(
Path(__file__).parent
/ "fixtures"
/ "default_with_excluded_data"
/ "my_package"
/ "data"
/ "sub_data"
/ "data2.txt"
)
.relative_to(project("default_with_excluded_data"))
.as_posix()
)
]
poetry = Factory().create_poetry(project("default_with_excluded_data"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
# Check setup.py
setup = builder.build_setup()
setup_ast = ast.parse(setup)
setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
ns = {}
exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
assert "package_dir" not in ns
assert ns["packages"] == ["my_package"]
assert ns["package_data"] == {
"": ["*"],
"my_package": ["data/*", "data/sub_data/data3.txt"],
}
builder.build()
sdist = (
fixtures_dir / "default_with_excluded_data" / "dist" / "my-package-1.2.3.tar.gz"
)
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
names = tar.getnames()
assert len(names) == len(set(names))
assert "my-package-1.2.3/LICENSE" in names
assert "my-package-1.2.3/README.rst" in names
assert "my-package-1.2.3/my_package/__init__.py" in names
assert "my-package-1.2.3/my_package/data/data1.txt" in names
assert "my-package-1.2.3/pyproject.toml" in names
assert "my-package-1.2.3/setup.py" in names
assert "my-package-1.2.3/PKG-INFO" in names
# all last modified times should be set to a valid timestamp
for tarinfo in tar.getmembers():
assert 0 < tarinfo.mtime
def test_src_excluded_nested_data():
module_path = fixtures_dir / "exclude_nested_data_toml"
poetry = Factory().create_poetry(module_path)
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = module_path / "dist" / "my-package-1.2.3.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
names = tar.getnames()
assert len(names) == len(set(names))
assert "my-package-1.2.3/LICENSE" in names
assert "my-package-1.2.3/README.rst" in names
assert "my-package-1.2.3/pyproject.toml" in names
assert "my-package-1.2.3/setup.py" in names
assert "my-package-1.2.3/PKG-INFO" in names
assert "my-package-1.2.3/my_package/__init__.py" in names
assert "my-package-1.2.3/my_package/data/sub_data/data2.txt" not in names
assert "my-package-1.2.3/my_package/data/sub_data/data3.txt" not in names
assert "my-package-1.2.3/my_package/data/data1.txt" not in names
assert "my-package-1.2.3/my_package/data/data2.txt" in names
assert "my-package-1.2.3/my_package/puplic/publicdata.txt" in names
assert "my-package-1.2.3/my_package/public/item1/itemdata1.txt" not in names
assert (
"my-package-1.2.3/my_package/public/item1/subitem/subitemdata.txt"
not in names
)
assert "my-package-1.2.3/my_package/public/item2/itemdata2.txt" not in names
def test_proper_python_requires_if_two_digits_precision_version_specified():
poetry = Factory().create_poetry(project("simple_version"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))
assert parsed["Requires-Python"] == ">=3.6,<3.7"
def test_proper_python_requires_if_three_digits_precision_version_specified():
poetry = Factory().create_poetry(project("single_python"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))
assert parsed["Requires-Python"] == "==2.7.15"
def test_excluded_subpackage():
poetry = Factory().create_poetry(project("excluded_subpackage"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
setup = builder.build_setup()
setup_ast = ast.parse(setup)
setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
ns = {}
exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
assert ns["packages"] == ["example"]
# -*- coding: utf-8 -*-
import shutil
import zipfile
import pytest
from clikit.io import NullIO
from poetry.factory import Factory
from poetry.masonry.builders.wheel import WheelBuilder
from poetry.masonry.publishing.uploader import Uploader
from poetry.utils._compat import Path
from poetry.utils.env import NullEnv
fixtures_dir = Path(__file__).parent / "fixtures"
@pytest.fixture(autouse=True)
def setup():
clear_samples_dist()
yield
clear_samples_dist()
def clear_samples_dist():
for dist in fixtures_dir.glob("**/dist"):
if dist.is_dir():
shutil.rmtree(str(dist))
def test_wheel_module():
module_path = fixtures_dir / "module1"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "module1-0.1-py2.py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert "module1.py" in z.namelist()
def test_wheel_package():
module_path = fixtures_dir / "complete"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert "my_package/sub_pkg1/__init__.py" in z.namelist()
def test_wheel_prerelease():
module_path = fixtures_dir / "prerelease"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "prerelease-0.1b1-py2.py3-none-any.whl"
assert whl.exists()
def test_wheel_excluded_data():
module_path = fixtures_dir / "default_with_excluded_data_toml"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert "my_package/__init__.py" in z.namelist()
assert "my_package/data/sub_data/data2.txt" in z.namelist()
assert "my_package/data/sub_data/data3.txt" in z.namelist()
assert "my_package/data/data1.txt" not in z.namelist()
def test_wheel_excluded_nested_data():
module_path = fixtures_dir / "exclude_nested_data_toml"
poetry = Factory().create_poetry(module_path)
WheelBuilder.make(poetry, NullEnv(), NullIO())
whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert "my_package/__init__.py" in z.namelist()
assert "my_package/data/sub_data/data2.txt" not in z.namelist()
assert "my_package/data/sub_data/data3.txt" not in z.namelist()
assert "my_package/data/data1.txt" not in z.namelist()
assert "my_package/data/data2.txt" in z.namelist()
assert "my_package/puplic/publicdata.txt" in z.namelist()
assert "my_package/public/item1/itemdata1.txt" not in z.namelist()
assert "my_package/public/item1/subitem/subitemdata.txt" not in z.namelist()
assert "my_package/public/item2/itemdata2.txt" not in z.namelist()
def test_wheel_localversionlabel():
module_path = fixtures_dir / "localversionlabel"
project = Factory().create_poetry(module_path)
WheelBuilder.make(project, NullEnv(), NullIO())
local_version_string = "localversionlabel-0.1b1+gitbranch.buildno.1"
whl = module_path / "dist" / (local_version_string + "-py2.py3-none-any.whl")
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert local_version_string + ".dist-info/METADATA" in z.namelist()
uploader = Uploader(project, NullIO())
assert whl in uploader.files
def test_wheel_package_src():
module_path = fixtures_dir / "source_package"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "package_src-0.1-py2.py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert "package_src/__init__.py" in z.namelist()
assert "package_src/module.py" in z.namelist()
def test_wheel_module_src():
module_path = fixtures_dir / "source_file"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "module_src-0.1-py2.py3-none-any.whl"
assert whl.exists()
with zipfile.ZipFile(str(whl)) as z:
assert "module_src.py" in z.namelist()
def test_dist_info_file_permissions():
module_path = fixtures_dir / "complete"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl"
with zipfile.ZipFile(str(whl)) as z:
assert (
z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr == 0o644 << 16
)
assert (
z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr
== 0o644 << 16
)
assert (
z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr == 0o644 << 16
)
assert (
z.getinfo("my_package-1.2.3.dist-info/entry_points.txt").external_attr
== 0o644 << 16
)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import tarfile
import zipfile
from contextlib import contextmanager
from poetry import __version__
from poetry.masonry import api
from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils.helpers import temporary_directory
@contextmanager
def cwd(directory):
prev = os.getcwd()
os.chdir(str(directory))
try:
yield
finally:
os.chdir(prev)
fixtures = os.path.join(os.path.dirname(__file__), "builders", "fixtures")
def test_get_requires_for_build_wheel():
expected = []
with cwd(os.path.join(fixtures, "complete")):
assert api.get_requires_for_build_wheel() == expected
def test_get_requires_for_build_sdist():
expected = []
with cwd(os.path.join(fixtures, "complete")):
assert api.get_requires_for_build_sdist() == expected
def test_build_wheel():
with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")):
filename = api.build_wheel(tmp_dir)
with zipfile.ZipFile(str(os.path.join(tmp_dir, filename))) as zip:
namelist = zip.namelist()
assert "my_package-1.2.3.dist-info/entry_points.txt" in namelist
assert "my_package-1.2.3.dist-info/WHEEL" in namelist
assert "my_package-1.2.3.dist-info/METADATA" in namelist
def test_build_sdist():
with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")):
filename = api.build_sdist(tmp_dir)
with tarfile.open(str(os.path.join(tmp_dir, filename))) as tar:
namelist = tar.getnames()
assert "my-package-1.2.3/LICENSE" in namelist
def test_prepare_metadata_for_build_wheel():
entry_points = """\
[console_scripts]
extra-script=my_package.extra:main[time]
my-2nd-script=my_package:main2
my-script=my_package:main
"""
wheel_data = """\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: true
Tag: py3-none-any
""".format(
__version__
)
metadata = """\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://python-poetry.org/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Maintainer: People Everywhere
Maintainer-email: people@everywhere.com
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")):
dirname = api.prepare_metadata_for_build_wheel(tmp_dir)
assert "my_package-1.2.3.dist-info" == dirname
dist_info = Path(tmp_dir, dirname)
assert (dist_info / "entry_points.txt").exists()
assert (dist_info / "WHEEL").exists()
assert (dist_info / "METADATA").exists()
with (dist_info / "entry_points.txt").open(encoding="utf-8") as f:
assert entry_points == decode(f.read())
with (dist_info / "WHEEL").open(encoding="utf-8") as f:
assert wheel_data == decode(f.read())
with (dist_info / "METADATA").open(encoding="utf-8") as f:
assert metadata == decode(f.read())
import pytest
from poetry.masonry.utils.package_include import PackageInclude
from poetry.utils._compat import Path
fixtures_dir = Path(__file__).parent / "fixtures"
with_includes = fixtures_dir / "with_includes"
def test_package_include_with_multiple_dirs():
pkg_include = PackageInclude(base=fixtures_dir, include="with_includes")
assert pkg_include.elements == [
with_includes / "__init__.py",
with_includes / "bar",
with_includes / "bar/baz.py",
with_includes / "extra_package",
with_includes / "extra_package/some_dir",
with_includes / "extra_package/some_dir/foo.py",
with_includes / "extra_package/some_dir/quux.py",
with_includes / "not_a_python_pkg",
with_includes / "not_a_python_pkg/baz.txt",
]
def test_package_include_with_simple_dir():
pkg_include = PackageInclude(base=with_includes, include="bar")
assert pkg_include.elements == [with_includes / "bar/baz.py"]
def test_package_include_with_nested_dir():
pkg_include = PackageInclude(base=with_includes, include="extra_package/**/*.py")
assert pkg_include.elements == [
with_includes / "extra_package/some_dir/foo.py",
with_includes / "extra_package/some_dir/quux.py",
]
def test_package_include_with_no_python_files_in_dir():
with pytest.raises(ValueError) as e:
PackageInclude(base=with_includes, include="not_a_python_pkg")
assert str(e.value) == "not_a_python_pkg is not a package."
from poetry.core.packages import Package
from poetry.mixology.failure import SolveFailure from poetry.mixology.failure import SolveFailure
from poetry.mixology.version_solver import VersionSolver from poetry.mixology.version_solver import VersionSolver
from poetry.packages import DependencyPackage from poetry.packages import DependencyPackage
from poetry.packages import Package
def add_to_repo(repository, name, version, deps=None, python=None): def add_to_repo(repository, name, version, deps=None, python=None):
......
...@@ -2,7 +2,7 @@ import pytest ...@@ -2,7 +2,7 @@ import pytest
from clikit.io import NullIO from clikit.io import NullIO
from poetry.packages.project_package import ProjectPackage from poetry.core.packages.project_package import ProjectPackage
from poetry.puzzle.provider import Provider from poetry.puzzle.provider import Provider
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
......
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_union(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"))
from poetry.packages import Dependency
from poetry.packages import Package
def test_accepts():
dependency = Dependency("A", "^1.0")
package = Package("A", "1.4")
assert dependency.accepts(package)
def test_accepts_prerelease():
dependency = Dependency("A", "^1.0", allows_prereleases=True)
package = Package("A", "1.4-beta.1")
assert dependency.accepts(package)
def test_accepts_python_versions():
dependency = Dependency("A", "^1.0")
dependency.python_versions = "^3.6"
package = Package("A", "1.4")
package.python_versions = "~3.6"
assert dependency.accepts(package)
def test_accepts_fails_with_different_names():
dependency = Dependency("A", "^1.0")
package = Package("B", "1.4")
assert not dependency.accepts(package)
def test_accepts_fails_with_version_mismatch():
dependency = Dependency("A", "~1.0")
package = Package("B", "1.4")
assert not dependency.accepts(package)
def test_accepts_fails_with_prerelease_mismatch():
dependency = Dependency("A", "^1.0")
package = Package("B", "1.4-beta.1")
assert not dependency.accepts(package)
def test_accepts_fails_with_python_versions_mismatch():
dependency = Dependency("A", "^1.0")
dependency.python_versions = "^3.6"
package = Package("B", "1.4")
package.python_versions = "~3.5"
assert not dependency.accepts(package)
def test_to_pep_508():
dependency = Dependency("Django", "^1.23")
result = dependency.to_pep_508()
assert result == "Django (>=1.23,<2.0)"
dependency = Dependency("Django", "^1.23")
dependency.python_versions = "~2.7 || ^3.6"
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_wilcard():
dependency = Dependency("Django", "*")
result = dependency.to_pep_508()
assert result == "Django"
def test_to_pep_508_in_extras():
dependency = Dependency("Django", "^1.23")
dependency.in_extras.append("foo")
result = dependency.to_pep_508()
assert result == 'Django (>=1.23,<2.0); extra == "foo"'
dependency.in_extras.append("bar")
result = dependency.to_pep_508()
assert result == 'Django (>=1.23,<2.0); extra == "foo" or extra == "bar"'
dependency.python_versions = "~2.7 || ^3.6"
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 (extra == "foo" or extra == "bar")'
)
def test_to_pep_508_with_single_version_excluded():
dependency = Dependency("foo", "!=1.2.3")
assert "foo (!=1.2.3)" == dependency.to_pep_508()
from subprocess import CalledProcessError
import pytest
from poetry.packages.directory_dependency import DirectoryDependency
from poetry.utils._compat import Path
from poetry.utils.env import EnvCommandError
from poetry.utils.env import MockEnv as BaseMockEnv
class MockEnv(BaseMockEnv):
def run(self, bin, *args):
raise EnvCommandError(CalledProcessError(1, "python", output=""))
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "git" / "github.com" / "demo"
def test_directory_dependency_must_exist():
with pytest.raises(ValueError):
DirectoryDependency("demo", DIST_PATH / "invalid")
import pytest
from poetry.packages import FileDependency
from poetry.utils._compat import Path
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "distributions"
def test_file_dependency_wrong_path():
with pytest.raises(ValueError):
FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz")
def test_file_dependency_dir():
with pytest.raises(ValueError):
FileDependency("demo", DIST_PATH)
...@@ -3,8 +3,8 @@ import tempfile ...@@ -3,8 +3,8 @@ import tempfile
import pytest import pytest
import tomlkit import tomlkit
from poetry.core.packages.project_package import ProjectPackage
from poetry.packages.locker import Locker from poetry.packages.locker import Locker
from poetry.packages.project_package import ProjectPackage
from ..helpers import get_dependency from ..helpers import get_dependency
from ..helpers import get_package from ..helpers import get_package
......
from poetry.packages import dependency_from_pep_508
def test_dependency_from_pep_508():
name = "requests"
dep = dependency_from_pep_508(name)
assert dep.name == name
assert str(dep.constraint) == "*"
def test_dependency_from_pep_508_with_version():
name = "requests==2.18.0"
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
def test_dependency_from_pep_508_with_parens():
name = "requests (==2.18.0)"
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
def test_dependency_from_pep_508_with_constraint():
name = "requests>=2.12.0,!=2.17.*,<3.0"
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == ">=2.12.0,<2.17.0 || >=2.18.0,<3.0"
def test_dependency_from_pep_508_with_extras():
name = 'requests==2.18.0; extra == "foo" or extra == "bar"'
dep = dependency_from_pep_508(name)
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"'
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"'
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"'
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 str(dep.marker) == 'sys_platform == "win32" or sys_platform == "darwin"'
def test_dependency_from_pep_508_complex():
name = (
"requests (==2.18.0); "
'python_version >= "2.7" and python_version != "3.2" '
'and (sys_platform == "win32" or sys_platform == "darwin") '
'and extra == "foo"'
)
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert dep.in_extras == ["foo"]
assert dep.python_versions == ">=2.7 !=3.2.*"
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'"
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'"
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'"
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
assert str(dep.marker) == 'sys_platform in "win32 darwin"'
def test_dependency_with_extra():
name = "requests[security] (==2.18.0)"
dep = dependency_from_pep_508(name)
assert dep.name == "requests"
assert str(dep.constraint) == "2.18.0"
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"'
)
def test_dependency_from_pep_508_with_not_in_op_marker():
name = (
"jinja2 (>=2.7,<2.8)"
'; python_version not in "3.0,3.1,3.2" and extra == "export"'
)
dep = dependency_from_pep_508(name)
assert dep.name == "jinja2"
assert str(dep.constraint) == ">=2.7,<2.8"
assert dep.in_extras == ["export"]
assert dep.python_versions == "!=3.0.*, !=3.1.*, !=3.2.*"
assert (
str(dep.marker) == 'python_version not in "3.0,3.1,3.2" and extra == "export"'
)
def test_dependency_from_pep_508_with_git_url():
name = "django-utils @ git+ssh://git@corp-gitlab.com/corp-utils.git@1.2"
dep = dependency_from_pep_508(name)
assert "django-utils" == dep.name
assert dep.is_vcs()
assert "git" == dep.vcs
assert "ssh://git@corp-gitlab.com/corp-utils.git" == dep.source
assert "1.2" == dep.reference
def test_dependency_from_pep_508_with_url():
name = "django-utils @ https://example.com/django-utils-1.0.0.tar.gz"
dep = dependency_from_pep_508(name)
assert "django-utils" == dep.name
assert dep.is_url()
assert "https://example.com/django-utils-1.0.0.tar.gz" == dep.url
def test_dependency_from_pep_508_with_wheel_url():
name = (
"example_wheel @ https://example.com/example_wheel-14.0.2-py2.py3-none-any.whl"
)
dep = dependency_from_pep_508(name)
assert "example-wheel" == dep.name
assert str(dep.constraint) == "14.0.2"
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import pytest
from poetry.packages import Package
def test_package_authors():
package = Package("foo", "0.1.0")
package.authors.append("Sébastien Eustace <sebastien@eustace.io>")
assert package.author_name == "Sébastien Eustace"
assert package.author_email == "sebastien@eustace.io"
package.authors.insert(
0, "Raphaël Yancey <raphael@badfile.net>"
) # With combining diacritics (ë = e + ¨ = e\u0308)
assert package.author_name == "Raphaël Yancey" # Is normalized into \u00EB
assert package.author_email == "raphael@badfile.net"
package.authors.insert(
0, "Raphaël Yancey <raphael@badfile.net>"
) # Without (ë = \u00EB)
assert package.author_name == "Raphaël Yancey"
assert package.author_email == "raphael@badfile.net"
package.authors.insert(0, "John Doe")
assert package.author_name == "John Doe"
assert package.author_email is None
@pytest.mark.parametrize("category", ["main", "dev"])
def test_package_add_dependency_vcs_category(category):
package = Package("foo", "0.1.0")
dependency = package.add_dependency(
"poetry",
constraint={"git": "https://github.com/python-poetry/poetry.git"},
category=category,
)
assert dependency.category == category
def test_package_add_dependency_vcs_category_default_main():
package = Package("foo", "0.1.0")
dependency = package.add_dependency(
"poetry", constraint={"git": "https://github.com/python-poetry/poetry.git"}
)
assert dependency.category == "main"
import pytest
from poetry.packages.vcs_dependency import VCSDependency
def test_to_pep_508():
dependency = VCSDependency(
"poetry", "git", "https://github.com/python-poetry/poetry.git"
)
expected = "poetry @ git+https://github.com/python-poetry/poetry.git@master"
assert expected == dependency.to_pep_508()
def test_to_pep_508_ssh():
dependency = VCSDependency("poetry", "git", "git@github.com:sdispater/poetry.git")
expected = "poetry @ git+ssh://git@github.com/sdispater/poetry.git@master"
assert expected == dependency.to_pep_508()
def test_to_pep_508_with_extras():
dependency = VCSDependency(
"poetry", "git", "https://github.com/python-poetry/poetry.git"
)
dependency.extras.append("foo")
expected = "poetry[foo] @ git+https://github.com/python-poetry/poetry.git@master"
assert expected == dependency.to_pep_508()
def test_to_pep_508_in_extras():
dependency = VCSDependency(
"poetry", "git", "https://github.com/python-poetry/poetry.git"
)
dependency.in_extras.append("foo")
expected = 'poetry @ git+https://github.com/python-poetry/poetry.git@master ; extra == "foo"'
assert expected == dependency.to_pep_508()
dependency = VCSDependency(
"poetry", "git", "https://github.com/python-poetry/poetry.git"
)
dependency.in_extras.append("foo")
dependency.extras.append("bar")
expected = 'poetry[bar] @ git+https://github.com/python-poetry/poetry.git@master ; extra == "foo"'
assert expected == dependency.to_pep_508()
dependency = VCSDependency(
"poetry", "git", "https://github.com/python-poetry/poetry.git", "b;ar;"
)
dependency.in_extras.append("foo;")
expected = 'poetry @ git+https://github.com/python-poetry/poetry.git@b;ar; ; extra == "foo;"'
assert expected == dependency.to_pep_508()
@pytest.mark.parametrize("category", ["main", "dev"])
def test_category(category):
dependency = VCSDependency(
"poetry",
"git",
"https://github.com/python-poetry/poetry.git",
category=category,
)
assert category == dependency.category
from poetry.packages.utils.utils import convert_markers
from poetry.version.markers import parse_marker
def test_convert_markers():
marker = parse_marker(
'sys_platform == "win32" and python_version < "3.6" '
'or sys_platform == "win32" and python_version < "3.6" and python_version >= "3.3" '
'or sys_platform == "win32" and python_version < "3.3"'
)
converted = convert_markers(marker)
assert converted["python_version"] == [
[("<", "3.6")],
[("<", "3.6"), (">=", "3.3")],
[("<", "3.3")],
]
marker = parse_marker('python_version == "2.7" or python_version == "2.6"')
converted = convert_markers(marker)
assert converted["python_version"] == [[("==", "2.7")], [("==", "2.6")]]
...@@ -4,13 +4,13 @@ import pytest ...@@ -4,13 +4,13 @@ import pytest
from poetry.factory import Factory from poetry.factory import Factory
from poetry.io.null_io import NullIO from poetry.io.null_io import NullIO
from poetry.masonry.publishing.publisher import Publisher from poetry.publishing.publisher import Publisher
from poetry.utils._compat import Path from poetry.utils._compat import Path
def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config poetry._config = config
poetry.config.merge( poetry.config.merge(
...@@ -28,8 +28,8 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): ...@@ -28,8 +28,8 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config poetry._config = config
poetry.config.merge( poetry.config.merge(
...@@ -62,8 +62,8 @@ def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, conf ...@@ -62,8 +62,8 @@ def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, conf
def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config): def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config):
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config poetry._config = config
poetry.config.merge({"pypi-token": {"pypi": "my-token"}}) poetry.config.merge({"pypi-token": {"pypi": "my-token"}})
...@@ -80,8 +80,8 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config): ...@@ -80,8 +80,8 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config):
def test_publish_uses_cert(fixture_dir, mocker, config): def test_publish_uses_cert(fixture_dir, mocker, config):
cert = "path/to/ca.pem" cert = "path/to/ca.pem"
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config poetry._config = config
poetry.config.merge( poetry.config.merge(
...@@ -104,7 +104,7 @@ def test_publish_uses_cert(fixture_dir, mocker, config): ...@@ -104,7 +104,7 @@ def test_publish_uses_cert(fixture_dir, mocker, config):
def test_publish_uses_client_cert(fixture_dir, mocker, config): def test_publish_uses_client_cert(fixture_dir, mocker, config):
client_cert = "path/to/client.pem" client_cert = "path/to/client.pem"
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config poetry._config = config
poetry.config.merge( poetry.config.merge(
...@@ -127,8 +127,8 @@ def test_publish_read_from_environment_variable(fixture_dir, environ, mocker, co ...@@ -127,8 +127,8 @@ def test_publish_read_from_environment_variable(fixture_dir, environ, mocker, co
os.environ["POETRY_REPOSITORIES_FOO_URL"] = "https://foo.bar" os.environ["POETRY_REPOSITORIES_FOO_URL"] = "https://foo.bar"
os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "bar" os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "bar"
os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "baz" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "baz"
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry = Factory().create_poetry(fixture_dir("sample_project"))
publisher = Publisher(poetry, NullIO()) publisher = Publisher(poetry, NullIO())
......
...@@ -2,12 +2,12 @@ import pytest ...@@ -2,12 +2,12 @@ import pytest
from poetry.factory import Factory from poetry.factory import Factory
from poetry.io.null_io import NullIO from poetry.io.null_io import NullIO
from poetry.masonry.publishing.uploader import Uploader from poetry.publishing.uploader import Uploader
from poetry.masonry.publishing.uploader import UploadError from poetry.publishing.uploader import UploadError
from poetry.utils._compat import Path from poetry.utils._compat import Path
fixtures_dir = Path(__file__).parent.parent.parent / "fixtures" fixtures_dir = Path(__file__).parent.parent / "fixtures"
def project(name): def project(name):
...@@ -35,7 +35,7 @@ def test_uploader_properly_handles_403_errors(http): ...@@ -35,7 +35,7 @@ def test_uploader_properly_handles_403_errors(http):
def test_uploader_registers_for_appropriate_400_errors(mocker, http): def test_uploader_registers_for_appropriate_400_errors(mocker, http):
register = mocker.patch("poetry.masonry.publishing.uploader.Uploader._register") register = mocker.patch("poetry.publishing.uploader.Uploader._register")
http.register_uri( http.register_uri(
http.POST, "https://foo.com", status=400, body="No package was ever registered" http.POST, "https://foo.com", status=400, body="No package was ever registered"
) )
......
...@@ -30,9 +30,9 @@ def mock_clone(self, source, dest): ...@@ -30,9 +30,9 @@ def mock_clone(self, source, dest):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup(mocker): def setup(mocker):
# Patch git module to not actually clone projects # Patch git module to not actually clone projects
mocker.patch("poetry.vcs.git.Git.clone", new=mock_clone) mocker.patch("poetry.core.vcs.git.Git.clone", new=mock_clone)
mocker.patch("poetry.vcs.git.Git.checkout", new=lambda *_: None) mocker.patch("poetry.core.vcs.git.Git.checkout", new=lambda *_: None)
p = mocker.patch("poetry.vcs.git.Git.rev_parse") p = mocker.patch("poetry.core.vcs.git.Git.rev_parse")
p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
yield yield
...@@ -4,10 +4,10 @@ import pytest ...@@ -4,10 +4,10 @@ import pytest
from clikit.io import NullIO from clikit.io import NullIO
from poetry.packages import ProjectPackage from poetry.core.packages import ProjectPackage
from poetry.packages.directory_dependency import DirectoryDependency from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.packages.file_dependency import FileDependency from poetry.core.packages.file_dependency import FileDependency
from poetry.packages.vcs_dependency import VCSDependency from poetry.core.packages.vcs_dependency import VCSDependency
from poetry.puzzle.provider import Provider from poetry.puzzle.provider import Provider
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
......
...@@ -2,15 +2,15 @@ import pytest ...@@ -2,15 +2,15 @@ import pytest
from clikit.io import NullIO from clikit.io import NullIO
from poetry.packages import ProjectPackage from poetry.core.packages import ProjectPackage
from poetry.packages import dependency_from_pep_508 from poetry.core.packages import dependency_from_pep_508
from poetry.core.version.markers import parse_marker
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.puzzle.exceptions import SolverProblemError from poetry.puzzle.exceptions import SolverProblemError
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.version.markers import parse_marker
from tests.helpers import get_dependency from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
from tests.repositories.test_legacy_repository import ( from tests.repositories.test_legacy_repository import (
......
...@@ -32,19 +32,17 @@ def test_load(mocker): ...@@ -32,19 +32,17 @@ def test_load(mocker):
return_value=INSTALLED_RESULTS, return_value=INSTALLED_RESULTS,
) )
mocker.patch( mocker.patch(
"poetry.vcs.git.Git.rev_parse", "poetry.core.vcs.git.Git.rev_parse",
return_value="bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6", return_value="bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6",
) )
mocker.patch( mocker.patch(
"poetry.vcs.git.Git.remote_urls", "poetry.core.vcs.git.Git.remote_urls",
side_effect=[ side_effect=[
{"remote.origin.url": "https://github.com/sdispater/pendulum.git"}, {"remote.origin.url": "https://github.com/sdispater/pendulum.git"},
{"remote.origin.url": "git@github.com:sdispater/pendulum.git"}, {"remote.origin.url": "git@github.com:sdispater/pendulum.git"},
], ],
) )
mocker.patch( mocker.patch("poetry.repositories.installed_repository._VENDORS", str(VENDOR_DIR))
"poetry.repositories.installed_repository._CURRENT_VENDOR", str(VENDOR_DIR)
)
repository = InstalledRepository.load(MockEnv(path=ENV_DIR)) repository = InstalledRepository.load(MockEnv(path=ENV_DIR))
assert len(repository.packages) == 3 assert len(repository.packages) == 3
......
...@@ -2,7 +2,7 @@ import shutil ...@@ -2,7 +2,7 @@ import shutil
import pytest import pytest
from poetry.packages import Dependency from poetry.core.packages import Dependency
from poetry.repositories.auth import Auth from poetry.repositories.auth import Auth
from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.exceptions import PackageNotFound
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
......
...@@ -8,7 +8,7 @@ import pytest ...@@ -8,7 +8,7 @@ import pytest
from requests.exceptions import TooManyRedirects from requests.exceptions import TooManyRedirects
from requests.models import Response from requests.models import Response
from poetry.packages import Dependency from poetry.core.packages import Dependency
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
from poetry.utils._compat import PY35 from poetry.utils._compat import PY35
from poetry.utils._compat import Path from poetry.utils._compat import Path
......
import pytest
from poetry.semver import Version
from poetry.semver import VersionRange
from poetry.semver import VersionUnion
from poetry.semver import parse_constraint
@pytest.mark.parametrize(
"input,constraint",
[
("*", VersionRange()),
("*.*", VersionRange()),
("v*.*", VersionRange()),
("*.x.*", VersionRange()),
("x.X.x.*", VersionRange()),
# ('!=1.0.0', Constraint('!=', '1.0.0.0')),
(">1.0.0", VersionRange(min=Version(1, 0, 0))),
("<1.2.3", VersionRange(max=Version(1, 2, 3))),
("<=1.2.3", VersionRange(max=Version(1, 2, 3), include_max=True)),
(">=1.2.3", VersionRange(min=Version(1, 2, 3), include_min=True)),
("=1.2.3", Version(1, 2, 3)),
("1.2.3", Version(1, 2, 3)),
("=1.0", Version(1, 0, 0)),
("1.2.3b5", Version(1, 2, 3, pre="b5")),
(">= 1.2.3", VersionRange(min=Version(1, 2, 3), include_min=True)),
(">dev", VersionRange(min=Version(0, 0, pre="dev"))), # Issue 206
],
)
def test_parse_constraint(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input,constraint",
[
("v2.*", VersionRange(Version(2, 0, 0), Version(3, 0, 0), True)),
("2.*.*", VersionRange(Version(2, 0, 0), Version(3, 0, 0), True)),
("20.*", VersionRange(Version(20, 0, 0), Version(21, 0, 0), True)),
("20.*.*", VersionRange(Version(20, 0, 0), Version(21, 0, 0), True)),
("2.0.*", VersionRange(Version(2, 0, 0), Version(2, 1, 0), True)),
("2.x", VersionRange(Version(2, 0, 0), Version(3, 0, 0), True)),
("2.x.x", VersionRange(Version(2, 0, 0), Version(3, 0, 0), True)),
("2.2.X", VersionRange(Version(2, 2, 0), Version(2, 3, 0), True)),
("0.*", VersionRange(max=Version(1, 0, 0))),
("0.*.*", VersionRange(max=Version(1, 0, 0))),
("0.x", VersionRange(max=Version(1, 0, 0))),
],
)
def test_parse_constraint_wildcard(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input,constraint",
[
("~v1", VersionRange(Version(1, 0, 0), Version(2, 0, 0), True)),
("~1.0", VersionRange(Version(1, 0, 0), Version(1, 1, 0), True)),
("~1.0.0", VersionRange(Version(1, 0, 0), Version(1, 1, 0), True)),
("~1.2", VersionRange(Version(1, 2, 0), Version(1, 3, 0), True)),
("~1.2.3", VersionRange(Version(1, 2, 3), Version(1, 3, 0), True)),
(
"~1.2-beta",
VersionRange(Version(1, 2, 0, pre="beta"), Version(1, 3, 0), True),
),
("~1.2-b2", VersionRange(Version(1, 2, 0, pre="b2"), Version(1, 3, 0), True)),
("~0.3", VersionRange(Version(0, 3, 0), Version(0, 4, 0), True)),
("~3.5", VersionRange(Version(3, 5, 0), Version(3, 6, 0), True)),
("~=3.5", VersionRange(Version(3, 5, 0), Version(4, 0, 0), True)), # PEP 440
("~=3.5.3", VersionRange(Version(3, 5, 3), Version(3, 6, 0), True)), # PEP 440
(
"~=3.5.3rc1",
VersionRange(Version(3, 5, 3, pre="rc1"), Version(3, 6, 0), True),
), # PEP 440
],
)
def test_parse_constraint_tilde(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input,constraint",
[
("^v1", VersionRange(Version(1, 0, 0), Version(2, 0, 0), True)),
("^0", VersionRange(Version(0, 0, 0), Version(1, 0, 0), True)),
("^0.0", VersionRange(Version(0, 0, 0), Version(0, 1, 0), True)),
("^1.2", VersionRange(Version(1, 2, 0), Version(2, 0, 0), True)),
(
"^1.2.3-beta.2",
VersionRange(Version(1, 2, 3, pre="beta.2"), Version(2, 0, 0), True),
),
("^1.2.3", VersionRange(Version(1, 2, 3), Version(2, 0, 0), True)),
("^0.2.3", VersionRange(Version(0, 2, 3), Version(0, 3, 0), True)),
("^0.2", VersionRange(Version(0, 2, 0), Version(0, 3, 0), True)),
("^0.2.0", VersionRange(Version(0, 2, 0), Version(0, 3, 0), True)),
("^0.0.3", VersionRange(Version(0, 0, 3), Version(0, 0, 4), True)),
],
)
def test_parse_constraint_caret(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input",
[
">2.0,<=3.0",
">2.0 <=3.0",
">2.0 <=3.0",
">2.0, <=3.0",
">2.0 ,<=3.0",
">2.0 , <=3.0",
">2.0 , <=3.0",
"> 2.0 <= 3.0",
"> 2.0 , <= 3.0",
" > 2.0 , <= 3.0 ",
],
)
def test_parse_constraint_multi(input):
assert parse_constraint(input) == VersionRange(
Version(2, 0, 0), Version(3, 0, 0), include_min=False, include_max=True
)
@pytest.mark.parametrize(
"input",
[">=2.7,!=3.0.*,!=3.1.*", ">=2.7, !=3.0.*, !=3.1.*", ">= 2.7, != 3.0.*, != 3.1.*"],
)
def test_parse_constraint_multi_wilcard(input):
assert parse_constraint(input) == VersionUnion(
VersionRange(Version(2, 7, 0), Version(3, 0, 0), True, False),
VersionRange(Version(3, 2, 0), None, True, False),
)
@pytest.mark.parametrize(
"input,constraint",
[
(
"!=v2.*",
VersionRange(max=Version.parse("2.0")).union(
VersionRange(Version.parse("3.0"), include_min=True)
),
),
(
"!=2.*.*",
VersionRange(max=Version.parse("2.0")).union(
VersionRange(Version.parse("3.0"), include_min=True)
),
),
(
"!=2.0.*",
VersionRange(max=Version.parse("2.0")).union(
VersionRange(Version.parse("2.1"), include_min=True)
),
),
("!=0.*", VersionRange(Version.parse("1.0"), include_min=True)),
("!=0.*.*", VersionRange(Version.parse("1.0"), include_min=True)),
],
)
def test_parse_constraints_negative_wildcard(input, constraint):
assert parse_constraint(input) == constraint
@pytest.mark.parametrize(
"input, expected",
[
("1", "1"),
("1.2", "1.2"),
("1.2.3", "1.2.3"),
("!=1", "!=1"),
("!=1.2", "!=1.2"),
("!=1.2.3", "!=1.2.3"),
("^1", ">=1,<2"),
("^1.0", ">=1.0,<2.0"),
("^1.0.0", ">=1.0.0,<2.0.0"),
("~1", ">=1,<2"),
("~1.0", ">=1.0,<1.1"),
("~1.0.0", ">=1.0.0,<1.1.0"),
],
)
def test_constraints_keep_version_precision(input, expected):
assert str(parse_constraint(input)) == expected
@pytest.mark.parametrize(
"unsorted, sorted_",
[
(["1.0.3", "1.0.2", "1.0.1"], ["1.0.1", "1.0.2", "1.0.3"]),
(["1.0.0.2", "1.0.0.0rc2"], ["1.0.0.0rc2", "1.0.0.2"]),
(["1.0.0.0", "1.0.0.0rc2"], ["1.0.0.0rc2", "1.0.0.0"]),
(["1.0.0.0.0", "1.0.0.0rc2"], ["1.0.0.0rc2", "1.0.0.0.0"]),
(["1.0.0rc2", "1.0.0rc1"], ["1.0.0rc1", "1.0.0rc2"]),
(["1.0.0rc2", "1.0.0b1"], ["1.0.0b1", "1.0.0rc2"]),
],
)
def test_versions_are_sortable(unsorted, sorted_):
unsorted = [parse_constraint(u) for u in unsorted]
sorted_ = [parse_constraint(s) for s in sorted_]
assert sorted(unsorted) == sorted_
import pytest
from poetry.semver import EmptyConstraint
from poetry.semver import Version
from poetry.semver import VersionRange
from poetry.semver.exceptions import ParseVersionError
@pytest.mark.parametrize(
"input,version",
[
("1.0.0", Version(1, 0, 0)),
("1", Version(1, 0, 0)),
("1.0", Version(1, 0, 0)),
("1b1", Version(1, 0, 0, pre="beta1")),
("1.0b1", Version(1, 0, 0, pre="beta1")),
("1.0.0b1", Version(1, 0, 0, pre="beta1")),
("1.0.0-b1", Version(1, 0, 0, pre="beta1")),
("1.0.0-beta.1", Version(1, 0, 0, pre="beta1")),
("1.0.0+1", Version(1, 0, 0, build="1")),
("1.0.0-1", Version(1, 0, 0, build="1")),
("1.0.0.0", Version(1, 0, 0)),
("1.0.0-post", Version(1, 0, 0)),
("1.0.0-post1", Version(1, 0, 0, build="1")),
("0.6c", Version(0, 6, 0, pre="rc0")),
("0.6pre", Version(0, 6, 0, pre="rc0")),
],
)
def test_parse_valid(input, version):
parsed = Version.parse(input)
assert parsed == version
assert parsed.text == input
@pytest.mark.parametrize("input", [(None, "example")])
def test_parse_invalid(input):
with pytest.raises(ParseVersionError):
Version.parse(input)
def test_comparison():
versions = [
"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0-rc.1+build.1",
"1.0.0",
"1.0.0+0.3.7",
"1.3.7+build",
"1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a",
"2.0.0",
"2.1.0",
"2.2.0",
"2.11.0",
"2.11.1",
]
for i in range(len(versions)):
for j in range(len(versions)):
a = Version.parse(versions[i])
b = Version.parse(versions[j])
assert (a < b) == (i < j)
assert (a > b) == (i > j)
assert (a <= b) == (i <= j)
assert (a >= b) == (i >= j)
assert (a == b) == (i == j)
assert (a != b) == (i != j)
def test_equality():
assert Version.parse("1.2.3") == Version.parse("01.2.3")
assert Version.parse("1.2.3") == Version.parse("1.02.3")
assert Version.parse("1.2.3") == Version.parse("1.2.03")
assert Version.parse("1.2.3-1") == Version.parse("1.2.3-01")
assert Version.parse("1.2.3+1") == Version.parse("1.2.3+01")
def test_allows():
v = Version.parse("1.2.3")
assert v.allows(v)
assert not v.allows(Version.parse("2.2.3"))
assert not v.allows(Version.parse("1.3.3"))
assert not v.allows(Version.parse("1.2.4"))
assert not v.allows(Version.parse("1.2.3-dev"))
assert not v.allows(Version.parse("1.2.3+build"))
def test_allows_all():
v = Version.parse("1.2.3")
assert v.allows_all(v)
assert not v.allows_all(Version.parse("0.0.3"))
assert not v.allows_all(
VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))
)
assert not v.allows_all(VersionRange())
assert v.allows_all(EmptyConstraint())
def test_allows_any():
v = Version.parse("1.2.3")
assert v.allows_any(v)
assert not v.allows_any(Version.parse("0.0.3"))
assert v.allows_any(VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4")))
assert v.allows_any(VersionRange())
assert not v.allows_any(EmptyConstraint())
def test_intersect():
v = Version.parse("1.2.3")
assert v.intersect(v) == v
assert v.intersect(Version.parse("1.1.4")).is_empty()
assert (
v.intersect(VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))) == v
)
assert (
Version.parse("1.1.4")
.intersect(VersionRange(v, Version.parse("1.2.4")))
.is_empty()
)
def test_union():
v = Version.parse("1.2.3")
assert v.union(v) == v
result = v.union(Version.parse("0.8.0"))
assert result.allows(v)
assert result.allows(Version.parse("0.8.0"))
assert not result.allows(Version.parse("1.1.4"))
range = VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))
assert v.union(range) == range
union = Version.parse("1.1.4").union(
VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))
)
assert union == VersionRange(
Version.parse("1.1.4"), Version.parse("1.2.4"), include_min=True
)
result = v.union(VersionRange(Version.parse("0.0.3"), Version.parse("1.1.4")))
assert result.allows(v)
assert result.allows(Version.parse("0.1.0"))
def test_difference():
v = Version.parse("1.2.3")
assert v.difference(v).is_empty()
assert v.difference(Version.parse("0.8.0")) == v
assert v.difference(
VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))
).is_empty()
assert (
v.difference(VersionRange(Version.parse("1.4.0"), Version.parse("3.0.0"))) == v
)
import pytest
from poetry.semver import EmptyConstraint
from poetry.semver import Version
from poetry.semver import VersionRange
@pytest.fixture()
def v003():
return Version.parse("0.0.3")
@pytest.fixture()
def v010():
return Version.parse("0.1.0")
@pytest.fixture()
def v080():
return Version.parse("0.8.0")
@pytest.fixture()
def v072():
return Version.parse("0.7.2")
@pytest.fixture()
def v114():
return Version.parse("1.1.4")
@pytest.fixture()
def v123():
return Version.parse("1.2.3")
@pytest.fixture()
def v124():
return Version.parse("1.2.4")
@pytest.fixture()
def v130():
return Version.parse("1.3.0")
@pytest.fixture()
def v140():
return Version.parse("1.4.0")
@pytest.fixture()
def v200():
return Version.parse("2.0.0")
@pytest.fixture()
def v234():
return Version.parse("2.3.4")
@pytest.fixture()
def v250():
return Version.parse("2.5.0")
@pytest.fixture()
def v300():
return Version.parse("3.0.0")
def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250, v300):
assert VersionRange(v123, v250).allows_all(EmptyConstraint())
range = VersionRange(v123, v250, include_max=True)
assert not range.allows_all(v123)
assert range.allows_all(v124)
assert range.allows_all(v250)
assert not range.allows_all(v300)
# with no min
range = VersionRange(max=v250)
assert range.allows_all(VersionRange(v080, v140))
assert not range.allows_all(VersionRange(v080, v300))
assert range.allows_all(VersionRange(max=v140))
assert not range.allows_all(VersionRange(max=v300))
assert range.allows_all(range)
assert not range.allows_all(VersionRange())
# with no max
range = VersionRange(min=v010)
assert range.allows_all(VersionRange(v080, v140))
assert not range.allows_all(VersionRange(v003, v140))
assert range.allows_all(VersionRange(v080))
assert not range.allows_all(VersionRange(v003))
assert range.allows_all(range)
assert not range.allows_all(VersionRange())
# Allows bordering range that is not more inclusive
exclusive = VersionRange(v010, v250)
inclusive = VersionRange(v010, v250, True, True)
assert inclusive.allows_all(exclusive)
assert inclusive.allows_all(inclusive)
assert not exclusive.allows_all(inclusive)
assert exclusive.allows_all(exclusive)
# Allows unions that are completely contained
range = VersionRange(v114, v200)
assert range.allows_all(VersionRange(v123, v124).union(v140))
assert not range.allows_all(VersionRange(v010, v124).union(v140))
assert not range.allows_all(VersionRange(v123, v234).union(v140))
def test_allows_any(
v003, v010, v072, v080, v114, v123, v124, v140, v200, v234, v250, v300
):
# disallows an empty constraint
assert not VersionRange(v123, v250).allows_any(EmptyConstraint())
# allows allowed versions
range = VersionRange(v123, v250, include_max=True)
assert not range.allows_any(v123)
assert range.allows_any(v124)
assert range.allows_any(v250)
assert not range.allows_any(v300)
# with no min
range = VersionRange(max=v200)
assert range.allows_any(VersionRange(v140, v300))
assert not range.allows_any(VersionRange(v234, v300))
assert range.allows_any(VersionRange(v140))
assert not range.allows_any(VersionRange(v234))
assert range.allows_any(range)
# with no max
range = VersionRange(min=v072)
assert range.allows_any(VersionRange(v003, v140))
assert not range.allows_any(VersionRange(v003, v010))
assert range.allows_any(VersionRange(max=v080))
assert not range.allows_any(VersionRange(max=v003))
assert range.allows_any(range)
# with min and max
range = VersionRange(v072, v200)
assert range.allows_any(VersionRange(v003, v140))
assert range.allows_any(VersionRange(v140, v300))
assert not range.allows_any(VersionRange(v003, v010))
assert not range.allows_any(VersionRange(v234, v300))
assert not range.allows_any(VersionRange(max=v010))
assert not range.allows_any(VersionRange(v234))
assert range.allows_any(range)
# allows a bordering range when both are inclusive
assert not VersionRange(max=v250).allows_any(VersionRange(min=v250))
assert not VersionRange(max=v250, include_max=True).allows_any(
VersionRange(min=v250)
)
assert not VersionRange(max=v250).allows_any(
VersionRange(min=v250, include_min=True)
)
assert not VersionRange(min=v250).allows_any(VersionRange(max=v250))
assert VersionRange(max=v250, include_max=True).allows_any(
VersionRange(min=v250, include_min=True)
)
# allows unions that are partially contained'
range = VersionRange(v114, v200)
assert range.allows_any(VersionRange(v010, v080).union(v140))
assert range.allows_any(VersionRange(v123, v234).union(v300))
assert not range.allows_any(VersionRange(v234, v300).union(v010))
# pre-release min does not allow lesser than itself
range = VersionRange(Version.parse("1.9b1"), include_min=True)
assert not range.allows_any(
VersionRange(
Version.parse("1.8.0"),
Version.parse("1.9.0"),
include_min=True,
always_include_max_prerelease=True,
)
)
def test_intersect(v114, v123, v124, v200, v250, v300):
# two overlapping ranges
assert VersionRange(v123, v250).intersect(VersionRange(v200, v300)) == VersionRange(
v200, v250
)
# a non-overlapping range allows no versions
a = VersionRange(v114, v124)
b = VersionRange(v200, v250)
assert a.intersect(b).is_empty()
# adjacent ranges allow no versions if exclusive
a = VersionRange(v114, v124)
b = VersionRange(v124, v200)
assert a.intersect(b).is_empty()
# adjacent ranges allow version if inclusive
a = VersionRange(v114, v124, include_max=True)
b = VersionRange(v124, v200, include_min=True)
assert a.intersect(b) == v124
# with an open range
open = VersionRange()
a = VersionRange(v114, v124)
assert open.intersect(open) == open
assert open.intersect(a) == a
# returns the version if the range allows it
assert VersionRange(v114, v124).intersect(v123) == v123
assert VersionRange(v123, v124).intersect(v114).is_empty()
def test_union(
v003, v010, v072, v080, v114, v123, v124, v130, v140, v200, v234, v250, v300
):
# with a version returns the range if it contains the version
range = VersionRange(v114, v124)
assert range.union(v123) == range
# with a version on the edge of the range, expands the range
range = VersionRange(v114, v124)
assert range.union(v124) == VersionRange(v114, v124, include_max=True)
assert range.union(v114) == VersionRange(v114, v124, include_min=True)
# with a version allows both the range and the version if the range
# doesn't contain the version
result = VersionRange(v003, v114).union(v124)
assert result.allows(v010)
assert not result.allows(v123)
assert result.allows(v124)
# returns a VersionUnion for a disjoint range
result = VersionRange(v003, v114).union(VersionRange(v130, v200))
assert result.allows(v080)
assert not result.allows(v123)
assert result.allows(v140)
# considers open ranges disjoint
result = VersionRange(v003, v114).union(VersionRange(v114, v200))
assert result.allows(v080)
assert not result.allows(v114)
assert result.allows(v140)
result = VersionRange(v114, v200).union(VersionRange(v003, v114))
assert result.allows(v080)
assert not result.allows(v114)
assert result.allows(v140)
# returns a merged range for an overlapping range
result = VersionRange(v003, v114).union(VersionRange(v080, v200))
assert result == VersionRange(v003, v200)
# considers closed ranges overlapping
result = VersionRange(v003, v114, include_max=True).union(VersionRange(v114, v200))
assert result == VersionRange(v003, v200)
result = VersionRange(v003, v114).union(VersionRange(v114, v200, include_min=True))
assert result == VersionRange(v003, v200)
from poetry.spdx import license_by_id
def test_classifier_name():
license = license_by_id("lgpl-3.0-or-later")
assert (
license.classifier_name
== "GNU Lesser General Public License v3 or later (LGPLv3+)"
)
def test_classifier_name_no_classifer_osi_approved():
license = license_by_id("LiLiQ-R-1.1")
assert license.classifier_name is None
def test_classifier_name_no_classifer():
license = license_by_id("Leptonica")
assert license.classifier_name == "Other/Proprietary License"
def test_classifier():
license = license_by_id("lgpl-3.0-or-later")
assert license.classifier == (
"License :: "
"OSI Approved :: "
"GNU Lesser General Public License v3 or later (LGPLv3+)"
)
def test_classifier_no_classifer_osi_approved():
license = license_by_id("LiLiQ-R-1.1")
assert license.classifier == "License :: OSI Approved"
def test_classifier_no_classifer():
license = license_by_id("Leptonica")
assert license.classifier == "License :: Other/Proprietary License"
def test_proprietary_license():
license = license_by_id("Proprietary")
assert "License :: Other/Proprietary License" == license.classifier
import pytest
from poetry.spdx import license_by_id
def test_license_by_id():
license = license_by_id("MIT")
assert license.id == "MIT"
assert license.name == "MIT License"
assert license.is_osi_approved
assert not license.is_deprecated
license = license_by_id("LGPL-3.0-or-later")
assert license.id == "LGPL-3.0-or-later"
assert license.name == "GNU Lesser General Public License v3.0 or later"
assert license.is_osi_approved
assert not license.is_deprecated
def test_license_by_id_is_case_insensitive():
license = license_by_id("mit")
assert license.id == "MIT"
license = license_by_id("miT")
assert license.id == "MIT"
def test_license_by_id_with_full_name():
license = license_by_id("GNU Lesser General Public License v3.0 or later")
assert license.id == "LGPL-3.0-or-later"
assert license.name == "GNU Lesser General Public License v3.0 or later"
assert license.is_osi_approved
assert not license.is_deprecated
def test_license_by_id_invalid():
with pytest.raises(ValueError):
license_by_id("invalid")
...@@ -113,9 +113,7 @@ def test_create_poetry(): ...@@ -113,9 +113,7 @@ def test_create_poetry():
def test_create_poetry_with_packages_and_includes(): def test_create_poetry_with_packages_and_includes():
poetry = Factory().create_poetry( poetry = Factory().create_poetry(fixtures_dir / "with-include")
fixtures_dir.parent / "masonry" / "builders" / "fixtures" / "with-include"
)
package = poetry.package package = poetry.package
......
...@@ -7,8 +7,8 @@ import tomlkit ...@@ -7,8 +7,8 @@ import tomlkit
from clikit.io import NullIO from clikit.io import NullIO
from poetry.core.semver import Version
from poetry.factory import Factory from poetry.factory import Factory
from poetry.semver import Version
from poetry.utils._compat import WINDOWS from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
......
import pytest import pytest
from poetry.packages import Package from poetry.core.packages import Package
from poetry.utils.extras import get_extra_package_names from poetry.utils.extras import get_extra_package_names
......
import pytest
from poetry.vcs.git import Git
from poetry.vcs.git import GitUrl
from poetry.vcs.git import ParsedUrl
@pytest.mark.parametrize(
"url, normalized",
[
(
"git+ssh://user@hostname:project.git#commit",
GitUrl("user@hostname:project.git", "commit"),
),
(
"git+http://user@hostname/project/blah.git@commit",
GitUrl("http://user@hostname/project/blah.git", "commit"),
),
(
"git+https://user@hostname/project/blah.git",
GitUrl("https://user@hostname/project/blah.git", None),
),
(
"git+https://user@hostname/project~_-.foo/blah~_-.bar.git",
GitUrl("https://user@hostname/project~_-.foo/blah~_-.bar.git", None),
),
(
"git+https://user@hostname:project/blah.git",
GitUrl("https://user@hostname/project/blah.git", None),
),
(
"git+ssh://git@github.com:sdispater/poetry.git#v1.0.27",
GitUrl("git@github.com:sdispater/poetry.git", "v1.0.27"),
),
(
"git+ssh://git@github.com:/sdispater/poetry.git",
GitUrl("git@github.com:/sdispater/poetry.git", None),
),
("git+ssh://git@github.com:org/repo", GitUrl("git@github.com:org/repo", None),),
(
"git+ssh://git@github.com/org/repo",
GitUrl("ssh://git@github.com/org/repo", None),
),
("git+ssh://foo:22/some/path", GitUrl("ssh://foo:22/some/path", None)),
("git@github.com:org/repo", GitUrl("git@github.com:org/repo", None)),
(
"git+https://github.com/sdispater/pendulum",
GitUrl("https://github.com/sdispater/pendulum", None),
),
(
"git+https://github.com/sdispater/pendulum#7a018f2d075b03a73409e8356f9b29c9ad4ea2c5",
GitUrl(
"https://github.com/sdispater/pendulum",
"7a018f2d075b03a73409e8356f9b29c9ad4ea2c5",
),
),
(
"git+ssh://git@git.example.com:b/b.git#v1.0.0",
GitUrl("git@git.example.com:b/b.git", "v1.0.0"),
),
(
"git+ssh://git@github.com:sdispater/pendulum.git#foo/bar",
GitUrl("git@github.com:sdispater/pendulum.git", "foo/bar"),
),
("git+file:///foo/bar.git", GitUrl("file:///foo/bar.git", None)),
(
"git+file://C:\\Users\\hello\\testing.git#zkat/windows-files",
GitUrl("file://C:\\Users\\hello\\testing.git", "zkat/windows-files"),
),
(
"git+https://git.example.com/sdispater/project/my_repo.git",
GitUrl("https://git.example.com/sdispater/project/my_repo.git", None),
),
(
"git+ssh://git@git.example.com:sdispater/project/my_repo.git",
GitUrl("git@git.example.com:sdispater/project/my_repo.git", None),
),
],
)
def test_normalize_url(url, normalized):
assert normalized == Git.normalize_url(url)
@pytest.mark.parametrize(
"url, parsed",
[
(
"git+ssh://user@hostname:project.git#commit",
ParsedUrl(
"ssh", "hostname", ":project.git", "user", None, "project", "commit"
),
),
(
"git+http://user@hostname/project/blah.git@commit",
ParsedUrl(
"http", "hostname", "/project/blah.git", "user", None, "blah", "commit"
),
),
(
"git+https://user@hostname/project/blah.git",
ParsedUrl(
"https", "hostname", "/project/blah.git", "user", None, "blah", None
),
),
(
"git+https://user@hostname/project~_-.foo/blah~_-.bar.git",
ParsedUrl(
"https",
"hostname",
"/project~_-.foo/blah~_-.bar.git",
"user",
None,
"blah~_-.bar",
None,
),
),
(
"git+https://user@hostname:project/blah.git",
ParsedUrl(
"https", "hostname", ":project/blah.git", "user", None, "blah", None
),
),
(
"git+ssh://git@github.com:sdispater/poetry.git#v1.0.27",
ParsedUrl(
"ssh",
"github.com",
":sdispater/poetry.git",
"git",
None,
"poetry",
"v1.0.27",
),
),
(
"git+ssh://git@github.com:/sdispater/poetry.git",
ParsedUrl(
"ssh",
"github.com",
":/sdispater/poetry.git",
"git",
None,
"poetry",
None,
),
),
(
"git+ssh://git@github.com:org/repo",
ParsedUrl("ssh", "github.com", ":org/repo", "git", None, "repo", None),
),
(
"git+ssh://git@github.com/org/repo",
ParsedUrl("ssh", "github.com", "/org/repo", "git", None, "repo", None),
),
(
"git+ssh://foo:22/some/path",
ParsedUrl("ssh", "foo", "/some/path", None, "22", "path", None),
),
(
"git@github.com:org/repo",
ParsedUrl(None, "github.com", ":org/repo", "git", None, "repo", None),
),
(
"git+https://github.com/sdispater/pendulum",
ParsedUrl(
"https",
"github.com",
"/sdispater/pendulum",
None,
None,
"pendulum",
None,
),
),
(
"git+https://github.com/sdispater/pendulum#7a018f2d075b03a73409e8356f9b29c9ad4ea2c5",
ParsedUrl(
"https",
"github.com",
"/sdispater/pendulum",
None,
None,
"pendulum",
"7a018f2d075b03a73409e8356f9b29c9ad4ea2c5",
),
),
(
"git+ssh://git@git.example.com:b/b.git#v1.0.0",
ParsedUrl("ssh", "git.example.com", ":b/b.git", "git", None, "b", "v1.0.0"),
),
(
"git+ssh://git@github.com:sdispater/pendulum.git#foo/bar",
ParsedUrl(
"ssh",
"github.com",
":sdispater/pendulum.git",
"git",
None,
"pendulum",
"foo/bar",
),
),
(
"git+file:///foo/bar.git",
ParsedUrl("file", None, "/foo/bar.git", None, None, "bar", None),
),
(
"git+file://C:\\Users\\hello\\testing.git#zkat/windows-files",
ParsedUrl(
"file",
"C",
":\\Users\\hello\\testing.git",
None,
None,
"testing",
"zkat/windows-files",
),
),
(
"git+https://git.example.com/sdispater/project/my_repo.git",
ParsedUrl(
"https",
"git.example.com",
"/sdispater/project/my_repo.git",
None,
None,
"my_repo",
None,
),
),
(
"git+ssh://git@git.example.com:sdispater/project/my_repo.git",
ParsedUrl(
"ssh",
"git.example.com",
":sdispater/project/my_repo.git",
"git",
None,
"my_repo",
None,
),
),
],
)
def test_parse_url(url, parsed):
result = ParsedUrl.parse(url)
assert parsed.name == result.name
assert parsed.pathname == result.pathname
assert parsed.port == result.port
assert parsed.protocol == result.protocol
assert parsed.resource == result.resource
assert parsed.rev == result.rev
assert parsed.url == result.url
assert parsed.user == result.user
def test_parse_url_should_fail():
url = "https://" + "@" * 64 + "!"
with pytest.raises(ValueError):
ParsedUrl.parse(url)
from poetry.semver import parse_constraint
from poetry.version.helpers import format_python_constraint
def test_format_python_constraint():
constraint = parse_constraint("~2.7 || ^3.6")
result = format_python_constraint(constraint)
assert result == ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
def test_format_python_constraint_single_version():
constraint = parse_constraint("3.6")
result = format_python_constraint(constraint)
assert result == ">=3.6,<3.7"
constraint = parse_constraint("3")
result = format_python_constraint(constraint)
assert result == ">=3.0,<4.0"
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_with_duplicate():
m = parse_marker('python_version < "4.0"')
intersection = m.intersect(
parse_marker('sys_platform == "darwin" and python_version < "4.0"')
)
assert str(intersection) == 'sys_platform == "darwin" and python_version < "4.0"'
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_marker_union_drops_unnecessary_markers():
m = parse_marker(
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
m2 = parse_marker(
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
intersection = m.intersect(m2)
expected = (
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
assert expected == str(intersection)
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_marker_union_all_any():
union = MarkerUnion(parse_marker(""), parse_marker(""))
assert union.is_any()
def test_marker_union_not_all_any():
union = MarkerUnion(parse_marker(""), parse_marker(""), parse_marker("<empty>"))
assert union.is_any()
def test_marker_union_all_empty():
union = MarkerUnion(parse_marker("<empty>"), parse_marker("<empty>"))
assert union.is_empty()
def test_marker_union_not_all_empty():
union = MarkerUnion(
parse_marker("<empty>"), parse_marker("<empty>"), parse_marker("")
)
assert not union.is_empty()
def test_marker_str_conversion_skips_empty_and_any():
union = MarkerUnion(
parse_marker("<empty>"),
parse_marker(
'sys_platform == "darwin" or python_version <= "3.6" or os_name == "Windows"'
),
parse_marker(""),
)
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
@pytest.mark.parametrize(
"marker, env",
[
(
'platform_release >= "9.0" and platform_release < "11.0"',
{"platform_release": "10.0"},
)
],
)
def test_parse_version_like_markers(marker, env):
m = parse_marker(marker)
assert m.validate(env)
@pytest.mark.parametrize(
"marker, expected",
[
('python_version >= "3.6"', 'python_version >= "3.6"'),
('python_version >= "3.6" and extra == "foo"', 'python_version >= "3.6"'),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar")',
'python_version >= "3.6"',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar") or implementation_name == "pypy"',
'python_version >= "3.6" or implementation_name == "pypy"',
),
(
'python_version >= "3.6" and extra == "foo" or implementation_name == "pypy" and extra == "bar"',
'python_version >= "3.6" or implementation_name == "pypy"',
),
(
'python_version >= "3.6" or extra == "foo" and implementation_name == "pypy" or extra == "bar"',
'python_version >= "3.6" or implementation_name == "pypy"',
),
],
)
def test_without_extras(marker, expected):
m = parse_marker(marker)
assert expected == str(m.without_extras())
@pytest.mark.parametrize(
"marker, excluded, expected",
[
('python_version >= "3.6"', "implementation_name", 'python_version >= "3.6"'),
('python_version >= "3.6"', "python_version", "*"),
(
'python_version >= "3.6" and extra == "foo"',
"extra",
'python_version >= "3.6"',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar")',
"python_version",
'(extra == "foo" or extra == "bar")',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar") or implementation_name == "pypy"',
"python_version",
'(extra == "foo" or extra == "bar") or implementation_name == "pypy"',
),
(
'python_version >= "3.6" and extra == "foo" or implementation_name == "pypy" and extra == "bar"',
"implementation_name",
'python_version >= "3.6" and extra == "foo" or extra == "bar"',
),
(
'python_version >= "3.6" or extra == "foo" and implementation_name == "pypy" or extra == "bar"',
"implementation_name",
'python_version >= "3.6" or extra == "foo" or extra == "bar"',
),
],
)
def test_exclude(marker, excluded, expected):
m = parse_marker(marker)
if expected == "*":
assert m.exclude(excluded).is_any()
else:
assert expected == str(m.exclude(excluded))
@pytest.mark.parametrize(
"marker, only, expected",
[
('python_version >= "3.6"', "python_version", 'python_version >= "3.6"'),
(
'python_version >= "3.6" and extra == "foo"',
"python_version",
'python_version >= "3.6"',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar")',
"extra",
'(extra == "foo" or extra == "bar")',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar") or implementation_name == "pypy"',
"implementation_name",
'implementation_name == "pypy"',
),
(
'python_version >= "3.6" and extra == "foo" or implementation_name == "pypy" and extra == "bar"',
"implementation_name",
'implementation_name == "pypy"',
),
(
'python_version >= "3.6" or extra == "foo" and implementation_name == "pypy" or extra == "bar"',
"implementation_name",
'implementation_name == "pypy"',
),
],
)
def test_only(marker, only, expected):
m = parse_marker(marker)
assert expected == str(m.only(only))
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