Commit fedecf6d by Sébastien Eustace

Merge branch 'master' into develop

parents 3a39e5ac 5e34b9e2
name: Main
name: Tests
on: [push, pull_request]
......@@ -85,12 +85,12 @@ jobs:
- name: Install Poetry
run: |
python get-poetry.py --preview -y
SET PATH=%USERPROFILE%\\.poetry\\bin;%PATH%
$env:Path += ";$env:Userprofile\.poetry\bin"
- name: Install dependencies
run: |
SET PATH=%USERPROFILE%\\.poetry\\bin;%PATH%
$env:Path += ";$env:Userprofile\.poetry\bin"
poetry install
- name: Test
run: |
SET PATH=%USERPROFILE%\\.poetry\\bin;%PATH%
$env:Path += ";$env:Userprofile\.poetry\bin"
poetry run pytest -q tests
......@@ -3,4 +3,3 @@ repos:
rev: stable
hooks:
- id: black
language_version: python3.6
......@@ -21,6 +21,8 @@
- Added support for url dependencies ([#1260](https://github.com/sdispater/poetry/pull/1260)).
- Publishing to PyPI using [API tokens](https://pypi.org/help/#apitoken) is now supported ([#1275](https://github.com/sdispater/poetry/pull/1275)).
- Licenses can now be identified by their full name.
- Added support for support for custom certificate authority and client certificates for private repositories.
- Poetry can now detect and use Conda environments.
### Changed
......@@ -35,6 +37,9 @@
- The `add` command will now automatically select the latest prerelease if only prereleases are available.
- The `add` command can now update a dependencies if an explicit constraint is given ([#1221](https://github.com/sdispater/poetry/pull/1221)).
- Removed the `--develop` option from the `install` command.
- Improved UX when searching for packages in the `init` command.
- The `shell` has been improved.
- The `poetry run` command now uses `os.execvp()` rather than spawning a new subprocess.
### Fixed
......@@ -42,6 +47,15 @@
- The `pyproject.toml` configuration is now properly validated.
- Fixed installing Poetry-based packages breaking with `pip`.
- Fixed packages with empty markers being added to the lock file.
- Fixed invalid lock file generation in some cases.
- Fixed local version identifier handling in wheel file names.
- Fixed packages with invalid metadata triggering an error instead of being skipped.
- Fixed the generation of invalid lock files in some cases.
- Git dependencies are now properly locked to a specific revision when specifying a branch or a tag.
- Fixed the behavior of the `~=` operator.
- Fixed dependency resolution for conditional development dependencies.
- Fixed generated dependency constraints when they contain inequality operators.
- The `run` command now properly handles the `--` separator.
## [0.12.17] - 2019-07-03
......@@ -725,7 +739,7 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.17...develop
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.17...master
[0.12.17]: https://github.com/sdispater/poetry/releases/tag/0.12.17
[0.12.16]: https://github.com/sdispater/poetry/releases/tag/0.12.16
[0.12.15]: https://github.com/sdispater/poetry/releases/tag/0.12.15
......
......@@ -47,8 +47,8 @@ wheel:
@poetry build -v
linux_release:
docker pull quay.io/pypa/manylinux1_x86_64
docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/make-linux-release.sh
docker pull quay.io/pypa/manylinux2010_x86_64
docker run --rm -t -i -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/make-linux-release.sh
# run tests against all supported python versions
tox:
......
......@@ -7,8 +7,7 @@ ensuring you have the right stack everywhere.
It supports Python 2.7 and 3.4+.
[![Unix Build Status](https://img.shields.io/travis/sdispater/poetry.svg?label=Unix)](https://travis-ci.org/sdispater/poetry)
[![Windows Build Status](https://img.shields.io/appveyor/ci/sdispater/poetry.svg?label=Windows)](https://ci.appveyor.com/project/sdispater/poetry)
![Tests Status](https://github.com/sdispater/poetry/workflows/Tests/badge.svg)
## Installation
......
......@@ -74,6 +74,16 @@ export POETRY_HTTP_BASIC_PYPI_PASSWORD=password
See [Using environment variables](/configuration#using-environment-variables) for more information
on how to configure Poetry with environment variables.
#### Custom certificate authority and mutual TLS authentication
Poetry supports repositories that are secured by a custom certificate authority as well as those that require
certificate-based client authentication. The following will configure the "foo" repository to validate the repository's
certificate using a custom certificate authority and use a client certificate (note that these config variables do not
both need to be set):
```bash
poetry config certificates.foo.cert /path/to/ca.pem
poetry config certificates.foo.client-cert /path/to/client.pem
```
### Install dependencies from a private repository
Now that you can publish to your private repository, you need to be able to
......@@ -105,8 +115,10 @@ From now on, Poetry will also look for packages in your private repository.
If your private repository requires HTTP Basic Auth be sure to add the username and
password to your `http-basic` configuration using the example above (be sure to use the
same name that is in the `tool.poetry.source` section). Poetry will use these values
to authenticate to your private repository when downloading or looking for packages.
same name that is in the `tool.poetry.source` section). If your repository requires either
a custom certificate authority or client certificates, similarly refer to the example above to configure the
`certificates` section. Poetry will use these values to authenticate to your private repository when downloading or
looking for packages.
### Disabling the PyPI repository
......
......@@ -6,7 +6,7 @@ title: {{ page.title|striptags|e }}
<section class="p-b-50 p-t-50 documentation-content">
<div class="container">
<div class="panel panel-transparent">
<div class="bg-white row p-l-20 p-r-20 p-b-20 p-t-5 xs-no-padding">
<div class="row p-l-20 p-r-20 p-b-20 p-t-5 xs-no-padding">
<div class="row">
<div class="col-md-3 documentation-toc">
<ul class="current">
......
{%- if nav_item.url %}
<a class="{% if nav_item.active%}current{%endif%}" href="{{ base_url }}/{{ nav_item.url }}">{{ nav_item.title }}</a>
{%- else %}
<span class="caption-text">{{ nav_item.title }}</span>
{%- endif %}
{%- if nav_item == page or nav_item.children %}
<ul class="subnav">
......
......@@ -299,7 +299,9 @@ class Installer:
r"(?:\+[^\s]+)?"
)
BASE_URL = "https://github.com/sdispater/poetry/releases/download/"
REPOSITORY_URL = "https://github.com/python-poetry/poetry"
BASE_URL = REPOSITORY_URL + "/releases/download/"
FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download/"
def __init__(
self,
......@@ -598,6 +600,9 @@ class Installer:
"""
Tries to update the $PATH automatically.
"""
if not self._modify_path:
return
if WINDOWS:
return self.add_to_windows_path()
......@@ -606,7 +611,6 @@ class Installer:
addition = "\n{}\n".format(export_string)
updated = []
profiles = self.get_unix_profiles()
for profile in profiles:
if not os.path.exists(profile):
......@@ -619,8 +623,6 @@ class Installer:
with open(profile, "a") as f:
f.write(u(addition))
updated.append(os.path.relpath(profile, HOME))
def add_to_windows_path(self):
try:
old_path = self.get_windows_path_var()
......@@ -849,6 +851,15 @@ def main():
args = parser.parse_args()
base_url = Installer.BASE_URL
try:
r = urlopen(Installer.REPOSITORY_URL)
except HTTPError as e:
if e.code == 404:
base_url = Installer.FALLBACK_BASE_URL
else:
raise
installer = Installer(
version=args.version or os.getenv("POETRY_VERSION"),
preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")),
......@@ -856,6 +867,7 @@ def main():
accept_all=args.accept_all
or string_to_bool(os.getenv("POETRY_ACCEPT", "0"))
or not is_interactive(),
base_url=base_url,
)
if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")):
......
#!/bin/bash
PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m"
PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp38"
cd /io
/opt/python/cp37-cp37m/bin/pip install pip -U
/opt/python/cp37-cp37m/bin/pip install poetry -U
/opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false
/opt/python/cp37-cp37m/bin/poetry install --no-dev
......@@ -10,5 +11,6 @@ cd /io
-P "3.4:/opt/python/cp34-cp34m/bin/python" \
-P "3.5:/opt/python/cp35-cp35m/bin/python" \
-P "3.6:/opt/python/cp36-cp36m/bin/python" \
-P "3.7:/opt/python/cp37-cp37m/bin/python"
-P "3.7:/opt/python/cp37-cp37m/bin/python" \
-P "3.8:/opt/python/cp38-cp38/bin/python"
cd -
......@@ -8,15 +8,6 @@ python-versions = "*"
version = "1.4.3"
[[package]]
category = "main"
description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
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\""
name = "asn1crypto"
optional = false
python-versions = "*"
version = "0.24.0"
[[package]]
category = "dev"
description = "A few extensions to pyyaml."
name = "aspy.yaml"
......@@ -41,12 +32,13 @@ description = "Classes Without Boilerplate"
name = "attrs"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.1.0"
version = "19.3.0"
[package.extras]
dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "pre-commit"]
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
docs = ["sphinx", "zope.interface"]
tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"]
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]]
category = "dev"
......@@ -114,7 +106,7 @@ marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platfo
name = "cffi"
optional = false
python-versions = "*"
version = "1.12.3"
version = "1.13.1"
[package.dependencies]
pycparser = "*"
......@@ -144,10 +136,10 @@ description = "Cleo allows you to create beautiful and testable command-line int
name = "cleo"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.7.5"
version = "0.7.6"
[package.dependencies]
clikit = ">=0.3.1,<0.4.0"
clikit = ">=0.4.0,<0.5.0"
[[package]]
category = "dev"
......@@ -164,7 +156,7 @@ description = "CliKit is a group of utilities to build beautiful and testable co
name = "clikit"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.3.2"
version = "0.4.0"
[package.dependencies]
pastel = ">=0.1.0,<0.2.0"
......@@ -201,13 +193,13 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytest-flake8", "pytest-black-multipy"]
[[package]]
category = "dev"
category = "main"
description = "Backports and enhancements for the contextlib module"
marker = "python_version < \"3\""
name = "contextlib2"
optional = false
python-versions = "*"
version = "0.5.5"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.6.0.post1"
[[package]]
category = "dev"
......@@ -224,10 +216,9 @@ marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platfo
name = "cryptography"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "2.7"
version = "2.8"
[package.dependencies]
asn1crypto = ">=0.21.0"
cffi = ">=1.8,<1.11.3 || >1.11.3"
six = ">=1.4.1"
......@@ -338,7 +329,7 @@ description = "HTTP client mock for Python"
name = "httpretty"
optional = false
python-versions = "*"
version = "0.9.6"
version = "0.9.7"
[package.dependencies]
six = "*"
......@@ -363,7 +354,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.8"
[[package]]
category = "dev"
category = "main"
description = "Read metadata from Python packages"
name = "importlib-metadata"
optional = false
......@@ -410,7 +401,7 @@ marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platfo
name = "ipaddress"
optional = false
python-versions = "*"
version = "1.0.22"
version = "1.0.23"
[[package]]
category = "main"
......@@ -426,12 +417,12 @@ dev = ["testpath"]
[[package]]
category = "dev"
description = "A small but fast and easy to use stand-alone template engine written in pure python."
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 = "*"
version = "2.10.1"
version = "2.10.3"
[package.dependencies]
MarkupSafe = ">=0.23"
......@@ -445,10 +436,11 @@ description = "An implementation of JSON Schema validation for Python"
name = "jsonschema"
optional = false
python-versions = "*"
version = "3.0.2"
version = "3.1.1"
[package.dependencies]
attrs = ">=17.4.0"
importlib-metadata = "*"
pyrsistent = ">=0.14.0"
setuptools = "*"
six = ">=1.11.0"
......@@ -591,7 +583,7 @@ docs = ["sphinx"]
test = ["pytest", "pytest-cov"]
[[package]]
category = "dev"
category = "main"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
......@@ -602,7 +594,7 @@ version = "5.0.0"
six = ">=1.0.0,<2.0.0"
[[package]]
category = "dev"
category = "main"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
......@@ -652,7 +644,7 @@ marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_versi
name = "pathlib2"
optional = false
python-versions = "*"
version = "2.3.4"
version = "2.3.5"
[package.dependencies]
six = "*"
......@@ -841,14 +833,13 @@ category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "4.6.5"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "4.6.6"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
importlib-metadata = ">=0.12"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
......@@ -867,6 +858,10 @@ version = ">=4.0.0"
python = "<3.0"
version = ">=1.0"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.dependencies.pathlib2]
python = "<3.6"
version = ">=2.2.0"
......@@ -880,7 +875,7 @@ description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.7.1"
version = "2.8.1"
[package.dependencies]
coverage = ">=4.4"
......@@ -895,7 +890,7 @@ description = "Thin-wrapper around the mock package for easier use with py.test"
name = "pytest-mock"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.10.4"
version = "1.11.2"
[package.dependencies]
pytest = ">=2.7"
......@@ -1068,7 +1063,7 @@ description = "Style preserving TOML library"
name = "tomlkit"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.5.5"
version = "0.5.8"
[package.dependencies]
[package.dependencies.enum34]
......@@ -1153,7 +1148,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and
name = "urllib3"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.25.5"
version = "1.25.6"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
......@@ -1165,8 +1160,8 @@ category = "main"
description = "Virtual Python Environment builder"
name = "virtualenv"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "16.7.5"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "16.7.7"
[package.extras]
docs = ["sphinx (>=1.8.0,<2)", "towncrier (>=18.5.0)", "sphinx-rtd-theme (>=0.4.2,<1)"]
......@@ -1189,7 +1184,7 @@ python-versions = "*"
version = "0.5.1"
[[package]]
category = "dev"
category = "main"
description = "Backport of pathlib-compatible object wrapper for zip files"
name = "zipp"
optional = false
......@@ -1204,95 +1199,522 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pathlib2", "contextlib2", "unittest2"]
[metadata]
content-hash = "fef0a14e6eed6d3fd20670fe4c048b1f17bc12a08a8b8a5482dfcff6f569cb1d"
content-hash = "402f356e484839dbb4361c25f986325cc08f1d441d777a15950cb28ce71ac005"
python-versions = "~2.7 || ^3.4"
[metadata.hashes]
appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"]
asn1crypto = ["2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", "9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"]
"aspy.yaml" = ["463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", "e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"]
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"]
black = ["09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", "68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"]
cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"]
cachy = ["186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1", "338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"]
certifi = ["e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"]
cffi = ["041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", "046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", "066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", "066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", "2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", "300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", "34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", "46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", "4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", "4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", "4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", "50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", "55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", "5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", "59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", "73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", "a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", "a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", "a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", "a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", "ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", "b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", "d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", "d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", "dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", "e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", "e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", "ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"]
cfgv = ["edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144", "fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"]
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
cleo = ["2f1cfd55a4b30c002ef86d740ededc9c11d6f89ea0dc56a14abe5aaf32c2015c", "6c28adbb0815b99e530bb4f783602459ab916c76c4db26c7b934223014eb1a34"]
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
clikit = ["229d259b9ec7adb6ce668aed70059ff79058b22794b958c178c50ece6ade5871", "b5034efd4dadb65e71544f978c70628dc56df4b0b47b0e6a7e521347dfc07fdf"]
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
configparser = ["254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c", "c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df"]
contextlib2 = ["509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", "f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"]
coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"]
cryptography = ["24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c", "25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643", "3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216", "41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799", "5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a", "5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9", "72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc", "7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8", "961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53", "96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1", "ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609", "b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292", "cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e", "e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6", "f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed", "f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"]
entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"]
filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"]
funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"]
functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"]
futures = ["49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16", "7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"]
glob2 = ["f5b0a686ff21f820c4d3f0c4edd216704cea59d79d00fa337e244a2f2ff83ed6"]
html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"]
httpretty = ["01b52d45077e702eda491f4fe75328d3468fd886aed5dcc530003e7b2b5939dc"]
identify = ["4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017", "d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e"]
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"]
importlib-resources = ["6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b", "d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"]
ipaddress = ["64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", "b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c"]
jeepney = ["13806f91a96e9b2623fd2a81b950d763ee471454aafd9eb6d75dbe7afce428fb", "f6a3f93464a0cf052f4e87da3c8b3ed1e27696758fb9739c63d3a74d9a1b6774"]
jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"]
jsonschema = ["5f9c0a719ca2ce14c5de2fd350a64fd2d13e8539db29836a86adc990bb1a068f", "8d4a2b7b6c2237e0199c8ea1a6d3e05bf118e289ae2b9d7ba444182a2959560d"]
keyring = ["67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838", "7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6", "91037ccaf0c9a112a76f7740e4a416b9457a69b66c2799421581bee710a974b3", "f5bb20ea6c57c2360daf0c591931c9ea0d7660a8d9e32ca84d63273f131ea605"]
livereload = ["78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b", "89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"]
lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"]
markdown = ["c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa", "d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c", "2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", "56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"]
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"]
mock = ["83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", "d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8"]
more-itertools = ["38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", "c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", "fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9", "409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
msgpack = ["0cc7ca04e575ba34fea7cfcd76039f55def570e6950e4155a4174368142c8e1b", "187794cd1eb73acccd528247e3565f6760bd842d7dc299241f830024a7dd5610", "1904b7cb65342d0998b75908304a03cb004c63ef31e16c8c43fee6b989d7f0d7", "24149a75643aeaa81ece4259084d11b792308a6cf74e796cbb35def94c89a25a", "30b88c47e0cdb6062daed88ca283b0d84fa0d2ad6c273aa0788152a1c643e408", "355f7fd0f90134229eaeefaee3cf42e0afc8518e8f3cd4b25f541a7104dcb8f9", "4abdb88a9b67e64810fb54b0c24a1fd76b12297b4f7a1467d85a14dd8367191a", "757bd71a9b89e4f1db0622af4436d403e742506dbea978eba566815dc65ec895", "76df51492bc6fa6cc8b65d09efdb67cbba3cbfe55004c3afc81352af92b4a43c", "774f5edc3475917cd95fe593e625d23d8580f9b48b570d8853d06cac171cd170", "8a3ada8401736df2bf497f65589293a86c56e197a80ae7634ec2c3150a2f5082", "a06efd0482a1942aad209a6c18321b5e22d64eb531ea20af138b28172d8f35ba", "b8b4bd3dafc7b92608ae5462add1c8cc881851c2d4f5d8977fdea5b081d17f21", "c6e5024fc0cdf7f83b6624850309ddd7e06c48a75fa0d1c5173de4d93300eb19", "db7ff14abc73577b0bcbcf73ecff97d3580ecaa0fc8724babce21fdf3fe08ef6", "dedf54d72d9e7b6d043c244c8213fe2b8bbfe66874b9a65b39c4cc892dd99dd4", "ea3c2f859346fcd55fc46e96885301d9c2f7a36d453f5d8f2967840efa1e1830", "f0f47bafe9c9b8ed03e19a100a743662dd8c6d0135e684feea720a0d0046d116"]
nodeenv = ["ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"]
packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"]
pastel = ["a904e1659512cc9880a028f66de77cc813a4c32f7ceb68725cbc8afad57ef7ef", "bf3b1901b2442ea0d8ab9a390594e5b0c9584709d543a3113506fe8b28cbace3"]
pathlib2 = ["2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", "446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8"]
pep562 = ["58cb1cc9ee63d93e62b4905a50357618d526d289919814bea1f0da8f53b79395", "d2a48b178ebf5f8dd31709cc26a19808ef794561fa2fe50ea01ea2bad4d667ef"]
pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"]
pkginfo = ["7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", "a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"]
pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"]
pre-commit = ["1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f", "fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a"]
ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"]
py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"]
pygments = ["5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", "e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d", "71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"]
pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"]
pymdown-extensions = ["25b0a7967fa697b5035e23340a48594e3e93acb10b06d74574218ace3347d1df", "6cf0cf36b5a03b291ace22dc2f320f4789ce56fbdb6635a3be5fadbf5d7694dd", "24c1a0afbae101c4e2b2675ff4dd936470a90133f93398b9cbe0c855e2d2ec10", "960486dea995f1759dfd517aa140b3d851cd7b44d4c48d276fd2c74fc4e1bce9"]
pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
pyrsistent = ["3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"]
pytest = ["8fc39199bdda3d9d025d3b1f4eb99a192c20828030ea7c9a0d2840721de7d347", "d100a02770f665f5dcf7e3f08202db29857fee6d15f34c942be0a511f39814f0"]
pytest-cov = ["2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", "e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"]
pytest-mock = ["43ce4e9dd5074993e7c021bb1c22cbb5363e612a2b5a76bc6d956775b10758b7", "5bf5771b1db93beac965a7347dc81c675ec4090cb841e49d9d34637a25c30568"]
pytest-sugar = ["26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283", "fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"]
pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"]
pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"]
requests = ["502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", "7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b", "11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", "f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"]
scandir = ["2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", "2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022", "2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f", "2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f", "4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae", "67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173", "7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4", "8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32", "92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188", "b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d", "cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"]
secretstorage = ["3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6", "20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92", "7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"]
shellingham = ["77d37a4fd287c1e663006f7ecf1b9deca9ad492d0082587bd813c44eb49e4e62", "985b23bbd1feae47ca6a6365eacd314d93d95a8a16f8f346945074c28fe6f3e0"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
subprocess32 = ["88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b", "eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"]
termcolor = ["1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"]
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
tomlkit = ["a8d806f3a453c2d292afe97918398354e405b93919e2e68771a3fd0a90e89576", "c6b0c11b85e888c12330c7605d43c1446aa148cd421163f90ca46ea813f2c336"]
tornado = ["0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", "4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", "732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", "8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", "8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", "d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", "e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444", "349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"]
tox = ["0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e", "c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1"]
typing = ["91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", "c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", "f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"]
urllib3 = ["2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb", "2f3eadfea5d92bc7899e75b5968410b749a054b492d5a6379c1344a1481bc2cb", "9c6c593cb28f52075016307fc26b0a0f8e82bc7d1ff19aaaa959b91710a56c47"]
virtualenv = ["680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", "f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2"]
wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
webencodings = ["a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", "b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"]
zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"]
[metadata.files]
appdirs = [
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
]
"aspy.yaml" = [
{file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"},
{file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"},
]
atomicwrites = [
{file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
{file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
]
attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
]
black = [
{file = "black-19.3b0-py36-none-any.whl", hash = "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf"},
{file = "black-19.3b0.tar.gz", hash = "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"},
]
cachecontrol = [
{file = "CacheControl-0.12.5.tar.gz", hash = "sha256:cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"},
]
cachy = [
{file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"},
{file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"},
]
certifi = [
{file = "certifi-2019.9.11-py2.py3-none-any.whl", hash = "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"},
{file = "certifi-2019.9.11.tar.gz", hash = "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50"},
]
cffi = [
{file = "cffi-1.13.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78"},
{file = "cffi-1.13.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa"},
{file = "cffi-1.13.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365"},
{file = "cffi-1.13.1-cp27-cp27m-win32.whl", hash = "sha256:1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98"},
{file = "cffi-1.13.1-cp27-cp27m-win_amd64.whl", hash = "sha256:0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400"},
{file = "cffi-1.13.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05"},
{file = "cffi-1.13.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8"},
{file = "cffi-1.13.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36"},
{file = "cffi-1.13.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc"},
{file = "cffi-1.13.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599"},
{file = "cffi-1.13.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e"},
{file = "cffi-1.13.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71"},
{file = "cffi-1.13.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0"},
{file = "cffi-1.13.1-cp35-cp35m-win32.whl", hash = "sha256:364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e"},
{file = "cffi-1.13.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2"},
{file = "cffi-1.13.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730"},
{file = "cffi-1.13.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43"},
{file = "cffi-1.13.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5"},
{file = "cffi-1.13.1-cp36-cp36m-win32.whl", hash = "sha256:e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891"},
{file = "cffi-1.13.1-cp36-cp36m-win_amd64.whl", hash = "sha256:47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1"},
{file = "cffi-1.13.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434"},
{file = "cffi-1.13.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa"},
{file = "cffi-1.13.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2"},
{file = "cffi-1.13.1-cp37-cp37m-win32.whl", hash = "sha256:6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4"},
{file = "cffi-1.13.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526"},
{file = "cffi-1.13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331"},
{file = "cffi-1.13.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a"},
{file = "cffi-1.13.1-cp38-cp38-win32.whl", hash = "sha256:819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8"},
{file = "cffi-1.13.1-cp38-cp38-win_amd64.whl", hash = "sha256:263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14"},
{file = "cffi-1.13.1.tar.gz", hash = "sha256:558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b"},
]
cfgv = [
{file = "cfgv-2.0.1-py2.py3-none-any.whl", hash = "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"},
{file = "cfgv-2.0.1.tar.gz", hash = "sha256:edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144"},
]
chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
cleo = [
{file = "cleo-0.7.6-py2.py3-none-any.whl", hash = "sha256:9443d67e5b2da79b32d820ae41758dd6a25618345cb10b9a022a695e26b291b9"},
{file = "cleo-0.7.6.tar.gz", hash = "sha256:99cf342406f3499cec43270fcfaf93c126c5164092eca201dfef0f623360b409"},
]
click = [
{file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"},
{file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
]
clikit = [
{file = "clikit-0.4.0-py2.py3-none-any.whl", hash = "sha256:9f07b56216bc1068f8ed441a5e67a3881fe503cfd6a3a7874f59f72be417cdda"},
{file = "clikit-0.4.0.tar.gz", hash = "sha256:6819a5b2a78523be485ea9e1ef380ae8d02da4bffed540f7881fdaafbff41390"},
]
colorama = [
{file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"},
{file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"},
]
configparser = [
{file = "configparser-4.0.2-py2.py3-none-any.whl", hash = "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c"},
{file = "configparser-4.0.2.tar.gz", hash = "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df"},
]
contextlib2 = [
{file = "contextlib2-0.6.0.post1-py2.py3-none-any.whl", hash = "sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b"},
{file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"},
]
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"},
]
cryptography = [
{file = "cryptography-2.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"},
{file = "cryptography-2.8-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2"},
{file = "cryptography-2.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad"},
{file = "cryptography-2.8-cp27-cp27m-win32.whl", hash = "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2"},
{file = "cryptography-2.8-cp27-cp27m-win_amd64.whl", hash = "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912"},
{file = "cryptography-2.8-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d"},
{file = "cryptography-2.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42"},
{file = "cryptography-2.8-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879"},
{file = "cryptography-2.8-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d"},
{file = "cryptography-2.8-cp34-abi3-manylinux2010_x86_64.whl", hash = "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9"},
{file = "cryptography-2.8-cp34-cp34m-win32.whl", hash = "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c"},
{file = "cryptography-2.8-cp34-cp34m-win_amd64.whl", hash = "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0"},
{file = "cryptography-2.8-cp35-cp35m-win32.whl", hash = "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf"},
{file = "cryptography-2.8-cp35-cp35m-win_amd64.whl", hash = "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793"},
{file = "cryptography-2.8-cp36-cp36m-win32.whl", hash = "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595"},
{file = "cryptography-2.8-cp36-cp36m-win_amd64.whl", hash = "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7"},
{file = "cryptography-2.8-cp37-cp37m-win32.whl", hash = "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff"},
{file = "cryptography-2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f"},
{file = "cryptography-2.8-cp38-cp38-win32.whl", hash = "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e"},
{file = "cryptography-2.8-cp38-cp38-win_amd64.whl", hash = "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13"},
{file = "cryptography-2.8.tar.gz", hash = "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651"},
]
entrypoints = [
{file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
{file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
]
enum34 = [
{file = "enum34-1.1.6-py2-none-any.whl", hash = "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79"},
{file = "enum34-1.1.6-py3-none-any.whl", hash = "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a"},
{file = "enum34-1.1.6.tar.gz", hash = "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"},
{file = "enum34-1.1.6.zip", hash = "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850"},
]
filelock = [
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
]
funcsigs = [
{file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"},
{file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"},
]
functools32 = [
{file = "functools32-3.2.3-2.tar.gz", hash = "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"},
{file = "functools32-3.2.3-2.zip", hash = "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0"},
]
futures = [
{file = "futures-3.3.0-py2-none-any.whl", hash = "sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16"},
{file = "futures-3.3.0.tar.gz", hash = "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"},
]
glob2 = [
{file = "glob2-0.6.tar.gz", hash = "sha256:f5b0a686ff21f820c4d3f0c4edd216704cea59d79d00fa337e244a2f2ff83ed6"},
]
html5lib = [
{file = "html5lib-1.0.1-py2.py3-none-any.whl", hash = "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3"},
{file = "html5lib-1.0.1.tar.gz", hash = "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"},
]
httpretty = [
{file = "httpretty-0.9.7.tar.gz", hash = "sha256:66216f26b9d2c52e81808f3e674a6fb65d4bf719721394a1a9be926177e55fbe"},
]
identify = [
{file = "identify-1.4.7-py2.py3-none-any.whl", hash = "sha256:4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017"},
{file = "identify-1.4.7.tar.gz", hash = "sha256:d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e"},
]
idna = [
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
]
importlib-metadata = [
{file = "importlib_metadata-0.23-py2.py3-none-any.whl", hash = "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"},
{file = "importlib_metadata-0.23.tar.gz", hash = "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26"},
]
importlib-resources = [
{file = "importlib_resources-1.0.2-py2.py3-none-any.whl", hash = "sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b"},
{file = "importlib_resources-1.0.2.tar.gz", hash = "sha256:d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"},
]
ipaddress = [
{file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"},
{file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"},
]
jeepney = [
{file = "jeepney-0.4.1-py3-none-any.whl", hash = "sha256:f6a3f93464a0cf052f4e87da3c8b3ed1e27696758fb9739c63d3a74d9a1b6774"},
{file = "jeepney-0.4.1.tar.gz", hash = "sha256:13806f91a96e9b2623fd2a81b950d763ee471454aafd9eb6d75dbe7afce428fb"},
]
jinja2 = [
{file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"},
{file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"},
]
jsonschema = [
{file = "jsonschema-3.1.1-py2.py3-none-any.whl", hash = "sha256:94c0a13b4a0616458b42529091624e66700a17f847453e52279e35509a5b7631"},
{file = "jsonschema-3.1.1.tar.gz", hash = "sha256:2fa0684276b6333ff3c0b1b27081f4b2305f0a36cf702a23db50edb141893c3f"},
]
keyring = [
{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-19.2.0-py2.py3-none-any.whl", hash = "sha256:f5bb20ea6c57c2360daf0c591931c9ea0d7660a8d9e32ca84d63273f131ea605"},
{file = "keyring-19.2.0.tar.gz", hash = "sha256:91037ccaf0c9a112a76f7740e4a416b9457a69b66c2799421581bee710a974b3"},
]
livereload = [
{file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"},
{file = "livereload-2.6.1.tar.gz", hash = "sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"},
]
lockfile = [
{file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"},
{file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"},
]
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.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"},
]
markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
mkdocs = [
{file = "mkdocs-1.0.4-py2.py3-none-any.whl", hash = "sha256:8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"},
{file = "mkdocs-1.0.4.tar.gz", hash = "sha256:17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939"},
]
mock = [
{file = "mock-3.0.5-py2.py3-none-any.whl", hash = "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8"},
{file = "mock-3.0.5.tar.gz", hash = "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3"},
]
more-itertools = [
{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-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"},
]
msgpack = [
{file = "msgpack-0.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:774f5edc3475917cd95fe593e625d23d8580f9b48b570d8853d06cac171cd170"},
{file = "msgpack-0.6.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a06efd0482a1942aad209a6c18321b5e22d64eb531ea20af138b28172d8f35ba"},
{file = "msgpack-0.6.2-cp27-cp27m-win32.whl", hash = "sha256:8a3ada8401736df2bf497f65589293a86c56e197a80ae7634ec2c3150a2f5082"},
{file = "msgpack-0.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:b8b4bd3dafc7b92608ae5462add1c8cc881851c2d4f5d8977fdea5b081d17f21"},
{file = "msgpack-0.6.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:24149a75643aeaa81ece4259084d11b792308a6cf74e796cbb35def94c89a25a"},
{file = "msgpack-0.6.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:757bd71a9b89e4f1db0622af4436d403e742506dbea978eba566815dc65ec895"},
{file = "msgpack-0.6.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:32fea0ea3cd1ef820286863a6202dcfd62a539b8ec3edcbdff76068a8c2cc6ce"},
{file = "msgpack-0.6.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:db7ff14abc73577b0bcbcf73ecff97d3580ecaa0fc8724babce21fdf3fe08ef6"},
{file = "msgpack-0.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:187794cd1eb73acccd528247e3565f6760bd842d7dc299241f830024a7dd5610"},
{file = "msgpack-0.6.2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:b24afc52e18dccc8c175de07c1d680bdf315844566f4952b5bedb908894bec79"},
{file = "msgpack-0.6.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:355f7fd0f90134229eaeefaee3cf42e0afc8518e8f3cd4b25f541a7104dcb8f9"},
{file = "msgpack-0.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:76df51492bc6fa6cc8b65d09efdb67cbba3cbfe55004c3afc81352af92b4a43c"},
{file = "msgpack-0.6.2-cp36-cp36m-win32.whl", hash = "sha256:f0f47bafe9c9b8ed03e19a100a743662dd8c6d0135e684feea720a0d0046d116"},
{file = "msgpack-0.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c6e5024fc0cdf7f83b6624850309ddd7e06c48a75fa0d1c5173de4d93300eb19"},
{file = "msgpack-0.6.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:30b88c47e0cdb6062daed88ca283b0d84fa0d2ad6c273aa0788152a1c643e408"},
{file = "msgpack-0.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:229a0ccdc39e9b6c6d1033cd8aecd9c296823b6c87f0de3943c59b8bc7c64bee"},
{file = "msgpack-0.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4abdb88a9b67e64810fb54b0c24a1fd76b12297b4f7a1467d85a14dd8367191a"},
{file = "msgpack-0.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dedf54d72d9e7b6d043c244c8213fe2b8bbfe66874b9a65b39c4cc892dd99dd4"},
{file = "msgpack-0.6.2-cp37-cp37m-win32.whl", hash = "sha256:0cc7ca04e575ba34fea7cfcd76039f55def570e6950e4155a4174368142c8e1b"},
{file = "msgpack-0.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:1904b7cb65342d0998b75908304a03cb004c63ef31e16c8c43fee6b989d7f0d7"},
{file = "msgpack-0.6.2.tar.gz", hash = "sha256:ea3c2f859346fcd55fc46e96885301d9c2f7a36d453f5d8f2967840efa1e1830"},
]
nodeenv = [
{file = "nodeenv-1.3.3.tar.gz", hash = "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"},
]
packaging = [
{file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"},
{file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"},
]
pastel = [
{file = "pastel-0.1.1-py2.py3-none-any.whl", hash = "sha256:a904e1659512cc9880a028f66de77cc813a4c32f7ceb68725cbc8afad57ef7ef"},
{file = "pastel-0.1.1.tar.gz", hash = "sha256:bf3b1901b2442ea0d8ab9a390594e5b0c9584709d543a3113506fe8b28cbace3"},
]
pathlib2 = [
{file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"},
{file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"},
]
pep562 = [
{file = "pep562-1.0-py2.py3-none-any.whl", hash = "sha256:d2a48b178ebf5f8dd31709cc26a19808ef794561fa2fe50ea01ea2bad4d667ef"},
{file = "pep562-1.0.tar.gz", hash = "sha256:58cb1cc9ee63d93e62b4905a50357618d526d289919814bea1f0da8f53b79395"},
]
pexpect = [
{file = "pexpect-4.7.0-py2.py3-none-any.whl", hash = "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1"},
{file = "pexpect-4.7.0.tar.gz", hash = "sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"},
]
pkginfo = [
{file = "pkginfo-1.5.0.1-py2.py3-none-any.whl", hash = "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"},
{file = "pkginfo-1.5.0.1.tar.gz", hash = "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb"},
]
pluggy = [
{file = "pluggy-0.13.0-py2.py3-none-any.whl", hash = "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6"},
{file = "pluggy-0.13.0.tar.gz", hash = "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"},
]
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"},
]
ptyprocess = [
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
{file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
]
py = [
{file = "py-1.8.0-py2.py3-none-any.whl", hash = "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa"},
{file = "py-1.8.0.tar.gz", hash = "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"},
]
pycparser = [
{file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"},
]
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.4.2-py2.py3-none-any.whl", hash = "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127"},
{file = "Pygments-2.4.2.tar.gz", hash = "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"},
]
pygments-github-lexers = [
{file = "pygments-github-lexers-0.0.5.tar.gz", hash = "sha256:aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"},
{file = "pygments_github_lexers-0.0.5-py3.4.egg", hash = "sha256:0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1"},
]
pylev = [
{file = "pylev-1.3.0-py2.py3-none-any.whl", hash = "sha256:1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"},
{file = "pylev-1.3.0.tar.gz", hash = "sha256:063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3"},
]
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.1.tar.gz", hash = "sha256:960486dea995f1759dfd517aa140b3d851cd7b44d4c48d276fd2c74fc4e1bce9"},
{file = "pymdown_extensions-6.1-py2.py3-none-any.whl", hash = "sha256:24c1a0afbae101c4e2b2675ff4dd936470a90133f93398b9cbe0c855e2d2ec10"},
]
pyparsing = [
{file = "pyparsing-2.4.2-py2.py3-none-any.whl", hash = "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"},
{file = "pyparsing-2.4.2.tar.gz", hash = "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80"},
]
pyrsistent = [
{file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"},
]
pytest = [
{file = "pytest-4.6.6-py2.py3-none-any.whl", hash = "sha256:5d0d20a9a66e39b5845ab14f8989f3463a7aa973700e6cdf02db69da9821e738"},
{file = "pytest-4.6.6.tar.gz", hash = "sha256:692d9351353ef709c1126266579edd4fd469dcf6b5f4f583050f72161d6f3592"},
]
pytest-cov = [
{file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"},
{file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
]
pytest-mock = [
{file = "pytest-mock-1.11.2.tar.gz", hash = "sha256:ea502c3891599c26243a3a847ccf0b1d20556678c528f86c98e3cd6d40c5cf11"},
{file = "pytest_mock-1.11.2-py2.py3-none-any.whl", hash = "sha256:b3514caac35fe3f05555923eabd9546abce11571cc2ddf7d8615959d04f2c89e"},
]
pytest-sugar = [
{file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"},
{file = "pytest_sugar-0.9.2-py2.py3-none-any.whl", hash = "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283"},
]
pywin32-ctypes = [
{file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
{file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
]
pyyaml = [
{file = "PyYAML-5.1.2-cp27-cp27m-win32.whl", hash = "sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8"},
{file = "PyYAML-5.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"},
{file = "PyYAML-5.1.2-cp34-cp34m-win32.whl", hash = "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9"},
{file = "PyYAML-5.1.2-cp34-cp34m-win_amd64.whl", hash = "sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696"},
{file = "PyYAML-5.1.2-cp35-cp35m-win32.whl", hash = "sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41"},
{file = "PyYAML-5.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73"},
{file = "PyYAML-5.1.2-cp36-cp36m-win32.whl", hash = "sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299"},
{file = "PyYAML-5.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b"},
{file = "PyYAML-5.1.2-cp37-cp37m-win32.whl", hash = "sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae"},
{file = "PyYAML-5.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34"},
{file = "PyYAML-5.1.2-cp38-cp38m-win32.whl", hash = "sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9"},
{file = "PyYAML-5.1.2-cp38-cp38m-win_amd64.whl", hash = "sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681"},
{file = "PyYAML-5.1.2.tar.gz", hash = "sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4"},
]
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.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"},
{file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"},
]
requests-toolbelt = [
{file = "requests-toolbelt-0.8.0.tar.gz", hash = "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"},
{file = "requests_toolbelt-0.8.0-py2.py3-none-any.whl", hash = "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237"},
]
scandir = [
{file = "scandir-1.10.0-cp27-cp27m-win32.whl", hash = "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188"},
{file = "scandir-1.10.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"},
{file = "scandir-1.10.0-cp34-cp34m-win32.whl", hash = "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f"},
{file = "scandir-1.10.0-cp34-cp34m-win_amd64.whl", hash = "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e"},
{file = "scandir-1.10.0-cp35-cp35m-win32.whl", hash = "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f"},
{file = "scandir-1.10.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32"},
{file = "scandir-1.10.0-cp36-cp36m-win32.whl", hash = "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022"},
{file = "scandir-1.10.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4"},
{file = "scandir-1.10.0-cp37-cp37m-win32.whl", hash = "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173"},
{file = "scandir-1.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d"},
{file = "scandir-1.10.0.tar.gz", hash = "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae"},
]
secretstorage = [
{file = "SecretStorage-2.3.1.tar.gz", hash = "sha256:3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6"},
{file = "SecretStorage-3.1.1-py3-none-any.whl", hash = "sha256:7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"},
{file = "SecretStorage-3.1.1.tar.gz", hash = "sha256:20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92"},
]
shellingham = [
{file = "shellingham-1.3.1-py2.py3-none-any.whl", hash = "sha256:77d37a4fd287c1e663006f7ecf1b9deca9ad492d0082587bd813c44eb49e4e62"},
{file = "shellingham-1.3.1.tar.gz", hash = "sha256:985b23bbd1feae47ca6a6365eacd314d93d95a8a16f8f346945074c28fe6f3e0"},
]
six = [
{file = "six-1.12.0-py2.py3-none-any.whl", hash = "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c"},
{file = "six-1.12.0.tar.gz", hash = "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"},
]
subprocess32 = [
{file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"},
{file = "subprocess32-3.5.4.tar.gz", hash = "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"},
]
termcolor = [
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
]
toml = [
{file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
{file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
{file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
]
tomlkit = [
{file = "tomlkit-0.5.8-py2.py3-none-any.whl", hash = "sha256:96e6369288571799a3052c1ef93b9de440e1ab751aa045f435b55e9d3bcd0690"},
{file = "tomlkit-0.5.8.tar.gz", hash = "sha256:32c10cc16ded7e4101c79f269910658cc2a0be5913f1252121c3cd603051c269"},
]
tornado = [
{file = "tornado-5.1.1-cp35-cp35m-win32.whl", hash = "sha256:732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f"},
{file = "tornado-5.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d"},
{file = "tornado-5.1.1-cp36-cp36m-win32.whl", hash = "sha256:8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f"},
{file = "tornado-5.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb"},
{file = "tornado-5.1.1-cp37-cp37m-win32.whl", hash = "sha256:e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444"},
{file = "tornado-5.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5"},
{file = "tornado-5.1.1.tar.gz", hash = "sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409"},
{file = "tornado-6.0.3-cp35-cp35m-win32.whl", hash = "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"},
{file = "tornado-6.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60"},
{file = "tornado-6.0.3-cp36-cp36m-win32.whl", hash = "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281"},
{file = "tornado-6.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c"},
{file = "tornado-6.0.3-cp37-cp37m-win32.whl", hash = "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5"},
{file = "tornado-6.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7"},
{file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"},
]
tox = [
{file = "tox-3.14.0-py2.py3-none-any.whl", hash = "sha256:0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e"},
{file = "tox-3.14.0.tar.gz", hash = "sha256:c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1"},
]
typing = [
{file = "typing-3.7.4.1-py2-none-any.whl", hash = "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36"},
{file = "typing-3.7.4.1-py3-none-any.whl", hash = "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"},
{file = "typing-3.7.4.1.tar.gz", hash = "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23"},
]
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.6-py2.py3-none-any.whl", hash = "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398"},
{file = "urllib3-1.25.6.tar.gz", hash = "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"},
]
virtualenv = [
{file = "virtualenv-16.7.7-py2.py3-none-any.whl", hash = "sha256:11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589"},
{file = "virtualenv-16.7.7.tar.gz", hash = "sha256:d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136"},
]
wcwidth = [
{file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"},
{file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"},
]
webencodings = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
zipp = [
{file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"},
{file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"},
]
__version__ = "1.0.0b1"
__version__ = "1.0.0b3"
......@@ -21,7 +21,7 @@ class BuildCommand(EnvCommand):
package = self.poetry.package
self.line(
"Building <info>{}</> (<comment>{}</>)".format(
"Building <c1>{}</c1> (<b>{}</b>)".format(
package.pretty_name, package.version
)
)
......
......@@ -228,6 +228,27 @@ To remove a repository (repo is a short alias for repositories):
return 0
# handle certs
m = re.match(
r"(?:certificates)\.([^.]+)\.(cert|client-cert)", self.argument("key")
)
if m:
if self.option("unset"):
config.auth_config_source.remove_property(
"certificates.{}.{}".format(m.group(1), m.group(2))
)
return 0
if len(values) == 1:
config.auth_config_source.add_property(
"certificates.{}.{}".format(m.group(1), m.group(2)), values[0]
)
else:
raise ValueError("You must pass exactly 1 value")
return 0
raise ValueError("Setting {} does not exist".format(self.argument("key")))
def _handle_single_value(self, source, key, callbacks, values):
......
......@@ -5,10 +5,10 @@ from typing import List
from cleo import argument
from cleo import option
from ..command import Command
from ..init import InitCommand
class DebugResolveCommand(Command):
class DebugResolveCommand(InitCommand):
name = "resolve"
description = "Debugs dependency resolution."
......@@ -43,12 +43,25 @@ class DebugResolveCommand(Command):
if not packages:
package = self.poetry.package
else:
# Using current pool for determine_requirements()
self._pool = self.poetry.pool
package = ProjectPackage(
self.poetry.package.name, self.poetry.package.version
)
requirements = self._format_requirements(packages)
for name, constraint in requirements.items():
# Silencing output
is_quiet = self.io.output.is_quiet()
if not is_quiet:
self.io.output.set_quiet(True)
requirements = self._determine_requirements(packages)
if not is_quiet:
self.io.output.set_quiet(False)
for constraint in requirements:
name = constraint.pop("name")
dep = package.add_dependency(name, constraint)
extras = []
for extra in self.option("extras"):
......@@ -90,7 +103,7 @@ class DebugResolveCommand(Command):
return 0
env = EnvManager(self.poetry.config).get(self.poetry.file.parent)
env = EnvManager(self.poetry).get()
current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info)
)
......@@ -103,11 +116,7 @@ class DebugResolveCommand(Command):
current_python_version
) or not env.is_valid_for_marker(pkg.marker):
continue
row = [
"<info>{}</info>".format(pkg.name),
"<b>{}</b>".format(pkg.version),
"",
]
row = ["<c1>{}</c1>".format(pkg.name), "<b>{}</b>".format(pkg.version), ""]
if not pkg.marker.is_any():
row[2] = str(pkg.marker)
......@@ -116,55 +125,3 @@ class DebugResolveCommand(Command):
table.set_rows(rows)
table.render(self.io)
def _determine_requirements(self, requires): # type: (List[str]) -> List[str]
from poetry.semver import parse_constraint
if not requires:
return []
requires = self._parse_name_version_pairs(requires)
for requirement in requires:
if "version" in requirement:
parse_constraint(requirement["version"])
return requires
def _parse_name_version_pairs(self, pairs): # type: (list) -> list
result = []
for i in range(len(pairs)):
if pairs[i].startswith("git+https://"):
url = pairs[i].lstrip("git+")
rev = None
if "@" in url:
url, rev = url.split("@")
pair = {"name": url.split("/")[-1].rstrip(".git"), "git": url}
if rev:
pair["rev"] = rev
result.append(pair)
continue
pair = re.sub("^([^=: ]+)[=: ](.*)$", "\\1 \\2", pairs[i].strip())
pair = pair.strip()
if " " in pair:
name, version = pair.split(" ", 2)
result.append({"name": name, "version": version})
else:
result.append({"name": pair, "version": "*"})
return result
def _format_requirements(self, requirements): # type: (List[str]) -> dict
requires = {}
requirements = self._determine_requirements(requirements)
for requirement in requirements:
name = requirement.pop("name")
requires[name] = requirement
return requires
......@@ -13,8 +13,7 @@ class EnvInfoCommand(Command):
def handle(self):
from poetry.utils.env import EnvManager
poetry = self.poetry
env = EnvManager(poetry.config).get(cwd=poetry.file.parent)
env = EnvManager(self.poetry).get()
if self.option("path"):
if not env.is_venv():
......
......@@ -13,11 +13,10 @@ class EnvListCommand(Command):
def handle(self):
from poetry.utils.env import EnvManager
poetry = self.poetry
manager = EnvManager(poetry.config)
current_env = manager.get(self.poetry.file.parent)
manager = EnvManager(self.poetry)
current_env = manager.get()
for venv in manager.list(self.poetry.file.parent):
for venv in manager.list():
name = venv.path.name
if self.option("full-path"):
name = str(venv.path)
......
......@@ -15,8 +15,7 @@ class EnvRemoveCommand(Command):
def handle(self):
from poetry.utils.env import EnvManager
poetry = self.poetry
manager = EnvManager(poetry.config)
venv = manager.remove(self.argument("python"), poetry.file.parent)
manager = EnvManager(self.poetry)
venv = manager.remove(self.argument("python"))
self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path))
......@@ -13,14 +13,13 @@ class EnvUseCommand(Command):
def handle(self):
from poetry.utils.env import EnvManager
poetry = self.poetry
manager = EnvManager(poetry.config)
manager = EnvManager(self.poetry)
if self.argument("python") == "system":
manager.deactivate(poetry.file.parent, self._io)
manager.deactivate(self._io)
return
env = manager.activate(self.argument("python"), poetry.file.parent, self._io)
env = manager.activate(self.argument("python"), self._io)
self.line("Using virtualenv: <comment>{}</>".format(env.path))
......@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os
import re
import sys
from typing import Dict
from typing import List
......@@ -15,7 +16,6 @@ from tomlkit import inline_table
from poetry.utils._compat import Path
from poetry.utils._compat import OrderedDict
from poetry.utils._compat import urlparse
from poetry.utils.helpers import temporary_directory
from .command import Command
from .env_command import EnvCommand
......@@ -52,7 +52,7 @@ class InitCommand(Command):
]
help = """\
The <info>init</info> command creates a basic <comment>pyproject.toml</> file in the current directory.
The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the current directory.
"""
def __init__(self):
......@@ -63,7 +63,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
def handle(self):
from poetry.layouts import layout
from poetry.utils._compat import Path
from poetry.utils.env import EnvManager
from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig
if (Path.cwd() / "pyproject.toml").exists():
......@@ -126,7 +126,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
question.set_validator(self._validate_license)
license = self.ask(question)
current_env = EnvManager().get(Path.cwd())
current_env = SystemEnv(Path(sys.executable))
default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2])
)
......@@ -209,7 +209,9 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
if not requires:
requires = []
package = self.ask("Add a package:")
package = self.ask(
"Search for package to add (or leave blank to continue):"
)
while package is not None:
constraint = self._parse_requirements([package])[0]
if (
......@@ -247,7 +249,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
choices.append(found_package.pretty_name)
self.line(
"Found <info>{}</info> packages matching <info>{}</info>".format(
"Found <info>{}</info> packages matching <c1>{}</c1>".format(
len(matches), package
)
)
......@@ -275,7 +277,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
)
self.line(
"Using version <info>{}</info> for <info>{}</info>".format(
"Using version <b>{}</b> for <c1>{}</c1>".format(
package_constraint, package
)
)
......@@ -304,7 +306,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
requirement["name"] = name
self.line(
"Using version <info>{}</> for <info>{}</>".format(version, name)
"Using version <b>{}</b> for <c1>{}</c1>".format(version, name)
)
else:
# check that the specified version/constraint exists
......
......@@ -77,7 +77,7 @@ exist it will look for <comment>pyproject.toml</> and do the same.
return 0
self.line(
" - Installing <info>{}</info> (<comment>{}</comment>)".format(
" - Installing <c1>{}</c1> (<b>{}</b>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version
)
)
......
import sys
from cleo import argument
from cleo import option
......@@ -17,8 +19,9 @@ class NewCommand(Command):
def handle(self):
from poetry.layouts import layout
from poetry.semver import parse_constraint
from poetry.utils._compat import Path
from poetry.utils.env import EnvManager
from poetry.utils.env import SystemEnv
from poetry.vcs.git import GitConfig
if self.option("src"):
......@@ -49,16 +52,25 @@ class NewCommand(Command):
if author_email:
author += " <{}>".format(author_email)
current_env = EnvManager().get(Path.cwd())
current_env = SystemEnv(Path(sys.executable))
default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2])
)
dev_dependencies = {}
python_constraint = parse_constraint(default_python)
if parse_constraint("<3.5").allows_any(python_constraint):
dev_dependencies["pytest"] = "^4.6"
if parse_constraint(">=3.5").allows_all(python_constraint):
dev_dependencies["pytest"] = "^5.2"
layout_ = layout_(
name,
"0.1.0",
author=author,
readme_format=readme_format,
python=default_python,
dev_dependencies=dev_dependencies,
)
layout_.create(path)
......
from cleo import option
from poetry.utils._compat import Path
from .command import Command
......@@ -14,6 +16,15 @@ class PublishCommand(Command):
),
option("username", "u", "The username to access the repository.", flag=False),
option("password", "p", "The password to access the repository.", flag=False),
option(
"cert", None, "Certificate authority to access the repository.", flag=False
),
option(
"client-cert",
None,
"Client certificate to access the repository.",
flag=False,
),
option("build", None, "Build the package before publishing."),
]
......@@ -57,6 +68,15 @@ the config command.
self.line("")
cert = Path(self.option("cert")) if self.option("cert") else None
client_cert = (
Path(self.option("client-cert")) if self.option("client-cert") else None
)
publisher.publish(
self.option("repository"), self.option("username"), self.option("password")
self.option("repository"),
self.option("username"),
self.option("password"),
cert,
client_cert,
)
......@@ -40,15 +40,13 @@ class RunCommand(EnvCommand):
cmd = ["python", "-c"]
cmd += [
'"import sys; '
"import sys; "
"from importlib import import_module; "
"sys.argv = {!r}; {}"
"import_module('{}').{}()\"".format(
args, src_in_sys_path, module, callable_
)
"import_module('{}').{}()".format(args, src_in_sys_path, module, callable_)
]
return self.env.run(*cmd, shell=True, call=True)
return self.env.execute(*cmd)
@property
def _module(self):
......
......@@ -29,7 +29,9 @@ class SelfUpdateCommand(Command):
arguments = [argument("version", "The version to update to.", optional=True)]
options = [option("preview", None, "Install prereleases.")]
BASE_URL = "https://github.com/sdispater/poetry/releases/download"
REPOSITORY_URL = "https://github.com/python-poetry/poetry"
BASE_URL = REPOSITORY_URL + "/releases/download"
FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download"
@property
def home(self):
......@@ -150,8 +152,17 @@ class SelfUpdateCommand(Command):
checksum = "poetry-{}-{}.sha256sum".format(version, platform)
base_url = self.BASE_URL
try:
r = urlopen(self.BASE_URL + "/{}/{}".format(version, checksum))
urlopen(self.REPOSITORY_URL)
except HTTPError as e:
if e.code == 404:
base_url = self.FALLBACK_BASE_URL
else:
raise
try:
r = urlopen(base_url + "/{}/{}".format(version, checksum))
except HTTPError as e:
if e.code == 404:
raise RuntimeError("Could not find {} file".format(checksum))
......@@ -163,7 +174,7 @@ class SelfUpdateCommand(Command):
# We get the payload from the remote host
name = "poetry-{}-{}.tar.gz".format(version, platform)
try:
r = urlopen(self.BASE_URL + "/{}/{}".format(version, name))
r = urlopen(base_url + "/{}/{}".format(version, name))
except HTTPError as e:
if e.code == 404:
raise RuntimeError("Could not find {} file".format(name))
......
......@@ -80,20 +80,20 @@ lists all packages available."""
return 0
rows = [
["<info>name</>", " : <info>{}</>".format(pkg.pretty_name)],
["<info>version</>", " : <comment>{}</>".format(pkg.pretty_version)],
["<info>name</>", " : <c1>{}</>".format(pkg.pretty_name)],
["<info>version</>", " : <b>{}</b>".format(pkg.pretty_version)],
["<info>description</>", " : {}".format(pkg.description)],
]
table.add_rows(rows)
table.render()
table.render(self.io)
if pkg.requires:
self.line("")
self.line("<info>dependencies</info>")
for dependency in pkg.requires:
self.line(
" - {} <comment>{}</>".format(
" - <c1>{}</c1> <b>{}</b>".format(
dependency.pretty_name, dependency.pretty_constraint
)
)
......@@ -211,7 +211,7 @@ lists all packages available."""
self.line(line)
def display_package_tree(self, io, package, installed_repo):
io.write("<info>{}</info>".format(package.pretty_name))
io.write("<c1>{}</c1>".format(package.pretty_name))
description = ""
if package.description:
description = " " + package.description
......
......@@ -47,7 +47,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
)
self.line(
"Bumping version from <comment>{}</> to <info>{}</>".format(
"Bumping version from <b>{}</> to <fg=green>{}</>".format(
self.poetry.package.pretty_version, version
)
)
......
import logging
from cleo.config import ApplicationConfig as BaseApplicationConfig
from clikit.api.event import ConsoleEvents
from clikit.api.event import PRE_HANDLE
from clikit.api.event import PreHandleEvent
from clikit.api.formatter import Style
from clikit.api.io import Input
from clikit.api.io import InputStream
from clikit.api.io import Output
from clikit.api.io import OutputStream
from clikit.api.io.flags import DEBUG
from clikit.api.io.flags import VERBOSE
from clikit.api.io.flags import VERY_VERBOSE
from clikit.formatter import AnsiFormatter
from clikit.formatter import PlainFormatter
from clikit.io.input_stream import StandardInputStream
from clikit.io.output_stream import ErrorOutputStream
from clikit.io.output_stream import StandardOutputStream
from poetry.console.commands.command import Command
from poetry.console.commands.env_command import EnvCommand
......@@ -16,22 +28,17 @@ class ApplicationConfig(BaseApplicationConfig):
super(ApplicationConfig, self).configure()
self.add_style(Style("c1").fg("cyan"))
self.add_style(Style("info").fg("cyan"))
self.add_style(Style("info").fg("blue"))
self.add_style(Style("comment").fg("green"))
self.add_style(Style("error").fg("red").bold())
self.add_style(Style("warning").fg("yellow"))
self.add_style(Style("debug").fg("black").bold())
self.add_event_listener(
ConsoleEvents.PRE_HANDLE.value, self.register_command_loggers
)
self.add_event_listener(ConsoleEvents.PRE_HANDLE.value, self.set_env)
self.add_event_listener(PRE_HANDLE, self.register_command_loggers)
self.add_event_listener(PRE_HANDLE, self.set_env)
def register_command_loggers(
self,
event, # type: PreHandleEvent
event_name, # type: str
_,
self, event, event_name, _ # type: PreHandleEvent # type: str
): # type: (...) -> None
command = event.command.config.handler
if not isinstance(command, Command):
......@@ -70,27 +77,100 @@ class ApplicationConfig(BaseApplicationConfig):
io = event.io
poetry = command.poetry
env_manager = EnvManager(poetry.config)
# Checking compatibility of the current environment with
# the python dependency specified in pyproject.toml
current_env = env_manager.get(poetry.file.parent)
supported_python = poetry.package.python_constraint
current_python = parse_constraint(
".".join(str(v) for v in current_env.version_info[:3])
)
if not supported_python.allows(current_python):
raise RuntimeError(
"The current Python version ({}) is not supported by the project ({})\n"
"Please activate a compatible Python version.".format(
current_python, poetry.package.python_versions
)
)
env = env_manager.create_venv(poetry.file.parent, io, poetry.package.name)
env_manager = EnvManager(poetry)
env = env_manager.create_venv(io)
if env.is_venv() and io.is_verbose():
io.write_line("Using virtualenv: <comment>{}</>".format(env.path))
command.set_env(env)
def resolve_help_command(
self, event, event_name, dispatcher
): # type: (PreResolveEvent, str, EventDispatcher) -> None
args = event.raw_args
application = event.application
if args.has_option_token("-h") or args.has_option_token("--help"):
from clikit.api.resolver import ResolvedCommand
resolved_command = self.command_resolver.resolve(args, application)
# If the current command is the run one, skip option
# check and interpret them as part of the executed command
if resolved_command.command.name == "run":
event.set_resolved_command(resolved_command)
return event.stop_propagation()
command = application.get_command("help")
# Enable lenient parsing
parsed_args = command.parse(args, True)
event.set_resolved_command(ResolvedCommand(command, parsed_args))
event.stop_propagation()
def create_io(
self,
application,
args,
input_stream=None,
output_stream=None,
error_stream=None,
): # type: (Application, RawArgs, InputStream, OutputStream, OutputStream) -> IO
if input_stream is None:
input_stream = StandardInputStream()
if output_stream is None:
output_stream = StandardOutputStream()
if error_stream is None:
error_stream = ErrorOutputStream()
style_set = application.config.style_set
if output_stream.supports_ansi():
output_formatter = AnsiFormatter(style_set)
else:
output_formatter = PlainFormatter(style_set)
if error_stream.supports_ansi():
error_formatter = AnsiFormatter(style_set)
else:
error_formatter = PlainFormatter(style_set)
io = self.io_class(
Input(input_stream),
Output(output_stream, output_formatter),
Output(error_stream, error_formatter),
)
resolved_command = application.resolve_command(args)
# If the current command is the run one, skip option
# check and interpret them as part of the executed command
if resolved_command.command.name == "run":
return io
if args.has_option_token("--no-ansi"):
formatter = PlainFormatter(style_set)
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
elif args.has_option_token("--ansi"):
formatter = AnsiFormatter(style_set, True)
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
if args.has_option_token("-vvv") or self.is_debug():
io.set_verbosity(DEBUG)
elif args.has_option_token("-vv"):
io.set_verbosity(VERY_VERBOSE)
elif args.has_option_token("-v"):
io.set_verbosity(VERBOSE)
if args.has_option_token("--quiet") or args.has_option_token("-q"):
io.set_quiet(True)
if args.has_option_token("--no-interaction") or args.has_option_token("-n"):
io.set_interactive(False)
return io
......@@ -233,7 +233,7 @@ class Factory:
): # type: (Dict[str, str], Config) -> LegacyRepository
from .repositories.auth import Auth
from .repositories.legacy_repository import LegacyRepository
from .utils.helpers import get_http_basic_auth
from .utils.helpers import get_client_cert, get_cert, get_http_basic_auth
if "url" in source:
# PyPI-like repository
......@@ -245,12 +245,18 @@ class Factory:
name = source["name"]
url = source["url"]
credentials = get_http_basic_auth(auth_config, name)
if not credentials:
return LegacyRepository(name, url)
auth = Auth(url, credentials[0], credentials[1])
return LegacyRepository(name, url, auth=auth)
if credentials:
auth = Auth(url, credentials[0], credentials[1])
else:
auth = None
return LegacyRepository(
name,
url,
auth=auth,
cert=get_cert(auth_config, name),
client_cert=get_client_cert(auth_config, name),
)
@classmethod
def validate(
......
......@@ -306,7 +306,7 @@ class Installer:
if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line(
" - Skipping <info>{}</> (<comment>{}</>) {}".format(
" - Skipping <c1>{}</c1> (<b>{}</b>) {}".format(
operation.package.pretty_name,
operation.package.full_pretty_version,
operation.skip_reason,
......@@ -317,7 +317,7 @@ class Installer:
if self._execute_operations or self.is_dry_run():
self._io.write_line(
" - Installing <info>{}</> (<comment>{}</>)".format(
" - Installing <c1>{}</c1> (<b>{}</b>)".format(
operation.package.pretty_name, operation.package.full_pretty_version
)
)
......@@ -334,7 +334,7 @@ class Installer:
if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line(
" - Skipping <info>{}</> (<comment>{}</>) {}".format(
" - Skipping <c1>{}</c1> (<b>{}</b>) {}".format(
target.pretty_name,
target.full_pretty_version,
operation.skip_reason,
......@@ -345,7 +345,7 @@ class Installer:
if self._execute_operations or self.is_dry_run():
self._io.write_line(
" - Updating <info>{}</> (<comment>{}</> -> <comment>{}</>)".format(
" - Updating <c1>{}</c1> (<b>{}</b> -> <b>{}</b>)".format(
target.pretty_name,
source.full_pretty_version,
target.full_pretty_version,
......@@ -361,7 +361,7 @@ class Installer:
if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line(
" - Not removing <info>{}</> (<comment>{}</>) {}".format(
" - Not removing <c1>{}</c1> (<b>{}</b>) {}".format(
operation.package.pretty_name,
operation.package.full_pretty_version,
operation.skip_reason,
......@@ -372,7 +372,7 @@ class Installer:
if self._execute_operations or self.is_dry_run():
self._io.write_line(
" - Removing <info>{}</> (<comment>{}</>)".format(
" - Removing <c1>{}</c1> (<b>{}</b>)".format(
operation.package.pretty_name, operation.package.full_pretty_version
)
)
......
......@@ -52,6 +52,12 @@ class PipInstaller(BaseInstaller):
)
args += ["--trusted-host", parsed.hostname]
if repository.cert:
args += ["--cert", str(repository.cert)]
if repository.client_cert:
args += ["--client-cert", str(repository.client_cert)]
index_url = repository.authenticated_url
args += ["--index-url", index_url]
......@@ -65,7 +71,7 @@ class PipInstaller(BaseInstaller):
if update:
args.append("-U")
if package.hashes and not package.source_type:
if package.files and not package.source_type:
# Format as a requirements.txt
# We need to create a requirements.txt file
# for each package in order to check hashes.
......@@ -112,8 +118,9 @@ class PipInstaller(BaseInstaller):
def requirement(self, package, formatted=False):
if formatted and not package.source_type:
req = "{}=={}".format(package.name, package.version)
for h in package.hashes:
for f in package.files:
hash_type = "sha256"
h = f["hash"]
if ":" in h:
hash_type, h = h.split(":")
......@@ -163,7 +170,7 @@ class PipInstaller(BaseInstaller):
def install_directory(self, package):
from poetry.masonry.builder import SdistBuilder
from poetry.poetry import Poetry
from poetry.factory import Factory
from poetry.utils._compat import decode
from poetry.utils.env import NullEnv
from poetry.utils.toml_file import TomlFile
......@@ -196,7 +203,9 @@ class PipInstaller(BaseInstaller):
# file since pip, as of this comment, does not support
# build-system for editable packages
# We also need it for non-PEP-517 packages
builder = SdistBuilder(Poetry.create(pyproject.parent), NullEnv(), NullIO())
builder = SdistBuilder(
Factory().create_poetry(pyproject.parent), NullEnv(), NullIO()
)
with open(setup, "w", encoding="utf-8") as f:
f.write(decode(builder.build_setup()))
......
......@@ -31,7 +31,9 @@ def validate_object(obj, schema_name): # type: (dict, str) -> List[str]
for error in validation_errors:
message = error.message
if error.path:
message = "[{}] {}".format(".".join(error.path), message)
message = "[{}] {}".format(
".".join(str(x) for x in error.absolute_path), message
)
errors.append(message)
......
......@@ -63,10 +63,7 @@ class Layout(object):
self._license = license
self._python = python
self._dependencies = dependencies or {}
if dev_dependencies is None:
dev_dependencies = {"pytest": "^3.5"}
self._dev_dependencies = dev_dependencies
self._dev_dependencies = dev_dependencies or {}
if not author:
author = "Your Name <you@example.com>"
......@@ -131,8 +128,6 @@ class Layout(object):
readme_file.touch()
def _create_tests(self, path):
self._dev_dependencies["pytest"] = "^3.0"
tests = path / "tests"
tests_init = tests / "__init__.py"
tests_default = tests / "test_{}.py".format(self._package_name)
......
......@@ -22,7 +22,7 @@ from .builder import Builder
SETUP = """\
# -*- coding: utf-8 -*-
from distutils.core import setup
from setuptools import setup
{before}
setup_kwargs = {{
......
......@@ -19,7 +19,7 @@ from poetry.__version__ import __version__
from poetry.semver import parse_constraint
from poetry.utils._compat import decode
from ..utils.helpers import normalize_file_permissions
from ..utils.helpers import normalize_file_permissions, escape_name, escape_version
from ..utils.package_include import PackageInclude
from ..utils.tags import get_abbr_impl
from ..utils.tags import get_abi_tag
......@@ -206,8 +206,8 @@ class WheelBuilder(Builder):
@property
def wheel_filename(self): # type: () -> str
return "{}-{}-{}.whl".format(
re.sub(r"[^\w\d.]+", "_", self._package.pretty_name, flags=re.UNICODE),
re.sub(r"[^\w\d.\+]+", "_", self._meta.version, flags=re.UNICODE),
escape_name(self._package.pretty_name),
escape_version(self._meta.version),
self.tag,
)
......@@ -217,8 +217,8 @@ class WheelBuilder(Builder):
)
def dist_info_name(self, distribution, version): # type: (...) -> str
escaped_name = re.sub(r"[^\w\d.]+", "_", distribution, flags=re.UNICODE)
escaped_version = re.sub(r"[^\w\d.+]+", "_", version, flags=re.UNICODE)
escaped_name = escape_name(distribution)
escaped_version = escape_version(version)
return "{}-{}.dist-info".format(escaped_name, escaped_version)
......
import logging
from poetry.utils.helpers import get_http_basic_auth
from poetry.utils.helpers import get_client_cert, get_cert, get_http_basic_auth
from .uploader import Uploader
......@@ -23,11 +23,11 @@ class Publisher:
def files(self):
return self._uploader.files
def publish(self, repository_name, username, password):
def publish(self, repository_name, username, password, cert=None, client_cert=None):
if repository_name:
self._io.write_line(
"Publishing <info>{}</info> (<comment>{}</comment>) "
"to <fg=cyan>{}</>".format(
"Publishing <c1>{}</c1> (<b>{}</b>) "
"to <info>{}</info>".format(
self._package.pretty_name,
self._package.pretty_version,
repository_name,
......@@ -35,8 +35,8 @@ class Publisher:
)
else:
self._io.write_line(
"Publishing <info>{}</info> (<comment>{}</comment>) "
"to <fg=cyan>PyPI</>".format(
"Publishing <c1>{}</c1> (<b>{}</b>) "
"to <info>PyPI</info>".format(
self._package.pretty_name, self._package.pretty_version
)
)
......@@ -74,15 +74,21 @@ class Publisher:
username = auth[0]
password = auth[1]
# Requesting missing credentials
if not username:
username = self._io.ask("Username:")
resolved_client_cert = client_cert or get_client_cert(
self._poetry.config, repository_name
)
# Requesting missing credentials but only if there is not a client cert defined.
if not resolved_client_cert:
if username is None:
username = self._io.ask("Username:")
if password is None:
password = self._io.ask_hidden("Password:")
# TODO: handle certificates
if password is None:
password = self._io.ask_hidden("Password:")
self._uploader.auth(username, password)
return self._uploader.upload(url)
return self._uploader.upload(
url,
cert=cert or get_cert(self._poetry.config, repository_name),
client_cert=resolved_client_cert,
)
......@@ -3,7 +3,7 @@ import io
import math
import re
from typing import List
from typing import List, Optional
import requests
......@@ -14,10 +14,12 @@ from requests_toolbelt import user_agent
from requests_toolbelt.multipart import MultipartEncoder, MultipartEncoderMonitor
from poetry.__version__ import __version__
from poetry.utils._compat import Path
from poetry.utils.helpers import normalize_version
from poetry.utils.patterns import wheel_file_re
from ..metadata import Metadata
from ..utils.helpers import escape_name, escape_version
_has_blake2 = hasattr(hashlib, "blake2b")
......@@ -63,10 +65,7 @@ class Uploader:
wheels = list(
dist.glob(
"{}-{}-*.whl".format(
re.sub(
r"[^\w\d.]+", "_", self._package.pretty_name, flags=re.UNICODE
),
re.sub(r"[^\w\d.]+", "_", version, flags=re.UNICODE),
escape_name(self._package.pretty_name), escape_version(version)
)
)
)
......@@ -94,9 +93,17 @@ class Uploader:
def is_authenticated(self):
return self._username is not None and self._password is not None
def upload(self, url):
def upload(
self, url, cert=None, client_cert=None
): # type: (str, Optional[Path], Optional[Path]) -> None
session = self.make_session()
if cert:
session.verify = str(cert)
if client_cert:
session.cert = str(client_cert)
try:
self._upload(session, url)
finally:
......@@ -222,7 +229,7 @@ class Uploader:
encoder = MultipartEncoder(data_to_send)
bar = self._io.progress_bar(encoder.len)
bar.set_format(
" - Uploading <info>{0}</> <comment>%percent%%</>".format(file.name)
" - Uploading <c1>{0}</c1> <b>%percent%%</b>".format(file.name)
)
monitor = MultipartEncoderMonitor(
encoder, lambda monitor: bar.set_progress(monitor.bytes_read)
......@@ -238,13 +245,18 @@ class Uploader:
)
if resp.ok:
bar.set_format(
" - Uploading <c1>{0}</c1> <fg=green>%percent%%</>".format(
file.name
)
)
bar.finish()
self._io.write_line("")
else:
if self._io.output.supports_ansi():
self._io.overwrite(
" - Uploading <info>{0}</> <error>{1}%</>".format(
" - Uploading <c1>{0}</c1> <error>{1}%</>".format(
file.name, int(math.floor(bar._percent * 100))
)
)
......
import re
def normalize_file_permissions(st_mode):
"""
Normalizes the permission bits in the st_mode field from stat to 644/755
......@@ -12,3 +15,17 @@ def normalize_file_permissions(st_mode):
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)
......@@ -144,9 +144,10 @@ class Dependency(object):
requirement += "[{}]".format(",".join(self.extras))
if isinstance(self.constraint, VersionUnion):
requirement += " ({})".format(
",".join([str(c).replace(" ", "") for c in self.constraint.ranges])
)
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():
......
......@@ -6,6 +6,9 @@ import poetry.repositories
from hashlib import sha256
from tomlkit import document
from tomlkit import inline_table
from tomlkit import item
from tomlkit import table
from typing import List
from poetry.utils._compat import Path
......@@ -84,7 +87,15 @@ class Locker(object):
package.description = info.get("description", "")
package.category = info["category"]
package.optional = info["optional"]
package.hashes = lock_data["metadata"]["hashes"][info["name"]]
if "hashes" in lock_data["metadata"]:
# Old lock so we create dummy files from the hashes
package.files = [
{"name": h, "hash": h}
for h in lock_data["metadata"]["hashes"][info["name"]]
]
else:
package.files = lock_data["metadata"]["files"][info["name"]]
package.python_versions = info["python-versions"]
extras = info.get("extras", {})
if extras:
......@@ -135,15 +146,24 @@ class Locker(object):
return packages
def set_lock_data(self, root, packages): # type: (...) -> bool
hashes = {}
files = table()
packages = self._lock_packages(packages)
# Retrieving hashes
for package in packages:
if package["name"] not in hashes:
hashes[package["name"]] = []
if package["name"] not in files:
files[package["name"]] = []
for f in package["files"]:
file_metadata = inline_table()
for k, v in sorted(f.items()):
file_metadata[k] = v
files[package["name"]].append(file_metadata)
hashes[package["name"]] += package["hashes"]
del package["hashes"]
if files[package["name"]]:
files[package["name"]] = item(files[package["name"]]).multiline(True)
del package["files"]
lock = document()
lock["package"] = packages
......@@ -157,7 +177,7 @@ class Locker(object):
lock["metadata"] = {
"python-versions": root.python_versions,
"content-hash": self._content_hash,
"hashes": hashes,
"files": files,
}
if not self.is_locked() or lock != self.lock_data:
......@@ -230,10 +250,15 @@ class Locker(object):
if not dependency.python_constraint.is_any():
constraint["python"] = str(dependency.python_constraint)
if len(constraint) == 1:
dependencies[dependency.pretty_name].append(constraint["version"])
else:
dependencies[dependency.pretty_name].append(constraint)
dependencies[dependency.pretty_name].append(constraint)
# All the constraints should have the same type,
# but we want to simplify them if it's possible
for dependency, constraints in tuple(dependencies.items()):
if all(len(constraint) == 1 for constraint in constraints):
dependencies[dependency] = [
constraint["version"] for constraint in constraints
]
data = {
"name": package.pretty_name,
......@@ -242,7 +267,7 @@ class Locker(object):
"category": package.category,
"optional": package.optional,
"python-versions": package.python_versions,
"hashes": sorted(package.hashes),
"files": sorted(package.files, key=lambda x: x["file"]),
}
if not package.marker.is_any():
data["marker"] = str(package.marker)
......
......@@ -27,7 +27,7 @@ AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+)(?: <(?P<email>.+?
class Package(object):
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"}
AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8"}
def __init__(self, name, version, pretty_version=None):
"""
......@@ -66,7 +66,7 @@ class Package(object):
self.requires_extras = []
self.category = "main"
self.hashes = []
self.files = []
self.optional = False
self.classifiers = []
......
......@@ -38,6 +38,7 @@ from poetry.utils.helpers import safe_rmtree
from poetry.utils.helpers import temporary_directory
from poetry.utils.env import EnvManager
from poetry.utils.env import EnvCommandError
from poetry.utils.env import VirtualEnv
from poetry.utils.inspector import Inspector
from poetry.utils.setup_reader import SetupReader
from poetry.utils.toml_file import TomlFile
......@@ -172,9 +173,6 @@ class Provider:
name=dependency.name,
)
if dependency.tag or dependency.rev:
package.source_reference = dependency.reference
for extra in dependency.extras:
if extra in package.extras:
for dep in package.extras[extra]:
......@@ -229,7 +227,9 @@ class Provider:
)
package.source_url = dependency.path.as_posix()
package.hashes = [dependency.hash()]
package.files = [
{"file": dependency.path.name, "hash": "sha256:" + dependency.hash()}
]
for extra in dependency.extras:
if extra in package.extras:
......@@ -278,6 +278,9 @@ class Provider:
package.source_url = dependency.path.as_posix()
if dependency.base != None:
package.root_dir = dependency.base.as_posix()
for extra in dependency.extras:
if extra in package.extras:
for dep in package.extras[extra]:
......@@ -324,9 +327,10 @@ class Provider:
os.chdir(str(directory))
try:
cwd = directory
venv = EnvManager().get(cwd)
venv.run("python", "setup.py", "egg_info")
with temporary_directory() as tmp_dir:
EnvManager.build_venv(tmp_dir)
venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir))
venv.run("python", "setup.py", "egg_info")
except EnvCommandError:
result = SetupReader.read_from_directory(directory)
if not result["name"]:
......@@ -530,8 +534,8 @@ class Provider:
): # type: (DependencyPackage) -> DependencyPackage
if package.is_root():
package = package.clone()
if not package.is_root() and package.source_type not in {
requires = package.all_requires
elif not package.is_root() and package.source_type not in {
"directory",
"file",
"url",
......@@ -546,10 +550,13 @@ class Provider:
repository=package.dependency.source_name,
),
)
requires = package.requires
else:
requires = package.requires
dependencies = [
r
for r in package.requires
for r in requires
if self._package.python_constraint.allows_any(r.python_constraint)
]
......@@ -701,44 +708,42 @@ class Provider:
m2 = re.match(r"(.+?) \((.+?)\)", m.group(1))
if m2:
name = m2.group(1)
version = " (<comment>{}</comment>)".format(m2.group(2))
version = " (<b>{}</b>)".format(m2.group(2))
else:
name = m.group(1)
version = ""
message = (
"<fg=blue>fact</>: <info>{}</info>{} "
"depends on <info>{}</info> (<comment>{}</comment>)".format(
"<fg=blue>fact</>: <c1>{}</c1>{} "
"depends on <c1>{}</c1> (<b>{}</b>)".format(
name, version, m.group(2), m.group(3)
)
)
elif " is " in message:
message = re.sub(
"fact: (.+) is (.+)",
"<fg=blue>fact</>: <info>\\1</info> is <comment>\\2</comment>",
"<fg=blue>fact</>: <c1>\\1</c1> is <b>\\2</b>",
message,
)
else:
message = re.sub(
r"(?<=: )(.+?) \((.+?)\)",
"<info>\\1</info> (<comment>\\2</comment>)",
message,
r"(?<=: )(.+?) \((.+?)\)", "<c1>\\1</c1> (<b>\\2</b>)", message
)
message = "<fg=blue>fact</>: {}".format(message.split("fact: ")[1])
elif message.startswith("selecting "):
message = re.sub(
r"selecting (.+?) \((.+?)\)",
"<fg=blue>selecting</> <info>\\1</info> (<comment>\\2</comment>)",
"<fg=blue>selecting</> <c1>\\1</c1> (<b>\\2</b>)",
message,
)
elif message.startswith("derived:"):
m = re.match(r"derived: (.+?) \((.+?)\)$", message)
if m:
message = "<fg=blue>derived</>: <info>{}</info> (<comment>{}</comment>)".format(
message = "<fg=blue>derived</>: <c1>{}</c1> (<b>{}</b>)".format(
m.group(1), m.group(2)
)
else:
message = "<fg=blue>derived</>: <info>{}</info>".format(
message = "<fg=blue>derived</>: <c1>{}</c1>".format(
message.split("derived: ")[1]
)
elif message.startswith("conflict:"):
......@@ -747,14 +752,14 @@ class Provider:
m2 = re.match(r"(.+?) \((.+?)\)", m.group(1))
if m2:
name = m2.group(1)
version = " (<comment>{}</comment>)".format(m2.group(2))
version = " (<b>{}</b>)".format(m2.group(2))
else:
name = m.group(1)
version = ""
message = (
"<fg=red;options=bold>conflict</>: <info>{}</info>{} "
"depends on <info>{}</info> (<comment>{}</comment>)".format(
"<fg=red;options=bold>conflict</>: <c1>{}</c1>{} "
"depends on <c1>{}</c1> (<b>{}</b>)".format(
name, version, m.group(2), m.group(3)
)
)
......
......@@ -21,6 +21,11 @@ from typing import Generator
from typing import Optional
from typing import Union
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
import requests
from cachecontrol import CacheControl
......@@ -155,8 +160,8 @@ class Page:
class LegacyRepository(PyPiRepository):
def __init__(
self, name, url, auth=None, disable_cache=False
): # type: (str, str, Optional[Auth], bool) -> None
self, name, url, auth=None, disable_cache=False, cert=None, client_cert=None
): # type: (str, str, Optional[Auth], bool, Optional[Path], Optional[Path]) -> None
if name == "pypi":
raise ValueError("The name [pypi] is reserved for repositories")
......@@ -164,6 +169,8 @@ class LegacyRepository(PyPiRepository):
self._name = name
self._url = url.rstrip("/")
self._auth = auth
self._client_cert = client_cert
self._cert = cert
self._inspector = Inspector()
self._cache_dir = Path(CACHE_DIR) / "cache" / "repositories" / name
self._cache = CacheManager(
......@@ -186,9 +193,23 @@ class LegacyRepository(PyPiRepository):
if not url_parts.username and self._auth:
self._session.auth = self._auth
if self._cert:
self._session.verify = str(self._cert)
if self._client_cert:
self._session.cert = str(self._client_cert)
self._disable_cache = disable_cache
@property
def cert(self): # type: () -> Optional[Path]
return self._cert
@property
def client_cert(self): # type: () -> Optional[Path]
return self._client_cert
@property
def authenticated_url(self): # type: () -> str
if not self._auth:
return self.url
......@@ -197,8 +218,8 @@ class LegacyRepository(PyPiRepository):
return "{scheme}://{username}:{password}@{netloc}{path}".format(
scheme=parsed.scheme,
username=self._auth.auth.username,
password=self._auth.auth.password,
username=quote(self._auth.auth.username),
password=quote(self._auth.auth.password),
netloc=parsed.netloc,
path=parsed.path,
)
......@@ -328,7 +349,7 @@ class LegacyRepository(PyPiRepository):
package.description = release_info.get("summary", "")
# Adding hashes information
package.hashes = release_info["digests"]
package.files = release_info["files"]
# Activate extra dependencies
for extra in extras:
......@@ -353,7 +374,7 @@ class LegacyRepository(PyPiRepository):
"summary": "",
"requires_dist": [],
"requires_python": None,
"digests": [],
"files": [],
"_cache_version": str(self.CACHE_VERSION),
}
......@@ -365,7 +386,7 @@ class LegacyRepository(PyPiRepository):
)
)
urls = defaultdict(list)
hashes = []
files = []
for link in links:
if link.is_wheel:
urls["bdist_wheel"].append(link.url)
......@@ -374,13 +395,12 @@ class LegacyRepository(PyPiRepository):
):
urls["sdist"].append(link.url)
hash = link.hash
if link.hash_name == "sha256":
hashes.append(hash)
elif hash:
hashes.append(link.hash_name + ":" + hash)
h = link.hash
if h:
h = link.hash_name + ":" + link.hash
files.append({"file": link.filename, "hash": h})
data["digests"] = hashes
data["files"] = files
info = self._get_info_from_urls(urls)
......
......@@ -50,7 +50,7 @@ logger = logging.getLogger(__name__)
class PyPiRepository(Repository):
CACHE_VERSION = parse_constraint("0.12.0")
CACHE_VERSION = parse_constraint("1.0.0b2")
def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
self._url = url
......@@ -210,7 +210,7 @@ class PyPiRepository(Repository):
package.platform = release_info["platform"]
# Adding hashes information
package.hashes = release_info["digests"]
package.files = release_info["files"]
# Activate extra dependencies
for extra in extras:
......@@ -311,7 +311,7 @@ class PyPiRepository(Repository):
"platform": info["platform"],
"requires_dist": info["requires_dist"],
"requires_python": info["requires_python"],
"digests": [],
"files": [],
"_cache_version": str(self.CACHE_VERSION),
}
......@@ -321,7 +321,12 @@ class PyPiRepository(Repository):
version_info = []
for file_info in version_info:
data["digests"].append(file_info["digests"]["sha256"])
data["files"].append(
{
"file": file_info["filename"],
"hash": "sha256:" + file_info["digests"]["sha256"],
}
)
if self._fallback and data["requires_dist"] is None:
self._log("No dependencies found, downloading archives", level="debug")
......
......@@ -64,7 +64,13 @@ class Repository(BaseRepository):
for package in self.packages:
if name == package.name:
if package.is_prerelease() and not allow_prereleases:
if (
package.is_prerelease()
and not allow_prereleases
and not package.source_type
):
# If prereleases are not allowed and the package is a prerelease
# and is a standard package then we skip it
continue
if constraint.allows(package.version):
......
......@@ -79,7 +79,7 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
low = version
high = version.stable.next_major
else:
low = Version(version.major, version.minor, 0)
low = Version(version.major, version.minor, version.patch)
high = version.stable.next_minor
return VersionRange(
......
......@@ -228,7 +228,7 @@ class VersionUnion(VersionConstraint):
raise ValueError("Unknown VersionConstraint type {}".format(constraint))
def _excludes_single_version(self): # type: () -> bool
def excludes_single_version(self): # type: () -> bool
from .version import Version
from .version_range import VersionRange
......@@ -243,7 +243,7 @@ class VersionUnion(VersionConstraint):
def __str__(self):
from .version_range import VersionRange
if self._excludes_single_version():
if self.excludes_single_version():
return "!={}".format(VersionRange().difference(self))
return " || ".join([str(r) for r in self._ranges])
......
......@@ -20,8 +20,9 @@ from typing import Tuple
from clikit.api.io import IO
from poetry.config.config import Config
from poetry.locations import CACHE_DIR
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 Path
......@@ -127,6 +128,26 @@ class EnvCommandError(EnvError):
super(EnvCommandError, self).__init__(message)
class NoCompatiblePythonVersionFound(EnvError):
def __init__(self, expected, given=None):
if given:
message = (
"The specified Python version ({}) "
"is not supported by the project ({}).\n"
"Please choose a compatible version "
"or loosen the python constraint specified "
"in the pyproject.toml file.".format(given, expected)
)
else:
message = (
"Poetry was unable to find a compatible version. "
"If you have one, you can explicitly use it "
'via the "env use" command.'
)
super(NoCompatiblePythonVersionFound, self).__init__(message)
class EnvManager(object):
"""
Environments manager
......@@ -136,19 +157,18 @@ class EnvManager(object):
ENVS_FILE = "envs.toml"
def __init__(self, config=None): # type: (Config) -> None
if config is None:
config = Config()
def __init__(self, poetry): # type: (Poetry) -> None
self._poetry = poetry
self._config = config
def activate(self, python, cwd, io): # type: (str, Optional[Path], IO) -> Env
venv_path = self._config.get("virtualenvs.path")
def activate(self, python, io): # type: (str, IO) -> Env
venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs"
else:
venv_path = Path(venv_path)
cwd = self._poetry.file.parent
envs_file = TomlFile(venv_path / self.ENVS_FILE)
try:
......@@ -182,7 +202,7 @@ class EnvManager(object):
create = False
envs = tomlkit.document()
base_env_name = self.generate_env_name(cwd.name, str(cwd))
base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if envs_file.exists():
envs = envs_file.read()
current_env = envs.get(base_env_name)
......@@ -211,23 +231,23 @@ class EnvManager(object):
if patch != current_patch:
create = True
self.create_venv(cwd, io, executable=python, force=create)
self.create_venv(io, executable=python, force=create)
# Activate
envs[base_env_name] = {"minor": minor, "patch": patch}
envs_file.write(envs)
return self.get(cwd, reload=True)
return self.get(reload=True)
def deactivate(self, cwd, io): # type: (Optional[Path], IO) -> None
venv_path = self._config.get("virtualenvs.path")
def deactivate(self, io): # type: (IO) -> None
venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs"
else:
venv_path = Path(venv_path)
name = cwd.name
name = self.generate_env_name(name, str(cwd))
name = self._poetry.package.name
name = self.generate_env_name(name, str(self._poetry.file.parent))
envs_file = TomlFile(venv_path / self.ENVS_FILE)
if envs_file.exists():
......@@ -243,21 +263,22 @@ class EnvManager(object):
envs_file.write(envs)
def get(self, cwd, reload=False): # type: (Path, bool) -> Env
def get(self, reload=False): # type: (bool) -> Env
if self._env is not None and not reload:
return self._env
python_minor = ".".join([str(v) for v in sys.version_info[:2]])
venv_path = self._config.get("virtualenvs.path")
venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs"
else:
venv_path = Path(venv_path)
cwd = self._poetry.file.parent
envs_file = TomlFile(venv_path / self.ENVS_FILE)
env = None
base_env_name = self.generate_env_name(cwd.name, str(cwd))
base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if envs_file.exists():
envs = envs_file.read()
env = envs.get(base_env_name)
......@@ -265,7 +286,13 @@ class EnvManager(object):
python_minor = env["minor"]
# Check if we are inside a virtualenv or not
in_venv = os.environ.get("VIRTUAL_ENV") is not None
# Conda sets CONDA_PREFIX in its envs, see
# https://github.com/conda/conda/issues/2764
env_prefix = os.environ.get("VIRTUAL_ENV", os.environ.get("CONDA_PREFIX"))
conda_env_name = os.environ.get("CONDA_DEFAULT_ENV")
# It's probably not a good idea to pollute Conda's global "base" env, since
# most users have it activated all the time.
in_venv = env_prefix is not None and conda_env_name != "base"
if not in_venv or env is not None:
# Checking if a local virtualenv exists
......@@ -274,12 +301,12 @@ class EnvManager(object):
return VirtualEnv(venv)
create_venv = self._config.get("virtualenvs.create", True)
create_venv = self._poetry.config.get("virtualenvs.create", True)
if not create_venv:
return SystemEnv(Path(sys.prefix))
venv_path = self._config.get("virtualenvs.path")
venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs"
else:
......@@ -294,8 +321,8 @@ class EnvManager(object):
return VirtualEnv(venv)
if os.environ.get("VIRTUAL_ENV") is not None:
prefix = Path(os.environ["VIRTUAL_ENV"])
if env_prefix is not None:
prefix = Path(env_prefix)
base_prefix = None
else:
prefix = Path(sys.prefix)
......@@ -303,13 +330,13 @@ class EnvManager(object):
return VirtualEnv(prefix, base_prefix)
def list(self, cwd, name=None): # type: (Path, Optional[str]) -> List[VirtualEnv]
def list(self, name=None): # type: (Optional[str]) -> List[VirtualEnv]
if name is None:
name = cwd.name
name = self._poetry.package.name
venv_name = self.generate_env_name(name, str(cwd))
venv_name = self.generate_env_name(name, str(self._poetry.file.parent))
venv_path = self._config.get("virtualenvs.path")
venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs"
else:
......@@ -320,18 +347,19 @@ class EnvManager(object):
for p in sorted(venv_path.glob("{}-py*".format(venv_name)))
]
def remove(self, python, cwd): # type: (str, Optional[Path]) -> Env
venv_path = self._config.get("virtualenvs.path")
def remove(self, python): # type: (str) -> Env
venv_path = self._poetry.config.get("virtualenvs.path")
if venv_path is None:
venv_path = Path(CACHE_DIR) / "virtualenvs"
else:
venv_path = Path(venv_path)
cwd = self._poetry.file.parent
envs_file = TomlFile(venv_path / self.ENVS_FILE)
base_env_name = self.generate_env_name(cwd.name, str(cwd))
base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if python.startswith(base_env_name):
venvs = self.list(cwd)
venvs = self.list()
for venv in venvs:
if venv.path.name == python:
# Exact virtualenv name
......@@ -413,20 +441,21 @@ class EnvManager(object):
return VirtualEnv(venv)
def create_venv(
self, cwd, io, name=None, executable=None, force=False
): # type: (Path, IO, Optional[str], Optional[str], bool) -> Env
self, io, name=None, executable=None, force=False
): # type: (IO, Optional[str], Optional[str], bool) -> Env
if self._env is not None and not force:
return self._env
env = self.get(cwd, reload=True)
cwd = self._poetry.file.parent
env = self.get(reload=True)
if env.is_venv() and not force:
# Already inside a virtualenv.
return env
create_venv = self._config.get("virtualenvs.create")
root_venv = self._config.get("virtualenvs.in-project")
create_venv = self._poetry.config.get("virtualenvs.create")
root_venv = self._poetry.config.get("virtualenvs.in-project")
venv_path = self._config.get("virtualenvs.path")
venv_path = self._poetry.config.get("virtualenvs.path")
if root_venv:
venv_path = cwd / ".venv"
elif venv_path is None:
......@@ -435,8 +464,9 @@ class EnvManager(object):
venv_path = Path(venv_path)
if not name:
name = cwd.name
name = self._poetry.package.name
python_patch = ".".join([str(v) for v in sys.version_info[:3]])
python_minor = ".".join([str(v) for v in sys.version_info[:2]])
if executable:
python_minor = decode(
......@@ -449,9 +479,82 @@ class EnvManager(object):
]
),
shell=True,
).strip()
)
supported_python = self._poetry.package.python_constraint
if not supported_python.allows(Version.parse(python_minor)):
# The currently activated or chosen Python version
# is not compatible with the Python constraint specified
# for the project.
# If an executable has been specified, we stop there
# and notify the user of the incompatibility.
# Otherwise, we try to find a compatible Python version.
if executable:
raise NoCompatiblePythonVersionFound(
self._poetry.package.python_versions, python_minor
)
io.write_line(
"<warning>The currently activated Python version {} "
"is not supported by the project ({}).\n"
"Trying to find and use a compatible version.</warning> ".format(
python_patch, self._poetry.package.python_versions
)
)
for python_to_try in reversed(
sorted(
self._poetry.package.AVAILABLE_PYTHONS,
key=lambda v: (v.startswith("3"), -len(v), v),
)
):
if len(python_to_try) == 1:
if not parse_constraint("^{}.0".format(python_to_try)).allows_any(
supported_python
):
continue
elif not supported_python.allows_all(
parse_constraint(python_to_try + ".*")
):
continue
python = "python" + python_to_try
if io.is_debug():
io.write_line("<debug>Trying {}</debug>".format(python))
try:
python_patch = decode(
subprocess.check_output(
" ".join(
[
python,
"-c",
"\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
]
),
stderr=subprocess.STDOUT,
shell=True,
).strip()
)
except CalledProcessError:
continue
if not python_patch:
continue
if supported_python.allows(Version.parse(python_patch)):
io.write_line("Using <c1>{}</c1> ({})".format(python, python_patch))
executable = python
python_minor = ".".join(python_patch.split(".")[:2])
break
if not executable:
raise NoCompatiblePythonVersionFound(
self._poetry.package.python_versions
)
if root_venv:
venv = venv_path
else:
......@@ -471,19 +574,19 @@ class EnvManager(object):
return SystemEnv(Path(sys.prefix))
io.write_line(
"Creating virtualenv <info>{}</> in {}".format(name, str(venv_path))
"Creating virtualenv <c1>{}</> in {}".format(name, str(venv_path))
)
self.build_venv(str(venv), executable=executable)
else:
if force:
io.write_line(
"Recreating virtualenv <info>{}</> in {}".format(name, str(venv))
"Recreating virtualenv <c1>{}</> in {}".format(name, str(venv))
)
self.remove_venv(str(venv))
self.build_venv(str(venv), executable=executable)
elif io.is_very_verbose():
io.write_line("Virtualenv <info>{}</> already exists.".format(name))
io.write_line("Virtualenv <c1>{}</> already exists.".format(name))
# venv detection:
# stdlib venv may symlink sys.executable, so we can't use realpath.
......@@ -503,7 +606,8 @@ class EnvManager(object):
return VirtualEnv(venv)
def build_venv(self, path, executable=None):
@classmethod
def build_venv(cls, path, executable=None):
if executable is not None:
# Create virtualenv by using an external executable
try:
......@@ -712,7 +816,16 @@ class Env(object):
def execute(self, bin, *args, **kwargs):
bin = self._bin(bin)
return subprocess.call([bin] + list(args), **kwargs)
if not self._is_windows:
args = [bin] + list(args)
if "env" in kwargs:
return os.execvpe(bin, args, kwargs["env"])
else:
return os.execvp(bin, args)
else:
exe = subprocess.Popen([bin] + list(args), **kwargs)
exe.communicate()
return exe.returncode
def is_venv(self): # type: () -> bool
raise NotImplementedError()
......
......@@ -107,9 +107,10 @@ class Exporter(object):
if package.source_type == "legacy" and package.source_url:
indexes.append(package.source_url)
if package.hashes and with_hashes:
if package.files and with_hashes:
hashes = []
for h in package.hashes:
for f in package.files:
h = f["hash"]
algorithm = "sha256"
if ":" in h:
algorithm, h = h.split(":")
......@@ -123,7 +124,7 @@ class Exporter(object):
line += " \\\n"
for i, h in enumerate(hashes):
line += " --hash={}{}".format(
h, " \\\n" if i < len(package.hashes) - 1 else ""
h, " \\\n" if i < len(hashes) - 1 else ""
)
line += "\n"
......
import collections
import os
import re
import shutil
import stat
import tempfile
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
from contextlib import contextmanager
from typing import List
from typing import Optional
......@@ -14,6 +18,7 @@ from keyring.errors import KeyringError
from poetry.config.config import Config
from poetry.version import Version
from poetry.utils._compat import Path
_canonicalize_regex = re.compile("[-_]+")
......@@ -133,6 +138,22 @@ def get_http_basic_auth(
return None
def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path]
cert = config.get("certificates.{}.cert".format(repository_name))
if cert:
return Path(cert)
else:
return None
def get_client_cert(config, repository_name): # type: (Config, str) -> Optional[Path]
client_cert = config.get("certificates.{}.client-cert".format(repository_name))
if client_cert:
return Path(client_cert)
else:
return None
def _on_rm_error(func, path, exc_info):
os.chmod(path, stat.S_IWRITE)
func(path)
......@@ -144,11 +165,7 @@ def safe_rmtree(path):
def merge_dicts(d1, d2):
for k, v in d2.items():
if (
k in d1
and isinstance(d1[k], dict)
and isinstance(d2[k], collections.Mapping)
):
if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping):
merge_dicts(d1[k], d2[k])
else:
d1[k] = d2[k]
......@@ -144,11 +144,11 @@ class Inspector:
pyproject = TomlFile(sdist_dir / "pyproject.toml")
if pyproject.exists():
from poetry.poetry import Poetry
from poetry.factory import Factory
pyproject_content = pyproject.read()
if "tool" in pyproject_content and "poetry" in pyproject_content["tool"]:
package = Poetry.create(sdist_dir).package
package = Factory().create_poetry(sdist_dir).package
return {
"name": package.name,
"version": package.version.text,
......
[tool.poetry]
name = "poetry"
version = "1.0.0b1"
version = "1.0.0b3"
description = "Python dependency management and packaging made easy."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
......@@ -23,18 +23,19 @@ classifiers = [
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
cleo = "^0.7.5"
cleo = "^0.7.6"
clikit = "^0.4.0"
requests = "^2.18"
cachy = "^0.3.0"
requests-toolbelt = "^0.8.0"
jsonschema = "^3.0a3"
jsonschema = "^3.1"
pyrsistent = "^0.14.2"
pyparsing = "^2.2"
cachecontrol = { version = "^0.12.4", extras = ["filecache"] }
pkginfo = "^1.4"
html5lib = "^1.0"
shellingham = "^1.1"
tomlkit = "^0.5.5"
tomlkit = "^0.5.8"
pexpect = "^4.7.0"
# The typing module is not in the stdlib in Python 2.7 and 3.4
......
......@@ -28,6 +28,7 @@ class MakeReleaseCommand(Command):
"3.5": "python3.5",
"3.6": "python3.6",
"3.7": "python3.7",
"3.8": "python3.8",
}
def handle(self):
......@@ -46,7 +47,7 @@ class MakeReleaseCommand(Command):
self.check_system(pythons)
from poetry import __version__
from poetry.poetry import Poetry
from poetry.factory import Factory
from poetry.puzzle import Solver
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository
......@@ -59,7 +60,7 @@ class MakeReleaseCommand(Command):
from poetry.utils.helpers import temporary_directory
from poetry.vcs import get_vcs
project = Poetry.create(Path.cwd())
project = Factory().create_poetry(Path.cwd())
package = project.package
del package.dev_requires[:]
......@@ -217,6 +218,7 @@ class MakeReleaseCommand(Command):
subprocess.check_output(
[python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS
)
subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"])
except subprocess.CalledProcessError:
raise RuntimeError("Python {} is not available".format(version))
......
......@@ -81,7 +81,7 @@ def mock_clone(_, source, dest):
parts = urlparse.urlparse(source)
folder = (
Path(__file__).parent.parent
Path(__file__).parent
/ "fixtures"
/ "git"
/ parts.netloc
......@@ -91,7 +91,6 @@ def mock_clone(_, source, dest):
if dest.exists():
shutil.rmtree(str(dest))
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest))
......
......@@ -11,7 +11,7 @@ def test_none_activated(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......@@ -34,7 +34,7 @@ def test_activated(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......
......@@ -11,7 +11,7 @@ def test_remove_by_python_version(app, tmp_dir, mocker):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......@@ -39,7 +39,7 @@ def test_remove_by_name(app, tmp_dir):
app.poetry.config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......
......@@ -57,7 +57,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(app, tmp_dir, m
tester.execute("3.7")
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
m.assert_called_with(
......@@ -88,7 +88,7 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
current_python = sys.version_info[:3]
python_minor = ".".join(str(v) for v in current_python[:2])
......@@ -130,7 +130,7 @@ def test_get_prefers_explicitly_activated_non_existing_virtualenvs_over_env_var(
os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name(
"simple_project", str(app.poetry.file.parent)
"simple-project", str(app.poetry.file.parent)
)
current_python = sys.version_info[:3]
python_minor = ".".join(str(v) for v in current_python[:2])
......
......@@ -99,3 +99,26 @@ def test_set_pypi_token(app, config, config_source, auth_config_source):
tester.execute("--list")
assert "mytoken" == auth_config_source.config["pypi-token"]["pypi"]
def test_set_client_cert(app, config_source, auth_config_source, mocker):
init = mocker.spy(ConfigSource, "__init__")
command = app.find("config")
tester = CommandTester(command)
tester.execute("certificates.foo.client-cert path/to/cert.pem")
assert (
"path/to/cert.pem"
== auth_config_source.config["certificates"]["foo"]["client-cert"]
)
def test_set_cert(app, config_source, auth_config_source, mocker):
init = mocker.spy(ConfigSource, "__init__")
command = app.find("config")
tester = CommandTester(command)
tester.execute("certificates.foo.cert path/to/ca.pem")
assert "path/to/ca.pem" == auth_config_source.config["certificates"]["foo"]["cert"]
from poetry.utils._compat import Path
def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http):
http.register_uri(
http.POST, "https://upload.pypi.org/legacy/", status=400, body="Bad Request"
......@@ -16,3 +19,22 @@ HTTP Error 400: Bad Request
"""
assert app_tester.io.fetch_output() == expected
def test_publish_with_cert(app_tester, mocker):
publisher_publish = mocker.patch("poetry.masonry.publishing.Publisher.publish")
app_tester.execute("publish --cert path/to/ca.pem")
assert [
(None, None, None, Path("path/to/ca.pem"), None)
] == publisher_publish.call_args
def test_publish_with_client_cert(app_tester, mocker):
publisher_publish = mocker.patch("poetry.masonry.publishing.Publisher.publish")
app_tester.execute("publish --client-cert path/to/client.pem")
assert [
(None, None, None, None, Path("path/to/client.pem"))
] == publisher_publish.call_args
[tool.poetry]
name = "prerelease"
version = "1.0.0.dev0"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
[tool.poetry.dev-dependencies]
......@@ -40,7 +40,7 @@ foo = ["C"]
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......
......@@ -37,7 +37,7 @@ foo = ["D"]
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......
......@@ -26,7 +26,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
"B" = []
"C" = []
......@@ -4,4 +4,4 @@ package = []
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
......@@ -10,5 +10,5 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
......@@ -10,5 +10,5 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
......@@ -42,7 +42,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
"B" = []
"C" = []
......
......@@ -21,6 +21,6 @@ A = "^1.0"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
"B" = []
......@@ -24,5 +24,5 @@ python = ">=3.6,<4.0"
python-versions = "~2.7 || ^3.4"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
......@@ -32,7 +32,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
"B" = []
"C" = []
......@@ -18,6 +18,6 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
"B" = []
......@@ -35,6 +35,6 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies"
content-hash = "123456789"
python-versions = "*"
[metadata.hashes]
[metadata.files]
project-with-extras = []
project-with-transitive-directory-dependencies = []
......@@ -30,6 +30,6 @@ url = "tests/fixtures/project_with_extras"
content-hash = "123456789"
python-versions = "*"
[metadata.hashes]
[metadata.files]
project-with-extras = []
pendulum = []
......@@ -35,7 +35,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
cachy = []
my-package = []
pendulum = []
......@@ -32,7 +32,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......@@ -58,7 +58,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......@@ -46,7 +46,9 @@ url = "tests/fixtures/directory/project_with_transitive_file_dependencies"
content-hash = "123456789"
python-versions = "*"
[metadata.hashes]
demo = ["70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"]
[metadata.files]
demo = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"},
]
pendulum = []
project-with-transitive-file-dependencies = []
......@@ -30,6 +30,8 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
demo = ["70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"]
[metadata.files]
demo = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"},
]
pendulum = []
......@@ -51,7 +51,7 @@ python-versions = "*"
python-versions = "~2.7 || ^3.4"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......@@ -34,7 +34,7 @@ foo = ["A"]
python-versions = "~2.7 || ^3.4"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
C = []
D = []
......@@ -43,7 +43,7 @@ foo = ["A"]
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......
......@@ -18,6 +18,6 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
"A" = []
"B" = []
......@@ -86,12 +86,36 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
attrs = ["1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", "a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"]
colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"]
funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"]
more-itertools = ["0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", "11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", "c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"]
pluggy = ["7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"]
py = ["29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", "983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"]
pytest = ["6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", "fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"]
six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"]
[metadata.files]
attrs = [
{file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"},
{file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"},
]
colorama = [
{file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"},
{file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"},
]
funcsigs = [
{file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"},
{file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"},
]
more-itertools = [
{file = "more-itertools-4.1.0.tar.gz", hash = "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"},
{file = "more_itertools-4.1.0-py2-none-any.whl", hash = "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e"},
{file = "more_itertools-4.1.0-py3-none-any.whl", hash = "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea"},
]
pluggy = [
{file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"},
]
py = [
{file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"},
{file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"},
]
pytest = [
{file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"},
{file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"},
]
six = [
{file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"},
{file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"},
]
......@@ -26,7 +26,7 @@ python-versions = "~2.7 || ^3.3"
python-versions = "~2.7 || ^3.4"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......@@ -40,7 +40,7 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
A = []
B = []
C = []
......
......@@ -30,6 +30,6 @@ python-versions = "*"
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
[metadata.files]
demo = []
pendulum = []
......@@ -15,5 +15,7 @@ url = "tests/fixtures/wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.wh
python-versions = "*"
content-hash = "123456789"
[metadata.hashes]
demo = ["c25eb81459126848a1788eb3520d1a32014eb51ce3d3bae88c56bfdde4ce02db"]
[metadata.files]
demo = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:c25eb81459126848a1788eb3520d1a32014eb51ce3d3bae88c56bfdde4ce02db"},
]
......@@ -1529,3 +1529,31 @@ def test_run_installs_with_url_file(installer, locker, repo, package):
assert locker.written_data == expected
assert len(installer.installer.installs) == 2
def test_installer_uses_prereleases_if_they_are_compatible(
installer, locker, package, repo
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency(
"prerelease", {"git": "https://github.com/demo/prerelease.git"}
)
package_b = get_package("b", "2.0.0")
package_b.add_dependency("prerelease", ">=0.19")
repo.add_package(package_b)
installer.run()
del installer.installer.installs[:]
locker.locked(True)
locker.mock_lock_data(locker.written_data)
package.add_dependency("b", "^2.0.0")
installer.whitelist(["b"])
installer.update(True)
installer.run()
assert len(installer.installer.installs) == 2
......@@ -5,6 +5,7 @@ from poetry.io.null_io import NullIO
from poetry.packages.package import Package
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pool import Pool
from poetry.utils._compat import Path
from poetry.utils.env import NullEnv
......@@ -29,9 +30,12 @@ def installer(pool):
def test_requirement(installer):
package = Package("ipython", "7.5.0")
package.hashes = [
"md5:dbdc53e3918f28fa335a173432402a00",
"e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
package.files = [
{"file": "foo-0.1.0.tar.gz", "hash": "md5:dbdc53e3918f28fa335a173432402a00"},
{
"file": "foo.0.1.0.whl",
"hash": "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
},
]
result = installer.requirement(package, formatted=True)
......@@ -86,6 +90,62 @@ def test_install_with_non_pypi_default_repository(pool, installer):
installer.install(bar)
def test_install_with_cert():
ca_path = "path/to/cert.pem"
pool = Pool()
default = LegacyRepository("default", "https://foo.bar", cert=Path(ca_path))
pool.add_repository(default, default=True)
null_env = NullEnv()
installer = PipInstaller(null_env, NullIO(), pool)
foo = Package("foo", "0.0.0")
foo.source_type = "legacy"
foo.source_reference = default._name
foo.source_url = default._url
installer.install(foo)
assert len(null_env.executed) == 1
cmd = null_env.executed[0]
assert "--cert" in cmd
cert_index = cmd.index("--cert")
# Need to do the str(Path()) bit because Windows paths get modified by Path
assert cmd[cert_index + 1] == str(Path(ca_path))
def test_install_with_client_cert():
client_path = "path/to/client.pem"
pool = Pool()
default = LegacyRepository(
"default", "https://foo.bar", client_cert=Path(client_path)
)
pool.add_repository(default, default=True)
null_env = NullEnv()
installer = PipInstaller(null_env, NullIO(), pool)
foo = Package("foo", "0.0.0")
foo.source_type = "legacy"
foo.source_reference = default._name
foo.source_url = default._url
installer.install(foo)
assert len(null_env.executed) == 1
cmd = null_env.executed[0]
assert "--client-cert" in cmd
cert_index = cmd.index("--client-cert")
# Need to do the str(Path()) bit because Windows paths get modified by Path
assert cmd[cert_index + 1] == str(Path(client_path))
def test_requirement_git_develop_true(installer, package_git):
package_git.develop = True
result = installer.requirement(package_git)
......
......@@ -88,6 +88,7 @@ def test_get_metadata_content():
"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",
]
......
......@@ -79,7 +79,7 @@ def test_wheel_c_extension():
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dmu?-.+
Tag: cp[23]\\d-cp[23]\\dm?u?-.+
$""".format(
__version__
),
......@@ -136,7 +136,7 @@ def test_wheel_c_extension_src_layout():
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dmu?-.+
Tag: cp[23]\\d-cp[23]\\dm?u?-.+
$""".format(
__version__
),
......@@ -216,6 +216,7 @@ 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
......@@ -318,6 +319,7 @@ 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
......
......@@ -7,6 +7,7 @@ from clikit.io import NullIO
from poetry.factory import Factory
from poetry.masonry.builders import WheelBuilder
from poetry.masonry.publishing.uploader import Uploader
from poetry.utils._compat import Path
from poetry.utils.env import NullEnv
......@@ -99,7 +100,8 @@ def test_wheel_excluded_nested_data():
def test_wheel_localversionlabel():
module_path = fixtures_dir / "localversionlabel"
WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO())
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")
......@@ -108,6 +110,9 @@ def test_wheel_localversionlabel():
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"
......
......@@ -3,6 +3,7 @@ import pytest
from poetry.factory import Factory
from poetry.io.null_io import NullIO
from poetry.masonry.publishing.publisher import Publisher
from poetry.utils._compat import Path
def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
......@@ -18,7 +19,10 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
publisher.publish(None, None, None)
assert [("foo", "bar")] == uploader_auth.call_args
assert [("https://upload.pypi.org/legacy/",)] == uploader_upload.call_args
assert [
("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None},
] == uploader_upload.call_args
def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
......@@ -37,7 +41,10 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
publisher.publish("my-repo", None, None)
assert [("foo", "bar")] == uploader_auth.call_args
assert [("http://foo.bar",)] == uploader_upload.call_args
assert [
("http://foo.bar",),
{"cert": None, "client_cert": None},
] == uploader_upload.call_args
def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, config):
......@@ -63,4 +70,52 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config):
publisher.publish(None, None, None)
assert [("__token__", "my-token")] == uploader_auth.call_args
assert [("https://upload.pypi.org/legacy/",)] == uploader_upload.call_args
assert [
("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None},
] == uploader_upload.call_args
def test_publish_uses_cert(fixture_dir, mocker, config):
cert = "path/to/ca.pem"
uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth")
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config
poetry.config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar"}},
"http-basic": {"foo": {"username": "foo", "password": "bar"}},
"certificates": {"foo": {"cert": cert}},
}
)
publisher = Publisher(poetry, NullIO())
publisher.publish("foo", None, None)
assert [("foo", "bar")] == uploader_auth.call_args
assert [
("https://foo.bar",),
{"cert": Path(cert), "client_cert": None},
] == uploader_upload.call_args
def test_publish_uses_client_cert(fixture_dir, mocker, config):
client_cert = "path/to/client.pem"
uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload")
poetry = Factory().create_poetry(fixture_dir("sample_project"))
poetry._config = config
poetry.config.merge(
{
"repositories": {"foo": {"url": "https://foo.bar"}},
"certificates": {"foo": {"client-cert": client_cert}},
}
)
publisher = Publisher(poetry, NullIO())
publisher.publish("foo", None, None)
assert [
("https://foo.bar",),
{"cert": None, "client_cert": Path(client_cert)},
] == uploader_upload.call_args
......@@ -94,6 +94,7 @@ 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
......
......@@ -102,3 +102,9 @@ def test_to_pep_508_in_extras():
") "
'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()
......@@ -6,6 +6,7 @@ import tomlkit
from poetry.packages.locker import Locker
from poetry.packages.project_package import ProjectPackage
from ..helpers import get_dependency
from ..helpers import get_package
......@@ -26,7 +27,7 @@ def root():
def test_lock_file_data_is_ordered(locker, root):
package_a = get_package("A", "1.0.0")
package_a.add_dependency("B", "^1.0")
package_a.hashes = ["456", "123"]
package_a.files = [{"file": "foo", "hash": "456"}, {"file": "bar", "hash": "123"}]
packages = [package_a, get_package("B", "1.2")]
locker.set_lock_data(root, packages)
......@@ -57,8 +58,11 @@ version = "1.2"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.hashes]
A = ["123", "456"]
[metadata.files]
A = [
{file = "bar", hash = "123"},
{file = "foo", hash = "456"},
]
B = []
"""
......@@ -91,7 +95,7 @@ redis = ["redis (>=2.10.5)"]
content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77"
python-versions = "~2.7 || ^3.4"
[metadata.hashes]
[metadata.files]
cachecontrol = []
"""
......@@ -130,8 +134,50 @@ version = "1.0.0"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.hashes]
[metadata.files]
A = []
"""
assert expected == content
def test_lock_file_should_not_have_mixed_types(locker, root):
package_a = get_package("A", "1.0.0")
package_a.add_dependency("B", "^1.0.0")
package_a.add_dependency("B", {"version": ">=1.0.0", "optional": True})
package_a.requires[-1].activate()
package_a.extras["foo"] = [get_dependency("B", ">=1.0.0")]
locker.set_lock_data(root, [package_a])
expected = """[[package]]
category = "main"
description = ""
name = "A"
optional = false
python-versions = "*"
version = "1.0.0"
[package.dependencies]
[[package.dependencies.B]]
version = "^1.0.0"
[[package.dependencies.B]]
optional = true
version = ">=1.0.0"
[package.extras]
foo = ["B (>=1.0.0)"]
[metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.files]
A = []
"""
with locker.lock.open(encoding="utf-8") as f:
content = f.read()
assert expected == content
......@@ -118,7 +118,10 @@ def test_search_for_vcs_read_setup_with_extras(provider, mocker):
def test_search_for_vcs_read_setup_raises_error_if_no_version(provider, mocker):
mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv())
mocker.patch(
"poetry.utils.env.VirtualEnv.run",
side_effect=EnvCommandError(CalledProcessError(1, "python", output="")),
)
dependency = VCSDependency("demo", "git", "https://github.com/demo/no-version.git")
......@@ -175,6 +178,46 @@ def test_search_for_directory_setup_egg_info_with_extras(provider):
}
@pytest.mark.parametrize("directory", ["demo", "non-canonical-name"])
def test_search_for_directory_setup_with_base(provider, directory):
dependency = DirectoryDependency(
"demo",
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ directory,
base=Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ directory,
)
package = provider.search_for_directory(dependency)[0]
assert package.name == "demo"
assert package.version.text == "0.1.2"
assert package.requires == [get_dependency("pendulum", ">=1.4.4")]
assert package.extras == {
"foo": [get_dependency("cleo")],
"bar": [get_dependency("tomlkit")],
}
assert (
package.root_dir
== (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ directory
).as_posix()
)
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_search_for_directory_setup_read_setup(provider, mocker):
mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv())
......
......@@ -928,6 +928,11 @@ def test_solver_can_resolve_git_dependencies(solver, repo, package):
],
)
op = ops[1]
assert op.package.source_type == "git"
assert op.package.source_reference.startswith("9cf87a2")
def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
......@@ -951,6 +956,37 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
)
@pytest.mark.parametrize(
"ref",
[{"branch": "a-branch"}, {"tag": "a-tag"}, {"rev": "9cf8"}],
ids=["branch", "tag", "rev"],
)
def test_solver_can_resolve_git_dependencies_with_ref(solver, repo, package, ref):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
git_config = {"git": "https://github.com/demo/demo.git"}
git_config.update(ref)
package.add_dependency("demo", git_config)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.2")},
],
)
op = ops[1]
assert op.package.source_type == "git"
assert op.package.source_reference.startswith("9cf87a2")
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
solver, repo, package
):
......@@ -1775,3 +1811,27 @@ def test_solver_discards_packages_with_empty_markers(
{"job": "install", "package": package_a},
],
)
def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.5"
package.add_dependency("A", {"version": "^1.0", "python": "~2.7"}, category="dev")
package.add_dependency("A", {"version": "^2.0", "python": "^3.5"}, category="dev")
package_a100 = get_package("A", "1.0.0")
package_a200 = get_package("A", "2.0.0")
repo.add_package(package_a100)
repo.add_package(package_a200)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_a100},
{"job": "install", "package": package_a200},
],
)
......@@ -7,6 +7,7 @@ except ImportError:
import urlparse
from poetry.packages import Dependency
from poetry.repositories.auth import Auth
from poetry.repositories.exceptions import PackageNotFound
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.legacy_repository import Page
......@@ -18,9 +19,9 @@ class MockRepository(LegacyRepository):
FIXTURES = Path(__file__).parent / "fixtures" / "legacy"
def __init__(self):
def __init__(self, auth=None):
super(MockRepository, self).__init__(
"legacy", url="http://foo.bar", disable_cache=True
"legacy", url="http://foo.bar", auth=auth, disable_cache=True
)
def _get(self, endpoint):
......@@ -242,11 +243,17 @@ def test_get_package_retrieves_non_sha256_hashes():
package = repo.package("ipython", "7.5.0")
expected = [
"md5:dbdc53e3918f28fa335a173432402a00",
"e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
{
"file": "ipython-7.5.0-py3-none-any.whl",
"hash": "md5:dbdc53e3918f28fa335a173432402a00",
},
{
"file": "ipython-7.5.0.tar.gz",
"hash": "sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
},
]
assert expected == package.hashes
assert expected == package.files
def test_get_package_retrieves_packages_with_no_hashes():
......@@ -254,4 +261,11 @@ def test_get_package_retrieves_packages_with_no_hashes():
package = repo.package("jupyter", "1.0.0")
assert [] == package.hashes
assert [] == package.files
def test_username_password_special_chars():
auth = Auth("http://foo.bar", "user:", "p@ssword")
repo = MockRepository(auth=auth)
assert "http://user%3A:p%40ssword@foo.bar" == repo.authenticated_url
......@@ -67,7 +67,7 @@ def test_parse_constraint_wildcard(input, constraint):
("~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, 0), Version(3, 6, 0), True)), # PEP 440
("~=3.5.3", VersionRange(Version(3, 5, 3), Version(3, 6, 0), True)), # PEP 440
],
)
def test_parse_constraint_tilde(input, constraint):
......
......@@ -107,6 +107,7 @@ def test_create_poetry():
"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",
]
......
......@@ -6,9 +6,11 @@ import tomlkit
from clikit.io import NullIO
from poetry.semver import Version
from poetry.factory import Factory
from poetry.utils._compat import Path
from poetry.utils.env import EnvManager
from poetry.utils.env import EnvCommandError
from poetry.utils.env import NoCompatiblePythonVersionFound
from poetry.utils.env import VirtualEnv
from poetry.utils.toml_file import TomlFile
......@@ -25,32 +27,56 @@ print("nullpackage loaded"),
"""
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir, config):
@pytest.fixture()
def poetry(config):
poetry = Factory().create_poetry(
Path(__file__).parent.parent / "fixtures" / "simple_project"
)
poetry.set_config(config)
return poetry
@pytest.fixture()
def manager(poetry):
return EnvManager(poetry)
@pytest.fixture
def tmp_venv(tmp_dir, manager):
venv_path = Path(tmp_dir) / "venv"
manager.build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
yield venv
shutil.rmtree(str(venv.path))
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir, manager):
venv_path = Path(tmp_dir) / "Virtual Env"
EnvManager(config).build_venv(str(venv_path))
manager.build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
assert venv.run("python", "-V", shell=True).startswith("Python")
def test_env_get_in_project_venv(tmp_dir, config):
def test_env_get_in_project_venv(manager, poetry):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
(Path(tmp_dir) / ".venv").mkdir()
(poetry.file.parent / ".venv").mkdir()
venv = EnvManager(config).get(Path(tmp_dir))
venv = manager.get()
assert venv.path == Path(tmp_dir) / ".venv"
assert venv.path == poetry.file.parent / ".venv"
shutil.rmtree(str(venv.path))
CWD = Path(__file__).parent.parent / "fixtures" / "simple_project"
def build_venv(path, executable=None):
os.mkdir(path)
......@@ -72,7 +98,7 @@ def check_output_wrapper(version=Version.parse("3.7.1")):
def test_activate_activates_non_existing_virtualenv_no_envs_file(
tmp_dir, config, mocker
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
......@@ -89,8 +115,8 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)
env = EnvManager(config).activate("python3.7", CWD, NullIO())
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
env = manager.activate("python3.7", NullIO())
venv_name = EnvManager.generate_env_name("simple-project", str(poetry.file.parent))
m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
......@@ -106,11 +132,13 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
assert env.base == Path("/prefix")
def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mocker):
def test_activate_activates_existing_virtualenv_no_envs_file(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name)))
......@@ -126,7 +154,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mo
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)
env = EnvManager(config).activate("python3.7", CWD, NullIO())
env = manager.activate("python3.7", NullIO())
m.assert_not_called()
......@@ -140,11 +168,13 @@ def test_activate_activates_existing_virtualenv_no_envs_file(tmp_dir, config, mo
assert env.base == Path("/prefix")
def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mocker):
def test_activate_activates_same_virtualenv_with_envs_file(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document()
......@@ -165,7 +195,7 @@ def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mock
)
m = mocker.patch("poetry.utils.env.EnvManager.create_venv")
env = EnvManager(config).activate("python3.7", CWD, NullIO())
env = manager.activate("python3.7", NullIO())
m.assert_not_called()
......@@ -179,12 +209,12 @@ def test_activate_activates_same_virtualenv_with_envs_file(tmp_dir, config, mock
def test_activate_activates_different_virtualenv_with_envs_file(
tmp_dir, config, mocker
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document()
doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"}
......@@ -204,7 +234,7 @@ def test_activate_activates_different_virtualenv_with_envs_file(
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)
env = EnvManager(config).activate("python3.6", CWD, NullIO())
env = manager.activate("python3.6", NullIO())
m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.6".format(venv_name)), executable="python3.6"
......@@ -219,11 +249,13 @@ def test_activate_activates_different_virtualenv_with_envs_file(
assert env.base == Path("/prefix")
def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocker):
def test_activate_activates_recreates_for_different_patch(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document()
doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"}
......@@ -254,7 +286,7 @@ def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocke
"poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv
)
env = EnvManager(config).activate("python3.7", CWD, NullIO())
env = manager.activate("python3.7", NullIO())
build_venv_m.assert_called_with(
os.path.join(tmp_dir, "{}-py3.7".format(venv_name)), executable="python3.7"
......@@ -273,11 +305,13 @@ def test_activate_activates_recreates_for_different_patch(tmp_dir, config, mocke
assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)).exists()
def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker):
def test_activate_does_not_recreate_when_switching_minor(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
envs_file = TomlFile(Path(tmp_dir) / "envs.toml")
doc = tomlkit.document()
doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"}
......@@ -303,7 +337,7 @@ def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker
"poetry.utils.env.EnvManager.remove_venv", side_effect=remove_venv
)
env = EnvManager(config).activate("python3.6", CWD, NullIO())
env = manager.activate("python3.6", NullIO())
build_venv_m.assert_not_called()
remove_venv_m.assert_not_called()
......@@ -318,11 +352,13 @@ def test_activate_does_not_recreate_when_switching_minor(tmp_dir, config, mocker
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker):
def test_deactivate_non_activated_but_existing(
tmp_dir, manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
(
Path(tmp_dir)
......@@ -336,8 +372,8 @@ def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker):
side_effect=check_output_wrapper(),
)
EnvManager(config).deactivate(CWD, NullIO())
env = EnvManager(config).get(CWD)
manager.deactivate(NullIO())
env = manager.get()
assert env.path == Path(tmp_dir) / "{}-py{}".format(
venv_name, ".".join(str(c) for c in sys.version_info[:2])
......@@ -345,11 +381,11 @@ def test_deactivate_non_activated_but_existing(tmp_dir, config, mocker):
assert Path("/prefix")
def test_deactivate_activated(tmp_dir, config, mocker):
def test_deactivate_activated(tmp_dir, manager, poetry, config, mocker):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
version = Version.parse(".".join(str(c) for c in sys.version_info[:3]))
other_version = Version.parse("3.4") if version.major == 2 else version.next_minor
(
......@@ -375,8 +411,8 @@ def test_deactivate_activated(tmp_dir, config, mocker):
side_effect=check_output_wrapper(),
)
EnvManager(config).deactivate(CWD, NullIO())
env = EnvManager(config).get(CWD)
manager.deactivate(NullIO())
env = manager.get()
assert env.path == Path(tmp_dir) / "{}-py{}.{}".format(
venv_name, version.major, version.minor
......@@ -388,11 +424,11 @@ def test_deactivate_activated(tmp_dir, config, mocker):
def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
tmp_dir, config, mocker
tmp_dir, manager, poetry, config, mocker
):
os.environ["VIRTUAL_ENV"] = "/environment/prefix"
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
config.merge({"virtualenvs": {"path": str(tmp_dir)}})
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
......@@ -411,30 +447,30 @@ def test_get_prefers_explicitly_activated_virtualenvs_over_env_var(
side_effect=[("/prefix", None)],
)
env = EnvManager(config).get(CWD)
env = manager.get()
assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name)
assert env.base == Path("/prefix")
def test_list(tmp_dir, config):
def test_list(tmp_dir, manager, poetry, config):
config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
venvs = EnvManager(config).list(CWD)
venvs = manager.list()
assert 2 == len(venvs)
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venvs[0].path
assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)) == venvs[1].path
def test_remove_by_python_version(tmp_dir, config, mocker):
def test_remove_by_python_version(tmp_dir, manager, poetry, config, mocker):
config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......@@ -443,18 +479,16 @@ def test_remove_by_python_version(tmp_dir, config, mocker):
side_effect=check_output_wrapper(Version.parse("3.6.6")),
)
manager = EnvManager(config)
venv = manager.remove("3.6", CWD)
venv = manager.remove("3.6")
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path
assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def test_remove_by_name(tmp_dir, config, mocker):
def test_remove_by_name(tmp_dir, manager, poetry, config, mocker):
config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......@@ -463,18 +497,16 @@ def test_remove_by_name(tmp_dir, config, mocker):
side_effect=check_output_wrapper(Version.parse("3.6.6")),
)
manager = EnvManager(config)
venv = manager.remove("{}-py3.6".format(venv_name), CWD)
venv = manager.remove("{}-py3.6".format(venv_name))
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path
assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def test_remove_also_deactivates(tmp_dir, config, mocker):
def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker):
config.merge({"virtualenvs": {"path": str(tmp_dir)}})
venv_name = EnvManager.generate_env_name("simple_project", str(CWD))
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
(Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir()
(Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir()
......@@ -488,9 +520,7 @@ def test_remove_also_deactivates(tmp_dir, config, mocker):
doc[venv_name] = {"minor": "3.6", "patch": "3.6.6"}
envs_file.write(doc)
manager = EnvManager(config)
venv = manager.remove("python3.6", CWD)
venv = manager.remove("python3.6")
assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path
assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
......@@ -499,18 +529,6 @@ def test_remove_also_deactivates(tmp_dir, config, mocker):
assert venv_name not in envs
@pytest.fixture
def tmp_venv(tmp_dir, config, request):
venv_path = Path(tmp_dir) / "venv"
EnvManager(config).build_venv(str(venv_path))
venv = VirtualEnv(venv_path)
yield venv
shutil.rmtree(str(venv.path))
def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv):
venv_available = False
try:
......@@ -533,8 +551,110 @@ def test_run_with_input(tmp_dir, tmp_venv):
def test_run_with_input_non_zero_return(tmp_dir, tmp_venv):
with pytest.raises(EnvCommandError) as processError:
# Test command that will return non-zero returncode.
result = tmp_venv.run("python", "-", input_=ERRORING_SCRIPT)
tmp_venv.run("python", "-", input_=ERRORING_SCRIPT)
assert processError.value.e.returncode == 1
def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^3.6"
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
mocker.patch("sys.version_info", (2, 7, 16))
mocker.patch(
"poetry.utils._compat.subprocess.check_output",
side_effect=check_output_wrapper(Version.parse("3.7.5")),
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
manager.create_venv(NullIO())
m.assert_called_with(
str(Path("/foo/virtualenvs/{}-py3.7".format(venv_name))), executable="python3"
)
def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific_ones(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^3.6"
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
mocker.patch("sys.version_info", (2, 7, 16))
mocker.patch(
"poetry.utils._compat.subprocess.check_output", side_effect=["3.5.3", "3.8.0"]
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
manager.create_venv(NullIO())
m.assert_called_with(
str(Path("/foo/virtualenvs/{}-py3.8".format(venv_name))), executable="python3.8"
)
def test_create_venv_fails_if_no_compatible_python_version_could_be_found(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^4.8"
mocker.patch(
"poetry.utils._compat.subprocess.check_output", side_effect=["", "", "", ""]
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
with pytest.raises(NoCompatiblePythonVersionFound) as e:
manager.create_venv(NullIO())
expected_message = (
"Poetry was unable to find a compatible version. "
"If you have one, you can explicitly use it "
'via the "env use" command.'
)
assert expected_message == str(e.value)
assert 0 == m.call_count
def test_create_venv_does_not_try_to_find_compatible_versions_with_executable(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
poetry.package.python_versions = "^4.8"
mocker.patch("poetry.utils._compat.subprocess.check_output", side_effect=["3.8.0"])
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
with pytest.raises(NoCompatiblePythonVersionFound) as e:
manager.create_venv(NullIO(), executable="3.8")
expected_message = (
"The specified Python version (3.8.0) is not supported by the project (^4.8).\n"
"Please choose a compatible version or loosen the python constraint "
"specified in the pyproject.toml file."
)
assert expected_message == str(e.value)
assert 0 == m.call_count
from poetry.utils.helpers import get_http_basic_auth
from poetry.utils._compat import Path
from poetry.utils.helpers import get_client_cert, get_cert, get_http_basic_auth
from poetry.utils.helpers import parse_requires
......@@ -65,3 +66,17 @@ def test_get_http_basic_auth_without_password(config):
def test_get_http_basic_auth_missing(config):
assert get_http_basic_auth(config, "foo") is None
def test_get_cert(config):
ca_cert = "path/to/ca.pem"
config.merge({"certificates": {"foo": {"cert": ca_cert}}})
assert get_cert(config, "foo") == Path(ca_cert)
def test_get_client_cert(config):
client_cert = "path/to/client.pem"
config.merge({"certificates": {"foo": {"client-cert": client_cert}}})
assert get_client_cert(config, "foo") == Path(client_cert)
[tox]
skipsdist = True
envlist = py27, py34, py35, py36, py37
envlist = py27, py34, py35, py36, py37, py38
[testenv]
whitelist_externals = poetry
......
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