Commit 0cf4bd79 by Sébastien Eustace

Merge branch 'master' into develop

# Conflicts:
#	poetry/masonry/builders/builder.py
#	poetry/masonry/builders/wheel.py
#	tests/masonry/builders/fixtures/default_with_excluded_data_toml/pyproject.toml
#	tests/masonry/builders/fixtures/exclude_nested_data_toml/pyproject.toml
#	tests/masonry/builders/test_wheel.py
parents 39c57cc3 01ec4a16
...@@ -16,8 +16,8 @@ assignees: '' ...@@ -16,8 +16,8 @@ assignees: ''
--> -->
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version. - [ ] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry version.
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [ ] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option). - [ ] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).
<!-- <!--
......
--- ---
name: "\U0001F4DA Documentation" name: "\U0001F4DA Documentation"
about: Did you find errors, problems, or anything unintelligible in the docs (https://poetry.eustace.io/docs)? about: Did you find errors, problems, or anything unintelligible in the docs (https://python-poetry.org/docs)?
title: '' title: ''
labels: 'Documentation' labels: 'Documentation'
assignees: '' assignees: ''
...@@ -16,7 +16,7 @@ assignees: '' ...@@ -16,7 +16,7 @@ assignees: ''
--> -->
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
## Issue ## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ --> <!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
...@@ -13,8 +13,8 @@ assignees: '' ...@@ -13,8 +13,8 @@ assignees: ''
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://poetry.eustace.io/docs/) and believe that my question is not covered. - [ ] I have searched the [documentation](https://python-poetry.org/docs/) and believe that my question is not covered.
## Issue ## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ --> <!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
...@@ -16,8 +16,8 @@ assignees: '' ...@@ -16,8 +16,8 @@ assignees: ''
--> -->
<!-- Checked checkbox should look like this: [x] --> <!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://poetry.eustace.io/docs/) and believe that my question is not covered. - [ ] I have searched the [documentation](https://python-poetry.org/docs/) and believe that my question is not covered.
## Feature Request ## Feature Request
<!-- Now feel free to write your idea for improvement. Thanks again 🙌 ❤️ --> <!-- Now feel free to write your idea for improvement. Thanks again 🙌 ❤️ -->
# Pull Request Check List # Pull Request Check List
This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes. But please read our [contribution guide](https://poetry.eustace.io/docs/contributing/) at least once, it will save you unnecessary review cycles! This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes. But please read our [contribution guide](https://python-poetry.org/docs/contributing/) at least once, it will save you unnecessary review cycles!
- [ ] Added **tests** for changed code. - [ ] Added **tests** for changed code.
- [ ] Updated **documentation** for changed code. - [ ] Updated **documentation** for changed code.
......
exemptProjects: true
exemptMilestones: true
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: >
Closing this issue automatically because it has not had any activity since
it has been marked as stale. If you think it is still relevant and should
be addressed, feel free to open a new one.
pulls:
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: >
Closing this pull request automatically because it has not had any activity since
it has been marked as stale. If you think it is still relevant and should
be addressed, feel free to open a new one.
...@@ -22,7 +22,7 @@ jobs: ...@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7] python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
...@@ -30,10 +30,16 @@ jobs: ...@@ -30,10 +30,16 @@ jobs:
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry - name: Install and set up Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
source $HOME/.poetry/env source $HOME/.poetry/env
poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v1
with:
path: .venv
key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
source $HOME/.poetry/env source $HOME/.poetry/env
...@@ -48,7 +54,7 @@ jobs: ...@@ -48,7 +54,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7] python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
...@@ -56,10 +62,16 @@ jobs: ...@@ -56,10 +62,16 @@ jobs:
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry - name: Install and set up Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
source $HOME/.poetry/env source $HOME/.poetry/env
poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v1
with:
path: .venv
key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
source $HOME/.poetry/env source $HOME/.poetry/env
...@@ -74,7 +86,7 @@ jobs: ...@@ -74,7 +86,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7] python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
...@@ -82,10 +94,16 @@ jobs: ...@@ -82,10 +94,16 @@ jobs:
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install Poetry - name: Install and setup Poetry
run: | run: |
python get-poetry.py --preview -y python get-poetry.py --preview -y
$env:Path += ";$env:Userprofile\.poetry\bin" $env:Path += ";$env:Userprofile\.poetry\bin"
poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v1
with:
path: .venv
key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
$env:Path += ";$env:Userprofile\.poetry\bin" $env:Path += ";$env:Userprofile\.poetry\bin"
......
name: Release
on:
push:
tags:
- '*.*.*'
jobs:
Linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get tag
id: tag
run: |
echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
- name: Building release
run: |
make linux_release
- name: Upload release file
uses: actions/upload-artifact@v1
with:
name: poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz
path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz
- name: Upload checksum file
uses: actions/upload-artifact@v1
with:
name: poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum
path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum
MacOS:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: Get tag
id: tag
run: |
echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: "3.8"
- name: Install Poetry
run: |
python get-poetry.py --preview -y
source $HOME/.poetry/env
- name: Install dependencies
run: |
source $HOME/.poetry/env
poetry install --no-dev
- name: Preparing Python executables
run: |
curl -L https://github.com/sdispater/python-binaries/releases/download/2.7.17/python-2.7.17.macos.tar.xz -o python-2.7.17.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.4.10/python-3.4.10.macos.tar.xz -o python-3.4.10.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.5.9/python-3.5.9.macos.tar.xz -o python-3.5.9.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.macos.tar.xz -o python-3.6.8.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.7.5/python-3.7.5.macos.tar.xz -o python-3.7.5.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.8.0/python-3.8.0.macos.tar.xz -o python-3.8.0.tar.xz
tar -zxf python-2.7.17.tar.xz
tar -zxf python-3.4.10.tar.xz
tar -zxf python-3.5.9.tar.xz
tar -zxf python-3.6.8.tar.xz
tar -zxf python-3.7.5.tar.xz
tar -zxf python-3.8.0.tar.xz
- name: Build specific release
run: |
source $HOME/.poetry/env
poetry run python sonnet make release --ansi -P "2.7:python-2.7.17/bin/python" -P "3.4:python-3.4.10/bin/python" -P "3.5:python-3.5.9/bin/python" -P "3.6:python-3.6.8/bin/python" -P "3.7:python-3.7.5/bin/python" -P "3.8:python-3.8.0/bin/python"
- name: Upload release file
uses: actions/upload-artifact@v1
with:
name: poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz
path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz
- name: Upload checksum file
uses: actions/upload-artifact@v1
with:
name: poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum
path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum
Windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Get tag
id: tag
shell: bash
run: |
echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: "3.8"
- name: Install Poetry
run: |
python get-poetry.py --preview -y
$env:Path += ";$env:Userprofile\.poetry\bin"
- name: Install dependencies
run: |
$env:Path += ";$env:Userprofile\.poetry\bin"
poetry install --no-dev
- name: Preparing Python executables
run: |
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/2.7.17/python-2.7.17.windows.tar.xz -O python-2.7.17.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.4.4/python-3.4.4.windows.tar.xz -O python-3.4.4.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.5.4/python-3.5.4.windows.tar.xz -O python-3.5.4.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.windows.tar.xz -O python-3.6.8.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.7.5/python-3.7.5.windows.tar.xz -O python-3.7.5.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.8.0/python-3.8.0.windows.tar.xz -O python-3.8.0.tar.xz
7z x python-2.7.17.tar.xz
7z x python-3.4.4.tar.xz
7z x python-3.5.4.tar.xz
7z x python-3.6.8.tar.xz
7z x python-3.7.5.tar.xz
7z x python-3.8.0.tar.xz
7z x python-2.7.17.tar
7z x python-3.4.4.tar
7z x python-3.5.4.tar
7z x python-3.6.8.tar
7z x python-3.7.5.tar
7z x python-3.8.0.tar
- name: Build specific release
run: |
$env:Path += ";$env:Userprofile\.poetry\bin"
poetry run python sonnet make release --ansi -P "2.7:python-2.7.17\python.exe" -P "3.4:python-3.4.4\python.exe" -P "3.5:python-3.5.4\python.exe" -P "3.6:python-3.6.8\python.exe" -P "3.7:python-3.7.5\python.exe" -P "3.8:python-3.8.0\python.exe"
- name: Upload release file
uses: actions/upload-artifact@v1
with:
name: poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz
path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz
- name: Upload checksum file
uses: actions/upload-artifact@v1
with:
name: poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum
path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum
Release:
needs: [Linux, MacOS, Windows]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Get tag
id: tag
run: |
echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
- name: Download Linux release file
uses: actions/download-artifact@master
with:
name: poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz
path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz
- name: Download Linux checksum file
uses: actions/download-artifact@master
with:
name: poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum
path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum
- name: Download MacOS release file
uses: actions/download-artifact@master
with:
name: poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz
path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz
- name: Download MacOS checksum file
uses: actions/download-artifact@master
with:
name: poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum
path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum
- name: Download Windows release file
uses: actions/download-artifact@master
with:
name: poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz
path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz
- name: Download Windows checksum file
uses: actions/download-artifact@master
with:
name: poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum
path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.tag.outputs.tag }}
release_name: ${{ steps.tag.outputs.tag }}
draft: false
prerelease: false
- name: Upload Linux release file asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz/poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz
asset_name: poetry-${{ steps.tag.outputs.tag }}-linux.tar.gz
asset_content_type: application/gzip
- name: Upload Linux checksum file asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum/poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum
asset_name: poetry-${{ steps.tag.outputs.tag }}-linux.sha256sum
asset_content_type: text/pain
- name: Upload MacOS release file asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz/poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz
asset_name: poetry-${{ steps.tag.outputs.tag }}-darwin.tar.gz
asset_content_type: application/gzip
- name: Upload MacOS checksum file asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum/poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum
asset_name: poetry-${{ steps.tag.outputs.tag }}-darwin.sha256sum
asset_content_type: text/pain
- name: Upload Windows release file asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz/poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz
asset_name: poetry-${{ steps.tag.outputs.tag }}-win32.tar.gz
asset_content_type: application/gzip
- name: Upload Windows checksum file asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: releases/poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum/poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum
asset_name: poetry-${{ steps.tag.outputs.tag }}-win32.sha256sum
asset_content_type: text/pain
- name: Install Poetry
run: |
python get-poetry.py --preview -y
- name: Install dependencies
run: |
source $HOME/.poetry/env
poetry install --no-dev
- name: Build project for distribution
run: |
source $HOME/.poetry/env
poetry run poetry build
- name: Publish to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
run: |
source $HOME/.poetry/env
poetry run poetry publish
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
# Packages # Packages
*.egg *.egg
*.egg-info !/tests/**/*.egg
/*.egg-info
/tests/fixtures/**/*.egg-info
/dist/* /dist/*
build build
_build _build
......
...@@ -20,5 +20,7 @@ repos: ...@@ -20,5 +20,7 @@ repos:
rev: v2.3.0 rev: v2.3.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
exclude: ^tests/.*/fixtures/.*
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: ^tests/.*/fixtures/.*
- id: debug-statements - id: debug-statements
# Change Log # Change Log
## [Unreleased] ## [1.0.1] - 2020-01-10
### Fixed
- Fixed an error in `env use` where the wrong Python executable was being used to check compatibility ([#1736](https://github.com/python-poetry/poetry/pull/1736)).
- Fixed an error where VCS dependencies were not properly categorized as development dependencies ([#1725](https://github.com/python-poetry/poetry/pull/1725)).
- Fixed an error where some shells would no longer be usable after using the `shell` command ([#1673](https://github.com/python-poetry/poetry/pull/1673)).
- Fixed an error where explicitly included files where not included in wheel distributions ([#1750](https://github.com/python-poetry/poetry/pull/1750)).
- Fixed an error where some Git dependencies url were not properly parsed ([#1756](https://github.com/python-poetry/poetry/pull/1756)).
- Fixed an error in the `env` commands on Windows if the path to the executable contained a space ([#1774](https://github.com/python-poetry/poetry/pull/1774)).
- Fixed several errors and UX issues caused by `keyring` on some systems ([#1788](https://github.com/python-poetry/poetry/pull/1788)).
- Fixed errors when trying to detect installed packages ([#1786](https://github.com/python-poetry/poetry/pull/1786)).
- Fixed an error when packaging projects where Python packages were not properly detected ([#1592](https://github.com/python-poetry/poetry/pull/1592)).
- Fixed an error where local file dependencies were exported as editable when using the `export` command ([#1840](https://github.com/python-poetry/poetry/pull/1840)).
- Fixed the way environment markers are propagated and evaluated when resolving dependencies ([#1829](https://github.com/python-poetry/poetry/pull/1829), [#1789](https://github.com/python-poetry/poetry/pull/1789)).
- Fixed an error in the PEP-508 compliant representation of directory and file dependencies ([#1796](https://github.com/python-poetry/poetry/pull/1796)).
- Fixed an error where invalid virtual environments would be silently used. They will not be recreated and a warning will be displayed ([#1797](https://github.com/python-poetry/poetry/pull/1797)).
- Fixed an error where dependencies were not properly detected when reading the `setup.py` file in some cases ([#1764](https://github.com/python-poetry/poetry/pull/1764)).
## [1.0.0] - 2019-12-12
### Added ### Added
...@@ -18,10 +38,10 @@ ...@@ -18,10 +38,10 @@
- Full environment markers are now supported for dependencies via the `markers` property. - Full environment markers are now supported for dependencies via the `markers` property.
- Added the ability to specify git dependencies directly in `add`, it no longer requires the `--git` option. - Added the ability to specify git dependencies directly in `add`, it no longer requires the `--git` option.
- Added the ability to specify path dependencies directly in `add`, it no longer requires the `--path` option. - Added the ability to specify path dependencies directly in `add`, it no longer requires the `--path` option.
- Added support for url dependencies ([#1260](https://github.com/sdispater/poetry/pull/1260)). - Added support for url dependencies ([#1260](https://github.com/python-poetry/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)). - Publishing to PyPI using [API tokens](https://pypi.org/help/#apitoken) is now supported ([#1275](https://github.com/python-poetry/poetry/pull/1275)).
- Licenses can now be identified by their full name. - Licenses can now be identified by their full name.
- Added support for support for custom certificate authority and client certificates for private repositories. - Added support for custom certificate authority and client certificates for private repositories.
- Poetry can now detect and use Conda environments. - Poetry can now detect and use Conda environments.
### Changed ### Changed
...@@ -35,11 +55,16 @@ ...@@ -35,11 +55,16 @@
- The `--git` option of the `add` command has been removed. - The `--git` option of the `add` command has been removed.
- The `--path` option of the `add` command has been removed. - The `--path` option of the `add` command has been removed.
- The `add` command will now automatically select the latest prerelease if only prereleases are available. - 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)). - The `add` command can now update a dependencies if an explicit constraint is given ([#1221](https://github.com/python-poetry/poetry/pull/1221)).
- Removed the `--develop` option from the `install` command. - Removed the `--develop` option from the `install` command.
- Improved UX when searching for packages in the `init` command. - Improved UX when searching for packages in the `init` command.
- The `shell` has been improved. - The `shell` command has been improved.
- The `poetry run` command now uses `os.execvp()` rather than spawning a new subprocess. - The `poetry run` command now uses `os.execvp()` rather than spawning a new subprocess.
- Specifying dependencies with `allows-prereleases` in the `pyproject.toml` file is deprecated for consistency with the `add` command. Use `allow-prereleases` instead.
- Improved the error message when the lock file is invalid.
- Whenever Poetry needs to use the "system" Python, it will now call `sys.executable` instead of the `python` command.
- Improved the error message displayed on conflicting Python requirements ([#1681](https://github.com/python-poetry/poetry/pull/1681)).
- Improved the `site-packages` directory detection ([#1683](https://github.com/python-poetry/poetry/pull/1683)).
### Fixed ### Fixed
...@@ -56,6 +81,18 @@ ...@@ -56,6 +81,18 @@
- Fixed dependency resolution for conditional development dependencies. - Fixed dependency resolution for conditional development dependencies.
- Fixed generated dependency constraints when they contain inequality operators. - Fixed generated dependency constraints when they contain inequality operators.
- The `run` command now properly handles the `--` separator. - The `run` command now properly handles the `--` separator.
- Fixed some issues with `path` dependencies being seen as `git` dependencies.
- Fixed various issues with the way `extra` markers in dependencies were handled.
- Fixed the option conflicts in the `run` command.
- Fixed wrong latest version being displayed when executing `show -l`.
- Fixed `TooManyRedirects` errors being raised when resolving dependencies.
- Fixed custom indices dependencies being constantly updated.
- Fixed the behavior of the `--install` option of the debug resolve command.
- Fixed an error in `show` when using the `-o/--outdated` option.
- Fixed PEP 508 url dependency handling.
- Fixed excluded files via the `exclude` being included in distributions.
- Fixed an error in `env use` if the `virtualenvs.in-project` setting is activated ([#1682](https://github.com/python-poetry/poetry/pull/1682))
- Fixed handling of `empty` and `any` markers in unions of markers ([#1650](https://github.com/python-poetry/poetry/pull/1650)).
## [0.12.17] - 2019-07-03 ## [0.12.17] - 2019-07-03
...@@ -739,56 +776,58 @@ Initial release ...@@ -739,56 +776,58 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.17...master [Unreleased]: https://github.com/python-poetry/poetry/compare/1.0.1...master
[0.12.17]: https://github.com/sdispater/poetry/releases/tag/0.12.17 [1.0.1]: https://github.com/python-poetry/poetry/releases/tag/1.0.1
[0.12.16]: https://github.com/sdispater/poetry/releases/tag/0.12.16 [1.0.0]: https://github.com/python-poetry/poetry/releases/tag/1.0.0
[0.12.15]: https://github.com/sdispater/poetry/releases/tag/0.12.15 [0.12.17]: https://github.com/python-poetry/poetry/releases/tag/0.12.17
[0.12.14]: https://github.com/sdispater/poetry/releases/tag/0.12.14 [0.12.16]: https://github.com/python-poetry/poetry/releases/tag/0.12.16
[0.12.13]: https://github.com/sdispater/poetry/releases/tag/0.12.13 [0.12.15]: https://github.com/python-poetry/poetry/releases/tag/0.12.15
[0.12.12]: https://github.com/sdispater/poetry/releases/tag/0.12.12 [0.12.14]: https://github.com/python-poetry/poetry/releases/tag/0.12.14
[0.12.11]: https://github.com/sdispater/poetry/releases/tag/0.12.11 [0.12.13]: https://github.com/python-poetry/poetry/releases/tag/0.12.13
[0.12.10]: https://github.com/sdispater/poetry/releases/tag/0.12.10 [0.12.12]: https://github.com/python-poetry/poetry/releases/tag/0.12.12
[0.12.9]: https://github.com/sdispater/poetry/releases/tag/0.12.9 [0.12.11]: https://github.com/python-poetry/poetry/releases/tag/0.12.11
[0.12.8]: https://github.com/sdispater/poetry/releases/tag/0.12.8 [0.12.10]: https://github.com/python-poetry/poetry/releases/tag/0.12.10
[0.12.7]: https://github.com/sdispater/poetry/releases/tag/0.12.7 [0.12.9]: https://github.com/python-poetry/poetry/releases/tag/0.12.9
[0.12.6]: https://github.com/sdispater/poetry/releases/tag/0.12.6 [0.12.8]: https://github.com/python-poetry/poetry/releases/tag/0.12.8
[0.12.5]: https://github.com/sdispater/poetry/releases/tag/0.12.5 [0.12.7]: https://github.com/python-poetry/poetry/releases/tag/0.12.7
[0.12.4]: https://github.com/sdispater/poetry/releases/tag/0.12.4 [0.12.6]: https://github.com/python-poetry/poetry/releases/tag/0.12.6
[0.12.3]: https://github.com/sdispater/poetry/releases/tag/0.12.3 [0.12.5]: https://github.com/python-poetry/poetry/releases/tag/0.12.5
[0.12.2]: https://github.com/sdispater/poetry/releases/tag/0.12.2 [0.12.4]: https://github.com/python-poetry/poetry/releases/tag/0.12.4
[0.12.1]: https://github.com/sdispater/poetry/releases/tag/0.12.1 [0.12.3]: https://github.com/python-poetry/poetry/releases/tag/0.12.3
[0.12.0]: https://github.com/sdispater/poetry/releases/tag/0.12.0 [0.12.2]: https://github.com/python-poetry/poetry/releases/tag/0.12.2
[0.11.5]: https://github.com/sdispater/poetry/releases/tag/0.11.5 [0.12.1]: https://github.com/python-poetry/poetry/releases/tag/0.12.1
[0.11.4]: https://github.com/sdispater/poetry/releases/tag/0.11.4 [0.12.0]: https://github.com/python-poetry/poetry/releases/tag/0.12.0
[0.11.3]: https://github.com/sdispater/poetry/releases/tag/0.11.3 [0.11.5]: https://github.com/python-poetry/poetry/releases/tag/0.11.5
[0.11.2]: https://github.com/sdispater/poetry/releases/tag/0.11.2 [0.11.4]: https://github.com/python-poetry/poetry/releases/tag/0.11.4
[0.11.1]: https://github.com/sdispater/poetry/releases/tag/0.11.1 [0.11.3]: https://github.com/python-poetry/poetry/releases/tag/0.11.3
[0.11.0]: https://github.com/sdispater/poetry/releases/tag/0.11.0 [0.11.2]: https://github.com/python-poetry/poetry/releases/tag/0.11.2
[0.10.3]: https://github.com/sdispater/poetry/releases/tag/0.10.3 [0.11.1]: https://github.com/python-poetry/poetry/releases/tag/0.11.1
[0.10.2]: https://github.com/sdispater/poetry/releases/tag/0.10.2 [0.11.0]: https://github.com/python-poetry/poetry/releases/tag/0.11.0
[0.10.1]: https://github.com/sdispater/poetry/releases/tag/0.10.1 [0.10.3]: https://github.com/python-poetry/poetry/releases/tag/0.10.3
[0.10.0]: https://github.com/sdispater/poetry/releases/tag/0.10.0 [0.10.2]: https://github.com/python-poetry/poetry/releases/tag/0.10.2
[0.9.1]: https://github.com/sdispater/poetry/releases/tag/0.9.1 [0.10.1]: https://github.com/python-poetry/poetry/releases/tag/0.10.1
[0.9.0]: https://github.com/sdispater/poetry/releases/tag/0.9.0 [0.10.0]: https://github.com/python-poetry/poetry/releases/tag/0.10.0
[0.8.6]: https://github.com/sdispater/poetry/releases/tag/0.8.6 [0.9.1]: https://github.com/python-poetry/poetry/releases/tag/0.9.1
[0.8.5]: https://github.com/sdispater/poetry/releases/tag/0.8.5 [0.9.0]: https://github.com/python-poetry/poetry/releases/tag/0.9.0
[0.8.4]: https://github.com/sdispater/poetry/releases/tag/0.8.4 [0.8.6]: https://github.com/python-poetry/poetry/releases/tag/0.8.6
[0.8.3]: https://github.com/sdispater/poetry/releases/tag/0.8.3 [0.8.5]: https://github.com/python-poetry/poetry/releases/tag/0.8.5
[0.8.2]: https://github.com/sdispater/poetry/releases/tag/0.8.2 [0.8.4]: https://github.com/python-poetry/poetry/releases/tag/0.8.4
[0.8.1]: https://github.com/sdispater/poetry/releases/tag/0.8.1 [0.8.3]: https://github.com/python-poetry/poetry/releases/tag/0.8.3
[0.8.0]: https://github.com/sdispater/poetry/releases/tag/0.8.0 [0.8.2]: https://github.com/python-poetry/poetry/releases/tag/0.8.2
[0.7.1]: https://github.com/sdispater/poetry/releases/tag/0.7.1 [0.8.1]: https://github.com/python-poetry/poetry/releases/tag/0.8.1
[0.7.0]: https://github.com/sdispater/poetry/releases/tag/0.7.0 [0.8.0]: https://github.com/python-poetry/poetry/releases/tag/0.8.0
[0.6.5]: https://github.com/sdispater/poetry/releases/tag/0.6.5 [0.7.1]: https://github.com/python-poetry/poetry/releases/tag/0.7.1
[0.6.4]: https://github.com/sdispater/poetry/releases/tag/0.6.4 [0.7.0]: https://github.com/python-poetry/poetry/releases/tag/0.7.0
[0.6.3]: https://github.com/sdispater/poetry/releases/tag/0.6.3 [0.6.5]: https://github.com/python-poetry/poetry/releases/tag/0.6.5
[0.6.2]: https://github.com/sdispater/poetry/releases/tag/0.6.2 [0.6.4]: https://github.com/python-poetry/poetry/releases/tag/0.6.4
[0.6.1]: https://github.com/sdispater/poetry/releases/tag/0.6.1 [0.6.3]: https://github.com/python-poetry/poetry/releases/tag/0.6.3
[0.6.0]: https://github.com/sdispater/poetry/releases/tag/0.6.0 [0.6.2]: https://github.com/python-poetry/poetry/releases/tag/0.6.2
[0.5.0]: https://github.com/sdispater/poetry/releases/tag/0.5.0 [0.6.1]: https://github.com/python-poetry/poetry/releases/tag/0.6.1
[0.4.2]: https://github.com/sdispater/poetry/releases/tag/0.4.2 [0.6.0]: https://github.com/python-poetry/poetry/releases/tag/0.6.0
[0.4.1]: https://github.com/sdispater/poetry/releases/tag/0.4.1 [0.5.0]: https://github.com/python-poetry/poetry/releases/tag/0.5.0
[0.4.0]: https://github.com/sdispater/poetry/releases/tag/0.4.0 [0.4.2]: https://github.com/python-poetry/poetry/releases/tag/0.4.2
[0.3.0]: https://github.com/sdispater/poetry/releases/tag/0.3.0 [0.4.1]: https://github.com/python-poetry/poetry/releases/tag/0.4.1
[0.2.0]: https://github.com/sdispater/poetry/releases/tag/0.2.0 [0.4.0]: https://github.com/python-poetry/poetry/releases/tag/0.4.0
[0.1.0]: https://github.com/sdispater/poetry/releases/tag/0.1.0 [0.3.0]: https://github.com/python-poetry/poetry/releases/tag/0.3.0
[0.2.0]: https://github.com/python-poetry/poetry/releases/tag/0.2.0
[0.1.0]: https://github.com/python-poetry/poetry/releases/tag/0.1.0
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at sebastien@eustace.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
...@@ -20,18 +20,18 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These ...@@ -20,18 +20,18 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These
This section guides you through submitting a bug report for Poetry. This section guides you through submitting a bug report for Poetry.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md), the information it asks helps the maintainers resolve the issue faster. Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md), the information it asks helps the maintainers resolve the issue faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before submitting a bug report #### Before submitting a bug report
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems. * **Check the [FAQs on the official website](https://python-poetry.org)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**. * **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues)**.
#### How do I submit a bug report? #### How do I submit a bug report?
Bugs are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md). Bugs are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem: Explain the problem and include additional details to help maintainers reproduce the problem:
...@@ -60,17 +60,17 @@ Include details about your configuration and environment: ...@@ -60,17 +60,17 @@ Include details about your configuration and environment:
This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/2_Feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed. Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/2_Feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before submitting an enhancement suggestion #### Before submitting an enhancement suggestion
* **Check the [FAQs on the official website](https://poetry.eustace.io) for a list of common questions and problems. * **Check the [FAQs on the official website](https://python-poetry.org) for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues). * **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues).
#### How do I submit an Enhancement suggestion? #### How do I submit an Enhancement suggestion?
Enhancement suggestions are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information: Enhancement suggestions are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion. * **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. * **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
...@@ -82,12 +82,12 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith ...@@ -82,12 +82,12 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith
#### Local development #### Local development
You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://poetry.eustace.io/docs/#introduction) to start using Poetry. You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://python-poetry.org/docs/#introduction) to start using Poetry.
You will first need to clone the repository using `git` and place yourself in its directory: You will first need to clone the repository using `git` and place yourself in its directory:
```bash ```bash
$ git clone git@github.com:sdispater/poetry.git $ git clone git@github.com:python-poetry/poetry.git
$ cd poetry $ cd poetry
``` ```
...@@ -123,6 +123,6 @@ will not be merged. ...@@ -123,6 +123,6 @@ will not be merged.
#### Pull requests #### Pull requests
* Fill in [the required template](https://github.com/sdispater/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md) * Fill in [the required template](https://github.com/python-poetry/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
* Be sure that your pull request contains tests that cover the changed or added code. * Be sure that your pull request contains tests that cover the changed or added code.
* If your changes warrant a documentation change, the pull request must also update the documentation. * If your changes warrant a documentation change, the pull request must also update the documentation.
# This file is part of Poetry # This file is part of Poetry
# https://github.com/sdispater/poetry # https://github.com/python-poetry/poetry
# Licensed under the MIT license: # Licensed under the MIT license:
# http://www.opensource.org/licenses/MIT-license # http://www.opensource.org/licenses/MIT-license
...@@ -48,7 +48,7 @@ wheel: ...@@ -48,7 +48,7 @@ wheel:
linux_release: linux_release:
docker pull quay.io/pypa/manylinux2010_x86_64 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 docker run --rm -i -v `pwd`:/io quay.io/pypa/manylinux2010_x86_64 /io/make-linux-release.sh
# run tests against all supported python versions # run tests against all supported python versions
tox: tox:
......
...@@ -3,11 +3,13 @@ ...@@ -3,11 +3,13 @@
Poetry helps you declare, manage and install dependencies of Python projects, Poetry helps you declare, manage and install dependencies of Python projects,
ensuring you have the right stack everywhere. ensuring you have the right stack everywhere.
![Poetry Install](https://raw.githubusercontent.com/sdispater/poetry/master/assets/install.gif) ![Poetry Install](https://raw.githubusercontent.com/python-poetry/poetry/master/assets/install.gif)
It supports Python 2.7 and 3.4+. It supports Python 2.7 and 3.4+.
![Tests Status](https://github.com/sdispater/poetry/workflows/Tests/badge.svg) ![Tests Status](https://github.com/python-poetry/poetry/workflows/Tests/badge.svg)
The [complete documentation](https://python-poetry.org/docs/) is available on the [official website](https://python-poetry.org).
## Installation ## Installation
...@@ -16,7 +18,7 @@ from the rest of your system by vendorizing its dependencies. This is the ...@@ -16,7 +18,7 @@ from the rest of your system by vendorizing its dependencies. This is the
recommended way of installing `poetry`. recommended way of installing `poetry`.
```bash ```bash
curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
``` ```
Alternatively, you can download the `get-poetry.py` file and execute it separately. Alternatively, you can download the `get-poetry.py` file and execute it separately.
...@@ -44,25 +46,29 @@ which might cause conflicts. ...@@ -44,25 +46,29 @@ which might cause conflicts.
## Updating `poetry` ## Updating `poetry`
Updating poetry to the latest stable version is as simple as calling the `self:update` command. Updating poetry to the latest stable version is as simple as calling the `self update` command.
```bash ```bash
poetry self:update poetry self update
``` ```
If you want to install prerelease versions, you can use the `--preview` option. If you want to install prerelease versions, you can use the `--preview` option.
```bash ```bash
poetry self:update --preview poetry self update --preview
``` ```
And finally, if you want to install a specific version you can pass it as an argument And finally, if you want to install a specific version you can pass it as an argument
to `self:update`. to `self update`.
```bash ```bash
poetry self:update 0.8.0 poetry self update 1.0.0
``` ```
!!!note
If you are still on poetry version < 1.0 use `poetry self:update` instead.
## Enable tab completion for Bash, Fish, or Zsh ## Enable tab completion for Bash, Fish, or Zsh
...@@ -118,8 +124,8 @@ authors = [ ...@@ -118,8 +124,8 @@ authors = [
readme = 'README.md' # Markdown files are supported readme = 'README.md' # Markdown files are supported
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
homepage = "https://github.com/sdispater/poetry" homepage = "https://github.com/python-poetry/poetry"
keywords = ['packaging', 'poetry'] keywords = ['packaging', 'poetry']
...@@ -129,7 +135,7 @@ toml = "^0.9" ...@@ -129,7 +135,7 @@ toml = "^0.9"
# Dependencies with extras # Dependencies with extras
requests = { version = "^2.13", extras = [ "security" ] } requests = { version = "^2.13", extras = [ "security" ] }
# Python specific dependencies with prereleases allowed # Python specific dependencies with prereleases allowed
pathlib2 = { version = "^2.2", python = "~2.7", allows-prereleases = true } pathlib2 = { version = "^2.2", python = "~2.7", allow-prereleases = true }
# Git dependencies # Git dependencies
cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" } cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" }
...@@ -238,599 +244,8 @@ so `poetry` will try to find a version of `oslo.i18n` that satisfies `pbr (>=0.6 ...@@ -238,599 +244,8 @@ so `poetry` will try to find a version of `oslo.i18n` that satisfies `pbr (>=0.6
By analyzing the releases of `oslo.i18n`, it will find `oslo.i18n==2.1.0` which requires `pbr (>=0.11,<2.0)`. By analyzing the releases of `oslo.i18n`, it will find `oslo.i18n==2.1.0` which requires `pbr (>=0.11,<2.0)`.
At this point the rest of the resolution is straightforward since there is no more conflict. At this point the rest of the resolution is straightforward since there is no more conflict.
#### Install command
When you specify a package to the `install` command it will add it as a wildcard
dependency. This means that **any** version of this package can be installed which
can lead to compatibility issues.
Also, you have to explicitly tell it to not update the locked packages when you
install new ones. This should be the default.
#### Uninstall command
The `uninstall` command will only remove the package specified but not its dependencies
if they are no longer needed.
You either have to use `sync` or `clean` to fix that.
#### Too limited in scope
Finally, the `Pipfile` is just a replacement from `requirements.txt` and, in the end, you will still need to
populate your `setup.py` file (or `setup.cfg`) with the exact same dependencies you declared in your `Pipfile`.
So, in the end, you will still need to manage a few configuration files to properly setup your project.
## Commands
### new
This command will help you kickstart your new Python project by creating
a directory structure suitable for most projects.
```bash
poetry new my-package
```
will create a folder as follows:
```text
my-package
├── pyproject.toml
├── README.rst
├── my_package
│ └── __init__.py
└── tests
├── __init__.py
└── test_my_package
```
If you want to name your project differently than the folder, you can pass
the `--name` option:
```bash
poetry new my-folder --name my-package
```
### init
This command will help you create a `pyproject.toml` file interactively
by prompting you to provide basic information about your package.
It will interactively ask you to fill in the fields, while using some smart defaults.
```bash
poetry init
```
#### Options
* `--name`: Name of the package.
* `--description`: Description of the package.
* `--author`: Author of the package.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--require`.
### install
The `install` command reads the `pyproject.toml` file from the current directory, resolves the dependencies,
and installs them.
```bash
poetry install
```
If there is a `poetry.lock` file in the current directory,
it will use the exact versions from there instead of resolving them.
This ensures that everyone using the library will get the same versions of the dependencies.
If there is no `poetry.lock` file, Poetry will create one after dependency resolution.
You can specify to the command that you do not want the development dependencies installed by passing
the `--no-dev` option.
```bash
poetry install --no-dev
```
You can also specify the extras you want installed
by passing the `-E|--extras` option (See [Extras](#extras) for more info)
```bash
poetry install --extras "mysql pgsql"
poetry install -E mysql -E pgsql
```
By default `poetry` will install your project's package everytime you run `install`:
```bash
$ poetry install
Installing dependencies from lock file
Nothing to install or update
- Installing <your-package-name> (x.x.x)
```
If you want to skip this installation, use the `--no-root` option.
```bash
poetry install --no-root
```
#### Options
* `--no-dev`: Do not install dev dependencies.
* `--no-root`: Do not install the root package (your project).
* `-E|--extras`: Features to install (multiple values allowed).
### update
In order to get the latest versions of the dependencies and to update the `poetry.lock` file,
you should use the `update` command.
```bash
poetry update
```
This will resolve all dependencies of the project and write the exact versions into `poetry.lock`.
If you just want to update a few packages and not all, you can list them as such:
```bash
poetry update requests toml
```
#### Options
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
* `--no-dev` : Do not install dev dependencies.
* `--lock` : Do not perform install (only update the lockfile).
### add
The `add` command adds required packages to your `pyproject.toml` and installs them.
If you do not specify a version constraint,
poetry will choose a suitable one based on the available package versions.
```bash
poetry add requests pendulum
```
#### Options
* `--D|dev`: Add package as development dependency.
* `--optional` : Add as an optional dependency.
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
### remove
The `remove` command removes a package from the current
list of installed packages
```bash
poetry remove pendulum
```
#### Options
* `--D|dev`: Removes a package from the development dependencies.
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
### show
To list all of the available packages, you can use the `show` command.
```bash
poetry show
```
If you want to see the details of a certain package, you can pass the package name.
```bash
poetry show pendulum
name : pendulum
version : 1.4.2
description : Python datetimes made easy
dependencies:
- python-dateutil >=2.6.1
- tzlocal >=1.4
- pytzdata >=2017.2.2
```
#### Options
* `--no-dev`: Do not list the dev dependencies.
* `--tree`: List the dependencies as a tree.
* `-l|--latest`: Show the latest version.
* `-o|--outdated`: Show the latest version but only for packages that are outdated.
### build
The `build` command builds the source and wheels archives.
```bash
poetry build
```
Note that, at the moment, only pure python wheels are supported.
#### Options
* `-f|--format`: Limit the format to either wheel or sdist.
### publish
This command builds (if not already built) and publishes the package to the remote repository.
It will automatically register the package before uploading if this is the first time it is submitted.
```bash
poetry publish
```
#### Options
* `-r|--repository`: The repository to register the package to (default: `pypi`).
Should match a repository name set by the [`config`](#config) command.
* `--username (-u)`: The username to access the repository.
* `--password (-p)`: The password to access the repository.
### `config`
The `config` command allows you to edit poetry config settings and repositories.
```bash
poetry config --list
```
#### Usage
````bash
poetry config [options] [setting-key] [setting-value1] ... [setting-valueN]
````
`setting-key` is a configuration option name and `setting-value1` is a configuration value.
#### Modifying repositories
In addition to modifying the config section,
the config command also supports making changes to the repositories section by using it the following way:
```bash
poetry config repositories.foo https://foo.bar/simple/
```
This will set the url for repository `foo` to `https://foo.bar/simple/`.
If you want to store your credentials for a specific repository, you can do so easily:
```bash
poetry config http-basic.foo username password
```
If you do not specify the password you will be prompted to write it.
#### Options
* `--unset`: Remove the configuration element named by `setting-key`.
* `--list`: Show the list of current config variables.
### search
This command searches for packages on a remote index.
```bash
poetry search requests pendulum
```
#### Options
* `-N|--only-name`: Search only in name.
### lock
This command locks (without installing) the dependencies specified in `pyproject.toml`.
```bash
poetry lock
```
### export
This command exports the lock file to other formats.
```bash
poetry export -f requirements.txt > requirements.txt
```
#### Options
* `--format (-f)`: the format to export to. Currently, only
`requirements.txt` is supported.
* `--output (-o)`: the name of the output file. If omitted, print to standard
output.
## The `pyproject.toml` file
The `tool.poetry` section of the `pyproject.toml` file is composed of multiple sections.
#### name
The name of the package. **Required**
#### version
The version of the package. **Required**
This should follow [semantic versioning](http://semver.org/). However it will not be enforced and you remain
free to follow another specification.
#### description
A short description of the package. **Required**
#### license
The license of the package.
The recommended notation for the most common licenses is (alphabetical):
* Apache-2.0
* BSD-2-Clause
* BSD-3-Clause
* BSD-4-Clause
* GPL-2.0
* GPL-2.0+
* GPL-3.0
* GPL-3.0+
* LGPL-2.1
* LGPL-2.1+
* LGPL-3.0
* LGPL-3.0+
* MIT
Optional, but it is highly recommended to supply this.
More identifiers are listed at the [SPDX Open Source License Registry](https://www.spdx.org/licenses/).
#### authors
The authors of the package. **Required**
This is a list of authors and should contain at least one author. Authors must be in the form `name <email>`.
#### readme
The readme file of the package. **Required**
The file can be either `README.rst` or `README.md`.
#### homepage
An URL to the website of the project. **Optional**
#### repository
An URL to the repository of the project. **Optional**
#### documentation
An URL to the documentation of the project. **Optional**
#### keywords
A list of keywords (max: 5) that the package is related to. **Optional**
#### include and exclude
A list of patterns that will be included in the final package.
You can explicitly specify to Poetry that a set of globs should be ignored or included for the purposes of packaging.
The globs specified in the exclude field identify a set of files that are not included when a package is built.
If a VCS is being used for a package, the exclude field will be seeded with the VCS’ ignore settings (`.gitignore` for git for example).
```toml
[tool.poetry]
# ...
include = ["package/**/*.py", "package/**/.c"]
```
```toml
exclude = ["package/excluded.py"]
```
### `dependencies` and `dev-dependencies`
Poetry is configured to look for dependencies on [PyPi](https://pypi.org) by default.
Only the name and a version string are required in this case.
```toml
[tool.poetry.dependencies]
requests = "^2.13.0"
```
If you want to use a private repository, you can add it to your `pyproject.toml` file, like so:
```toml
[[tool.poetry.source]]
name = 'private'
url = 'http://example.com/simple'
```
Be aware that declaring the python version for which your package
is compatible is mandatory:
```toml
[tool.poetry.dependencies]
python = "^3.6"
```
#### Caret requirement
**Caret requirements** allow SemVer compatible updates to a specified version.
An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping.
In this case, if we ran `poetry update requests`, poetry would update us to version `2.14.0` if it was available,
but would not update us to `3.0.0`.
If instead we had specified the version string as `^0.1.13`, poetry would update to `0.1.14` but not `0.2.0`.
`0.0.x` is not considered compatible with any other version.
Here are some more examples of caret requirements and the versions that would be allowed with them:
```text
^1.2.3 := >=1.2.3 <2.0.0
^1.2 := >=1.2.0 <2.0.0
^1 := >=1.0.0 <2.0.0
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4
^0.0 := >=0.0.0 <0.1.0
^0 := >=0.0.0 <1.0.0
```
#### Tilde requirements
**Tilde requirements** specify a minimal version with some ability to update.
If you specify a major, minor, and patch version or only a major and minor version, only patch-level changes are allowed.
If you only specify a major version, then minor- and patch-level changes are allowed.
`~1.2.3` is an example of a tilde requirement.
```text
~1.2.3 := >=1.2.3 <1.3.0
~1.2 := >=1.2.0 <1.3.0
~1 := >=1.0.0 <2.0.0
```
#### Wildcard requirements
**Wildcard requirements** allow for any version where the wildcard is positioned.
`*`, `1.*` and `1.2.*` are examples of wildcard requirements.
```text
* := >=0.0.0
1.* := >=1.0.0 <2.0.0
1.2.* := >=1.2.0 <1.3.0
```
#### Inequality requirements
**Inequality requirements** allow manually specifying a version range or an exact version to depend on.
Here are some examples of inequality requirements:
```text
>= 1.2.0
> 1
< 2
!= 1.2.3
```
#### Multiple requirements
Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, < 1.5`.
#### `git` dependencies
To depend on a library located in a `git` repository,
the minimum information you need to specify is the location of the repository with the git key:
```toml
[tool.poetry.dependencies]
requests = { git = "https://github.com/requests/requests.git" }
```
Since we haven’t specified any other information,
Poetry assumes that we intend to use the latest commit on the `master` branch to build our project.
You can combine the `git` key with the `rev`, `tag`, or `branch` keys to specify something else.
Here's an example of specifying that you want to use the latest commit on a branch named `next`:
```toml
[tool.poetry.dependencies]
requests = { git = "https://github.com/kennethreitz/requests.git", branch = "next" }
```
#### Python restricted dependencies
You can also specify that a dependency should be installed only for specific Python versions:
```toml
[tool.poetry.dependencies]
pathlib2 = { version = "^2.2", python = "~2.7" }
```
```toml
[tool.poetry.dependencies]
pathlib2 = { version = "^2.2", python = ["~2.7", "^3.2"] }
```
### `scripts`
This section describe the scripts or executable that will be installed when installing the package
```toml
[tool.poetry.scripts]
poetry = 'poetry.console:run'
```
After installing a package with the above toml, `poetry` will be a global command available from the command line that will execute `console.run` in the `poetry` package.
### `extras`
Poetry supports extras to allow expression of:
* optional dependencies, which enhance a package, but are not required; and
* clusters of optional dependencies.
```toml
[tool.poetry]
name = "awesome"
[tool.poetry.dependencies]
# These packages are mandatory and form the core of this package’s distribution.
mandatory = "^1.0"
# A list of all of the optional dependencies, some of which are included in the
# below `extras`. They can be opted into by apps.
psycopg2 = { version = "^2.7", optional = true }
mysqlclient = { version = "^1.3", optional = true }
[tool.poetry.extras]
mysql = ["mysqlclient"]
pgsql = ["psycopg2"]
```
When installing packages, you can specify extras by using the `-E|--extras` option:
```bash
poetry install --extras "mysql pgsql"
poetry install -E mysql -E pgsql
```
### `plugins`
Poetry supports arbitrary plugins which work similarly to
[setuptools entry points](http://setuptools.readthedocs.io/en/latest/setuptools.html).
To match the example in the setuptools documentation, you would use the following:
```toml
[tool.poetry.plugins] # Optional super table
[tool.poetry.plugins."blogtool.parsers"]
".rst" = "some_module::SomeClass"
```
## Resources ## Resources
* [Official Website](https://poetry.eustace.io) * [Official Website](https://python-poetry.org)
* [Issue Tracker](https://github.com/sdispater/poetry/issues) * [Issue Tracker](https://github.com/python-poetry/poetry/issues)
* [Discord](https://discordapp.com/invite/awxPgve)
...@@ -69,7 +69,8 @@ It will automatically find a suitable version constraint **and install** the pac ...@@ -69,7 +69,8 @@ It will automatically find a suitable version constraint **and install** the pac
In our example, we are requesting the `pendulum` package with the version constraint `^1.4`. In our example, we are requesting the `pendulum` package with the version constraint `^1.4`.
This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`). This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`).
Please read [versions](/docs/versions/) for more in-depth information on versions, how versions relate to each other, and on version constraints. Please read [Dependency specification](/docs/dependency-specification) for more in-depth information on versions,
how versions relate to each other, and on the different ways you can specify dependencies.
!!!note !!!note
...@@ -147,107 +148,3 @@ and update the lock file with the new versions. ...@@ -147,107 +148,3 @@ and update the lock file with the new versions.
Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml` Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml`
are not synchronized. are not synchronized.
## Poetry and virtualenvs
When you execute the `install` command (or any other "install" commands like `add` or `remove`),
Poetry will check if it's currently inside a virtualenv and, if not, will use an existing one
or create a brand new one for you to always work isolated from your global Python installation.
By default, Poetry will use the currently activated Python version
to create the virtualenv for the current project.
To easily switch between Python versions, it is recommended to
use [pyenv](https://github.com/pyenv/pyenv) or similar tools.
For instance, if your project is Python 2.7 only, a standard workflow
would be:
```bash
pyenv install 2.7.15
pyenv local 2.7.15 # Activate Python 2.7 for the current project
poetry install
```
However, this might not be feasible for your system, especially Windows where `pyenv`,
is not available. To circumvent that you can use the `env use` command to tell
Poetry which Python version to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtualenv, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
If you want to get basic information about the currently activated virtualenv,
you can use the `env info` command:
```bash
poetry env info
```
will output something similar to this:
```text
Virtualenv
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
System
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtualenv, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
You can also list all the virtualenvs associated with the current virtualenv
with the `env list` command:
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py2.7
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
Finally, you can delete existing virtualenvs by using `env remove`:
```bash
poetry env remove /full/path/to/python
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
If your remove the currently activated virtualenv, it will be automatically deactivated.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
You've already learned how to use the command-line interface to do some things. You've already learned how to use the command-line interface to do some things.
This chapter documents all the available commands. This chapter documents all the available commands.
To get help from the command-line, simply call `poetry` or `poetry list` to see the complete list of commands, To get help from the command-line, simply call `poetry` to see the complete list of commands,
then `--help` combined with any of those can give you more information. then `--help` combined with any of those can give you more information.
As `Poetry` uses [cleo](https://github.com/sdispater/cleo) you can call commands by short name if it's not ambiguous. As `Poetry` uses [cleo](https://github.com/sdispater/cleo) you can call commands by short name if it's not ambiguous.
...@@ -130,7 +130,7 @@ By default `poetry` will install your project's package everytime you run `insta ...@@ -130,7 +130,7 @@ By default `poetry` will install your project's package everytime you run `insta
$ poetry install $ poetry install
Installing dependencies from lock file Installing dependencies from lock file
Nothing to install or update No dependencies to install or update
- Installing <your-package-name> (x.x.x) - Installing <your-package-name> (x.x.x)
...@@ -208,8 +208,8 @@ If you need to checkout a specific branch, tag or revision, ...@@ -208,8 +208,8 @@ If you need to checkout a specific branch, tag or revision,
you can specify it when using `add`: you can specify it when using `add`:
```bash ```bash
poetry add git+https://github.com/sdispater/pendulum.git@develop poetry add git+https://github.com/sdispater/pendulum.git#develop
poetry add git+https://github.com/sdispater/pendulum.git@2.0.5 poetry add git+https://github.com/sdispater/pendulum.git#2.0.5
``` ```
or make them point to a local directory or file: or make them point to a local directory or file:
...@@ -451,106 +451,4 @@ poetry export -f requirements.txt > requirements.txt ...@@ -451,106 +451,4 @@ poetry export -f requirements.txt > requirements.txt
The `env` command regroups sub commands to interact with the virtualenvs The `env` command regroups sub commands to interact with the virtualenvs
associated with a specific project. associated with a specific project.
### env use See [Managing environments](./managing-environments.md) for more information about these commands.
The `env use` command tells Poetry which Python version
to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtualenv, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
### env info
The `env info` command displays basic information about the currently activated virtualenv:
```bash
poetry env info
```
will output something similar to this:
```text
Virtualenv
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
System
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtualenv, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
#### Options
* `--path`: Only display the path of the virtualenv.
### env list
The `env list` command lists all the virtualenvs associated with the current virtualenv.
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py2.7
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
#### Options
* `--full-path`: Display the full path of the virtualenvs.
### env remove
The `env remove` command deletes virtualenvs associated with the current project:
```bash
poetry env remove /full/path/to/python
```
Similarly to `env use`, you can either pass `python3.7`, `3.7` or the name of
the virtualenv (as returned by `env list`):
```bash
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
!!!note
If your remove the currently activated virtualenv, it will be automatically deactivated.
...@@ -100,11 +100,11 @@ Defaults to one of the following directories: ...@@ -100,11 +100,11 @@ Defaults to one of the following directories:
- macOS: `~/Library/Caches/pypoetry` - macOS: `~/Library/Caches/pypoetry`
- Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache` - Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache`
- Unix: `~/.cache/pypoetry/virtualenvs` - Unix: `~/.cache/pypoetry`
### `virtualenvs.create`: boolean ### `virtualenvs.create`: boolean
Create a new virtualenv if one doesn't already exist. Create a new virtual environment if one doesn't already exist.
Defaults to `true`. Defaults to `true`.
### `virtualenvs.in-project`: boolean ### `virtualenvs.in-project`: boolean
...@@ -114,7 +114,7 @@ Defaults to `false`. ...@@ -114,7 +114,7 @@ Defaults to `false`.
### `virtualenvs.path`: string ### `virtualenvs.path`: string
Directory where virtualenvs will be created. Directory where virtual environments will be created.
Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows).
### `repositories.<name>`: string ### `repositories.<name>`: string
......
# Contributing to Poetry {!../CONTRIBUTING.md!}
First off, thank for taking the time to contribute!
The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table of Contents
[How to contribute](#how-to-contribute)
* [Reporting bugs](#reporting-bugs)
* [Suggesting enhancements](#suggesting-enhancements)
* [Contributing to code](#contributing-to-code)
## How to contribute
### Reporting bugs
This section guides you through submitting a bug report for Poetry.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md), the information it asks helps the maintainers resolve the issue faster.
!!!note
If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before submitting a bug report
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**.
#### How do I submit a bug report
Bugs are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/1_Bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible.
* **Provide your pyproject.toml file** in a [Gist](https://gist.github.com) after removing potential private information (like private package repositories).
* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples.
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **If the problem is an unexpected error being raised**, execute the corresponding command in **debug** mode (the `-vvv` option).
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version of Poetry) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Poetry?** What's the most recent version in which the problem doesn't happen?
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
Include details about your configuration and environment:
* **Which version of Poetry are you using?** You can get the exact version by running `poetry -V` in your terminal.
* **Which Python version Poetry has been installed for?** Execute the `debug:info` to get the information.
* **What's the name and version of the OS you're using**?
### Suggesting enhancements
This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/sdispater/poetry/blob/master/.github/ISSUE_TEMPLATE/2_Feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before submitting an enhancement suggestion
* **Check the [FAQs on the official website](https://poetry.eustace.io)** for a list of common questions and problems.
* **Check that your issue does not already exist in the [issue tracker](https://github.com/sdispater/poetry/issues)**.
#### How do I submit an enhancement suggestion?
Enhancement suggestions are tracked on the [official issue tracker](https://github.com/sdispater/poetry/issues) where you can create a new one and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**..
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
### Contributing to code
#### Local development
You will need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://poetry.eustace.io/docs/#introduction) to start using Poetry.
You will first need to clone the repository using `git` and place yourself in its directory:
```bash
$ git clone git@github.com:sdispater/poetry.git
$ cd poetry
```
Now, you will need to install the required dependency for Poetry and be sure that the current
tests are passing on your machine:
```bash
$ poetry install
$ poetry run pytest tests/
```
Poetry uses the [black](https://github.com/ambv/black) coding style and you must ensure that your
code follows it. If not, the CI will fail and your Pull Request will not be merged.
Similarly, the import statements are sorted with [isort](https://github.com/timothycrosley/isort)
and special care must be taken to respect it. If you don't, the CI will fail as well.
To make sure that you don't accidentally commit code that does not follow the coding style, you can
install a pre-commit hook that will check that everything is in order:
```bash
$ poetry run pre-commit install
```
You can also run it anytime using:
```bash
$ poetry run pre-commit run --all-files
```
Your code must always be accompanied by corresponding tests, if tests are not present your code
will not be merged.
#### Pull requests
* Fill in [the required template](https://github.com/sdispater/poetry/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
* Be sure that you pull request contains tests that cover the changed or added code.
* If you changes warrant a documentation change, the pull request must also update the documentation.
# Versions and constraints # Dependency specification
Poetry recommends following [semantic versioning](https://semver.org) but will not enforce it. Dependencies for a project can be specified in various forms, which depend on the type
of the dependency and on the optional constraints that might be needed for it to be installed.
## Version constraints ## Version constraints
...@@ -74,7 +75,7 @@ If other dependencies require a different version, the solver will ultimately fa ...@@ -74,7 +75,7 @@ If other dependencies require a different version, the solver will ultimately fa
Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, < 1.5`. Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, < 1.5`.
### `git` dependencies ## `git` dependencies
To depend on a library located in a `git` repository, To depend on a library located in a `git` repository,
the minimum information you need to specify is the location of the repository with the git key: the minimum information you need to specify is the location of the repository with the git key:
...@@ -85,16 +86,24 @@ requests = { git = "https://github.com/requests/requests.git" } ...@@ -85,16 +86,24 @@ requests = { git = "https://github.com/requests/requests.git" }
``` ```
Since we haven’t specified any other information, Since we haven’t specified any other information,
Poetry assumes that we intend to use the latest commit on the `master` branch to build our project. Poetry assumes that we intend to use the latest commit on the `master` branch
You can combine the `git` key with the `rev`, `tag`, or `branch` keys to specify something else. to build our project.
Here's an example of specifying that you want to use the latest commit on a branch named `next`:
You can combine the `git` key with the `branch` key to use another branch.
Alternatively, use `rev` or `tag` to pin a dependency to a specific commit hash
or tagged ref, respectively. For example:
```toml ```toml
[tool.poetry.dependencies] [tool.poetry.dependencies]
# Get the latest revision on the branch named "next"
requests = { git = "https://github.com/kennethreitz/requests.git", branch = "next" } requests = { git = "https://github.com/kennethreitz/requests.git", branch = "next" }
# Get a revision by its commit hash
flask = { git = "https://github.com/pallets/flask.git", rev = "38eb5d3b" }
# Get a revision by its tag
numpy = { git = "https://github.com/numpy/numpy.git", tag = "v0.13.2" }
``` ```
### `path` dependencies ## `path` dependencies
To depend on a library located in a local directory or file, To depend on a library located in a local directory or file,
you can use the `path` property: you can use the `path` property:
...@@ -109,7 +118,7 @@ my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" } ...@@ -109,7 +118,7 @@ my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" }
``` ```
### `url` dependencies ## `url` dependencies
To depend on a library located on a remote archive, To depend on a library located on a remote archive,
you can use the `url` property: you can use the `url` property:
...@@ -127,7 +136,7 @@ poetry add https://example.com/my-package-0.1.0.tar.gz ...@@ -127,7 +136,7 @@ poetry add https://example.com/my-package-0.1.0.tar.gz
``` ```
### Python restricted dependencies ## Python restricted dependencies
You can also specify that a dependency should be installed only for specific Python versions: You can also specify that a dependency should be installed only for specific Python versions:
...@@ -141,7 +150,7 @@ pathlib2 = { version = "^2.2", python = "~2.7" } ...@@ -141,7 +150,7 @@ pathlib2 = { version = "^2.2", python = "~2.7" }
pathlib2 = { version = "^2.2", python = "~2.7 || ^3.2" } pathlib2 = { version = "^2.2", python = "~2.7 || ^3.2" }
``` ```
### Using environment markers ## Using environment markers
If you need more complex install conditions for your dependencies, If you need more complex install conditions for your dependencies,
Poetry supports [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers) Poetry supports [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers)
...@@ -153,7 +162,7 @@ pathlib2 = { version = "^2.2", markers = "python_version ~= '2.7' or sys_platfor ...@@ -153,7 +162,7 @@ pathlib2 = { version = "^2.2", markers = "python_version ~= '2.7' or sys_platfor
``` ```
### Multiple constraints dependencies ## Multiple constraints dependencies
Sometimes, one of your dependency may have different version ranges depending Sometimes, one of your dependency may have different version ranges depending
on the target Python versions. on the target Python versions.
......
...@@ -60,14 +60,14 @@ commands = ...@@ -60,14 +60,14 @@ commands =
poetry run pytest tests/ poetry run pytest tests/
``` ```
## I don't want Poetry to manage my virtualenvs. Can I disable it? ## I don't want Poetry to manage my virtual environments. Can I disable it?
While Poetry automatically creates virtualenvs to always work isolated While Poetry automatically creates virtual environments to always work isolated
from the global Python installation, there are valid reasons why it's not necessary from the global Python installation, there are valid reasons why it's not necessary
and is an overhead, like when working with containers. and is an overhead, like when working with containers.
In this case, you can disable this feature by setting the `virtualenvs.create` setting to `false`: In this case, you can disable this feature by setting the `virtualenvs.create` setting to `false`:
```bash ```bash
poetry config settings.virtualenvs.create false poetry config virtualenvs.create false
``` ```
...@@ -16,8 +16,13 @@ Poetry provides a custom installer that will install `poetry` isolated ...@@ -16,8 +16,13 @@ Poetry provides a custom installer that will install `poetry` isolated
from the rest of your system by vendorizing its dependencies. This is the from the rest of your system by vendorizing its dependencies. This is the
recommended way of installing `poetry`. recommended way of installing `poetry`.
### osx / linux / bashonwindows install instructions
```bash ```bash
curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
```
### windows powershell install instructions
```powershell
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python
``` ```
!!! note !!! note
...@@ -141,6 +146,10 @@ poetry self update 0.8.0 ...@@ -141,6 +146,10 @@ poetry self update 0.8.0
The `self update` command will only work if you used the recommended The `self update` command will only work if you used the recommended
installer to install Poetry. installer to install Poetry.
!!!note
If you are still on poetry version < 1.0 use `poetry self:update` instead.
## Enable tab completion for Bash, Fish, or Zsh ## Enable tab completion for Bash, Fish, or Zsh
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
This chapter will tell you how to make your library installable through Poetry. This chapter will tell you how to make your library installable through Poetry.
## Every project is a package
As soon as you have a `pyproject.toml` in a directory, that directory is a package.
However, to make it accessible to others you will need to package and publish it.
## Versioning ## Versioning
...@@ -14,7 +9,7 @@ While Poetry does not enforce any convention regarding package versioning, ...@@ -14,7 +9,7 @@ While Poetry does not enforce any convention regarding package versioning,
it **strongly** recommends to follow [semantic versioning](https://semver.org). it **strongly** recommends to follow [semantic versioning](https://semver.org).
This has many advantages for the end users and allows them to set appropriate This has many advantages for the end users and allows them to set appropriate
[version constraints](/docs/versions/). [version constraints](/docs/dependency-specification/#version-constraints).
## Lock file ## Lock file
......
# Managing environments
Poetry makes project environment isolation one of its core feature.
What this means is that it will always work isolated from your global Python installation.
To achieve this, it will first check if it's currently running inside a virtual environment.
If it is, it will use it directly without creating a new one. But if it's not, it will use
one that it has already created or create a brand new one for you.
By default, Poetry will try to use the currently activated Python version
to create the virtual environment for the current project.
However, for various reasons, this Python version might not be compatible
with the `python` requirement of the project. In this case, Poetry will try
to find one that is and use it. If it's unable to do so then you will be prompted
to activate one explicitly, see [Switching environments](#switching-between-environments).
!!!note
To easily switch between Python versions, it is recommended to
use [pyenv](https://github.com/pyenv/pyenv) or similar tools.
For instance, if your project is Python 2.7 only, a standard workflow
would be:
```bash
pyenv install 2.7.15
pyenv local 2.7.15 # Activate Python 2.7 for the current project
poetry install
```
## Switching between environments
Sometimes this might not be feasible for your system, especially Windows where `pyenv`
is not available, or you simply prefer to have a more explicit control over your environment.
For this specific purpose, you can use the `env use` command to tell Poetry
which Python version to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtual environment, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
## Displaying the environment information
If you want to get basic information about the currently activated virtual environment,
you can use the `env info` command:
```bash
poetry env info
```
will output something similar to this:
```text
Virtual environment
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
System
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtual environment, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
## Listing the environments associated with the project
You can also list all the virtual environments associated with the current virtual environment
with the `env list` command:
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py2.7
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
## Deleting the environments
Finally, you can delete existing virtual environments by using `env remove`:
```bash
poetry env remove /full/path/to/python
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
If your remove the currently activated virtual environment, it will be automatically deactivated.
...@@ -27,14 +27,14 @@ The recommended notation for the most common licenses is (alphabetical): ...@@ -27,14 +27,14 @@ The recommended notation for the most common licenses is (alphabetical):
* BSD-2-Clause * BSD-2-Clause
* BSD-3-Clause * BSD-3-Clause
* BSD-4-Clause * BSD-4-Clause
* GPL-2.0 * GPL-2.0-only
* GPL-2.0+ * GPL-2.0-or-later
* GPL-3.0 * GPL-3.0-only
* GPL-3.0+ * GPL-3.0-or-later
* LGPL-2.1 * LGPL-2.1-only
* LGPL-2.1+ * LGPL-2.1-or-later
* LGPL-3.0 * LGPL-3.0-only
* LGPL-3.0+ * LGPL-3.0-or-later
* MIT * MIT
Optional, but it is highly recommended to supply this. Optional, but it is highly recommended to supply this.
...@@ -264,7 +264,7 @@ any custom url in the `urls` section. ...@@ -264,7 +264,7 @@ any custom url in the `urls` section.
```toml ```toml
[tool.poetry.urls] [tool.poetry.urls]
"Bug Tracker" = "https://github.com/sdispater/poetry/issues" "Bug Tracker" = "https://github.com/python-poetry/poetry/issues"
``` ```
If you publish you package on PyPI, they will appear in the `Project Links` section. If you publish you package on PyPI, they will appear in the `Project Links` section.
......
...@@ -71,7 +71,7 @@ export POETRY_HTTP_BASIC_PYPI_USERNAME=username ...@@ -71,7 +71,7 @@ export POETRY_HTTP_BASIC_PYPI_USERNAME=username
export POETRY_HTTP_BASIC_PYPI_PASSWORD=password export POETRY_HTTP_BASIC_PYPI_PASSWORD=password
``` ```
See [Using environment variables](/configuration#using-environment-variables) for more information See [Using environment variables](/docs/configuration/#using-environment-variables) for more information
on how to configure Poetry with environment variables. on how to configure Poetry with environment variables.
#### Custom certificate authority and mutual TLS authentication #### Custom certificate authority and mutual TLS authentication
......
...@@ -14,7 +14,8 @@ nav: ...@@ -14,7 +14,8 @@ nav:
- Commands: cli.md - Commands: cli.md
- Configuration: configuration.md - Configuration: configuration.md
- Repositories: repositories.md - Repositories: repositories.md
- Versions: versions.md - Managing environments: managing-environments.md
- Dependency specification: dependency-specification.md
- The pyproject.toml file: pyproject.md - The pyproject.toml file: pyproject.md
- Contributing: contributing.md - Contributing: contributing.md
- FAQ: faq.md - FAQ: faq.md
...@@ -25,3 +26,5 @@ markdown_extensions: ...@@ -25,3 +26,5 @@ markdown_extensions:
- pymdownx.superfences - pymdownx.superfences
- toc: - toc:
permalink: permalink:
- markdown_include.include:
base_path: docs
""" """
This script will install poetry and its dependencies This script will install Poetry and its dependencies
in isolation from the rest of the system. in isolation from the rest of the system.
It does, in order: It does, in order:
...@@ -65,6 +65,7 @@ try: ...@@ -65,6 +65,7 @@ try:
except NameError: except NameError:
u = str u = str
SHELL = os.getenv("SHELL", "")
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
...@@ -226,8 +227,7 @@ It will add the `poetry` command to {poetry}'s bin directory, located at: ...@@ -226,8 +227,7 @@ It will add the `poetry` command to {poetry}'s bin directory, located at:
{platform_msg} {platform_msg}
You can uninstall at any time with `poetry self:uninstall`, You can uninstall at any time by executing this script with the --uninstall option,
or by executing this script with the --uninstall option,
and these changes will be reverted. and these changes will be reverted.
""" """
...@@ -249,6 +249,9 @@ modifying the profile file{plural} located at: ...@@ -249,6 +249,9 @@ modifying the profile file{plural} located at:
{rcfiles}""" {rcfiles}"""
PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by
modifying the `fish_user_paths` universal variable."""
PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."""
...@@ -264,6 +267,12 @@ automatically. ...@@ -264,6 +267,12 @@ automatically.
To configure your current shell run `source {poetry_home_env}` To configure your current shell run `source {poetry_home_env}`
""" """
POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great!
{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH`
environment variable by modifying the `fish_user_paths` universal variable.
"""
POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
...@@ -279,6 +288,15 @@ environment variable. ...@@ -279,6 +288,15 @@ environment variable.
To configure your current shell run `source {poetry_home_env}` To configure your current shell run `source {poetry_home_env}`
""" """
POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great!
To get started you need {poetry}'s bin directory ({poetry_home_bin})
in your `PATH` environment variable, which you can add by running
the following command:
set -U fish_user_paths {poetry_home_bin} $fish_user_paths
"""
POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
...@@ -605,6 +623,9 @@ class Installer: ...@@ -605,6 +623,9 @@ class Installer:
if not self._modify_path: if not self._modify_path:
return return
if "fish" in SHELL:
return self.add_to_fish_path()
if WINDOWS: if WINDOWS:
return self.add_to_windows_path() return self.add_to_windows_path()
...@@ -625,6 +646,40 @@ class Installer: ...@@ -625,6 +646,40 @@ class Installer:
with open(profile, "a") as f: with open(profile, "a") as f:
f.write(u(addition)) f.write(u(addition))
def add_to_fish_path(self):
"""
Ensure POETRY_BIN directory is on Fish shell $PATH
"""
current_path = os.environ.get("PATH", None)
if current_path is None:
print(
colorize(
"warning",
"\nUnable to get the PATH value. It will not be updated automatically.",
)
)
self._modify_path = False
return
if POETRY_BIN not in current_path:
fish_user_paths = subprocess.check_output(
["fish", "-c", "echo $fish_user_paths"]
).decode("utf-8")
if POETRY_BIN not in fish_user_paths:
cmd = "set -U fish_user_paths {} $fish_user_paths".format(POETRY_BIN)
set_fish_user_path = ["fish", "-c", "{}".format(cmd)]
subprocess.check_output(set_fish_user_path)
else:
print(
colorize(
"warning",
"\nPATH already contains {} and thus was not modified.".format(
POETRY_BIN
),
)
)
def add_to_windows_path(self): def add_to_windows_path(self):
try: try:
old_path = self.get_windows_path_var() old_path = self.get_windows_path_var()
...@@ -685,11 +740,25 @@ class Installer: ...@@ -685,11 +740,25 @@ class Installer:
) )
def remove_from_path(self): def remove_from_path(self):
if WINDOWS: if "fish" in SHELL:
return self.remove_from_fish_path()
elif WINDOWS:
return self.remove_from_windows_path() return self.remove_from_windows_path()
return self.remove_from_unix_path() return self.remove_from_unix_path()
def remove_from_fish_path(self):
fish_user_paths = subprocess.check_output(
["fish", "-c", "echo $fish_user_paths"]
).decode("utf-8")
if POETRY_BIN in fish_user_paths:
cmd = "set -U fish_user_paths (string match -v {} $fish_user_paths)".format(
POETRY_BIN
)
set_fish_user_path = ["fish", "-c", "{}".format(cmd)]
subprocess.check_output(set_fish_user_path)
def remove_from_windows_path(self): def remove_from_windows_path(self):
path = self.get_windows_path_var() path = self.get_windows_path_var()
...@@ -741,8 +810,7 @@ class Installer: ...@@ -741,8 +810,7 @@ class Installer:
def get_unix_profiles(self): def get_unix_profiles(self):
profiles = [os.path.join(HOME, ".profile")] profiles = [os.path.join(HOME, ".profile")]
shell = os.getenv("SHELL", "") if "zsh" in SHELL:
if "zsh" in shell:
zdotdir = os.getenv("ZDOTDIR", HOME) zdotdir = os.getenv("ZDOTDIR", HOME)
profiles.append(os.path.join(zdotdir, ".zprofile")) profiles.append(os.path.join(zdotdir, ".zprofile"))
...@@ -766,7 +834,9 @@ class Installer: ...@@ -766,7 +834,9 @@ class Installer:
if not self._modify_path: if not self._modify_path:
kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH
else: else:
if WINDOWS: if "fish" in SHELL:
kwargs["platform_msg"] = PRE_MESSAGE_FISH
elif WINDOWS:
kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS
else: else:
profiles = [ profiles = [
...@@ -809,6 +879,12 @@ class Installer: ...@@ -809,6 +879,12 @@ class Installer:
poetry_home_bin = POETRY_BIN.replace( poetry_home_bin = POETRY_BIN.replace(
os.getenv("USERPROFILE", ""), "%USERPROFILE%" os.getenv("USERPROFILE", ""), "%USERPROFILE%"
) )
elif "fish" in SHELL:
message = POST_MESSAGE_FISH
if not self._modify_path:
message = POST_MESSAGE_FISH_NO_MODIFY_PATH
poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
else: else:
message = POST_MESSAGE_UNIX message = POST_MESSAGE_UNIX
if not self._modify_path: if not self._modify_path:
......
...@@ -3,8 +3,8 @@ PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp3 ...@@ -3,8 +3,8 @@ PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m cp38-cp3
cd /io cd /io
/opt/python/cp37-cp37m/bin/pip install pip -U /opt/python/cp37-cp37m/bin/pip install pip -U
/opt/python/cp37-cp37m/bin/pip install poetry -U /opt/python/cp37-cp37m/bin/pip install poetry -U --pre
/opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false /opt/python/cp37-cp37m/bin/poetry config virtualenvs.create false
/opt/python/cp37-cp37m/bin/poetry install --no-dev /opt/python/cp37-cp37m/bin/poetry install --no-dev
/opt/python/cp37-cp37m/bin/python sonnet make release --ansi \ /opt/python/cp37-cp37m/bin/python sonnet make release --ansi \
-P "2.7:/opt/python/cp27-cp27m/bin/python" \ -P "2.7:/opt/python/cp27-cp27m/bin/python" \
......
...@@ -67,10 +67,10 @@ description = "httplib2 caching for requests" ...@@ -67,10 +67,10 @@ description = "httplib2 caching for requests"
name = "cachecontrol" name = "cachecontrol"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.12.5" version = "0.12.6"
[package.dependencies] [package.dependencies]
msgpack = "*" msgpack = ">=0.5.2"
requests = "*" requests = "*"
[package.dependencies.lockfile] [package.dependencies.lockfile]
...@@ -100,7 +100,7 @@ description = "Python package for providing Mozilla's CA Bundle." ...@@ -100,7 +100,7 @@ description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2019.9.11" version = "2019.11.28"
[[package]] [[package]]
category = "main" category = "main"
...@@ -109,7 +109,7 @@ marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platfo ...@@ -109,7 +109,7 @@ marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platfo
name = "cffi" name = "cffi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.13.1" version = "1.13.2"
[package.dependencies] [package.dependencies]
pycparser = "*" pycparser = "*"
...@@ -159,7 +159,7 @@ description = "CliKit is a group of utilities to build beautiful and testable co ...@@ -159,7 +159,7 @@ description = "CliKit is a group of utilities to build beautiful and testable co
name = "clikit" name = "clikit"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.0" version = "0.4.1"
[package.dependencies] [package.dependencies]
pastel = ">=0.1.0,<0.2.0" pastel = ">=0.1.0,<0.2.0"
...@@ -176,16 +176,25 @@ version = ">=3.6,<4.0" ...@@ -176,16 +176,25 @@ version = ">=3.6,<4.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\"" marker = "sys_platform == \"win32\" and python_version == \"3.4\""
name = "colorama" name = "colorama"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.1" version = "0.4.1"
[[package]] [[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\" and python_version != \"3.4\" or platform_system == \"Windows\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "main" category = "main"
description = "Updated configparser from Python 3.7 for Python 2.6+." description = "Updated configparser from Python 3.7 for Python 2.6+."
marker = "python_version == \"2.7\" and python_version < \"2.8\" or python_version < \"3\"" marker = "python_version == \"2.7\" and python_version == \"2.7\" or python_version < \"3\""
name = "configparser" name = "configparser"
optional = false optional = false
python-versions = ">=2.6" python-versions = ">=2.6"
...@@ -213,6 +222,17 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" ...@@ -213,6 +222,17 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
version = "4.5.4" version = "4.5.4"
[[package]] [[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "5.0.2"
[package.extras]
toml = ["toml"]
[[package]]
category = "main" category = "main"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.4\" and python_version < \"3.5\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.5\" and python_version < \"4.0\" and sys_platform == \"linux\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" and (sys_platform == \"linux2\" or sys_platform == \"linux\") or python_version >= \"3.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\""
...@@ -243,7 +263,7 @@ test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", " ...@@ -243,7 +263,7 @@ test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "
[[package]] [[package]]
category = "main" category = "main"
description = "Discover and load entry points from installed packages." description = "Discover and load entry points from installed packages."
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version >= \"3.5\" and python_version < \"4.0\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""
name = "entrypoints" name = "entrypoints"
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=2.7"
...@@ -342,8 +362,8 @@ category = "dev" ...@@ -342,8 +362,8 @@ category = "dev"
description = "File identification library for Python" description = "File identification library for Python"
name = "identify" name = "identify"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "1.4.7" version = "1.4.9"
[package.extras] [package.extras]
license = ["editdistance"] license = ["editdistance"]
...@@ -361,8 +381,8 @@ category = "main" ...@@ -361,8 +381,8 @@ category = "main"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
name = "importlib-metadata" name = "importlib-metadata"
optional = false optional = false
python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "0.23" version = "1.1.3"
[package.dependencies] [package.dependencies]
zipp = ">=0.5" zipp = ">=0.5"
...@@ -413,7 +433,7 @@ marker = "python_version >= \"3.5\" and python_version < \"4.0\" and sys_platfor ...@@ -413,7 +433,7 @@ marker = "python_version >= \"3.5\" and python_version < \"4.0\" and sys_platfor
name = "jeepney" name = "jeepney"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "0.4.1" version = "0.4.2"
[package.extras] [package.extras]
dev = ["testpath"] dev = ["testpath"]
...@@ -439,11 +459,10 @@ description = "An implementation of JSON Schema validation for Python" ...@@ -439,11 +459,10 @@ description = "An implementation of JSON Schema validation for Python"
name = "jsonschema" name = "jsonschema"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.1.1" version = "3.2.0"
[package.dependencies] [package.dependencies]
attrs = ">=17.4.0" attrs = ">=17.4.0"
importlib-metadata = "*"
pyrsistent = ">=0.14.0" pyrsistent = ">=0.14.0"
setuptools = "*" setuptools = "*"
six = ">=1.11.0" six = ">=1.11.0"
...@@ -452,8 +471,13 @@ six = ">=1.11.0" ...@@ -452,8 +471,13 @@ six = ">=1.11.0"
python = "<3" python = "<3"
version = "*" version = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.extras] [package.extras]
format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
[[package]] [[package]]
category = "main" category = "main"
...@@ -483,16 +507,19 @@ marker = "python_version >= \"3.5\" and python_version < \"4.0\"" ...@@ -483,16 +507,19 @@ marker = "python_version >= \"3.5\" and python_version < \"4.0\""
name = "keyring" name = "keyring"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "19.2.0" version = "20.0.1"
[package.dependencies] [package.dependencies]
entrypoints = "*"
pywin32-ctypes = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1" pywin32-ctypes = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1"
secretstorage = "*" secretstorage = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = "*"
[package.extras] [package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs", "pytest-flake8", "pytest-black-multipy"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"]
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -510,7 +537,6 @@ tornado = "*" ...@@ -510,7 +537,6 @@ tornado = "*"
[[package]] [[package]]
category = "main" category = "main"
description = "Platform-independent file locking module" description = "Platform-independent file locking module"
marker = "extra == \"filecache\""
name = "lockfile" name = "lockfile"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -540,6 +566,17 @@ testing = ["coverage", "pyyaml"] ...@@ -540,6 +566,17 @@ testing = ["coverage", "pyyaml"]
[[package]] [[package]]
category = "dev" category = "dev"
description = "This is an extension to Python-Markdown which provides an \"include\" function, similar to that found in LaTeX (and also the C pre-processor and Fortran). I originally wrote it for my FORD Fortran auto-documentation generator."
name = "markdown-include"
optional = false
python-versions = "*"
version = "0.5.1"
[package.dependencies]
markdown = "*"
[[package]]
category = "dev"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\"" marker = "python_version >= \"2.7.9\" and python_version < \"2.8.0\" or python_version >= \"3.4\" and python_version < \"4.0\""
name = "markupsafe" name = "markupsafe"
...@@ -588,6 +625,7 @@ test = ["pytest", "pytest-cov"] ...@@ -588,6 +625,7 @@ test = ["pytest", "pytest-cov"]
[[package]] [[package]]
category = "main" category = "main"
description = "More routines for operating on iterables, beyond itertools" description = "More routines for operating on iterables, beyond itertools"
marker = "python_version < \"3.8\""
name = "more-itertools" name = "more-itertools"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -599,6 +637,7 @@ six = ">=1.0.0,<2.0.0" ...@@ -599,6 +637,7 @@ six = ">=1.0.0,<2.0.0"
[[package]] [[package]]
category = "main" category = "main"
description = "More routines for operating on iterables, beyond itertools" description = "More routines for operating on iterables, beyond itertools"
marker = "python_version < \"3.8\" or python_version > \"2.7\""
name = "more-itertools" name = "more-itertools"
optional = false optional = false
python-versions = ">=3.4" python-versions = ">=3.4"
...@@ -606,6 +645,15 @@ version = "7.2.0" ...@@ -606,6 +645,15 @@ version = "7.2.0"
[[package]] [[package]]
category = "main" category = "main"
description = "More routines for operating on iterables, beyond itertools"
marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\" or python_version > \"2.7\""
name = "more-itertools"
optional = false
python-versions = ">=3.5"
version = "8.0.2"
[[package]]
category = "main"
description = "MessagePack (de)serializer." description = "MessagePack (de)serializer."
name = "msgpack" name = "msgpack"
optional = false optional = false
...@@ -618,7 +666,7 @@ description = "Node.js virtual environment builder" ...@@ -618,7 +666,7 @@ description = "Node.js virtual environment builder"
name = "nodeenv" name = "nodeenv"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.3.3" version = "1.3.4"
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -626,7 +674,7 @@ description = "Core utilities for Python packages" ...@@ -626,7 +674,7 @@ description = "Core utilities for Python packages"
name = "packaging" name = "packaging"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.2" version = "20.0"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
...@@ -662,8 +710,8 @@ description = "Utility library for gitignore style pattern matching of file path ...@@ -662,8 +710,8 @@ description = "Utility library for gitignore style pattern matching of file path
marker = "python_version >= \"3.6\" and python_version < \"4.0\"" marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "pathspec" name = "pathspec"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.6.0" version = "0.7.0"
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -701,7 +749,7 @@ description = "plugin and hook calling mechanisms for python" ...@@ -701,7 +749,7 @@ description = "plugin and hook calling mechanisms for python"
name = "pluggy" name = "pluggy"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.0" version = "0.13.1"
[package.dependencies] [package.dependencies]
[package.dependencies.importlib-metadata] [package.dependencies.importlib-metadata]
...@@ -739,8 +787,8 @@ category = "dev" ...@@ -739,8 +787,8 @@ category = "dev"
description = "A framework for managing and maintaining multi-language pre-commit hooks." description = "A framework for managing and maintaining multi-language pre-commit hooks."
name = "pre-commit" name = "pre-commit"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.20.0" version = "1.21.0"
[package.dependencies] [package.dependencies]
"aspy.yaml" = "*" "aspy.yaml" = "*"
...@@ -778,7 +826,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili ...@@ -778,7 +826,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili
name = "py" name = "py"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.8.0" version = "1.8.1"
[[package]] [[package]]
category = "main" category = "main"
...@@ -803,7 +851,7 @@ description = "Pygments is a syntax highlighting package written in Python." ...@@ -803,7 +851,7 @@ description = "Pygments is a syntax highlighting package written in Python."
name = "pygments" name = "pygments"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.4.2" version = "2.5.2"
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -841,7 +889,7 @@ description = "Extension pack for Python Markdown." ...@@ -841,7 +889,7 @@ description = "Extension pack for Python Markdown."
name = "pymdown-extensions" name = "pymdown-extensions"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "6.1" version = "6.2.1"
[package.dependencies] [package.dependencies]
Markdown = ">=3.0.1" Markdown = ">=3.0.1"
...@@ -853,7 +901,7 @@ description = "Python parsing module" ...@@ -853,7 +901,7 @@ description = "Python parsing module"
name = "pyparsing" name = "pyparsing"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.2" version = "2.4.6"
[[package]] [[package]]
category = "main" category = "main"
...@@ -872,18 +920,25 @@ description = "pytest: simple powerful testing with Python" ...@@ -872,18 +920,25 @@ description = "pytest: simple powerful testing with Python"
name = "pytest" name = "pytest"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "4.6.6" version = "4.6.9"
[package.dependencies] [package.dependencies]
atomicwrites = ">=1.0" atomicwrites = ">=1.0"
attrs = ">=17.4.0" attrs = ">=17.4.0"
colorama = "*"
packaging = "*" packaging = "*"
pluggy = ">=0.12,<1.0" pluggy = ">=0.12,<1.0"
py = ">=1.5.0" py = ">=1.5.0"
six = ">=1.10.0" six = ">=1.10.0"
wcwidth = "*" wcwidth = "*"
[[package.dependencies.colorama]]
python = "<3.4.0 || >=3.5.0"
version = "*"
[[package.dependencies.colorama]]
python = ">=3.4,<3.5"
version = "<=0.4.1"
[[package.dependencies.more-itertools]] [[package.dependencies.more-itertools]]
python = "<2.8" python = "<2.8"
version = ">=4.0.0,<6.0.0" version = ">=4.0.0,<6.0.0"
...@@ -928,7 +983,7 @@ description = "Thin-wrapper around the mock package for easier use with py.test" ...@@ -928,7 +983,7 @@ description = "Thin-wrapper around the mock package for easier use with py.test"
name = "pytest-mock" name = "pytest-mock"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.11.2" version = "1.13.0"
[package.dependencies] [package.dependencies]
pytest = ">=2.7" pytest = ">=2.7"
...@@ -968,7 +1023,15 @@ description = "YAML parser and emitter for Python" ...@@ -968,7 +1023,15 @@ description = "YAML parser and emitter for Python"
name = "pyyaml" name = "pyyaml"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.1.2" version = "5.2"
[[package]]
category = "dev"
description = "YAML parser and emitter for Python"
name = "pyyaml"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "5.3"
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -977,7 +1040,7 @@ marker = "python_version >= \"3.6\" and python_version < \"4.0\"" ...@@ -977,7 +1040,7 @@ marker = "python_version >= \"3.6\" and python_version < \"4.0\""
name = "regex" name = "regex"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2019.08.19" version = "2020.1.8"
[[package]] [[package]]
category = "main" category = "main"
...@@ -1029,7 +1092,7 @@ requests = ">=2.0.1,<3.0.0" ...@@ -1029,7 +1092,7 @@ requests = ">=2.0.1,<3.0.0"
[[package]] [[package]]
category = "main" category = "main"
description = "scandir, a better directory iterator and faster os.walk()" description = "scandir, a better directory iterator and faster os.walk()"
marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\" or python_version < \"3.5\"" marker = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.4\" and python_version < \"3.5\""
name = "scandir" name = "scandir"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -1057,11 +1120,11 @@ marker = "python_version >= \"3.5\" and python_version < \"4.0\" and sys_platfor ...@@ -1057,11 +1120,11 @@ marker = "python_version >= \"3.5\" and python_version < \"4.0\" and sys_platfor
name = "secretstorage" name = "secretstorage"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "3.1.1" version = "3.1.2"
[package.dependencies] [package.dependencies]
cryptography = "*" cryptography = "*"
jeepney = "*" jeepney = ">=0.4.2"
[[package]] [[package]]
category = "main" category = "main"
...@@ -1077,7 +1140,7 @@ description = "Python 2 and 3 compatibility utilities" ...@@ -1077,7 +1140,7 @@ description = "Python 2 and 3 compatibility utilities"
name = "six" name = "six"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*"
version = "1.12.0" version = "1.13.0"
[[package]] [[package]]
category = "main" category = "main"
...@@ -1149,24 +1212,46 @@ description = "tox is a generic virtualenv management and test command line tool ...@@ -1149,24 +1212,46 @@ description = "tox is a generic virtualenv management and test command line tool
name = "tox" name = "tox"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.14.0" version = "3.12.1"
[package.dependencies]
filelock = ">=3.0.0,<4"
pluggy = ">=0.3.0,<1"
py = ">=1.4.17,<2"
setuptools = ">=30.0.0"
six = ">=1.0.0,<2"
toml = ">=0.9.4"
virtualenv = ">=14.0.0"
[package.extras]
docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"]
testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=3.0.0,<5)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.2.3,<2)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"]
[[package]]
category = "dev"
description = "tox is a generic virtualenv management and test command line tool"
name = "tox"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "3.14.3"
[package.dependencies] [package.dependencies]
colorama = ">=0.4.1"
filelock = ">=3.0.0,<4" filelock = ">=3.0.0,<4"
packaging = ">=14" packaging = ">=14"
pluggy = ">=0.12.0,<1" pluggy = ">=0.12.0,<1"
py = ">=1.4.17,<2" py = ">=1.4.17,<2"
six = ">=1.0.0,<2" six = ">=1.0.0,<2"
toml = ">=0.9.4" toml = ">=0.9.4"
virtualenv = ">=14.0.0" virtualenv = ">=16.0.0"
[package.dependencies.importlib-metadata] [package.dependencies.importlib-metadata]
python = "<3.8" python = "<3.8"
version = ">=0.12,<1" version = ">=0.12,<2"
[package.extras] [package.extras]
docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"]
testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.2.3,<2)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"]
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -1204,7 +1289,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and ...@@ -1204,7 +1289,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and
name = "urllib3" name = "urllib3"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.25.6" version = "1.25.7"
[package.extras] [package.extras]
brotli = ["brotlipy (>=0.6.0)"] brotli = ["brotlipy (>=0.6.0)"]
...@@ -1217,7 +1302,7 @@ description = "Virtual Python Environment builder" ...@@ -1217,7 +1302,7 @@ description = "Virtual Python Environment builder"
name = "virtualenv" name = "virtualenv"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "16.7.7" version = "16.7.9"
[package.extras] [package.extras]
docs = ["sphinx (>=1.8.0,<2)", "towncrier (>=18.5.0)", "sphinx-rtd-theme (>=0.4.2,<1)"] docs = ["sphinx (>=1.8.0,<2)", "towncrier (>=18.5.0)", "sphinx-rtd-theme (>=0.4.2,<1)"]
...@@ -1229,7 +1314,7 @@ description = "Measures number of Terminal column cells of wide-character codes" ...@@ -1229,7 +1314,7 @@ description = "Measures number of Terminal column cells of wide-character codes"
name = "wcwidth" name = "wcwidth"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.1.7" version = "0.1.8"
[[package]] [[package]]
category = "main" category = "main"
...@@ -1242,6 +1327,7 @@ version = "0.5.1" ...@@ -1242,6 +1327,7 @@ version = "0.5.1"
[[package]] [[package]]
category = "main" category = "main"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\""
name = "zipp" name = "zipp"
optional = false optional = false
python-versions = ">=2.7" python-versions = ">=2.7"
...@@ -1255,7 +1341,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] ...@@ -1255,7 +1341,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pathlib2", "contextlib2", "unittest2"] testing = ["pathlib2", "contextlib2", "unittest2"]
[metadata] [metadata]
content-hash = "0a019fe9f27e3e3fc226c506b4c9de219d7b9d021e998ce88563f7b9c945aa35" content-hash = "e0b632d8363fdf9f70d93901ff537714611bfc31705a897f6d2fb3bc010bca0a"
python-versions = "~2.7 || ^3.4" python-versions = "~2.7 || ^3.4"
[metadata.files] [metadata.files]
...@@ -1280,49 +1366,51 @@ black = [ ...@@ -1280,49 +1366,51 @@ black = [
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
] ]
cachecontrol = [ cachecontrol = [
{file = "CacheControl-0.12.5.tar.gz", hash = "sha256:cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"}, {file = "CacheControl-0.12.6-py2.py3-none-any.whl", hash = "sha256:10d056fa27f8563a271b345207402a6dcce8efab7e5b377e270329c62471b10d"},
{file = "CacheControl-0.12.6.tar.gz", hash = "sha256:be9aa45477a134aee56c8fac518627e1154df063e85f67d4f83ce0ccc23688e8"},
] ]
cachy = [ cachy = [
{file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"},
{file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"},
] ]
certifi = [ certifi = [
{file = "certifi-2019.9.11-py2.py3-none-any.whl", hash = "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"}, {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
{file = "certifi-2019.9.11.tar.gz", hash = "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50"}, {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
] ]
cffi = [ cffi = [
{file = "cffi-1.13.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78"}, {file = "cffi-1.13.2-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43"},
{file = "cffi-1.13.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa"}, {file = "cffi-1.13.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396"},
{file = "cffi-1.13.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365"}, {file = "cffi-1.13.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54"},
{file = "cffi-1.13.1-cp27-cp27m-win32.whl", hash = "sha256:1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98"}, {file = "cffi-1.13.2-cp27-cp27m-win32.whl", hash = "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159"},
{file = "cffi-1.13.1-cp27-cp27m-win_amd64.whl", hash = "sha256:0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400"}, {file = "cffi-1.13.2-cp27-cp27m-win_amd64.whl", hash = "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97"},
{file = "cffi-1.13.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05"}, {file = "cffi-1.13.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579"},
{file = "cffi-1.13.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8"}, {file = "cffi-1.13.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc"},
{file = "cffi-1.13.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36"}, {file = "cffi-1.13.2-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f"},
{file = "cffi-1.13.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc"}, {file = "cffi-1.13.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858"},
{file = "cffi-1.13.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599"}, {file = "cffi-1.13.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42"},
{file = "cffi-1.13.1-cp34-cp34m-win32.whl", hash = "sha256:b8f09f21544b9899defb09afbdaeb200e6a87a2b8e604892940044cf94444644"}, {file = "cffi-1.13.2-cp34-cp34m-win32.whl", hash = "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b"},
{file = "cffi-1.13.1-cp34-cp34m-win_amd64.whl", hash = "sha256:8a2bcae2258d00fcfc96a9bde4a6177bc4274fe033f79311c5dd3d3148c26518"}, {file = "cffi-1.13.2-cp34-cp34m-win_amd64.whl", hash = "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20"},
{file = "cffi-1.13.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e"}, {file = "cffi-1.13.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3"},
{file = "cffi-1.13.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71"}, {file = "cffi-1.13.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25"},
{file = "cffi-1.13.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0"}, {file = "cffi-1.13.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5"},
{file = "cffi-1.13.1-cp35-cp35m-win32.whl", hash = "sha256:364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e"}, {file = "cffi-1.13.2-cp35-cp35m-win32.whl", hash = "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c"},
{file = "cffi-1.13.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2"}, {file = "cffi-1.13.2-cp35-cp35m-win_amd64.whl", hash = "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b"},
{file = "cffi-1.13.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730"}, {file = "cffi-1.13.2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04"},
{file = "cffi-1.13.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43"}, {file = "cffi-1.13.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652"},
{file = "cffi-1.13.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5"}, {file = "cffi-1.13.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57"},
{file = "cffi-1.13.1-cp36-cp36m-win32.whl", hash = "sha256:e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891"}, {file = "cffi-1.13.2-cp36-cp36m-win32.whl", hash = "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e"},
{file = "cffi-1.13.1-cp36-cp36m-win_amd64.whl", hash = "sha256:47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1"}, {file = "cffi-1.13.2-cp36-cp36m-win_amd64.whl", hash = "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"},
{file = "cffi-1.13.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434"}, {file = "cffi-1.13.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410"},
{file = "cffi-1.13.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa"}, {file = "cffi-1.13.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a"},
{file = "cffi-1.13.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2"}, {file = "cffi-1.13.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12"},
{file = "cffi-1.13.1-cp37-cp37m-win32.whl", hash = "sha256:6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4"}, {file = "cffi-1.13.2-cp37-cp37m-win32.whl", hash = "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e"},
{file = "cffi-1.13.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526"}, {file = "cffi-1.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a"},
{file = "cffi-1.13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331"}, {file = "cffi-1.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d"},
{file = "cffi-1.13.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a"}, {file = "cffi-1.13.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3"},
{file = "cffi-1.13.1-cp38-cp38-win32.whl", hash = "sha256:819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8"}, {file = "cffi-1.13.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db"},
{file = "cffi-1.13.1-cp38-cp38-win_amd64.whl", hash = "sha256:263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14"}, {file = "cffi-1.13.2-cp38-cp38-win32.whl", hash = "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506"},
{file = "cffi-1.13.1.tar.gz", hash = "sha256:558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b"}, {file = "cffi-1.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba"},
{file = "cffi-1.13.2.tar.gz", hash = "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346"},
] ]
cfgv = [ cfgv = [
{file = "cfgv-2.0.1-py2.py3-none-any.whl", hash = "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"}, {file = "cfgv-2.0.1-py2.py3-none-any.whl", hash = "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"},
...@@ -1341,12 +1429,14 @@ click = [ ...@@ -1341,12 +1429,14 @@ click = [
{file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
] ]
clikit = [ clikit = [
{file = "clikit-0.4.0-py2.py3-none-any.whl", hash = "sha256:9f07b56216bc1068f8ed441a5e67a3881fe503cfd6a3a7874f59f72be417cdda"}, {file = "clikit-0.4.1-py2.py3-none-any.whl", hash = "sha256:80b0bfee42310a715773dded69590c4c33fa9fc9a351fa7c262cb67f21d0758f"},
{file = "clikit-0.4.0.tar.gz", hash = "sha256:6819a5b2a78523be485ea9e1ef380ae8d02da4bffed540f7881fdaafbff41390"}, {file = "clikit-0.4.1.tar.gz", hash = "sha256:8ae4766b974d7b1983e39d501da9a0aadf118a907a0c9b50714d027c8b59ea81"},
] ]
colorama = [ colorama = [
{file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"}, {file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"},
{file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"}, {file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"},
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
] ]
configparser = [ configparser = [
{file = "configparser-4.0.2-py2.py3-none-any.whl", hash = "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c"}, {file = "configparser-4.0.2-py2.py3-none-any.whl", hash = "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c"},
...@@ -1389,6 +1479,37 @@ coverage = [ ...@@ -1389,6 +1479,37 @@ coverage = [
{file = "coverage-4.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351"}, {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-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"},
{file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"}, {file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"},
{file = "coverage-5.0.2-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:511ec0c00840e12fb4e852e4db58fa6a01ca4da72f36a9766fae344c3d502033"},
{file = "coverage-5.0.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:d22b4297e7e4225ccf01f1aa55e7a96412ea0796b532dd614c3fcbafa341128e"},
{file = "coverage-5.0.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:593853aa1ac6dcc6405324d877544c596c9d948ef20d2e9512a0f5d2d3202356"},
{file = "coverage-5.0.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:e65a5aa1670db6263f19fdc03daee1d7dbbadb5cb67fd0a1f16033659db13c1d"},
{file = "coverage-5.0.2-cp27-cp27m-win32.whl", hash = "sha256:d4a2b578a7a70e0c71f662705262f87a456f1e6c1e40ada7ea699abaf070a76d"},
{file = "coverage-5.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:28f7f73b34a05e23758e860a89a7f649b85c6749e252eff60ebb05532d180e86"},
{file = "coverage-5.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7d1cc7acc9ce55179616cf72154f9e648136ea55987edf84addbcd9886ffeba2"},
{file = "coverage-5.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:2d0cb9b1fe6ad0d915d45ad3d87f03a38e979093a98597e755930db1f897afae"},
{file = "coverage-5.0.2-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:bfe102659e2ec13b86c7f3b1db6c9a4e7beea4255058d006351339e6b342d5d2"},
{file = "coverage-5.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:23688ff75adfa8bfa2a67254d889f9bdf9302c27241d746e17547c42c732d3f4"},
{file = "coverage-5.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1bf7ba2af1d373a1750888724f84cffdfc697738f29a353c98195f98fc011509"},
{file = "coverage-5.0.2-cp35-cp35m-win32.whl", hash = "sha256:569f9ee3025682afda6e9b0f5bb14897c0db03f1a1dc088b083dd36e743f92bb"},
{file = "coverage-5.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:cf908840896f7aa62d0ec693beb53264b154f972eb8226fb864ac38975590c4f"},
{file = "coverage-5.0.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:eaad65bd20955131bcdb3967a4dea66b4e4d4ca488efed7c00d91ee0173387e8"},
{file = "coverage-5.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:225e79a5d485bc1642cb7ba02281419c633c216cdc6b26c26494ba959f09e69f"},
{file = "coverage-5.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bd82b684bb498c60ef47bb1541a50e6d006dde8579934dcbdbc61d67d1ea70d9"},
{file = "coverage-5.0.2-cp36-cp36m-win32.whl", hash = "sha256:7ca3db38a61f3655a2613ee2c190d63639215a7a736d3c64cc7bbdb002ce6310"},
{file = "coverage-5.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:47874b4711c5aeb295c31b228a758ce3d096be83dc37bd56da48ed99efb8813b"},
{file = "coverage-5.0.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:955ec084f549128fa2702f0b2dc696392001d986b71acd8fd47424f28289a9c3"},
{file = "coverage-5.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f4ee8e2e4243971618bc16fcc4478317405205f135e95226c2496e2a3b8dbbf"},
{file = "coverage-5.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f45fba420b94165c17896861bb0e8b27fb7abdcedfeb154895d8553df90b7b00"},
{file = "coverage-5.0.2-cp37-cp37m-win32.whl", hash = "sha256:cca38ded59105f7705ef6ffe1e960b8db6c7d8279c1e71654a4775ab4454ca15"},
{file = "coverage-5.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cb2b74c123f65e8166f7e1265829a6c8ed755c3cd16d7f50e75a83456a5f3fd7"},
{file = "coverage-5.0.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:53e7438fef0c97bc248f88ba1edd10268cd94d5609970aaf87abbe493691af87"},
{file = "coverage-5.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1e4e39e43057396a5e9d069bfbb6ffeee892e40c5d2effbd8cd71f34ee66c4d"},
{file = "coverage-5.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b0a07158360d22492f9abd02a0f2ee7981b33f0646bf796598b7673f6bbab14"},
{file = "coverage-5.0.2-cp38-cp38m-win32.whl", hash = "sha256:88b51153657612aea68fa684a5b88037597925260392b7bb4509d4f9b0bdd889"},
{file = "coverage-5.0.2-cp38-cp38m-win_amd64.whl", hash = "sha256:189aac76d6e0d7af15572c51892e7326ee451c076c5a50a9d266406cd6c49708"},
{file = "coverage-5.0.2-cp39-cp39m-win32.whl", hash = "sha256:d095a7b473f8a95f7efe821f92058c8a2ecfb18f8db6677ae3819e15dc11aaae"},
{file = "coverage-5.0.2-cp39-cp39m-win_amd64.whl", hash = "sha256:ddeb42a3d5419434742bf4cc71c9eaa22df3b76808e23a82bd0b0bd360f1a9f1"},
{file = "coverage-5.0.2.tar.gz", hash = "sha256:b251c7092cbb6d789d62dc9c9e7c4fb448c9138b51285c36aeb72462cad3600e"},
] ]
cryptography = [ cryptography = [
{file = "cryptography-2.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"}, {file = "cryptography-2.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"},
...@@ -1450,16 +1571,16 @@ httpretty = [ ...@@ -1450,16 +1571,16 @@ httpretty = [
{file = "httpretty-0.9.7.tar.gz", hash = "sha256:66216f26b9d2c52e81808f3e674a6fb65d4bf719721394a1a9be926177e55fbe"}, {file = "httpretty-0.9.7.tar.gz", hash = "sha256:66216f26b9d2c52e81808f3e674a6fb65d4bf719721394a1a9be926177e55fbe"},
] ]
identify = [ identify = [
{file = "identify-1.4.7-py2.py3-none-any.whl", hash = "sha256:4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017"}, {file = "identify-1.4.9-py2.py3-none-any.whl", hash = "sha256:72e9c4ed3bc713c7045b762b0d2e2115c572b85abfc1f4604f5a4fd4c6642b71"},
{file = "identify-1.4.7.tar.gz", hash = "sha256:d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e"}, {file = "identify-1.4.9.tar.gz", hash = "sha256:6f44e637caa40d1b4cb37f6ed3b262ede74901d28b1cc5b1fc07360871edd65d"},
] ]
idna = [ idna = [
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
] ]
importlib-metadata = [ importlib-metadata = [
{file = "importlib_metadata-0.23-py2.py3-none-any.whl", hash = "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"}, {file = "importlib_metadata-1.1.3-py2.py3-none-any.whl", hash = "sha256:7c7f8ac40673f507f349bef2eed21a0e5f01ddf5b2a7356a6c65eb2099b53764"},
{file = "importlib_metadata-0.23.tar.gz", hash = "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26"}, {file = "importlib_metadata-1.1.3.tar.gz", hash = "sha256:7a99fb4084ffe6dae374961ba7a6521b79c1d07c658ab3a28aa264ee1d1b14e3"},
] ]
importlib-resources = [ importlib-resources = [
{file = "importlib_resources-1.0.2-py2.py3-none-any.whl", hash = "sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b"}, {file = "importlib_resources-1.0.2-py2.py3-none-any.whl", hash = "sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b"},
...@@ -1470,22 +1591,22 @@ ipaddress = [ ...@@ -1470,22 +1591,22 @@ ipaddress = [
{file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"}, {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"},
] ]
jeepney = [ jeepney = [
{file = "jeepney-0.4.1-py3-none-any.whl", hash = "sha256:f6a3f93464a0cf052f4e87da3c8b3ed1e27696758fb9739c63d3a74d9a1b6774"}, {file = "jeepney-0.4.2-py3-none-any.whl", hash = "sha256:6f45dce1125cf6c58a1c88123d3831f36a789f9204fbad3172eac15f8ccd08d0"},
{file = "jeepney-0.4.1.tar.gz", hash = "sha256:13806f91a96e9b2623fd2a81b950d763ee471454aafd9eb6d75dbe7afce428fb"}, {file = "jeepney-0.4.2.tar.gz", hash = "sha256:0ba6d8c597e9bef1ebd18aaec595f942a264e25c1a48f164d46120eacaa2e9bb"},
] ]
jinja2 = [ jinja2 = [
{file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"},
{file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"},
] ]
jsonschema = [ jsonschema = [
{file = "jsonschema-3.1.1-py2.py3-none-any.whl", hash = "sha256:94c0a13b4a0616458b42529091624e66700a17f847453e52279e35509a5b7631"}, {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
{file = "jsonschema-3.1.1.tar.gz", hash = "sha256:2fa0684276b6333ff3c0b1b27081f4b2305f0a36cf702a23db50edb141893c3f"}, {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
] ]
keyring = [ keyring = [
{file = "keyring-18.0.1-py2.py3-none-any.whl", hash = "sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6"}, {file = "keyring-18.0.1-py2.py3-none-any.whl", hash = "sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6"},
{file = "keyring-18.0.1.tar.gz", hash = "sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838"}, {file = "keyring-18.0.1.tar.gz", hash = "sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838"},
{file = "keyring-19.2.0-py2.py3-none-any.whl", hash = "sha256:f5bb20ea6c57c2360daf0c591931c9ea0d7660a8d9e32ca84d63273f131ea605"}, {file = "keyring-20.0.1-py2.py3-none-any.whl", hash = "sha256:c674f032424b4bffc62abeac5523ec49cc84aed07a480c3233e0baf618efc15c"},
{file = "keyring-19.2.0.tar.gz", hash = "sha256:91037ccaf0c9a112a76f7740e4a416b9457a69b66c2799421581bee710a974b3"}, {file = "keyring-20.0.1.tar.gz", hash = "sha256:963bfa7f090269d30bdc5e25589e5fd9dad2cf2a7c6f176a7f2386910e5d0d8d"},
] ]
livereload = [ livereload = [
{file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"}, {file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"},
...@@ -1501,6 +1622,9 @@ markdown = [ ...@@ -1501,6 +1622,9 @@ markdown = [
{file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"},
{file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"},
] ]
markdown-include = [
{file = "markdown-include-0.5.1.tar.gz", hash = "sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"},
]
markupsafe = [ markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, {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_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
...@@ -1545,6 +1669,8 @@ more-itertools = [ ...@@ -1545,6 +1669,8 @@ more-itertools = [
{file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"}, {file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"},
{file = "more-itertools-7.2.0.tar.gz", hash = "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832"}, {file = "more-itertools-7.2.0.tar.gz", hash = "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832"},
{file = "more_itertools-7.2.0-py3-none-any.whl", hash = "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"}, {file = "more_itertools-7.2.0-py3-none-any.whl", hash = "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"},
{file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"},
{file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"},
] ]
msgpack = [ msgpack = [
{file = "msgpack-0.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:774f5edc3475917cd95fe593e625d23d8580f9b48b570d8853d06cac171cd170"}, {file = "msgpack-0.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:774f5edc3475917cd95fe593e625d23d8580f9b48b570d8853d06cac171cd170"},
...@@ -1570,11 +1696,11 @@ msgpack = [ ...@@ -1570,11 +1696,11 @@ msgpack = [
{file = "msgpack-0.6.2.tar.gz", hash = "sha256:ea3c2f859346fcd55fc46e96885301d9c2f7a36d453f5d8f2967840efa1e1830"}, {file = "msgpack-0.6.2.tar.gz", hash = "sha256:ea3c2f859346fcd55fc46e96885301d9c2f7a36d453f5d8f2967840efa1e1830"},
] ]
nodeenv = [ nodeenv = [
{file = "nodeenv-1.3.3.tar.gz", hash = "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"}, {file = "nodeenv-1.3.4-py2.py3-none-any.whl", hash = "sha256:561057acd4ae3809e665a9aaaf214afff110bbb6a6d5c8a96121aea6878408b3"},
] ]
packaging = [ packaging = [
{file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, {file = "packaging-20.0-py2.py3-none-any.whl", hash = "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb"},
{file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, {file = "packaging-20.0.tar.gz", hash = "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"},
] ]
pastel = [ pastel = [
{file = "pastel-0.1.1-py2.py3-none-any.whl", hash = "sha256:a904e1659512cc9880a028f66de77cc813a4c32f7ceb68725cbc8afad57ef7ef"}, {file = "pastel-0.1.1-py2.py3-none-any.whl", hash = "sha256:a904e1659512cc9880a028f66de77cc813a4c32f7ceb68725cbc8afad57ef7ef"},
...@@ -1585,7 +1711,8 @@ pathlib2 = [ ...@@ -1585,7 +1711,8 @@ pathlib2 = [
{file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"},
] ]
pathspec = [ pathspec = [
{file = "pathspec-0.6.0.tar.gz", hash = "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c"}, {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
{file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},
] ]
pep562 = [ pep562 = [
{file = "pep562-1.0-py2.py3-none-any.whl", hash = "sha256:d2a48b178ebf5f8dd31709cc26a19808ef794561fa2fe50ea01ea2bad4d667ef"}, {file = "pep562-1.0-py2.py3-none-any.whl", hash = "sha256:d2a48b178ebf5f8dd31709cc26a19808ef794561fa2fe50ea01ea2bad4d667ef"},
...@@ -1600,22 +1727,22 @@ pkginfo = [ ...@@ -1600,22 +1727,22 @@ pkginfo = [
{file = "pkginfo-1.5.0.1.tar.gz", hash = "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb"}, {file = "pkginfo-1.5.0.1.tar.gz", hash = "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb"},
] ]
pluggy = [ pluggy = [
{file = "pluggy-0.13.0-py2.py3-none-any.whl", hash = "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.0.tar.gz", hash = "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
] ]
pre-commit = [ pre-commit = [
{file = "pre_commit-1.18.3-py2.py3-none-any.whl", hash = "sha256:fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a"}, {file = "pre_commit-1.18.3-py2.py3-none-any.whl", hash = "sha256:fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a"},
{file = "pre_commit-1.18.3.tar.gz", hash = "sha256:1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f"}, {file = "pre_commit-1.18.3.tar.gz", hash = "sha256:1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f"},
{file = "pre_commit-1.20.0-py2.py3-none-any.whl", hash = "sha256:c2e4810d2d3102d354947907514a78c5d30424d299dc0fe48f5aa049826e9b50"}, {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"},
{file = "pre_commit-1.20.0.tar.gz", hash = "sha256:9f152687127ec90642a2cc3e4d9e1e6240c4eb153615cb02aa1ad41d331cbb6e"}, {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"},
] ]
ptyprocess = [ ptyprocess = [
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
{file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
] ]
py = [ py = [
{file = "py-1.8.0-py2.py3-none-any.whl", hash = "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa"}, {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"},
{file = "py-1.8.0.tar.gz", hash = "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"}, {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"},
] ]
pycparser = [ pycparser = [
{file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"}, {file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"},
...@@ -1623,8 +1750,8 @@ pycparser = [ ...@@ -1623,8 +1750,8 @@ pycparser = [
pygments = [ pygments = [
{file = "Pygments-2.3.1-py2.py3-none-any.whl", hash = "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"}, {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.3.1.tar.gz", hash = "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a"},
{file = "Pygments-2.4.2-py2.py3-none-any.whl", hash = "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127"}, {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
{file = "Pygments-2.4.2.tar.gz", hash = "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"}, {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
] ]
pygments-github-lexers = [ pygments-github-lexers = [
{file = "pygments-github-lexers-0.0.5.tar.gz", hash = "sha256:aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"}, {file = "pygments-github-lexers-0.0.5.tar.gz", hash = "sha256:aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"},
...@@ -1637,27 +1764,27 @@ pylev = [ ...@@ -1637,27 +1764,27 @@ pylev = [
pymdown-extensions = [ pymdown-extensions = [
{file = "pymdown-extensions-6.0.tar.gz", hash = "sha256:6cf0cf36b5a03b291ace22dc2f320f4789ce56fbdb6635a3be5fadbf5d7694dd"}, {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.0-py2.py3-none-any.whl", hash = "sha256:25b0a7967fa697b5035e23340a48594e3e93acb10b06d74574218ace3347d1df"},
{file = "pymdown-extensions-6.1.tar.gz", hash = "sha256:960486dea995f1759dfd517aa140b3d851cd7b44d4c48d276fd2c74fc4e1bce9"}, {file = "pymdown-extensions-6.2.1.tar.gz", hash = "sha256:3bbe6048275f8a0d13a0fe44e0ea201e67268aa7bb40c2544eef16abbf168f7b"},
{file = "pymdown_extensions-6.1-py2.py3-none-any.whl", hash = "sha256:24c1a0afbae101c4e2b2675ff4dd936470a90133f93398b9cbe0c855e2d2ec10"}, {file = "pymdown_extensions-6.2.1-py2.py3-none-any.whl", hash = "sha256:dce5e17b93be0572322b7d06c9a13c13a9d98694d6468277911d50ca87d26f29"},
] ]
pyparsing = [ pyparsing = [
{file = "pyparsing-2.4.2-py2.py3-none-any.whl", hash = "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"}, {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
{file = "pyparsing-2.4.2.tar.gz", hash = "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80"}, {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
] ]
pyrsistent = [ pyrsistent = [
{file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"}, {file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"},
] ]
pytest = [ pytest = [
{file = "pytest-4.6.6-py2.py3-none-any.whl", hash = "sha256:5d0d20a9a66e39b5845ab14f8989f3463a7aa973700e6cdf02db69da9821e738"}, {file = "pytest-4.6.9-py2.py3-none-any.whl", hash = "sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"},
{file = "pytest-4.6.6.tar.gz", hash = "sha256:692d9351353ef709c1126266579edd4fd469dcf6b5f4f583050f72161d6f3592"}, {file = "pytest-4.6.9.tar.gz", hash = "sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339"},
] ]
pytest-cov = [ pytest-cov = [
{file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, {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"}, {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
] ]
pytest-mock = [ pytest-mock = [
{file = "pytest-mock-1.11.2.tar.gz", hash = "sha256:ea502c3891599c26243a3a847ccf0b1d20556678c528f86c98e3cd6d40c5cf11"}, {file = "pytest-mock-1.13.0.tar.gz", hash = "sha256:e24a911ec96773022ebcc7030059b57cd3480b56d4f5d19b7c370ec635e6aed5"},
{file = "pytest_mock-1.11.2-py2.py3-none-any.whl", hash = "sha256:b3514caac35fe3f05555923eabd9546abce11571cc2ddf7d8615959d04f2c89e"}, {file = "pytest_mock-1.13.0-py2.py3-none-any.whl", hash = "sha256:67e414b3caef7bff6fc6bd83b22b5bc39147e4493f483c2679bc9d4dc485a94d"},
] ]
pytest-sugar = [ pytest-sugar = [
{file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"}, {file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"},
...@@ -1668,32 +1795,51 @@ pywin32-ctypes = [ ...@@ -1668,32 +1795,51 @@ pywin32-ctypes = [
{file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
] ]
pyyaml = [ pyyaml = [
{file = "PyYAML-5.1.2-cp27-cp27m-win32.whl", hash = "sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8"}, {file = "PyYAML-5.2-cp27-cp27m-win32.whl", hash = "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc"},
{file = "PyYAML-5.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"}, {file = "PyYAML-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"},
{file = "PyYAML-5.1.2-cp34-cp34m-win32.whl", hash = "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9"}, {file = "PyYAML-5.2-cp35-cp35m-win32.whl", hash = "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15"},
{file = "PyYAML-5.1.2-cp34-cp34m-win_amd64.whl", hash = "sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696"}, {file = "PyYAML-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075"},
{file = "PyYAML-5.1.2-cp35-cp35m-win32.whl", hash = "sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41"}, {file = "PyYAML-5.2-cp36-cp36m-win32.whl", hash = "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31"},
{file = "PyYAML-5.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73"}, {file = "PyYAML-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc"},
{file = "PyYAML-5.1.2-cp36-cp36m-win32.whl", hash = "sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299"}, {file = "PyYAML-5.2-cp37-cp37m-win32.whl", hash = "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04"},
{file = "PyYAML-5.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b"}, {file = "PyYAML-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd"},
{file = "PyYAML-5.1.2-cp37-cp37m-win32.whl", hash = "sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae"}, {file = "PyYAML-5.2-cp38-cp38-win32.whl", hash = "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f"},
{file = "PyYAML-5.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34"}, {file = "PyYAML-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803"},
{file = "PyYAML-5.1.2-cp38-cp38m-win32.whl", hash = "sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9"}, {file = "PyYAML-5.2.tar.gz", hash = "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c"},
{file = "PyYAML-5.1.2-cp38-cp38m-win_amd64.whl", hash = "sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681"}, {file = "PyYAML-5.3-cp27-cp27m-win32.whl", hash = "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d"},
{file = "PyYAML-5.1.2.tar.gz", hash = "sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4"}, {file = "PyYAML-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6"},
{file = "PyYAML-5.3-cp35-cp35m-win32.whl", hash = "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e"},
{file = "PyYAML-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689"},
{file = "PyYAML-5.3-cp36-cp36m-win32.whl", hash = "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994"},
{file = "PyYAML-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e"},
{file = "PyYAML-5.3-cp37-cp37m-win32.whl", hash = "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5"},
{file = "PyYAML-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf"},
{file = "PyYAML-5.3-cp38-cp38-win32.whl", hash = "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811"},
{file = "PyYAML-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20"},
{file = "PyYAML-5.3.tar.gz", hash = "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"},
] ]
regex = [ regex = [
{file = "regex-2019.08.19-cp27-none-win32.whl", hash = "sha256:1e9f9bc44ca195baf0040b1938e6801d2f3409661c15fe57f8164c678cfc663f"}, {file = "regex-2020.1.8-cp27-cp27m-win32.whl", hash = "sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161"},
{file = "regex-2019.08.19-cp27-none-win_amd64.whl", hash = "sha256:93f6c9da57e704e128d90736430c5c59dd733327882b371b0cae8833106c2a21"}, {file = "regex-2020.1.8-cp27-cp27m-win_amd64.whl", hash = "sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242"},
{file = "regex-2019.08.19-cp35-none-win32.whl", hash = "sha256:c5c8999b3a341b21ac2c6ec704cfcccbc50f1fedd61b6a8ee915ca7fd4b0a557"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26"},
{file = "regex-2019.08.19-cp35-none-win_amd64.whl", hash = "sha256:f2b37b5b2c2a9d56d9e88efef200ec09c36c7f323f9d58d0b985a90923df386d"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149"},
{file = "regex-2019.08.19-cp36-none-win32.whl", hash = "sha256:d96479257e8e4d1d7800adb26bf9c5ca5bab1648a1eddcac84d107b73dc68327"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d"},
{file = "regex-2019.08.19-cp36-none-win_amd64.whl", hash = "sha256:a46f27d267665016acb3ec8c6046ec5eae8cf80befe85ba47f43c6f5ec636dcd"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525"},
{file = "regex-2019.08.19-cp37-none-win32.whl", hash = "sha256:d4d1829cf97632673aa49f378b0a2c3925acd795148c5ace8ef854217abbee89"}, {file = "regex-2020.1.8-cp36-cp36m-win32.whl", hash = "sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0"},
{file = "regex-2019.08.19-cp37-none-win_amd64.whl", hash = "sha256:fe765b809a1f7ce642c2edeee351e7ebd84391640031ba4b60af8d91a9045890"}, {file = "regex-2020.1.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003"},
{file = "regex-2019.08.19-cp38-none-win32.whl", hash = "sha256:f20f4912daf443220436759858f96fefbfc6c6ba9e67835fd6e4e9b73582791a"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576"},
{file = "regex-2019.08.19-cp38-none-win_amd64.whl", hash = "sha256:835ccdcdc612821edf132c20aef3eaaecfb884c9454fdc480d5887562594ac61"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b"},
{file = "regex-2019.08.19.tar.gz", hash = "sha256:587b62d48ca359d2d4f02d486f1f0aa9a20fbaf23a9d4198c4bed72ab2f6c849"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77"},
{file = "regex-2020.1.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35"},
{file = "regex-2020.1.8-cp37-cp37m-win32.whl", hash = "sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146"},
{file = "regex-2020.1.8-cp37-cp37m-win_amd64.whl", hash = "sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5"},
{file = "regex-2020.1.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f"},
{file = "regex-2020.1.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1"},
{file = "regex-2020.1.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9"},
{file = "regex-2020.1.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b"},
{file = "regex-2020.1.8-cp38-cp38-win32.whl", hash = "sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461"},
{file = "regex-2020.1.8-cp38-cp38-win_amd64.whl", hash = "sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c"},
{file = "regex-2020.1.8.tar.gz", hash = "sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351"},
] ]
requests = [ requests = [
{file = "requests-2.21.0-py2.py3-none-any.whl", hash = "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"}, {file = "requests-2.21.0-py2.py3-none-any.whl", hash = "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"},
...@@ -1720,16 +1866,16 @@ scandir = [ ...@@ -1720,16 +1866,16 @@ scandir = [
] ]
secretstorage = [ secretstorage = [
{file = "SecretStorage-2.3.1.tar.gz", hash = "sha256:3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6"}, {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.2-py3-none-any.whl", hash = "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b"},
{file = "SecretStorage-3.1.1.tar.gz", hash = "sha256:20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92"}, {file = "SecretStorage-3.1.2.tar.gz", hash = "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6"},
] ]
shellingham = [ shellingham = [
{file = "shellingham-1.3.1-py2.py3-none-any.whl", hash = "sha256:77d37a4fd287c1e663006f7ecf1b9deca9ad492d0082587bd813c44eb49e4e62"}, {file = "shellingham-1.3.1-py2.py3-none-any.whl", hash = "sha256:77d37a4fd287c1e663006f7ecf1b9deca9ad492d0082587bd813c44eb49e4e62"},
{file = "shellingham-1.3.1.tar.gz", hash = "sha256:985b23bbd1feae47ca6a6365eacd314d93d95a8a16f8f346945074c28fe6f3e0"}, {file = "shellingham-1.3.1.tar.gz", hash = "sha256:985b23bbd1feae47ca6a6365eacd314d93d95a8a16f8f346945074c28fe6f3e0"},
] ]
six = [ six = [
{file = "six-1.12.0-py2.py3-none-any.whl", hash = "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c"}, {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"},
{file = "six-1.12.0.tar.gz", hash = "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"}, {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"},
] ]
subprocess32 = [ subprocess32 = [
{file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"}, {file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"},
...@@ -1764,8 +1910,10 @@ tornado = [ ...@@ -1764,8 +1910,10 @@ tornado = [
{file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"}, {file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"},
] ]
tox = [ tox = [
{file = "tox-3.14.0-py2.py3-none-any.whl", hash = "sha256:0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e"}, {file = "tox-3.12.1-py2.py3-none-any.whl", hash = "sha256:f5c8e446b51edd2ea97df31d4ded8c8b72e7d6c619519da6bb6084b9dd5770f9"},
{file = "tox-3.14.0.tar.gz", hash = "sha256:c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1"}, {file = "tox-3.12.1.tar.gz", hash = "sha256:f87fd33892a2df0950e5e034def9468988b8d008c7e9416be665fcc0dd45b14f"},
{file = "tox-3.14.3-py2.py3-none-any.whl", hash = "sha256:806d0a9217584558cc93747a945a9d9bff10b141a5287f0c8429a08828a22192"},
{file = "tox-3.14.3.tar.gz", hash = "sha256:06ba73b149bf838d5cd25dc30c2dd2671ae5b2757cf98e5c41a35fe449f131b3"},
] ]
typed-ast = [ typed-ast = [
{file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e"}, {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e"},
...@@ -1797,16 +1945,16 @@ typing = [ ...@@ -1797,16 +1945,16 @@ typing = [
urllib3 = [ urllib3 = [
{file = "urllib3-1.24.3-py2.py3-none-any.whl", hash = "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"}, {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.24.3.tar.gz", hash = "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4"},
{file = "urllib3-1.25.6-py2.py3-none-any.whl", hash = "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398"}, {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"},
{file = "urllib3-1.25.6.tar.gz", hash = "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"}, {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"},
] ]
virtualenv = [ virtualenv = [
{file = "virtualenv-16.7.7-py2.py3-none-any.whl", hash = "sha256:11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589"}, {file = "virtualenv-16.7.9-py2.py3-none-any.whl", hash = "sha256:55059a7a676e4e19498f1aad09b8313a38fcc0cdbe4fdddc0e9b06946d21b4bb"},
{file = "virtualenv-16.7.7.tar.gz", hash = "sha256:d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136"}, {file = "virtualenv-16.7.9.tar.gz", hash = "sha256:0d62c70883c0342d59c11d0ddac0d954d0431321a41ab20851facf2b222598f3"},
] ]
wcwidth = [ wcwidth = [
{file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"}, {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
{file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"}, {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
] ]
webencodings = [ webencodings = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
......
__version__ = "1.0.0b3" __version__ = "1.0.1"
...@@ -12,5 +12,5 @@ class AboutCommand(Command): ...@@ -12,5 +12,5 @@ class AboutCommand(Command):
"""<info>Poetry - Package Management for Python</info> """<info>Poetry - Package Management for Python</info>
<comment>Poetry is a dependency manager tracking local dependencies of your projects and libraries. <comment>Poetry is a dependency manager tracking local dependencies of your projects and libraries.
See <fg=blue>https://github.com/sdispater/poetry</> for more information.</comment>""" See <fg=blue>https://github.com/python-poetry/poetry</> for more information.</comment>"""
) )
...@@ -5,8 +5,6 @@ from cleo import argument ...@@ -5,8 +5,6 @@ from cleo import argument
from cleo import option from cleo import option
from poetry.factory import Factory from poetry.factory import Factory
from poetry.utils.helpers import keyring_repository_password_del
from poetry.utils.helpers import keyring_repository_password_set
from .command import Command from .command import Command
...@@ -181,11 +179,14 @@ To remove a repository (repo is a short alias for repositories): ...@@ -181,11 +179,14 @@ To remove a repository (repo is a short alias for repositories):
# handle auth # handle auth
m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key")) m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key"))
if m: if m:
from poetry.utils.password_manager import PasswordManager
password_manager = PasswordManager(config)
if self.option("unset"): if self.option("unset"):
keyring_repository_password_del(config, m.group(2)) if m.group(1) == "http-basic":
config.auth_config_source.remove_property( password_manager.delete_http_password(m.group(2))
"{}.{}".format(m.group(1), m.group(2)) elif m.group(1) == "pypi-token":
) password_manager.delete_pypi_token(m.group(2))
return 0 return 0
...@@ -203,15 +204,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -203,15 +204,7 @@ To remove a repository (repo is a short alias for repositories):
username = values[0] username = values[0]
password = values[1] password = values[1]
property_value = dict(username=username) password_manager.set_http_password(m.group(2), username, password)
try:
keyring_repository_password_set(m.group(2), username, password)
except RuntimeError:
property_value.update(password=password)
config.auth_config_source.add_property(
"{}.{}".format(m.group(1), m.group(2)), property_value
)
elif m.group(1) == "pypi-token": elif m.group(1) == "pypi-token":
if len(values) != 1: if len(values) != 1:
raise ValueError( raise ValueError(
...@@ -220,9 +213,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -220,9 +213,7 @@ To remove a repository (repo is a short alias for repositories):
token = values[0] token = values[0]
config.auth_config_source.add_property( password_manager.set_pypi_token(m.group(2), token)
"{}.{}".format(m.group(1), m.group(2)), token
)
return 0 return 0
......
...@@ -28,10 +28,11 @@ class DebugResolveCommand(InitCommand): ...@@ -28,10 +28,11 @@ class DebugResolveCommand(InitCommand):
loggers = ["poetry.repositories.pypi_repository"] loggers = ["poetry.repositories.pypi_repository"]
def handle(self): def handle(self):
from poetry.io.null_io import NullIO
from poetry.packages import ProjectPackage from poetry.packages import ProjectPackage
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.semver import parse_constraint
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
packages = self.argument("package") packages = self.argument("package")
...@@ -99,20 +100,30 @@ class DebugResolveCommand(InitCommand): ...@@ -99,20 +100,30 @@ class DebugResolveCommand(InitCommand):
return 0 return 0
env = EnvManager(self.poetry).get()
current_python_version = parse_constraint(
".".join(str(v) for v in env.version_info)
)
table = self.table([], style="borderless") table = self.table([], style="borderless")
rows = [] rows = []
if self.option("install"):
env = EnvManager(self.poetry).get()
current_python_version = ".".join(str(v) for v in env.version_info)
pool = Pool()
locked_repository = Repository()
for op in ops:
locked_repository.add_package(op.package)
pool.add_repository(locked_repository)
with package.with_python_versions(current_python_version):
solver = Solver(package, pool, Repository(), Repository(), NullIO())
ops = solver.solve()
for op in ops: for op in ops:
pkg = op.package pkg = op.package
if self.option("install"): row = [
if not pkg.python_constraint.allows( "<c1>{}</c1>".format(pkg.name),
current_python_version "<b>{}</b>".format(pkg.version),
) or not env.is_valid_for_marker(pkg.marker): "",
continue ]
row = ["<c1>{}</c1>".format(pkg.name), "<b>{}</b>".format(pkg.version), ""]
if not pkg.marker.is_any(): if not pkg.marker.is_any():
row[2] = str(pkg.marker) row[2] = str(pkg.marker)
......
...@@ -147,8 +147,8 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -147,8 +147,8 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
"You can specify a package in the following forms:\n" "You can specify a package in the following forms:\n"
" - A single name (<b>requests</b>)\n" " - A single name (<b>requests</b>)\n"
" - A name and a constraint (<b>requests ^2.23.0</b>)\n" " - A name and a constraint (<b>requests ^2.23.0</b>)\n"
" - A git url (<b>https://github.com/sdispater/poetry.git</b>)\n" " - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)\n"
" - A git url with a revision (<b>https://github.com/sdispater/poetry.git@develop</b>)\n" " - A git url with a revision (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)\n"
" - A file path (<b>../my-package/my-package.whl</b>)\n" " - A file path (<b>../my-package/my-package.whl</b>)\n"
" - A directory (<b>../my-package/</b>)\n" " - A directory (<b>../my-package/</b>)\n"
" - An url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n" " - An url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n"
...@@ -365,22 +365,21 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -365,22 +365,21 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
if url_parsed.scheme and url_parsed.netloc: if url_parsed.scheme and url_parsed.netloc:
# Url # Url
if url_parsed.scheme in ["git+https", "git+ssh"]: if url_parsed.scheme in ["git+https", "git+ssh"]:
url = requirement.lstrip("git+") from poetry.vcs.git import Git
rev = None from poetry.vcs.git import ParsedUrl
if "@" in url:
url, rev = url.split("@")
pair = OrderedDict( parsed = ParsedUrl.parse(requirement)
[("name", url.split("/")[-1].rstrip(".git")), ("git", url)] url = Git.normalize_url(requirement)
)
if rev: pair = OrderedDict([("name", parsed.name), ("git", url.url)])
pair["rev"] = rev if parsed.rev:
pair["rev"] = url.revision
if extras: if extras:
pair["extras"] = extras pair["extras"] = extras
package = Provider.get_package_from_vcs( package = Provider.get_package_from_vcs(
"git", url, reference=pair.get("rev") "git", url.url, reference=pair.get("rev")
) )
pair["name"] = package.name pair["name"] = package.name
result.append(pair) result.append(pair)
...@@ -426,6 +425,11 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the ...@@ -426,6 +425,11 @@ The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the
require = OrderedDict() require = OrderedDict()
if " " in pair: if " " in pair:
name, version = pair.split(" ", 2) name, version = pair.split(" ", 2)
extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
if extras_m:
extras = [e.strip() for e in extras_m.group(1).split(",")]
name, _ = name.split("[")
require["name"] = name require["name"] = name
if version != "latest": if version != "latest":
require["version"] = version require["version"] = version
......
...@@ -6,8 +6,10 @@ class LockCommand(EnvCommand): ...@@ -6,8 +6,10 @@ class LockCommand(EnvCommand):
name = "lock" name = "lock"
description = "Locks the project dependencies." description = "Locks the project dependencies."
help = """The <info>lock</info> command reads the <comment>pyproject.toml</> file from help = """
the current directory, processes it, and locks the depdencies in the <comment>poetry.lock</> file. The <info>lock</info> command reads the <comment>pyproject.toml</> file from the
current directory, processes it, and locks the dependencies in the <comment>poetry.lock</>
file.
<info>poetry lock</info> <info>poetry lock</info>
""" """
......
...@@ -3,6 +3,8 @@ import sys ...@@ -3,6 +3,8 @@ import sys
from cleo import argument from cleo import argument
from cleo import option from cleo import option
from poetry.utils.helpers import module_name
from .command import Command from .command import Command
...@@ -76,6 +78,6 @@ class NewCommand(Command): ...@@ -76,6 +78,6 @@ class NewCommand(Command):
self.line( self.line(
"Created package <info>{}</> in <fg=blue>{}</>".format( "Created package <info>{}</> in <fg=blue>{}</>".format(
name, path.relative_to(Path.cwd()) module_name(name), path.relative_to(Path.cwd())
) )
) )
...@@ -172,6 +172,13 @@ lists all packages available.""" ...@@ -172,6 +172,13 @@ lists all packages available."""
# Non installed in non decorated mode # Non installed in non decorated mode
install_marker = " (!)" install_marker = " (!)"
if (
show_latest
and self.option("outdated")
and latest_statuses[locked.pretty_name] == "up-to-date"
):
continue
line = "<fg={}>{:{}}{}</>".format( line = "<fg={}>{:{}}{}</>".format(
color, name, name_length - len(install_marker), install_marker color, name, name_length - len(install_marker), install_marker
) )
...@@ -183,9 +190,6 @@ lists all packages available.""" ...@@ -183,9 +190,6 @@ lists all packages available."""
latest = latest_packages[locked.pretty_name] latest = latest_packages[locked.pretty_name]
update_status = latest_statuses[locked.pretty_name] update_status = latest_statuses[locked.pretty_name]
if self.option("outdated") and update_status == "up-to-date":
continue
if write_latest: if write_latest:
color = "green" color = "green"
if update_status == "semver-safe-update": if update_status == "semver-safe-update":
......
...@@ -7,7 +7,7 @@ class VersionCommand(Command): ...@@ -7,7 +7,7 @@ class VersionCommand(Command):
name = "version" name = "version"
description = ( description = (
"Shows the version of the project or bumps it when a valid" "Shows the version of the project or bumps it when a valid "
"bump rule is provided." "bump rule is provided."
) )
...@@ -59,7 +59,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -59,7 +59,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
self.poetry.file.write(content) self.poetry.file.write(content)
else: else:
self.line( self.line(
"Project (<comment>{}</>) version is <info>{}</>".format( "<comment>{}</> <info>{}</>".format(
self.poetry.package.name, self.poetry.package.pretty_version self.poetry.package.name, self.poetry.package.pretty_version
) )
) )
......
import logging import logging
from typing import Any
from cleo.config import ApplicationConfig as BaseApplicationConfig from cleo.config import ApplicationConfig as BaseApplicationConfig
from clikit.api.application.application import Application from clikit.api.application.application import Application
from clikit.api.args.raw_args import RawArgs from clikit.api.args.raw_args import RawArgs
...@@ -43,15 +45,15 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -43,15 +45,15 @@ class ApplicationConfig(BaseApplicationConfig):
self.add_event_listener(PRE_HANDLE, self.set_env) self.add_event_listener(PRE_HANDLE, self.set_env)
def register_command_loggers( def register_command_loggers(
self, event, event_name, _ # type: PreHandleEvent # type: str self, event, event_name, _
): # type: (...) -> None ): # type: (PreHandleEvent, str, Any) -> None
command = event.command.config.handler command = event.command.config.handler
if not isinstance(command, Command): if not isinstance(command, Command):
return return
io = event.io io = event.io
loggers = ["poetry.packages.package"] loggers = ["poetry.packages.package", "poetry.utils.password_manager"]
loggers += command.loggers loggers += command.loggers
...@@ -72,7 +74,7 @@ class ApplicationConfig(BaseApplicationConfig): ...@@ -72,7 +74,7 @@ class ApplicationConfig(BaseApplicationConfig):
logger.setLevel(level) logger.setLevel(level)
def set_env(self, event, event_name, _): # type: (PreHandleEvent, str, _) -> None def set_env(self, event, event_name, _): # type: (PreHandleEvent, str, Any) -> None
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
command = event.command.config.handler # type: EnvCommand command = event.command.config.handler # type: EnvCommand
......
...@@ -233,7 +233,8 @@ class Factory: ...@@ -233,7 +233,8 @@ class Factory:
): # type: (Dict[str, str], Config) -> LegacyRepository ): # type: (Dict[str, str], Config) -> LegacyRepository
from .repositories.auth import Auth from .repositories.auth import Auth
from .repositories.legacy_repository import LegacyRepository from .repositories.legacy_repository import LegacyRepository
from .utils.helpers import get_client_cert, get_cert, get_http_basic_auth from .utils.helpers import get_client_cert, get_cert
from .utils.password_manager import PasswordManager
if "url" in source: if "url" in source:
# PyPI-like repository # PyPI-like repository
...@@ -242,11 +243,12 @@ class Factory: ...@@ -242,11 +243,12 @@ class Factory:
else: else:
raise RuntimeError("Unsupported source specified") raise RuntimeError("Unsupported source specified")
password_manager = PasswordManager(auth_config)
name = source["name"] name = source["name"]
url = source["url"] url = source["url"]
credentials = get_http_basic_auth(auth_config, name) credentials = password_manager.get_http_auth(name)
if credentials: if credentials:
auth = Auth(url, credentials[0], credentials[1]) auth = Auth(url, credentials["username"], credentials["password"])
else: else:
auth = None auth = None
...@@ -298,7 +300,7 @@ class Factory: ...@@ -298,7 +300,7 @@ class Factory:
result["warnings"].append( result["warnings"].append(
'The "{}" dependency specifies ' 'The "{}" dependency specifies '
'the "allows-prereleases" property, which is deprecated. ' 'the "allows-prereleases" property, which is deprecated. '
'Use "allow-preleases" instead.'.format(name) 'Use "allow-prereleases" instead.'.format(name)
) )
# Checking for scripts with extras # Checking for scripts with extras
......
...@@ -234,7 +234,7 @@ class Installer: ...@@ -234,7 +234,7 @@ class Installer:
# Execute operations # Execute operations
actual_ops = [op for op in ops if not op.skipped] actual_ops = [op for op in ops if not op.skipped]
if not actual_ops and (self._execute_operations or self._dry_run): if not actual_ops and (self._execute_operations or self._dry_run):
self._io.write_line("Nothing to install or update") self._io.write_line("No dependencies to install or update")
if actual_ops and (self._execute_operations or self._dry_run): if actual_ops and (self._execute_operations or self._dry_run):
installs = [] installs = []
......
...@@ -40,7 +40,10 @@ class PipInstaller(BaseInstaller): ...@@ -40,7 +40,10 @@ class PipInstaller(BaseInstaller):
args = ["install", "--no-deps"] args = ["install", "--no-deps"]
if package.source_type == "legacy" and package.source_url: if (
package.source_type not in {"git", "directory", "file", "url"}
and package.source_url
):
repository = self._pool.repository(package.source_reference) repository = self._pool.repository(package.source_reference)
parsed = urlparse.urlparse(package.source_url) parsed = urlparse.urlparse(package.source_url)
if parsed.scheme == "http": if parsed.scheme == "http":
...@@ -70,7 +73,7 @@ class PipInstaller(BaseInstaller): ...@@ -70,7 +73,7 @@ class PipInstaller(BaseInstaller):
if update: if update:
args.append("-U") args.append("-U")
if package.files and not package.source_type: if package.files and not package.source_url:
# Format as a requirements.txt # Format as a requirements.txt
# We need to create a requirements.txt file # We need to create a requirements.txt file
# for each package in order to check hashes. # for each package in order to check hashes.
...@@ -93,7 +96,12 @@ class PipInstaller(BaseInstaller): ...@@ -93,7 +96,12 @@ class PipInstaller(BaseInstaller):
self.run(*args) self.run(*args)
def update(self, _, target): def update(self, package, target):
if package.source_type != target.source_type:
# If the source type has changed, we remove the current
# package to avoid perpetual updates in some cases
self.remove(package)
self.install(target, update=True) self.install(target, update=True)
def remove(self, package): def remove(self, package):
...@@ -112,7 +120,7 @@ class PipInstaller(BaseInstaller): ...@@ -112,7 +120,7 @@ class PipInstaller(BaseInstaller):
raise raise
def run(self, *args, **kwargs): # type: (...) -> str def run(self, *args, **kwargs): # type: (...) -> str
return self._env.run("python", "-m", "pip", *args, **kwargs) return self._env.run_pip(*args, **kwargs)
def requirement(self, package, formatted=False): def requirement(self, package, formatted=False):
if formatted and not package.source_type: if formatted and not package.source_type:
......
...@@ -11,7 +11,6 @@ from typing import Union ...@@ -11,7 +11,6 @@ from typing import Union
from clikit.api.io.flags import VERY_VERBOSE from clikit.api.io.flags import VERY_VERBOSE
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils._compat import glob from poetry.utils._compat import glob
from poetry.utils._compat import lru_cache from poetry.utils._compat import lru_cache
from poetry.utils._compat import to_str from poetry.utils._compat import to_str
...@@ -84,15 +83,6 @@ class Builder(object): ...@@ -84,15 +83,6 @@ class Builder(object):
explicitely_excluded = set() explicitely_excluded = set()
for excluded_glob in self._package.exclude: for excluded_glob in self._package.exclude:
excluded_path = Path(self._path, excluded_glob)
try:
is_dir = excluded_path.is_dir()
except OSError:
# On Windows, testing if a path with a glob is a directory will raise an OSError
is_dir = False
if is_dir:
excluded_glob = Path(excluded_glob, "**/*")
for excluded in glob( for excluded in glob(
Path(self._path, excluded_glob).as_posix(), recursive=True Path(self._path, excluded_glob).as_posix(), recursive=True
...@@ -112,10 +102,18 @@ class Builder(object): ...@@ -112,10 +102,18 @@ class Builder(object):
return result return result
def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool
if not isinstance(filepath, basestring): exclude_path = Path(filepath)
filepath = filepath.as_posix()
while True:
if exclude_path.as_posix() in self.find_excluded_files():
return True
if len(exclude_path.parts) > 1:
exclude_path = exclude_path.parent
else:
break
return filepath in self.find_excluded_files() return False
def find_files_to_add(self, exclude_build=True): # type: (bool) -> list def find_files_to_add(self, exclude_build=True): # type: (bool) -> list
""" """
...@@ -164,7 +162,7 @@ class Builder(object): ...@@ -164,7 +162,7 @@ class Builder(object):
) )
to_add.append(license_file.relative_to(self._path)) to_add.append(license_file.relative_to(self._path))
# If a README is specificed we need to include it # If a README is specified we need to include it
# to avoid errors # to avoid errors
if "readme" in self._poetry.local_config: if "readme" in self._poetry.local_config:
readme = self._path / self._poetry.local_config["readme"] readme = self._path / self._poetry.local_config["readme"]
......
...@@ -31,16 +31,14 @@ class EditableBuilder(Builder): ...@@ -31,16 +31,14 @@ class EditableBuilder(Builder):
try: try:
if self._env.pip_version < Version(19, 0): if self._env.pip_version < Version(19, 0):
self._env.run("python", "-m", "pip", "install", "-e", str(self._path)) self._env.run_pip("install", "-e", str(self._path))
else: else:
# Temporarily rename pyproject.toml # Temporarily rename pyproject.toml
shutil.move( shutil.move(
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp")) str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
) )
try: try:
self._env.run( self._env.run_pip("install", "-e", str(self._path))
"python", "-m", "pip", "install", "-e", str(self._path)
)
finally: finally:
shutil.move( shutil.move(
str(self._poetry.file.with_suffix(".tmp")), str(self._poetry.file.with_suffix(".tmp")),
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import os import os
import re import re
import tarfile import tarfile
import time
from collections import defaultdict from collections import defaultdict
from copy import copy from copy import copy
...@@ -83,12 +84,14 @@ class SdistBuilder(Builder): ...@@ -83,12 +84,14 @@ class SdistBuilder(Builder):
setup = self.build_setup() setup = self.build_setup()
tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py")) tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py"))
tar_info.size = len(setup) tar_info.size = len(setup)
tar_info.mtime = time.time()
tar.addfile(tar_info, BytesIO(setup)) tar.addfile(tar_info, BytesIO(setup))
pkg_info = self.build_pkg_info() pkg_info = self.build_pkg_info()
tar_info = tarfile.TarInfo(pjoin(tar_dir, "PKG-INFO")) tar_info = tarfile.TarInfo(pjoin(tar_dir, "PKG-INFO"))
tar_info.size = len(pkg_info) tar_info.size = len(pkg_info)
tar_info.mtime = time.time()
tar.addfile(tar_info, BytesIO(pkg_info)) tar.addfile(tar_info, BytesIO(pkg_info))
finally: finally:
tar.close() tar.close()
...@@ -234,7 +237,7 @@ class SdistBuilder(Builder): ...@@ -234,7 +237,7 @@ class SdistBuilder(Builder):
if from_top_level == ".": if from_top_level == ".":
continue continue
is_subpkg = "__init__.py" in filenames is_subpkg = any([filename.endswith(".py") for filename in filenames])
if is_subpkg: if is_subpkg:
subpkg_paths.add(from_top_level) subpkg_paths.add(from_top_level)
parts = from_top_level.split(os.sep) parts = from_top_level.split(os.sep)
......
...@@ -115,9 +115,9 @@ class WheelBuilder(Builder): ...@@ -115,9 +115,9 @@ class WheelBuilder(Builder):
return return
lib = lib[0] lib = lib[0]
excluded = self.find_excluded_files()
for pkg in lib.glob("**/*"): for pkg in lib.glob("**/*"):
if pkg.is_dir() or pkg in excluded: if pkg.is_dir() or self.is_excluded(pkg):
continue continue
rel_path = str(pkg.relative_to(lib)) rel_path = str(pkg.relative_to(lib))
...@@ -132,7 +132,7 @@ class WheelBuilder(Builder): ...@@ -132,7 +132,7 @@ class WheelBuilder(Builder):
self._add_file(wheel, pkg, rel_path) self._add_file(wheel, pkg, rel_path)
def _copy_module(self, wheel): def _copy_module(self, wheel):
excluded = self.find_excluded_files()
to_add = [] to_add = []
for include in self._module.includes: for include in self._module.includes:
...@@ -153,7 +153,9 @@ class WheelBuilder(Builder): ...@@ -153,7 +153,9 @@ class WheelBuilder(Builder):
else: else:
rel_file = file.relative_to(self._path) rel_file = file.relative_to(self._path)
if rel_file.as_posix() in excluded: if self.is_excluded(rel_file.as_posix()) and isinstance(
include, PackageInclude
):
continue continue
if file.suffix == ".pyc": if file.suffix == ".pyc":
......
...@@ -2,7 +2,7 @@ import logging ...@@ -2,7 +2,7 @@ import logging
from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_cert
from poetry.utils.helpers import get_client_cert from poetry.utils.helpers import get_client_cert
from poetry.utils.helpers import get_http_basic_auth from poetry.utils.password_manager import PasswordManager
from .uploader import Uploader from .uploader import Uploader
...@@ -20,6 +20,7 @@ class Publisher: ...@@ -20,6 +20,7 @@ class Publisher:
self._package = poetry.package self._package = poetry.package
self._io = io self._io = io
self._uploader = Uploader(poetry, io) self._uploader = Uploader(poetry, io)
self._password_manager = PasswordManager(poetry.config)
@property @property
def files(self): def files(self):
...@@ -60,21 +61,21 @@ class Publisher: ...@@ -60,21 +61,21 @@ class Publisher:
if not (username and password): if not (username and password):
# Check if we have a token first # Check if we have a token first
token = self._poetry.config.get("pypi-token.{}".format(repository_name)) token = self._password_manager.get_pypi_token(repository_name)
if token: if token:
logger.debug("Found an API token for {}.".format(repository_name)) logger.debug("Found an API token for {}.".format(repository_name))
username = "__token__" username = "__token__"
password = token password = token
else: else:
auth = get_http_basic_auth(self._poetry.config, repository_name) auth = self._password_manager.get_http_auth(repository_name)
if auth: if auth:
logger.debug( logger.debug(
"Found authentication information for {}.".format( "Found authentication information for {}.".format(
repository_name repository_name
) )
) )
username = auth[0] username = auth["username"]
password = auth[1] password = auth["password"]
resolved_client_cert = client_cert or get_client_cert( resolved_client_cert = client_cert or get_client_cert(
self._poetry.config, repository_name self._poetry.config, repository_name
......
...@@ -12,7 +12,6 @@ class PackageInclude(Include): ...@@ -12,7 +12,6 @@ class PackageInclude(Include):
base = base / source base = base / source
super(PackageInclude, self).__init__(base, include, formats=formats) super(PackageInclude, self).__init__(base, include, formats=formats)
self.check_elements() self.check_elements()
@property @property
...@@ -35,6 +34,8 @@ class PackageInclude(Include): ...@@ -35,6 +34,8 @@ class PackageInclude(Include):
return self.check_elements() return self.check_elements()
def check_elements(self): # type: () -> PackageInclude def check_elements(self): # type: () -> PackageInclude
root = self._elements[0]
if not self._elements: if not self._elements:
raise ValueError( raise ValueError(
"{} does not contain any element".format(self._base / self._include) "{} does not contain any element".format(self._base / self._include)
...@@ -44,20 +45,24 @@ class PackageInclude(Include): ...@@ -44,20 +45,24 @@ class PackageInclude(Include):
# Probably glob # Probably glob
self._is_package = True self._is_package = True
# The __init__.py file should be first # Packages no longer need an __init__.py in python3, but there must
root = self._elements[0] # at least be one .py file for it to be considered a package
if root.name != "__init__.py": if not any([element.suffix == ".py" for element in self._elements]):
raise ValueError("{} is not a package.".format(root)) raise ValueError("{} is not a package.".format(root.name))
self._package = root.parent.name self._package = root.parent.name
else: else:
if self._elements[0].is_dir(): if root.is_dir():
# If it's a directory, we include everything inside it # If it's a directory, we include everything inside it
self._package = self._elements[0].name self._package = root.name
self._elements = sorted(list(self._elements[0].glob("**/*"))) self._elements = sorted(list(root.glob("**/*")))
if not any([element.suffix == ".py" for element in self._elements]):
raise ValueError("{} is not a package.".format(root.name))
self._is_package = True self._is_package = True
else: else:
self._package = self._elements[0].stem self._package = root.stem
self._is_module = True self._is_module = True
return self return self
...@@ -31,18 +31,27 @@ class _Writer: ...@@ -31,18 +31,27 @@ class _Writer:
def write(self): def write(self):
buffer = [] buffer = []
required_python_version = None required_python_version_notification = False
for incompatibility in self._root.external_incompatibilities: for incompatibility in self._root.external_incompatibilities:
if isinstance(incompatibility.cause, PythonCause): if isinstance(incompatibility.cause, PythonCause):
required_python_version = incompatibility.cause.root_python_version if not required_python_version_notification:
break buffer.append(
"The current project's Python requirement ({}) "
"is not compatible with some of the required "
"packages Python requirement:".format(
incompatibility.cause.root_python_version
)
)
required_python_version_notification = True
if required_python_version is not None:
buffer.append( buffer.append(
"The current project must support the following Python versions: {}".format( " - {} requires Python {}".format(
required_python_version incompatibility.terms[0].dependency.name,
incompatibility.cause.python_version,
) )
) )
if required_python_version_notification:
buffer.append("") buffer.append("")
if isinstance(self._root.cause, ConflictCause): if isinstance(self._root.cause, ConflictCause):
......
...@@ -25,6 +25,8 @@ from .vcs_dependency import VCSDependency ...@@ -25,6 +25,8 @@ from .vcs_dependency import VCSDependency
def dependency_from_pep_508(name): def dependency_from_pep_508(name):
from poetry.vcs.git import ParsedUrl
# Removing comments # Removing comments
parts = name.split("#", 1) parts = name.split("#", 1)
name = parts[0].strip() name = parts[0].strip()
...@@ -46,6 +48,8 @@ def dependency_from_pep_508(name): ...@@ -46,6 +48,8 @@ def dependency_from_pep_508(name):
if is_url(name): if is_url(name):
link = Link(name) link = Link(name)
elif req.url:
link = Link(req.url)
else: else:
p, extras = strip_extras(path) p, extras = strip_extras(path)
if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")): if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")):
...@@ -74,10 +78,15 @@ def dependency_from_pep_508(name): ...@@ -74,10 +78,15 @@ def dependency_from_pep_508(name):
version = m.group("ver") version = m.group("ver")
dep = Dependency(name, version) dep = Dependency(name, version)
else: else:
name = link.egg_fragment name = req.name or link.egg_fragment
if link.scheme == "git": if link.scheme.startswith("git+"):
url = ParsedUrl.parse(link.url)
dep = VCSDependency(name, "git", url.url, rev=url.rev)
elif link.scheme == "git":
dep = VCSDependency(name, "git", link.url_without_fragment) dep = VCSDependency(name, "git", link.url_without_fragment)
elif link.scheme in ["http", "https"]:
dep = URLDependency(name, link.url_without_fragment)
else: else:
dep = Dependency(name, "*") dep = Dependency(name, "*")
else: else:
......
...@@ -55,6 +55,7 @@ class Dependency(object): ...@@ -55,6 +55,7 @@ class Dependency(object):
self._python_constraint = parse_constraint("*") self._python_constraint = parse_constraint("*")
self._transitive_python_versions = None self._transitive_python_versions = None
self._transitive_python_constraint = None self._transitive_python_constraint = None
self._transitive_marker = None
self._extras = [] self._extras = []
self._in_extras = [] self._in_extras = []
...@@ -118,6 +119,17 @@ class Dependency(object): ...@@ -118,6 +119,17 @@ class Dependency(object):
self._transitive_python_constraint = parse_constraint(value) self._transitive_python_constraint = parse_constraint(value)
@property @property
def transitive_marker(self):
if self._transitive_marker is None:
return self.marker
return self._transitive_marker
@transitive_marker.setter
def transitive_marker(self, value):
self._transitive_marker = value
@property
def python_constraint(self): def python_constraint(self):
return self._python_constraint return self._python_constraint
......
...@@ -74,6 +74,17 @@ class DirectoryDependency(Dependency): ...@@ -74,6 +74,17 @@ class DirectoryDependency(Dependency):
def develop(self): def develop(self):
return self._develop return self._develop
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
requirement += " @ {}".format(self._path)
return requirement
def supports_poetry(self): def supports_poetry(self):
return self._supports_poetry return self._supports_poetry
......
...@@ -49,6 +49,17 @@ class FileDependency(Dependency): ...@@ -49,6 +49,17 @@ class FileDependency(Dependency):
def full_path(self): def full_path(self):
return self._full_path.resolve() return self._full_path.resolve()
@property
def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name
if self.extras:
requirement += "[{}]".format(",".join(self.extras))
requirement += " @ {}".format(self._path)
return requirement
def is_file(self): def is_file(self):
return True return True
......
...@@ -8,6 +8,7 @@ from tomlkit import document ...@@ -8,6 +8,7 @@ from tomlkit import document
from tomlkit import inline_table from tomlkit import inline_table
from tomlkit import item from tomlkit import item
from tomlkit import table from tomlkit import table
from tomlkit.exceptions import TOMLKitError
import poetry.packages import poetry.packages
import poetry.repositories import poetry.repositories
...@@ -137,8 +138,11 @@ class Locker(object): ...@@ -137,8 +138,11 @@ class Locker(object):
package.add_dependency(dep_name, constraint) package.add_dependency(dep_name, constraint)
if "develop" in info:
package.develop = info["develop"]
if "source" in info: if "source" in info:
package.source_type = info["source"]["type"] package.source_type = info["source"].get("type", "")
package.source_url = info["source"]["url"] package.source_url = info["source"]["url"]
package.source_reference = info["source"]["reference"] package.source_reference = info["source"]["reference"]
...@@ -217,7 +221,10 @@ class Locker(object): ...@@ -217,7 +221,10 @@ class Locker(object):
if not self._lock.exists(): if not self._lock.exists():
raise RuntimeError("No lockfile found. Unable to read locked packages") raise RuntimeError("No lockfile found. Unable to read locked packages")
try:
return self._lock.read() return self._lock.read()
except TOMLKitError as e:
raise RuntimeError("Unable to read the lock file ({}).".format(e))
def _lock_packages( def _lock_packages(
self, packages self, packages
...@@ -290,11 +297,14 @@ class Locker(object): ...@@ -290,11 +297,14 @@ class Locker(object):
data["dependencies"] = dependencies data["dependencies"] = dependencies
if package.source_type: if package.source_url:
data["source"] = { data["source"] = {
"type": package.source_type,
"url": package.source_url, "url": package.source_url,
"reference": package.source_reference, "reference": package.source_reference,
} }
if package.source_type:
data["source"]["type"] = package.source_type
if package.source_type == "directory":
data["develop"] = package.develop
return data return data
...@@ -279,7 +279,7 @@ class Package(object): ...@@ -279,7 +279,7 @@ class Package(object):
message = ( message = (
'The "{}" dependency specifies ' 'The "{}" dependency specifies '
'the "allows-prereleases" property, which is deprecated. ' 'the "allows-prereleases" property, which is deprecated. '
'Use "allow-preleases" instead.'.format(name) 'Use "allow-prereleases" instead.'.format(name)
) )
warn(message, DeprecationWarning) warn(message, DeprecationWarning)
logger.warning(message) logger.warning(message)
...@@ -297,6 +297,7 @@ class Package(object): ...@@ -297,6 +297,7 @@ class Package(object):
branch=constraint.get("branch", None), branch=constraint.get("branch", None),
tag=constraint.get("tag", None), tag=constraint.get("tag", None),
rev=constraint.get("rev", None), rev=constraint.get("rev", None),
category=category,
optional=optional, optional=optional,
) )
elif "file" in constraint: elif "file" in constraint:
......
...@@ -5,8 +5,13 @@ import re ...@@ -5,8 +5,13 @@ import re
from poetry.packages.constraints.constraint import Constraint from poetry.packages.constraints.constraint import Constraint
from poetry.packages.constraints.multi_constraint import MultiConstraint from poetry.packages.constraints.multi_constraint import MultiConstraint
from poetry.packages.constraints.union_constraint import UnionConstraint from poetry.packages.constraints.union_constraint import UnionConstraint
from poetry.semver import EmptyConstraint
from poetry.semver import Version from poetry.semver import Version
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver import VersionUnion from poetry.semver import VersionUnion
from poetry.semver import parse_constraint
from poetry.version.markers import BaseMarker
from poetry.version.markers import MarkerUnion from poetry.version.markers import MarkerUnion
from poetry.version.markers import MultiMarker from poetry.version.markers import MultiMarker
from poetry.version.markers import SingleMarker from poetry.version.markers import SingleMarker
...@@ -236,3 +241,66 @@ def create_nested_marker(name, constraint): ...@@ -236,3 +241,66 @@ def create_nested_marker(name, constraint):
marker = '{} {} "{}"'.format(name, op, version) marker = '{} {} "{}"'.format(name, op, version)
return marker return marker
def get_python_constraint_from_marker(
marker,
): # type: (BaseMarker) -> VersionConstraint
python_marker = marker.only("python_version")
if python_marker.is_any():
return VersionRange()
if python_marker.is_empty():
return EmptyConstraint()
markers = convert_markers(marker)
ors = []
for or_ in markers["python_version"]:
ands = []
for op, version in or_:
# Expand python version
if op == "==":
version = "~" + version
op = ""
elif op == "!=":
version += ".*"
elif op in ("<=", ">"):
parsed_version = Version.parse(version)
if parsed_version.precision == 1:
if op == "<=":
op = "<"
version = parsed_version.next_major.text
elif op == ">":
op = ">="
version = parsed_version.next_major.text
elif parsed_version.precision == 2:
if op == "<=":
op = "<"
version = parsed_version.next_minor.text
elif op == ">":
op = ">="
version = parsed_version.next_minor.text
elif op in ("in", "not in"):
versions = []
for v in re.split("[ ,]+", version):
split = v.split(".")
if len(split) in [1, 2]:
split.append("*")
op_ = "" if op == "in" else "!="
else:
op_ = "==" if op == "in" else "!="
versions.append(op_ + ".".join(split))
glue = " || " if op == "in" else ", "
if versions:
ands.append(glue.join(versions))
continue
ands.append("{}{}".format(op, version))
ors.append(" ".join(ands))
return parse_constraint(" || ".join(ors))
from poetry.vcs import git
from .dependency import Dependency from .dependency import Dependency
...@@ -7,7 +9,15 @@ class VCSDependency(Dependency): ...@@ -7,7 +9,15 @@ class VCSDependency(Dependency):
""" """
def __init__( def __init__(
self, name, vcs, source, branch=None, tag=None, rev=None, optional=False self,
name,
vcs,
source,
branch=None,
tag=None,
rev=None,
category="main",
optional=False,
): ):
self._vcs = vcs self._vcs = vcs
self._source = source self._source = source
...@@ -21,7 +31,7 @@ class VCSDependency(Dependency): ...@@ -21,7 +31,7 @@ class VCSDependency(Dependency):
self._rev = rev self._rev = rev
super(VCSDependency, self).__init__( super(VCSDependency, self).__init__(
name, "*", optional=optional, allows_prereleases=True name, "*", category=category, optional=optional, allows_prereleases=True
) )
@property @property
...@@ -65,11 +75,17 @@ class VCSDependency(Dependency): ...@@ -65,11 +75,17 @@ class VCSDependency(Dependency):
@property @property
def base_pep_508_name(self): # type: () -> str def base_pep_508_name(self): # type: () -> str
requirement = self.pretty_name requirement = self.pretty_name
parsed_url = git.ParsedUrl.parse(self._source)
if self.extras: if self.extras:
requirement += "[{}]".format(",".join(self.extras)) requirement += "[{}]".format(",".join(self.extras))
if parsed_url.protocol is not None:
requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference) requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference)
else:
requirement += " @ {}+ssh://{}@{}".format(
self._vcs, parsed_url.format(), self.reference
)
return requirement return requirement
......
...@@ -6,6 +6,7 @@ import time ...@@ -6,6 +6,7 @@ import time
from contextlib import contextmanager from contextlib import contextmanager
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any
from typing import List from typing import List
from typing import Optional from typing import Optional
...@@ -27,6 +28,7 @@ from poetry.packages import PackageCollection ...@@ -27,6 +28,7 @@ from poetry.packages import PackageCollection
from poetry.packages import URLDependency from poetry.packages import URLDependency
from poetry.packages import VCSDependency from poetry.packages import VCSDependency
from poetry.packages import dependency_from_pep_508 from poetry.packages import dependency_from_pep_508
from poetry.packages.utils.utils import get_python_constraint_from_marker
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.utils._compat import PY35 from poetry.utils._compat import PY35
from poetry.utils._compat import OrderedDict from poetry.utils._compat import OrderedDict
...@@ -61,9 +63,7 @@ class Provider: ...@@ -61,9 +63,7 @@ class Provider:
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"} UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"}
def __init__( def __init__(self, package, pool, io): # type: (Package, Pool, Any) -> None
self, package, pool, io # type: Package # type: Pool
): # type: (...) -> None
self._package = package self._package = package
self._pool = pool self._pool = pool
self._io = io self._io = io
...@@ -275,6 +275,7 @@ class Provider: ...@@ -275,6 +275,7 @@ class Provider:
) )
package.source_url = dependency.path.as_posix() package.source_url = dependency.path.as_posix()
package.develop = dependency.develop
if dependency.base is not None: if dependency.base is not None:
package.root_dir = dependency.base.as_posix() package.root_dir = dependency.base.as_posix()
...@@ -489,14 +490,15 @@ class Provider: ...@@ -489,14 +490,15 @@ class Provider:
if not package.python_constraint.allows_all( if not package.python_constraint.allows_all(
self._package.python_constraint self._package.python_constraint
): ):
intersection = package.python_constraint.intersect( transitive_python_constraint = get_python_constraint_from_marker(
package.dependency.transitive_python_constraint package.dependency.transitive_marker
) )
difference = package.dependency.transitive_python_constraint.difference( intersection = package.python_constraint.intersect(
intersection transitive_python_constraint
) )
difference = transitive_python_constraint.difference(intersection)
if ( if (
package.dependency.transitive_python_constraint.is_any() transitive_python_constraint.is_any()
or self._package.python_constraint.intersect( or self._package.python_constraint.intersect(
package.dependency.python_constraint package.dependency.python_constraint
).is_empty() ).is_empty()
...@@ -604,7 +606,7 @@ class Provider: ...@@ -604,7 +606,7 @@ class Provider:
new_markers = [] new_markers = []
for dep in _deps: for dep in _deps:
marker = dep.marker.without_extras() marker = dep.marker.without_extras()
if marker.is_empty(): if marker.is_any():
# No marker or only extras # No marker or only extras
continue continue
...@@ -671,13 +673,28 @@ class Provider: ...@@ -671,13 +673,28 @@ class Provider:
raise CompatibilityError(*python_constraints) raise CompatibilityError(*python_constraints)
# Modifying dependencies as needed # Modifying dependencies as needed
clean_dependencies = []
for dep in dependencies: for dep in dependencies:
if not package.dependency.transitive_marker.without_extras().is_any():
marker_intersection = package.dependency.transitive_marker.without_extras().intersect(
dep.marker.without_extras()
)
if marker_intersection.is_empty():
# The dependency is not needed, since the markers specified
# for the current package selection are not compatible with
# the markers for the current dependency, so we skip it
continue
dep.transitive_marker = marker_intersection
if not package.dependency.python_constraint.is_any(): if not package.dependency.python_constraint.is_any():
dep.transitive_python_versions = str( python_constraint_intersection = dep.python_constraint.intersect(
dep.python_constraint.intersect(
package.dependency.python_constraint package.dependency.python_constraint
) )
) if python_constraint_intersection.is_empty():
# This dependency is not needed under current python constraint.
continue
dep.transitive_python_versions = str(python_constraint_intersection)
if (package.dependency.is_directory() or package.dependency.is_file()) and ( if (package.dependency.is_directory() or package.dependency.is_file()) and (
dep.is_directory() or dep.is_file() dep.is_directory() or dep.is_file()
...@@ -691,8 +708,9 @@ class Provider: ...@@ -691,8 +708,9 @@ class Provider:
# TODO: Improve the way we set the correct relative path for dependencies # TODO: Improve the way we set the correct relative path for dependencies
dep._path = relative dep._path = relative
clean_dependencies.append(dep)
package.requires = dependencies package.requires = clean_dependencies
return package return package
......
...@@ -56,12 +56,20 @@ class Solver: ...@@ -56,12 +56,20 @@ class Solver:
installed = True installed = True
if pkg.source_type == "git" and package.source_type == "git": if pkg.source_type == "git" and package.source_type == "git":
from poetry.vcs.git import Git
# Trying to find the currently installed version # Trying to find the currently installed version
pkg_source_url = Git.normalize_url(pkg.source_url)
package_source_url = Git.normalize_url(package.source_url)
for locked in self._locked.packages: for locked in self._locked.packages:
if locked.name != pkg.name or locked.source_type != "git":
continue
locked_source_url = Git.normalize_url(locked.source_url)
if ( if (
locked.name == pkg.name locked.name == pkg.name
and locked.source_type == pkg.source_type and locked.source_type == pkg.source_type
and locked.source_url == pkg.source_url and locked_source_url == pkg_source_url
and locked.source_reference == pkg.source_reference and locked.source_reference == pkg.source_reference
): ):
pkg = Package(pkg.name, locked.version) pkg = Package(pkg.name, locked.version)
...@@ -70,7 +78,7 @@ class Solver: ...@@ -70,7 +78,7 @@ class Solver:
pkg.source_reference = locked.source_reference pkg.source_reference = locked.source_reference
break break
if pkg.source_url != package.source_url or ( if pkg_source_url != package_source_url or (
pkg.source_reference != package.source_reference pkg.source_reference != package.source_reference
and not pkg.source_reference.startswith( and not pkg.source_reference.startswith(
package.source_reference package.source_reference
...@@ -84,6 +92,8 @@ class Solver: ...@@ -84,6 +92,8 @@ class Solver:
elif package.version != pkg.version: elif package.version != pkg.version:
# Checking version # Checking version
operations.append(Update(pkg, package)) operations.append(Update(pkg, package))
elif package.source_type != pkg.source_type:
operations.append(Update(pkg, package))
else: else:
operations.append(Install(package).skip("Already installed")) operations.append(Install(package).skip("Already installed"))
...@@ -212,8 +222,12 @@ class Solver: ...@@ -212,8 +222,12 @@ class Solver:
else: else:
category = dep.category category = dep.category
optional = dep.is_optional() and not dep.is_activated() optional = dep.is_optional() and not dep.is_activated()
intersection = previous["marker"].intersect(previous_dep.marker) intersection = (
intersection = intersection.intersect(package.marker) previous["marker"]
.without_extras()
.intersect(previous_dep.transitive_marker.without_extras())
)
intersection = intersection.intersect(package.marker.without_extras())
marker = intersection marker = intersection
......
import re
from poetry.packages import Package from poetry.packages import Package
from poetry.utils._compat import Path
from poetry.utils._compat import metadata
from poetry.utils.env import Env from poetry.utils.env import Env
from .repository import Repository from .repository import Repository
...@@ -15,30 +15,52 @@ class InstalledRepository(Repository): ...@@ -15,30 +15,52 @@ class InstalledRepository(Repository):
For now, it uses the pip "freeze" command. For now, it uses the pip "freeze" command.
""" """
repo = cls() repo = cls()
seen = set()
freeze_output = env.run("python", "-m", "pip", "freeze") for entry in env.sys_path:
for line in freeze_output.split("\n"): for distribution in sorted(
if "==" in line: metadata.distributions(path=[entry]), key=lambda d: str(d._path),
name, version = re.split("={2,3}", line) ):
repo.add_package(Package(name, version, version)) name = distribution.metadata["name"]
elif line.startswith("-e "): version = distribution.metadata["version"]
line = line[3:].strip() package = Package(name, version, version)
if line.startswith("git+"): package.description = distribution.metadata.get("summary", "")
url = line.lstrip("git+")
if "@" in url: if package.name in seen:
url, rev = url.rsplit("@", 1) continue
else:
rev = "master" seen.add(package.name)
name = url.split("/")[-1].rstrip(".git")
if "#egg=" in rev:
rev, name = rev.split("#egg=")
package = Package(name, "0.0.0")
package.source_type = "git"
package.source_url = url
package.source_reference = rev
repo.add_package(package) repo.add_package(package)
path = Path(str(distribution._path))
is_standard_package = True
try:
path.relative_to(env.site_packages)
except ValueError:
is_standard_package = False
if is_standard_package:
continue
src_path = env.path / "src"
# A VCS dependency should have been installed
# in the src directory. If not, it's a path dependency
try:
path.relative_to(src_path)
from poetry.vcs.git import Git
git = Git()
revision = git.rev_parse("HEAD", src_path / package.name).strip()
url = git.remote_url(src_path / package.name)
package.source_type = "git"
package.source_url = url
package.source_reference = revision
except ValueError:
package.source_type = "directory"
package.source_url = str(path.parent)
return repo return repo
...@@ -269,7 +269,6 @@ class LegacyRepository(PyPiRepository): ...@@ -269,7 +269,6 @@ class LegacyRepository(PyPiRepository):
for version in versions: for version in versions:
package = Package(name, version) package = Package(name, version)
package.source_type = "legacy"
package.source_url = self._url package.source_url = self._url
if extras is not None: if extras is not None:
...@@ -314,7 +313,6 @@ class LegacyRepository(PyPiRepository): ...@@ -314,7 +313,6 @@ class LegacyRepository(PyPiRepository):
if release_info["requires_python"]: if release_info["requires_python"]:
package.python_versions = release_info["requires_python"] package.python_versions = release_info["requires_python"]
package.source_type = "legacy"
package.source_url = self._url package.source_url = self._url
package.source_reference = self.name package.source_reference = self.name
......
...@@ -8,10 +8,12 @@ from typing import Union ...@@ -8,10 +8,12 @@ from typing import Union
from cachecontrol import CacheControl from cachecontrol import CacheControl
from cachecontrol.caches.file_cache import FileCache from cachecontrol.caches.file_cache import FileCache
from cachecontrol.controller import logger as cache_control_logger
from cachy import CacheManager from cachy import CacheManager
from html5lib.html5parser import parse from html5lib.html5parser import parse
from requests import get from requests import get
from requests import session from requests import session
from requests.exceptions import TooManyRedirects
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.packages import Package from poetry.packages import Package
...@@ -39,12 +41,14 @@ except ImportError: ...@@ -39,12 +41,14 @@ except ImportError:
import urlparse import urlparse
cache_control_logger.setLevel(logging.ERROR)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PyPiRepository(Repository): class PyPiRepository(Repository):
CACHE_VERSION = parse_constraint("1.0.0b2") CACHE_VERSION = parse_constraint("1.0.0")
def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True): def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
self._url = url self._url = url
...@@ -63,9 +67,8 @@ class PyPiRepository(Repository): ...@@ -63,9 +67,8 @@ class PyPiRepository(Repository):
} }
) )
self._session = CacheControl( self._cache_control_cache = FileCache(str(release_cache_dir / "_http"))
session(), cache=FileCache(str(release_cache_dir / "_http")) self._session = CacheControl(session(), cache=self._cache_control_cache)
)
self._inspector = Inspector() self._inspector = Inspector()
super(PyPiRepository, self).__init__() super(PyPiRepository, self).__init__()
...@@ -357,7 +360,14 @@ class PyPiRepository(Repository): ...@@ -357,7 +360,14 @@ class PyPiRepository(Repository):
return data return data
def _get(self, endpoint): # type: (str) -> Union[dict, None] def _get(self, endpoint): # type: (str) -> Union[dict, None]
try:
json_response = self._session.get(self._url + endpoint) json_response = self._session.get(self._url + endpoint)
except TooManyRedirects:
# Cache control redirect loop.
# We try to remove the cache and try again
self._cache_control_cache.delete(self._url + endpoint)
json_response = self._session.get(self._url + endpoint)
if json_response.status_code == 404: if json_response.status_code == 404:
return None return None
......
...@@ -76,14 +76,12 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint ...@@ -76,14 +76,12 @@ def parse_single_constraint(constraint): # type: (str) -> VersionConstraint
version = Version.parse(m.group(1)) version = Version.parse(m.group(1))
if precision == 2: if precision == 2:
low = version
high = version.stable.next_major high = version.stable.next_major
else: else:
low = Version(version.major, version.minor, version.patch)
high = version.stable.next_minor high = version.stable.next_minor
return VersionRange( return VersionRange(
low, high, include_min=True, always_include_max_prerelease=True version, high, include_min=True, always_include_max_prerelease=True
) )
# Caret range # Caret range
......
...@@ -12,6 +12,13 @@ except ImportError: ...@@ -12,6 +12,13 @@ except ImportError:
from glob import glob from glob import glob
try: try:
from importlib import metadata
import zipfile as zipp
except ImportError:
import importlib_metadata as metadata
import zipp
try:
import urllib.parse as urlparse import urllib.parse as urlparse
except ImportError: except ImportError:
import urlparse import urlparse
......
...@@ -94,6 +94,13 @@ import sys ...@@ -94,6 +94,13 @@ import sys
print('.'.join([str(s) for s in sys.version_info[:3]])) print('.'.join([str(s) for s in sys.version_info[:3]]))
""" """
GET_SYS_PATH = """\
import json
import sys
print(json.dumps(sys.path))
"""
CREATE_VENV_COMMAND = """\ CREATE_VENV_COMMAND = """\
path = {!r} path = {!r}
...@@ -183,7 +190,7 @@ class EnvManager(object): ...@@ -183,7 +190,7 @@ class EnvManager(object):
try: try:
python_version = decode( python_version = decode(
subprocess.check_output( subprocess.check_output(
" ".join( list_to_shell_command(
[ [
python, python,
"-c", "-c",
...@@ -201,6 +208,24 @@ class EnvManager(object): ...@@ -201,6 +208,24 @@ class EnvManager(object):
patch = python_version.text patch = python_version.text
create = False create = False
is_root_venv = self._poetry.config.get("virtualenvs.in-project")
# If we are required to create the virtual environment in the root folder,
# create or recreate it if needed
if is_root_venv:
create = False
venv = self._poetry.file.parent / ".venv"
if venv.exists():
# We need to check if the patch version is correct
_venv = VirtualEnv(venv)
current_patch = ".".join(str(v) for v in _venv.version_info[:3])
if patch != current_patch:
create = True
self.create_venv(io, executable=python, force=create)
return self.get(reload=True)
envs = tomlkit.document() envs = tomlkit.document()
base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
if envs_file.exists(): if envs_file.exists():
...@@ -402,7 +427,7 @@ class EnvManager(object): ...@@ -402,7 +427,7 @@ class EnvManager(object):
try: try:
python_version = decode( python_version = decode(
subprocess.check_output( subprocess.check_output(
" ".join( list_to_shell_command(
[ [
python, python,
"-c", "-c",
...@@ -448,6 +473,10 @@ class EnvManager(object): ...@@ -448,6 +473,10 @@ class EnvManager(object):
cwd = self._poetry.file.parent cwd = self._poetry.file.parent
env = self.get(reload=True) env = self.get(reload=True)
if not env.is_sane():
force = True
if env.is_venv() and not force: if env.is_venv() and not force:
# Already inside a virtualenv. # Already inside a virtualenv.
return env return env
...@@ -469,21 +498,22 @@ class EnvManager(object): ...@@ -469,21 +498,22 @@ class EnvManager(object):
python_patch = ".".join([str(v) for v in sys.version_info[:3]]) python_patch = ".".join([str(v) for v in sys.version_info[:3]])
python_minor = ".".join([str(v) for v in sys.version_info[:2]]) python_minor = ".".join([str(v) for v in sys.version_info[:2]])
if executable: if executable:
python_minor = decode( python_patch = decode(
subprocess.check_output( subprocess.check_output(
" ".join( list_to_shell_command(
[ [
executable, executable,
"-c", "-c",
"\"import sys; print('.'.join([str(s) for s in sys.version_info[:2]]))\"", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
] ]
), ),
shell=True, shell=True,
).strip() ).strip()
) )
python_minor = ".".join(python_patch.split(".")[:2])
supported_python = self._poetry.package.python_constraint supported_python = self._poetry.package.python_constraint
if not supported_python.allows(Version.parse(python_minor)): if not supported_python.allows(Version.parse(python_patch)):
# The currently activated or chosen Python version # The currently activated or chosen Python version
# is not compatible with the Python constraint specified # is not compatible with the Python constraint specified
# for the project. # for the project.
...@@ -492,7 +522,7 @@ class EnvManager(object): ...@@ -492,7 +522,7 @@ class EnvManager(object):
# Otherwise, we try to find a compatible Python version. # Otherwise, we try to find a compatible Python version.
if executable: if executable:
raise NoCompatiblePythonVersionFound( raise NoCompatiblePythonVersionFound(
self._poetry.package.python_versions, python_minor self._poetry.package.python_versions, python_patch
) )
io.write_line( io.write_line(
...@@ -527,7 +557,7 @@ class EnvManager(object): ...@@ -527,7 +557,7 @@ class EnvManager(object):
try: try:
python_patch = decode( python_patch = decode(
subprocess.check_output( subprocess.check_output(
" ".join( list_to_shell_command(
[ [
python, python,
"-c", "-c",
...@@ -580,6 +610,12 @@ class EnvManager(object): ...@@ -580,6 +610,12 @@ class EnvManager(object):
self.build_venv(str(venv), executable=executable) self.build_venv(str(venv), executable=executable)
else: else:
if force: if force:
if not env.is_sane():
io.write_line(
"<warning>The virtual environment found in {} seems to be broken.</warning>".format(
env.path
)
)
io.write_line( io.write_line(
"Recreating virtualenv <c1>{}</> in {}".format(name, str(venv)) "Recreating virtualenv <c1>{}</> in {}".format(name, str(venv))
) )
...@@ -612,7 +648,9 @@ class EnvManager(object): ...@@ -612,7 +648,9 @@ class EnvManager(object):
# Create virtualenv by using an external executable # Create virtualenv by using an external executable
try: try:
p = subprocess.Popen( p = subprocess.Popen(
" ".join([executable, "-"]), stdin=subprocess.PIPE, shell=True list_to_shell_command([executable, "-"]),
stdin=subprocess.PIPE,
shell=True,
) )
p.communicate(encode(CREATE_VENV_COMMAND.format(path))) p.communicate(encode(CREATE_VENV_COMMAND.format(path)))
except CalledProcessError as e: except CalledProcessError as e:
...@@ -677,6 +715,7 @@ class Env(object): ...@@ -677,6 +715,7 @@ class Env(object):
self._marker_env = None self._marker_env = None
self._pip_version = None self._pip_version = None
self._site_packages = None
@property @property
def path(self): # type: () -> Path def path(self): # type: () -> Path
...@@ -732,15 +771,29 @@ class Env(object): ...@@ -732,15 +771,29 @@ class Env(object):
@property @property
def site_packages(self): # type: () -> Path def site_packages(self): # type: () -> Path
if self._is_windows: if self._site_packages is None:
return self._path / "Lib" / "site-packages" site_packages = []
dist_packages = []
for entry in self.sys_path:
entry = Path(entry)
if entry.name == "site-packages":
site_packages.append(entry)
elif entry.name == "dist-packages":
dist_packages.append(entry)
if not site_packages and not dist_packages:
raise RuntimeError("Unable to find the site-packages directory")
if site_packages:
self._site_packages = site_packages[0]
else:
self._site_packages = dist_packages[0]
return ( return self._site_packages
self._path
/ "lib" @property
/ "python{}.{}".format(*self.version_info[:2]) def sys_path(self): # type: () -> List[str]
/ "site-packages" raise NotImplementedError()
)
@classmethod @classmethod
def get_base_prefix(cls): # type: () -> Path def get_base_prefix(cls): # type: () -> Path
...@@ -761,6 +814,9 @@ class Env(object): ...@@ -761,6 +814,9 @@ class Env(object):
def get_marker_env(self): # type: () -> Dict[str, Any] def get_marker_env(self): # type: () -> Dict[str, Any]
raise NotImplementedError() raise NotImplementedError()
def get_pip_command(self): # type: () -> List[str]
raise NotImplementedError()
def config_var(self, var): # type: (str) -> Any def config_var(self, var): # type: (str) -> Any
raise NotImplementedError() raise NotImplementedError()
...@@ -777,12 +833,19 @@ class Env(object): ...@@ -777,12 +833,19 @@ class Env(object):
return True return True
def run(self, bin, *args, **kwargs): def run(self, bin, *args, **kwargs):
bin = self._bin(bin)
cmd = [bin] + list(args)
return self._run(cmd, **kwargs)
def run_pip(self, *args, **kwargs):
pip = self.get_pip_command()
cmd = pip + list(args)
return self._run(cmd, **kwargs)
def _run(self, cmd, **kwargs):
""" """
Run a command inside the Python environment. Run a command inside the Python environment.
""" """
bin = self._bin(bin)
cmd = [bin] + list(args)
shell = kwargs.get("shell", False) shell = kwargs.get("shell", False)
call = kwargs.pop("call", False) call = kwargs.pop("call", False)
input_ = kwargs.pop("input_", None) input_ = kwargs.pop("input_", None)
...@@ -865,12 +928,21 @@ class SystemEnv(Env): ...@@ -865,12 +928,21 @@ class SystemEnv(Env):
A system (i.e. not a virtualenv) Python environment. A system (i.e. not a virtualenv) Python environment.
""" """
@property
def sys_path(self): # type: () -> List[str]
return sys.path
def get_version_info(self): # type: () -> Tuple[int] def get_version_info(self): # type: () -> Tuple[int]
return sys.version_info return sys.version_info
def get_python_implementation(self): # type: () -> str def get_python_implementation(self): # type: () -> str
return platform.python_implementation() return platform.python_implementation()
def get_pip_command(self): # type: () -> List[str]
# If we're not in a venv, assume the interpreter we're running on
# has a pip and use that
return [sys.executable, "-m", "pip"]
def get_marker_env(self): # type: () -> Dict[str, Any] def get_marker_env(self): # type: () -> Dict[str, Any]
if hasattr(sys, "implementation"): if hasattr(sys, "implementation"):
info = sys.implementation.version info = sys.implementation.version
...@@ -931,6 +1003,12 @@ class VirtualEnv(Env): ...@@ -931,6 +1003,12 @@ class VirtualEnv(Env):
if base is None: if base is None:
self._base = Path(self.run("python", "-", input_=GET_BASE_PREFIX).strip()) self._base = Path(self.run("python", "-", input_=GET_BASE_PREFIX).strip())
@property
def sys_path(self): # type: () -> List[str]
output = self.run("python", "-", input_=GET_SYS_PATH)
return json.loads(output)
def get_version_info(self): # type: () -> Tuple[int] def get_version_info(self): # type: () -> Tuple[int]
output = self.run("python", "-", input_=GET_PYTHON_VERSION) output = self.run("python", "-", input_=GET_PYTHON_VERSION)
...@@ -939,6 +1017,11 @@ class VirtualEnv(Env): ...@@ -939,6 +1017,11 @@ class VirtualEnv(Env):
def get_python_implementation(self): # type: () -> str def get_python_implementation(self): # type: () -> str
return self.marker_env["platform_python_implementation"] return self.marker_env["platform_python_implementation"]
def get_pip_command(self): # type: () -> List[str]
# We're in a virtualenv that is known to be sane,
# so assume that we have a functional pip
return [self._bin("pip")]
def get_marker_env(self): # type: () -> Dict[str, Any] def get_marker_env(self): # type: () -> Dict[str, Any]
output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO) output = self.run("python", "-", input_=GET_ENVIRONMENT_INFO)
...@@ -963,7 +1046,7 @@ class VirtualEnv(Env): ...@@ -963,7 +1046,7 @@ class VirtualEnv(Env):
return value return value
def get_pip_version(self): # type: () -> Version def get_pip_version(self): # type: () -> Version
output = self.run("python", "-m", "pip", "--version").strip() output = self.run_pip("--version").strip()
m = re.match("pip (.+?)(?: from .+)?$", output) m = re.match("pip (.+?)(?: from .+)?$", output)
if not m: if not m:
return Version.parse("0.0") return Version.parse("0.0")
...@@ -977,7 +1060,7 @@ class VirtualEnv(Env): ...@@ -977,7 +1060,7 @@ class VirtualEnv(Env):
# A virtualenv is considered sane if both "python" and "pip" exist. # A virtualenv is considered sane if both "python" and "pip" exist.
return os.path.exists(self._bin("python")) and os.path.exists(self._bin("pip")) return os.path.exists(self._bin("python")) and os.path.exists(self._bin("pip"))
def run(self, bin, *args, **kwargs): def _run(self, cmd, **kwargs):
with self.temp_environ(): with self.temp_environ():
os.environ["PATH"] = self._updated_path() os.environ["PATH"] = self._updated_path()
os.environ["VIRTUAL_ENV"] = str(self._path) os.environ["VIRTUAL_ENV"] = str(self._path)
...@@ -985,7 +1068,7 @@ class VirtualEnv(Env): ...@@ -985,7 +1068,7 @@ class VirtualEnv(Env):
self.unset_env("PYTHONHOME") self.unset_env("PYTHONHOME")
self.unset_env("__PYVENV_LAUNCHER__") self.unset_env("__PYVENV_LAUNCHER__")
return super(VirtualEnv, self).run(bin, *args, **kwargs) return super(VirtualEnv, self)._run(cmd, **kwargs)
def execute(self, bin, *args, **kwargs): def execute(self, bin, *args, **kwargs):
with self.temp_environ(): with self.temp_environ():
...@@ -1024,11 +1107,11 @@ class NullEnv(SystemEnv): ...@@ -1024,11 +1107,11 @@ class NullEnv(SystemEnv):
self._execute = execute self._execute = execute
self.executed = [] self.executed = []
def run(self, bin, *args, **kwargs): def _run(self, cmd, **kwargs):
self.executed.append([bin] + list(args)) self.executed.append(cmd)
if self._execute: if self._execute:
return super(NullEnv, self).run(bin, *args, **kwargs) return super(NullEnv, self)._run(cmd, **kwargs)
def execute(self, bin, *args, **kwargs): def execute(self, bin, *args, **kwargs):
self.executed.append([bin] + list(args)) self.executed.append([bin] + list(args))
...@@ -1049,6 +1132,7 @@ class MockEnv(NullEnv): ...@@ -1049,6 +1132,7 @@ class MockEnv(NullEnv):
os_name="posix", os_name="posix",
is_venv=False, is_venv=False,
pip_version="19.1", pip_version="19.1",
sys_path=None,
**kwargs **kwargs
): ):
super(MockEnv, self).__init__(**kwargs) super(MockEnv, self).__init__(**kwargs)
...@@ -1059,6 +1143,7 @@ class MockEnv(NullEnv): ...@@ -1059,6 +1143,7 @@ class MockEnv(NullEnv):
self._os_name = os_name self._os_name = os_name
self._is_venv = is_venv self._is_venv = is_venv
self._pip_version = Version.parse(pip_version) self._pip_version = Version.parse(pip_version)
self._sys_path = sys_path
@property @property
def version_info(self): # type: () -> Tuple[int] def version_info(self): # type: () -> Tuple[int]
...@@ -1080,5 +1165,12 @@ class MockEnv(NullEnv): ...@@ -1080,5 +1165,12 @@ class MockEnv(NullEnv):
def pip_version(self): def pip_version(self):
return self._pip_version return self._pip_version
@property
def sys_path(self):
if self._sys_path is None:
return super(MockEnv, self).sys_path
return self._sys_path
def is_venv(self): # type: () -> bool def is_venv(self): # type: () -> bool
return self._is_venv return self._is_venv
...@@ -54,7 +54,7 @@ class Exporter(object): ...@@ -54,7 +54,7 @@ class Exporter(object):
extras=None, extras=None,
with_credentials=False, with_credentials=False,
): # type: (Path, Union[IO, str], bool, bool, bool) -> None ): # type: (Path, Union[IO, str], bool, bool, bool) -> None
indexes = [] indexes = set()
content = "" content = ""
packages = self._poetry.locker.locked_repository(dev).packages packages = self._poetry.locker.locked_repository(dev).packages
...@@ -94,7 +94,7 @@ class Exporter(object): ...@@ -94,7 +94,7 @@ class Exporter(object):
dependency.marker = package.marker dependency.marker = package.marker
line = "{}".format(package.source_url) line = "{}".format(package.source_url)
if package.develop: if package.develop and package.source_type == "directory":
line = "-e " + line line = "-e " + line
else: else:
dependency = package.to_dependency() dependency = package.to_dependency()
...@@ -104,8 +104,11 @@ class Exporter(object): ...@@ -104,8 +104,11 @@ class Exporter(object):
if ";" in requirement: if ";" in requirement:
line += "; {}".format(requirement.split(";")[1].strip()) line += "; {}".format(requirement.split(";")[1].strip())
if package.source_type == "legacy" and package.source_url: if (
indexes.append(package.source_url) package.source_type not in {"git", "directory", "file", "url"}
and package.source_url
):
indexes.add(package.source_url)
if package.files and with_hashes: if package.files and with_hashes:
hashes = [] hashes = []
...@@ -131,10 +134,9 @@ class Exporter(object): ...@@ -131,10 +134,9 @@ class Exporter(object):
content += line content += line
if indexes: if indexes:
# If we have extra indexes, we add them to the begin # If we have extra indexes, we add them to the beginning of the output
# of the output
indexes_header = "" indexes_header = ""
for index in indexes: for index in sorted(indexes):
repository = [ repository = [
r r
for r in self._poetry.pool.repositories for r in self._poetry.pool.repositories
......
...@@ -8,11 +8,6 @@ from contextlib import contextmanager ...@@ -8,11 +8,6 @@ from contextlib import contextmanager
from typing import List from typing import List
from typing import Optional from typing import Optional
from keyring import delete_password
from keyring import get_password
from keyring import set_password
from keyring.errors import KeyringError
from poetry.config.config import Config from poetry.config.config import Config
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.version import Version from poetry.version import Version
...@@ -95,53 +90,6 @@ def parse_requires(requires): # type: (str) -> List[str] ...@@ -95,53 +90,6 @@ def parse_requires(requires): # type: (str) -> List[str]
return requires_dist return requires_dist
def keyring_service_name(repository_name): # type: (str) -> str
return "{}-{}".format("poetry-repository", repository_name)
def keyring_repository_password_get(
repository_name, username
): # type: (str, str) -> Optional[str]
try:
return get_password(keyring_service_name(repository_name), username)
except (RuntimeError, KeyringError):
return None
def keyring_repository_password_set(
repository_name, username, password
): # type: (str, str, str) -> None
try:
set_password(keyring_service_name(repository_name), username, password)
except (RuntimeError, KeyringError):
raise RuntimeError("Failed to store password in keyring")
def keyring_repository_password_del(
config, repository_name
): # type: (Config, str) -> None
try:
repo_auth = config.get("http-basic.{}".format(repository_name))
if repo_auth and "username" in repo_auth:
delete_password(
keyring_service_name(repository_name), repo_auth["username"]
)
except (RuntimeError, KeyringError):
pass
def get_http_basic_auth(
config, repository_name
): # type: (Config, str) -> Optional[tuple]
repo_auth = config.get("http-basic.{}".format(repository_name))
if repo_auth:
username, password = repo_auth["username"], repo_auth.get("password")
if password is None:
password = keyring_repository_password_get(repository_name, username)
return username, password
return None
def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path] def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path]
cert = config.get("certificates.{}.cert".format(repository_name)) cert = config.get("certificates.{}.cert".format(repository_name))
if cert: if cert:
...@@ -159,11 +107,17 @@ def get_client_cert(config, repository_name): # type: (Config, str) -> Optional ...@@ -159,11 +107,17 @@ def get_client_cert(config, repository_name): # type: (Config, str) -> Optional
def _on_rm_error(func, path, exc_info): def _on_rm_error(func, path, exc_info):
if not os.path.exists(path):
return
os.chmod(path, stat.S_IWRITE) os.chmod(path, stat.S_IWRITE)
func(path) func(path)
def safe_rmtree(path): def safe_rmtree(path):
if Path(path).is_symlink():
return os.unlink(str(path))
shutil.rmtree(path, onerror=_on_rm_error) shutil.rmtree(path, onerror=_on_rm_error)
......
import logging
logger = logging.getLogger(__name__)
class PasswordManagerError(Exception):
pass
class KeyRingError(Exception):
pass
class KeyRing:
def __init__(self, namespace):
self._namespace = namespace
self._is_available = True
self._check()
def is_available(self):
return self._is_available
def get_password(self, name, username):
if not self.is_available():
return
import keyring
import keyring.errors
name = self.get_entry_name(name)
try:
return keyring.get_password(name, username)
except (RuntimeError, keyring.errors.KeyringError):
raise KeyRingError(
"Unable to retrieve the password for {} from the key ring".format(name)
)
def set_password(self, name, username, password):
if not self.is_available():
return
import keyring
import keyring.errors
name = self.get_entry_name(name)
try:
keyring.set_password(name, username, password)
except (RuntimeError, keyring.errors.KeyringError) as e:
raise KeyRingError(
"Unable to store the password for {} in the key ring: {}".format(
name, str(e)
)
)
def delete_password(self, name, username):
if not self.is_available():
return
import keyring
import keyring.errors
name = self.get_entry_name(name)
try:
keyring.delete_password(name, username)
except (RuntimeError, keyring.errors.KeyringError):
raise KeyRingError(
"Unable to delete the password for {} from the key ring".format(name)
)
def get_entry_name(self, name):
return "{}-{}".format(self._namespace, name)
def _check(self):
try:
import keyring
except Exception as e:
logger.debug("An error occurred while importing keyring: {}".format(str(e)))
self._is_available = False
return
backend = keyring.get_keyring()
name = backend.name.split(" ")[0]
if name == "fail":
logger.debug("No suitable keyring backend found")
self._is_available = False
elif "plaintext" in backend.name.lower():
logger.debug("Only a plaintext keyring backend is available. Not using it.")
self._is_available = False
elif name == "chainer":
try:
import keyring.backend
backends = keyring.backend.get_all_keyring()
self._is_available = any(
[
b.name.split(" ")[0] not in ["chainer", "fail"]
and "plaintext" not in b.name.lower()
for b in backends
]
)
except Exception:
self._is_available = False
if not self._is_available:
logger.warning("No suitable keyring backends were found")
class PasswordManager:
def __init__(self, config):
self._config = config
self._keyring = KeyRing("poetry-repository")
if not self._keyring.is_available():
logger.warning("Using a plaintext file to store and retrieve credentials")
@property
def keyring(self):
return self._keyring
def set_pypi_token(self, name, token):
if not self._keyring.is_available():
self._config.auth_config_source.add_property(
"pypi-token.{}".format(name), token
)
else:
self._keyring.set_password(name, "__token__", token)
def get_pypi_token(self, name):
if not self._keyring.is_available():
return self._config.get("pypi-token.{}".format(name))
return self._keyring.get_password(name, "__token__")
def delete_pypi_token(self, name):
if not self._keyring.is_available():
return self._config.auth_config_source.remove_property(
"pypi-token.{}".format(name)
)
self._keyring.delete_password(name, "__token__")
def get_http_auth(self, name):
auth = self._config.get("http-basic.{}".format(name))
if not auth:
return None
username, password = auth["username"], auth.get("password")
if password is None:
password = self._keyring.get_password(name, username)
return {
"username": username,
"password": password,
}
def set_http_password(self, name, username, password):
auth = {"username": username}
if not self._keyring.is_available():
auth["password"] = password
else:
self._keyring.set_password(name, username, password)
self._config.auth_config_source.add_property("http-basic.{}".format(name), auth)
def delete_http_password(self, name):
auth = self.get_http_auth(name)
if not auth or "username" not in auth:
return
try:
self._keyring.delete_password(name, auth["username"])
except KeyRingError:
pass
self._config.auth_config_source.remove_property("http-basic.{}".format(name))
...@@ -181,10 +181,11 @@ class SetupReader(object): ...@@ -181,10 +181,11 @@ class SetupReader(object):
continue continue
func = value.func func = value.func
if not isinstance(func, ast.Name): if not (isinstance(func, ast.Name) and func.id == "setup") and not (
continue isinstance(func, ast.Attribute)
and func.value.id == "setuptools"
if func.id != "setup": and func.attr == "setup"
):
continue continue
return value, elements return value, elements
......
...@@ -58,7 +58,9 @@ class Shell: ...@@ -58,7 +58,9 @@ class Shell:
self._path, ["-i"], dimensions=(terminal.height, terminal.width) self._path, ["-i"], dimensions=(terminal.height, terminal.width)
) )
if self._name == "zsh":
c.setecho(False) c.setecho(False)
activate_script = self._get_activate_script() activate_script = self._get_activate_script()
bin_dir = "Scripts" if WINDOWS else "bin" bin_dir = "Scripts" if WINDOWS else "bin"
activate_path = env.path / bin_dir / activate_script activate_path = env.path / bin_dir / activate_script
......
...@@ -2,9 +2,103 @@ ...@@ -2,9 +2,103 @@
import re import re
import subprocess import subprocess
from collections import namedtuple
from poetry.utils._compat import decode from poetry.utils._compat import decode
PATTERNS = [
re.compile(
r"(git\+)?"
r"((?P<protocol>\w+)://)"
r"((?P<user>\w+)@)?"
r"(?P<resource>[\w.\-]+)"
r"(:(?P<port>\d+))?"
r"(?P<pathname>(/(?P<owner>\w+)/)"
r"((?P<projects>([\w\-/]+)/)?(?P<name>[\w\-]+)(\.git|/)?)?)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
re.compile(
r"^(git\+)?"
r"(?P<protocol>https?|git|ssh|rsync|file)://"
r"(?:(?P<user>.+)@)*"
r"(?P<resource>[a-z0-9_.-]*)"
r"(:?P<port>[\d]+)?"
r"(?P<pathname>[:/]((?P<owner>[\w\-]+)/(?P<projects>([\w\-/]+)/)?)?"
r"((?P<name>[\w\-.]+?)(\.git|/)?)?)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
re.compile(
r"^(?:(?P<user>.+)@)*"
r"(?P<resource>[a-z0-9_.-]*)[:]*"
r"(?P<port>[\d]+)?"
r"(?P<pathname>/?(?P<owner>.+)/(?P<projects>([\w\-/]+)/)?(?P<name>.+).git)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
re.compile(
r"((?P<user>\w+)@)?"
r"(?P<resource>[\w.\-]+)"
r"[:/]{1,2}"
r"(?P<pathname>((?P<owner>\w+)/)?"
r"(?P<projects>([\w\-/]+)/)?"
r"((?P<name>[\w\-]+)(\.git|/)?)?)"
r"([@#](?P<rev>[^@#]+))?"
r"$"
),
]
class ParsedUrl:
def __init__(self, protocol, resource, pathname, user, port, name, rev):
self.protocol = protocol
self.resource = resource
self.pathname = pathname
self.user = user
self.port = port
self.name = name
self.rev = rev
@classmethod
def parse(cls, url): # type: () -> ParsedUrl
for pattern in PATTERNS:
m = pattern.match(url)
if m:
groups = m.groupdict()
return ParsedUrl(
groups.get("protocol"),
groups.get("resource"),
groups.get("pathname"),
groups.get("user"),
groups.get("port"),
groups.get("name"),
groups.get("rev"),
)
raise ValueError('Invalid git url "{}"'.format(url))
@property
def url(self): # type: () -> str
return "{}{}{}{}{}".format(
"{}://".format(self.protocol) if self.protocol else "",
"{}@".format(self.user) if self.user else "",
self.resource,
":{}".format(self.port) if self.port else "",
"/" + self.pathname.lstrip(":/"),
)
def format(self):
return "{}".format(self.url, "#{}".format(self.rev) if self.rev else "",)
def __str__(self): # type: () -> str
return self.format()
GitUrl = namedtuple("GitUrl", ["url", "revision"])
class GitConfig: class GitConfig:
def __init__(self, requires_git_presence=False): def __init__(self, requires_git_presence=False):
self._config = {} self._config = {}
...@@ -36,6 +130,30 @@ class Git: ...@@ -36,6 +130,30 @@ class Git:
self._config = GitConfig(requires_git_presence=True) self._config = GitConfig(requires_git_presence=True)
self._work_dir = work_dir self._work_dir = work_dir
@classmethod
def normalize_url(cls, url): # type: (str) -> GitUrl
parsed = ParsedUrl.parse(url)
formatted = re.sub(r"^git\+", "", url)
if parsed.rev:
formatted = re.sub(r"[#@]{}$".format(parsed.rev), "", formatted)
altered = parsed.format() != formatted
if altered:
if re.match(r"^git\+https?", url) and re.match(
r"^/?:[^0-9]", parsed.pathname
):
normalized = re.sub(r"git\+(.*:[^:]+):(.*)", "\\1/\\2", url)
elif re.match(r"^git\+file", url):
normalized = re.sub(r"git\+", "", url)
else:
normalized = re.sub(r"^(?:git\+)?ssh://", "", url)
else:
normalized = parsed.format()
return GitUrl(re.sub(r"#[^#]*$", "", normalized), parsed.rev)
@property @property
def config(self): # type: () -> GitConfig def config(self): # type: () -> GitConfig
return self._config return self._config
...@@ -93,9 +211,35 @@ class Git: ...@@ -93,9 +211,35 @@ class Git:
args += ["ls-files", "--others", "-i", "--exclude-standard"] args += ["ls-files", "--others", "-i", "--exclude-standard"]
output = self.run(*args) output = self.run(*args)
return output.split("\n") return output.strip().split("\n")
def remote_urls(self, folder=None): # type: (...) -> dict
output = self.run(
"config", "--get-regexp", r"remote\..*\.url", folder=folder
).strip()
urls = {}
for url in output.splitlines():
name, url = url.split(" ", 1)
urls[name.strip()] = url.strip()
return urls
def remote_url(self, folder=None): # type: (...) -> str
urls = self.remote_urls(folder=folder)
return urls.get("remote.origin.url", urls[list(urls.keys())[0]])
def run(self, *args, **kwargs): # type: (...) -> str
folder = kwargs.pop("folder", None)
if folder:
args = (
"--git-dir",
(folder / ".git").as_posix(),
"--work-tree",
folder.as_posix(),
) + args
def run(self, *args): # type: (...) -> str
return decode( return decode(
subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT) subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT)
) ).strip()
...@@ -175,6 +175,12 @@ class BaseMarker(object): ...@@ -175,6 +175,12 @@ class BaseMarker(object):
def without_extras(self): # type: () -> BaseMarker def without_extras(self): # type: () -> BaseMarker
raise NotImplementedError() raise NotImplementedError()
def exclude(self, marker_name): # type: (str) -> BaseMarker
raise NotImplementedError()
def only(self, marker_name): # type: (str) -> BaseMarker
raise NotImplementedError()
def __repr__(self): def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self)) return "<{} {}>".format(self.__class__.__name__, str(self))
...@@ -198,12 +204,27 @@ class AnyMarker(BaseMarker): ...@@ -198,12 +204,27 @@ class AnyMarker(BaseMarker):
def without_extras(self): def without_extras(self):
return self return self
def exclude(self, marker_name): # type: (str) -> AnyMarker
return self
def only(self, marker_name): # type: (str) -> AnyMarker
return self
def __str__(self): def __str__(self):
return "" return ""
def __repr__(self): def __repr__(self):
return "<AnyMarker>" return "<AnyMarker>"
def __hash__(self):
return hash(("<any>", "<any>"))
def __eq__(self, other):
if not isinstance(other, BaseMarker):
return NotImplemented
return isinstance(other, AnyMarker)
class EmptyMarker(BaseMarker): class EmptyMarker(BaseMarker):
def intersect(self, other): def intersect(self, other):
...@@ -224,12 +245,27 @@ class EmptyMarker(BaseMarker): ...@@ -224,12 +245,27 @@ class EmptyMarker(BaseMarker):
def without_extras(self): def without_extras(self):
return self return self
def exclude(self, marker_name): # type: (str) -> EmptyMarker
return self
def only(self, marker_name): # type: (str) -> EmptyMarker
return self
def __str__(self): def __str__(self):
return "<empty>" return "<empty>"
def __repr__(self): def __repr__(self):
return "<EmptyMarker>" return "<EmptyMarker>"
def __hash__(self):
return hash(("<empty>", "<empty>"))
def __eq__(self, other):
if not isinstance(other, BaseMarker):
return NotImplemented
return isinstance(other, EmptyMarker)
class SingleMarker(BaseMarker): class SingleMarker(BaseMarker):
...@@ -329,7 +365,7 @@ class SingleMarker(BaseMarker): ...@@ -329,7 +365,7 @@ class SingleMarker(BaseMarker):
if self == other: if self == other:
return self return self
return MarkerUnion(self, other) return MarkerUnion.of(self, other)
return other.union(self) return other.union(self)
...@@ -343,7 +379,16 @@ class SingleMarker(BaseMarker): ...@@ -343,7 +379,16 @@ class SingleMarker(BaseMarker):
return self._constraint.allows(self._parser(environment[self._name])) return self._constraint.allows(self._parser(environment[self._name]))
def without_extras(self): def without_extras(self):
if self.name == "extra": return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
if self.name == marker_name:
return AnyMarker()
return self
def only(self, marker_name): # type: (str) -> BaseMarker
if self.name != marker_name:
return EmptyMarker() return EmptyMarker()
return self return self
...@@ -392,7 +437,7 @@ class MultiMarker(BaseMarker): ...@@ -392,7 +437,7 @@ class MultiMarker(BaseMarker):
markers = _flatten_markers(markers, MultiMarker) markers = _flatten_markers(markers, MultiMarker)
for marker in markers: for marker in markers:
if marker in new_markers or marker.is_empty(): if marker in new_markers:
continue continue
if isinstance(marker, SingleMarker): if isinstance(marker, SingleMarker):
...@@ -408,11 +453,9 @@ class MultiMarker(BaseMarker): ...@@ -408,11 +453,9 @@ class MultiMarker(BaseMarker):
intersection = mark.constraint.intersect(marker.constraint) intersection = mark.constraint.intersect(marker.constraint)
if intersection == mark.constraint: if intersection == mark.constraint:
intersected = True intersected = True
break
elif intersection == marker.constraint: elif intersection == marker.constraint:
new_markers[i] = marker new_markers[i] = marker
intersected = True intersected = True
break
elif intersection.is_empty(): elif intersection.is_empty():
return EmptyMarker() return EmptyMarker()
...@@ -421,9 +464,12 @@ class MultiMarker(BaseMarker): ...@@ -421,9 +464,12 @@ class MultiMarker(BaseMarker):
new_markers.append(marker) new_markers.append(marker)
if not new_markers: if any(m.is_empty() for m in new_markers) or not new_markers:
return EmptyMarker() return EmptyMarker()
if len(new_markers) == 1 and new_markers[0].is_any():
return AnyMarker()
return MultiMarker(*new_markers) return MultiMarker(*new_markers)
@property @property
...@@ -443,7 +489,7 @@ class MultiMarker(BaseMarker): ...@@ -443,7 +489,7 @@ class MultiMarker(BaseMarker):
def union(self, other): def union(self, other):
if isinstance(other, (SingleMarker, MultiMarker)): if isinstance(other, (SingleMarker, MultiMarker)):
return MarkerUnion(self, other) return MarkerUnion.of(self, other)
return other.union(self) return other.union(self)
...@@ -455,10 +501,32 @@ class MultiMarker(BaseMarker): ...@@ -455,10 +501,32 @@ class MultiMarker(BaseMarker):
return True return True
def without_extras(self): def without_extras(self):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name == marker_name:
# The marker is not relevant since it must be excluded
continue
marker = m.exclude(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def only(self, marker_name): # type: (str) -> BaseMarker
new_markers = [] new_markers = []
for m in self._markers: for m in self._markers:
marker = m.without_extras() if isinstance(m, SingleMarker) and m.name != marker_name:
# The marker is not relevant since it's not one we want
continue
marker = m.only(marker_name)
if not marker.is_empty(): if not marker.is_empty():
new_markers.append(marker) new_markers.append(marker)
...@@ -493,17 +561,24 @@ class MultiMarker(BaseMarker): ...@@ -493,17 +561,24 @@ class MultiMarker(BaseMarker):
class MarkerUnion(BaseMarker): class MarkerUnion(BaseMarker):
def __init__(self, *markers): def __init__(self, *markers):
self._markers = [] self._markers = list(markers)
@property
def markers(self):
return self._markers
markers = _flatten_markers(markers, MarkerUnion) @classmethod
def of(cls, *markers): # type: (tuple) -> MarkerUnion
flattened_markers = _flatten_markers(markers, MarkerUnion)
for marker in markers: markers = []
if marker in self._markers: for marker in flattened_markers:
if marker in markers:
continue continue
if isinstance(marker, SingleMarker) and marker.name == "python_version": if isinstance(marker, SingleMarker) and marker.name == "python_version":
intersected = False intersected = False
for i, mark in enumerate(self._markers): for i, mark in enumerate(markers):
if ( if (
not isinstance(mark, SingleMarker) not isinstance(mark, SingleMarker)
or isinstance(mark, SingleMarker) or isinstance(mark, SingleMarker)
...@@ -516,18 +591,19 @@ class MarkerUnion(BaseMarker): ...@@ -516,18 +591,19 @@ class MarkerUnion(BaseMarker):
intersected = True intersected = True
break break
elif intersection == marker.constraint: elif intersection == marker.constraint:
self._markers[i] = marker markers[i] = marker
intersected = True intersected = True
break break
if intersected: if intersected:
continue continue
self._markers.append(marker) markers.append(marker)
@property if any(m.is_any() for m in markers):
def markers(self): return AnyMarker()
return self._markers
return MarkerUnion(*markers)
def append(self, marker): def append(self, marker):
if marker in self._markers: if marker in self._markers:
...@@ -557,7 +633,7 @@ class MarkerUnion(BaseMarker): ...@@ -557,7 +633,7 @@ class MarkerUnion(BaseMarker):
if not intersection.is_empty(): if not intersection.is_empty():
new_markers.append(intersection) new_markers.append(intersection)
return MarkerUnion(*new_markers) return MarkerUnion.of(*new_markers)
def union(self, other): def union(self, other):
if other.is_any(): if other.is_any():
...@@ -568,7 +644,7 @@ class MarkerUnion(BaseMarker): ...@@ -568,7 +644,7 @@ class MarkerUnion(BaseMarker):
new_markers = self._markers + [other] new_markers = self._markers + [other]
return MarkerUnion(*new_markers) return MarkerUnion.of(*new_markers)
def validate(self, environment): def validate(self, environment):
for m in self._markers: for m in self._markers:
...@@ -578,15 +654,37 @@ class MarkerUnion(BaseMarker): ...@@ -578,15 +654,37 @@ class MarkerUnion(BaseMarker):
return False return False
def without_extras(self): def without_extras(self):
return self.exclude("extra")
def exclude(self, marker_name): # type: (str) -> BaseMarker
new_markers = [] new_markers = []
for m in self._markers: for m in self._markers:
marker = m.without_extras() if isinstance(m, SingleMarker) and m.name == marker_name:
# The marker is not relevant since it must be excluded
continue
marker = m.exclude(marker_name)
if not marker.is_empty(): if not marker.is_empty():
new_markers.append(marker) new_markers.append(marker)
return MarkerUnion(*new_markers) return self.of(*new_markers)
def only(self, marker_name): # type: (str) -> BaseMarker
new_markers = []
for m in self._markers:
if isinstance(m, SingleMarker) and m.name != marker_name:
# The marker is not relevant since it's not one we want
continue
marker = m.only(marker_name)
if not marker.is_empty():
new_markers.append(marker)
return self.of(*new_markers)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, MarkerUnion): if not isinstance(other, MarkerUnion):
...@@ -602,7 +700,15 @@ class MarkerUnion(BaseMarker): ...@@ -602,7 +700,15 @@ class MarkerUnion(BaseMarker):
return h return h
def __str__(self): def __str__(self):
return " or ".join(str(m) for m in self._markers) return " or ".join(
str(m) for m in self._markers if not m.is_any() and not m.is_empty()
)
def is_any(self):
return any(m.is_any() for m in self._markers)
def is_empty(self):
return all(m.is_empty() for m in self._markers)
def parse_marker(marker): def parse_marker(marker):
...@@ -654,4 +760,4 @@ def _compact_markers(markers): ...@@ -654,4 +760,4 @@ def _compact_markers(markers):
if len(groups) == 1: if len(groups) == 1:
return groups[0] return groups[0]
return MarkerUnion(*groups) return MarkerUnion.of(*groups)
...@@ -28,7 +28,7 @@ from .markers import parse_marker ...@@ -28,7 +28,7 @@ from .markers import parse_marker
try: try:
import urllib.parse as urlparse import urllib.parse as urlparse
except ImportError: except ImportError:
from urlparse import urlparse import urlparse
LEGACY_REGEX = r""" LEGACY_REGEX = r"""
......
...@@ -35,8 +35,7 @@ class VersionSelector(object): ...@@ -35,8 +35,7 @@ class VersionSelector(object):
dependency = Dependency(package_name, constraint) dependency = Dependency(package_name, constraint)
# Select highest version if we have many package = None
package = candidates[0]
for candidate in candidates: for candidate in candidates:
if ( if (
candidate.is_prerelease() candidate.is_prerelease()
...@@ -47,9 +46,11 @@ class VersionSelector(object): ...@@ -47,9 +46,11 @@ class VersionSelector(object):
continue continue
# Select highest version of the two # Select highest version of the two
if package.version < candidate.version: if package is None or package.version < candidate.version:
package = candidate package = candidate
if package is None:
return False
return package return package
def find_recommended_require_version(self, package): def find_recommended_require_version(self, package):
......
[tool.poetry] [tool.poetry]
name = "poetry" name = "poetry"
version = "1.0.0b3" version = "1.0.1"
description = "Python dependency management and packaging made easy." description = "Python dependency management and packaging made easy."
authors = [ authors = [
"Sébastien Eustace <sebastien@eustace.io>" "Sébastien Eustace <sebastien@eustace.io>"
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.md" readme = "README.md"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
...@@ -24,7 +24,7 @@ classifiers = [ ...@@ -24,7 +24,7 @@ classifiers = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.4" python = "~2.7 || ^3.4"
cleo = "^0.7.6" cleo = "^0.7.6"
clikit = "^0.4.0" clikit = "^0.4.1"
requests = "^2.18" requests = "^2.18"
cachy = "^0.3.0" cachy = "^0.3.0"
requests-toolbelt = "^0.8.0" requests-toolbelt = "^0.8.0"
...@@ -46,15 +46,16 @@ pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" } ...@@ -46,15 +46,16 @@ pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" }
# Use glob2 for Python 2.7 and 3.4 # Use glob2 for Python 2.7 and 3.4
glob2 = { version = "^0.6", python = "~2.7 || ~3.4" } glob2 = { version = "^0.6", python = "~2.7 || ~3.4" }
# Use virtualenv for Python 2.7 since venv does not exist # Use virtualenv for Python 2.7 since venv does not exist
virtualenv = { version = "^16.0", python = "~2.7" } virtualenv = { version = "^16.7.9", python = "~2.7" }
# functools32 is needed for Python 2.7 # functools32 is needed for Python 2.7
functools32 = { version = "^3.2.3", python = "~2.7" } functools32 = { version = "^3.2.3", python = "~2.7" }
keyring = [ keyring = [
{ version = "^18.0", python = "~2.7 || ~3.4" }, { version = "^18.0.1", python = "~2.7 || ~3.4" },
{ version = "^19.0", python = "^3.5" } { version = "^20.0.1", python = "^3.5" }
] ]
# Use subprocess32 for Python 2.7 and 3.4 # Use subprocess32 for Python 2.7 and 3.4
subprocess32 = { version = "^3.5", python = "~2.7 || ~3.4" } subprocess32 = { version = "^3.5", python = "~2.7 || ~3.4" }
importlib-metadata = {version = "~1.1.3", python = "<3.8"}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^4.1" pytest = "^4.1"
...@@ -69,11 +70,20 @@ pre-commit = "^1.10" ...@@ -69,11 +70,20 @@ pre-commit = "^1.10"
tox = "^3.0" tox = "^3.0"
pytest-sugar = "^0.9.2" pytest-sugar = "^0.9.2"
httpretty = "^0.9.6" httpretty = "^0.9.6"
markdown-include = "^0.5.1"
[tool.poetry.scripts] [tool.poetry.scripts]
poetry = "poetry.console:main" poetry = "poetry.console:main"
[build-system]
requires = ["intreehooks"]
build-backend = "intreehooks:loader"
[tool.intreehooks]
build-backend = "poetry.masonry.api"
[tool.isort] [tool.isort]
line_length = 88 line_length = 88
force_single_line = true force_single_line = true
......
...@@ -219,6 +219,9 @@ class MakeReleaseCommand(Command): ...@@ -219,6 +219,9 @@ class MakeReleaseCommand(Command):
subprocess.check_output( subprocess.check_output(
[python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS [python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS
) )
if version == "3.4" and WINDOWS:
continue
subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"]) subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"])
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise RuntimeError("Python {} is not available".format(version)) raise RuntimeError("Python {} is not available".format(version))
......
...@@ -10,15 +10,9 @@ import pytest ...@@ -10,15 +10,9 @@ import pytest
from poetry.config.config import Config as BaseConfig from poetry.config.config import Config as BaseConfig
from poetry.config.dict_config_source import DictConfigSource from poetry.config.dict_config_source import DictConfigSource
from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from tests.helpers import mock_clone
from tests.helpers import mock_download
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
class Config(BaseConfig): class Config(BaseConfig):
...@@ -58,6 +52,11 @@ def auth_config_source(): ...@@ -58,6 +52,11 @@ def auth_config_source():
@pytest.fixture @pytest.fixture
def config(config_source, auth_config_source, mocker): def config(config_source, auth_config_source, mocker):
import keyring
from keyring.backends.fail import Keyring
keyring.set_keyring(Keyring())
c = Config() c = Config()
c.merge(config_source.config) c.merge(config_source.config)
c.set_config_source(config_source) c.set_config_source(config_source)
...@@ -69,47 +68,6 @@ def config(config_source, auth_config_source, mocker): ...@@ -69,47 +68,6 @@ def config(config_source, auth_config_source, mocker):
return c return c
def mock_clone(_, source, dest):
# Checking source to determine which folder we need to copy
parts = urlparse.urlparse(source)
folder = (
Path(__file__).parent
/ "fixtures"
/ "git"
/ parts.netloc
/ parts.path.lstrip("/").rstrip(".git")
)
if dest.exists():
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest))
def mock_download(self, url, dest):
parts = urlparse.urlparse(url)
fixtures = Path(__file__).parent / "fixtures"
fixture = fixtures / parts.path.lstrip("/")
if dest.exists():
os.unlink(str(dest))
# Python2 does not support os.symlink on Windows whereas Python3 does. os.symlink requires either administrative
# privileges or developer mode on Win10, throwing an OSError is neither is active.
if WINDOWS:
if PY2:
shutil.copyfile(str(fixture), str(dest))
else:
try:
os.symlink(str(fixture), str(dest))
except OSError:
shutil.copyfile(str(fixture), str(dest))
else:
os.symlink(str(fixture), str(dest))
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def download_mock(mocker): def download_mock(mocker):
# Patch download to not download anything but to just copy from fixtures # Patch download to not download anything but to just copy from fixtures
......
...@@ -11,7 +11,7 @@ def test_about(app): ...@@ -11,7 +11,7 @@ def test_about(app):
Poetry - Package Management for Python Poetry - Package Management for Python
Poetry is a dependency manager tracking local dependencies of your projects and libraries. Poetry is a dependency manager tracking local dependencies of your projects and libraries.
See https://github.com/sdispater/poetry for more information. See https://github.com/python-poetry/poetry for more information.
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -109,7 +109,7 @@ def test_add_constraint_with_extras(app, repo, installer): ...@@ -109,7 +109,7 @@ def test_add_constraint_with_extras(app, repo, installer):
repo.add_package(cachy1) repo.add_package(cachy1)
repo.add_package(get_package("msgpack-python", "0.5.3")) repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy[msgpack]^0.1.0") tester.execute("cachy[msgpack]>=0.1.0,<0.2.0")
expected = """\ expected = """\
...@@ -264,6 +264,42 @@ Package operations: 4 installs, 0 updates, 0 removals ...@@ -264,6 +264,42 @@ Package operations: 4 installs, 0 updates, 0 removals
} }
def test_add_git_ssh_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("git+ssh://git@github.com/demo/demo.git@develop")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 2
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"git": "ssh://git@github.com/demo/demo.git",
"rev": "develop",
}
def test_add_directory_constraint(app, repo, installer, mocker): def test_add_directory_constraint(app, repo, installer, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd") p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".." p.return_value = Path(__file__) / ".."
...@@ -456,7 +492,7 @@ def test_add_url_constraint_wheel(app, repo, installer, mocker): ...@@ -456,7 +492,7 @@ def test_add_url_constraint_wheel(app, repo, installer, mocker):
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute( tester.execute(
"https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
) )
expected = """\ expected = """\
...@@ -470,7 +506,7 @@ Writing lock file ...@@ -470,7 +506,7 @@ Writing lock file
Package operations: 2 installs, 0 updates, 0 removals Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4) - Installing pendulum (1.4.4)
- Installing demo (0.1.0 https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl) - Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -481,7 +517,7 @@ Package operations: 2 installs, 0 updates, 0 removals ...@@ -481,7 +517,7 @@ Package operations: 2 installs, 0 updates, 0 removals
assert "demo" in content["dependencies"] assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == { assert content["dependencies"]["demo"] == {
"url": "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" "url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
} }
...@@ -494,7 +530,7 @@ def test_add_url_constraint_wheel_with_extras(app, repo, installer, mocker): ...@@ -494,7 +530,7 @@ def test_add_url_constraint_wheel_with_extras(app, repo, installer, mocker):
repo.add_package(get_package("tomlkit", "0.5.5")) repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute( tester.execute(
"https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl[foo,bar]" "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl[foo,bar]"
) )
expected = """\ expected = """\
...@@ -510,7 +546,7 @@ Package operations: 4 installs, 0 updates, 0 removals ...@@ -510,7 +546,7 @@ Package operations: 4 installs, 0 updates, 0 removals
- Installing cleo (0.6.5) - Installing cleo (0.6.5)
- Installing pendulum (1.4.4) - Installing pendulum (1.4.4)
- Installing tomlkit (0.5.5) - Installing tomlkit (0.5.5)
- Installing demo (0.1.0 https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl) - Installing demo (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl)
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -521,7 +557,7 @@ Package operations: 4 installs, 0 updates, 0 removals ...@@ -521,7 +557,7 @@ Package operations: 4 installs, 0 updates, 0 removals
assert "demo" in content["dependencies"] assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == { assert content["dependencies"]["demo"] == {
"url": "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl", "url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
"extras": ["foo", "bar"], "extras": ["foo", "bar"],
} }
......
...@@ -36,14 +36,14 @@ def test_check_invalid(app, mocker): ...@@ -36,14 +36,14 @@ def test_check_invalid(app, mocker):
Error: u'description' is a required property Error: u'description' is a required property
Error: INVALID is not a valid license Error: INVALID is not a valid license
Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one. Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one.
Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-preleases" instead. Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-prereleases" instead.
""" """
else: else:
expected = """\ expected = """\
Error: 'description' is a required property Error: 'description' is a required property
Error: INVALID is not a valid license Error: INVALID is not a valid license
Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one. Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one.
Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-preleases" instead. Warning: The "pendulum" dependency specifies the "allows-prereleases" property, which is deprecated. Use "allow-prereleases" instead.
""" """
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
...@@ -26,9 +26,9 @@ license = "MIT" ...@@ -26,9 +26,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -379,6 +379,178 @@ cachy 0.1.0 0.2.0 Cachy package ...@@ -379,6 +379,178 @@ cachy 0.1.0 0.2.0 Cachy package
assert expected == tester.io.fetch_output() assert expected == tester.io.fetch_output()
def test_show_outdated_with_only_up_to_date_packages(app, poetry, installed, repo):
command = app.find("show")
tester = CommandTester(command)
cachy_020 = get_package("cachy", "0.2.0")
cachy_020.description = "Cachy package"
installed.add_package(cachy_020)
repo.add_package(cachy_020)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.2.0",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": []},
},
}
)
tester.execute("--outdated")
expected = ""
assert expected == tester.io.fetch_output()
def test_show_outdated_has_prerelease_but_not_allowed(app, poetry, installed, repo):
command = app.find("show")
tester = CommandTester(command)
cachy_010 = get_package("cachy", "0.1.0")
cachy_010.description = "Cachy package"
cachy_020 = get_package("cachy", "0.2.0")
cachy_020.description = "Cachy package"
cachy_030dev = get_package("cachy", "0.3.0.dev123")
cachy_030dev.description = "Cachy package"
pendulum_200 = get_package("pendulum", "2.0.0")
pendulum_200.description = "Pendulum package"
installed.add_package(cachy_010)
installed.add_package(pendulum_200)
# sorting isn't used, so this has to be the first element to
# replicate the issue in PR #1548
repo.add_package(cachy_030dev)
repo.add_package(cachy_010)
repo.add_package(cachy_020)
repo.add_package(pendulum_200)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.1.0",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pendulum",
"version": "2.0.0",
"description": "Pendulum package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": [], "pendulum": []},
},
}
)
tester.execute("--outdated")
expected = """\
cachy 0.1.0 0.2.0 Cachy package
"""
assert expected == tester.io.fetch_output()
def test_show_outdated_has_prerelease_and_allowed(app, poetry, installed, repo):
command = app.find("show")
tester = CommandTester(command)
cachy_010dev = get_package("cachy", "0.1.0.dev1")
cachy_010dev.description = "Cachy package"
cachy_020 = get_package("cachy", "0.2.0")
cachy_020.description = "Cachy package"
cachy_030dev = get_package("cachy", "0.3.0.dev123")
cachy_030dev.description = "Cachy package"
pendulum_200 = get_package("pendulum", "2.0.0")
pendulum_200.description = "Pendulum package"
installed.add_package(cachy_010dev)
installed.add_package(pendulum_200)
# sorting isn't used, so this has to be the first element to
# replicate the issue in PR #1548
repo.add_package(cachy_030dev)
repo.add_package(cachy_010dev)
repo.add_package(cachy_020)
repo.add_package(pendulum_200)
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "cachy",
"version": "0.1.0.dev1",
"description": "Cachy package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
{
"name": "pendulum",
"version": "2.0.0",
"description": "Pendulum package",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"cachy": [], "pendulum": []},
},
}
)
tester.execute("--outdated")
expected = """\
cachy 0.1.0.dev1 0.3.0.dev123 Cachy package
"""
assert expected == tester.io.fetch_output()
def test_show_outdated_formatting(app, poetry, installed, repo): def test_show_outdated_formatting(app, poetry, installed, repo):
command = app.find("show") command = app.find("show")
tester = CommandTester(command) tester = CommandTester(command)
......
...@@ -44,4 +44,4 @@ def test_version_show(app): ...@@ -44,4 +44,4 @@ def test_version_show(app):
command = app.find("version") command = app.find("version")
tester = CommandTester(command) tester = CommandTester(command)
tester.execute() tester.execute()
assert "Project (simple-project) version is 1.2.3\n" == tester.io.fetch_output() assert "simple-project 1.2.3\n" == tester.io.fetch_output()
import os import os
import shutil
import pytest import pytest
...@@ -13,16 +12,10 @@ from poetry.poetry import Poetry as BasePoetry ...@@ -13,16 +12,10 @@ from poetry.poetry import Poetry as BasePoetry
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository as BaseRepository from poetry.repositories import Repository as BaseRepository
from poetry.repositories.exceptions import PackageNotFound from poetry.repositories.exceptions import PackageNotFound
from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from tests.helpers import mock_clone
from tests.helpers import mock_download
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
@pytest.fixture() @pytest.fixture()
...@@ -30,45 +23,6 @@ def installer(): ...@@ -30,45 +23,6 @@ def installer():
return NoopInstaller() return NoopInstaller()
def mock_clone(self, source, dest):
# Checking source to determine which folder we need to copy
parts = urlparse.urlparse(source)
folder = (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ parts.netloc
/ parts.path.lstrip("/").rstrip(".git")
)
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest))
def mock_download(self, url, dest):
parts = urlparse.urlparse(url)
fixtures = Path(__file__).parent.parent / "fixtures"
fixture = fixtures / parts.path.lstrip("/")
if dest.exists():
shutil.rmtree(str(dest))
# Python2 does not support os.symlink on Windows whereas Python3 does. os.symlink requires either administrative
# privileges or developer mode on Win10, throwing an OSError is neither is active.
if WINDOWS:
if PY2:
shutil.copyfile(str(fixture), str(dest))
else:
try:
os.symlink(str(fixture), str(dest))
except OSError:
shutil.copyfile(str(fixture), str(dest))
else:
os.symlink(str(fixture), str(dest))
@pytest.fixture @pytest.fixture
def installed(): def installed():
return BaseRepository() return BaseRepository()
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -7,9 +7,9 @@ authors = [ ...@@ -7,9 +7,9 @@ authors = [
] ]
license = "MIT" license = "MIT"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
[virtualenvs] [virtualenvs]
in-project = false
create = false create = false
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io" homepage = "https://python-poetry.org"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
import os
import shutil
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.packages import Package from poetry.packages import Package
from poetry.utils._compat import PY2
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import urlparse
from poetry.vcs.git import ParsedUrl
FIXTURE_PATH = Path(__file__).parent / "fixtures" FIXTURE_PATH = Path(__file__).parent / "fixtures"
...@@ -27,3 +34,57 @@ def fixture(path=None): ...@@ -27,3 +34,57 @@ def fixture(path=None):
return FIXTURE_PATH / path return FIXTURE_PATH / path
else: else:
return FIXTURE_PATH return FIXTURE_PATH
def copy_or_symlink(source, dest):
if dest.exists():
if dest.is_symlink():
os.unlink(str(dest))
elif dest.is_dir():
shutil.rmtree(str(dest))
else:
os.unlink(str(dest))
# Python2 does not support os.symlink on Windows whereas Python3 does.
# os.symlink requires either administrative privileges or developer mode on Win10,
# throwing an OSError if neither is active.
if WINDOWS:
if PY2:
if source.is_dir():
shutil.copytree(str(source), str(dest))
else:
shutil.copyfile(str(source), str(dest))
else:
try:
os.symlink(str(source), str(dest), target_is_directory=source.is_dir())
except OSError:
if source.is_dir():
shutil.copytree(str(source), str(dest))
else:
shutil.copyfile(str(source), str(dest))
else:
os.symlink(str(source), str(dest))
def mock_clone(_, source, dest):
# Checking source to determine which folder we need to copy
parsed = ParsedUrl.parse(source)
folder = (
Path(__file__).parent
/ "fixtures"
/ "git"
/ parsed.resource
/ parsed.pathname.lstrip("/").rstrip(".git")
)
copy_or_symlink(folder, dest)
def mock_download(self, url, dest):
parts = urlparse.urlparse(url)
fixtures = Path(__file__).parent / "fixtures"
fixture = fixtures / parts.path.lstrip("/")
copy_or_symlink(fixture, dest)
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-extras" name = "project-with-extras"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -18,6 +19,7 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies/. ...@@ -18,6 +19,7 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies/.
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-transitive-directory-dependencies" name = "project-with-transitive-directory-dependencies"
optional = false optional = false
python-versions = "*" python-versions = "*"
......
...@@ -9,6 +9,7 @@ version = "1.4.4" ...@@ -9,6 +9,7 @@ version = "1.4.4"
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-extras" name = "project-with-extras"
optional = false optional = false
python-versions = "*" python-versions = "*"
......
...@@ -9,6 +9,7 @@ python-versions = "*" ...@@ -9,6 +9,7 @@ python-versions = "*"
[[package]] [[package]]
name = "my-package" name = "my-package"
version = "0.1.2" version = "0.1.2"
develop = true
description = "Demo project." description = "Demo project."
category = "main" category = "main"
optional = false optional = false
......
...@@ -23,6 +23,7 @@ C = "1.5" ...@@ -23,6 +23,7 @@ C = "1.5"
[[package]] [[package]]
name = "C" name = "C"
version = "1.5" version = "1.5"
marker = "python_version >= \"2.7\""
description = "" description = ""
category = "main" category = "main"
optional = false optional = false
......
...@@ -29,6 +29,7 @@ version = "1.4.4" ...@@ -29,6 +29,7 @@ version = "1.4.4"
[[package]] [[package]]
category = "main" category = "main"
description = "" description = ""
develop = true
name = "project-with-transitive-file-dependencies" name = "project-with-transitive-file-dependencies"
optional = false optional = false
python-versions = "*" python-versions = "*"
......
...@@ -9,7 +9,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" ...@@ -9,7 +9,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.source] [package.source]
type = "url" type = "url"
reference = "" reference = ""
url = "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
[package.dependencies] [package.dependencies]
pendulum = ">=1.4.4" pendulum = ">=1.4.4"
......
...@@ -25,6 +25,9 @@ from tests.repositories.test_legacy_repository import ( ...@@ -25,6 +25,9 @@ from tests.repositories.test_legacy_repository import (
from tests.repositories.test_pypi_repository import MockRepository from tests.repositories.test_pypi_repository import MockRepository
fixtures_dir = Path("tests/fixtures")
class Installer(BaseInstaller): class Installer(BaseInstaller):
def _get_installer(self): def _get_installer(self):
return NoopInstaller() return NoopInstaller()
...@@ -662,7 +665,7 @@ def test_installer_with_pypi_repository(package, locker, installed): ...@@ -662,7 +665,7 @@ def test_installer_with_pypi_repository(package, locker, installed):
def test_run_installs_with_local_file(installer, locker, repo, package): def test_run_installs_with_local_file(installer, locker, repo, package):
file_path = Path("tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl") file_path = fixtures_dir / "distributions/demo-0.1.0-py2.py3-none-any.whl"
package.add_dependency("demo", {"file": str(file_path)}) package.add_dependency("demo", {"file": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
...@@ -677,8 +680,8 @@ def test_run_installs_with_local_file(installer, locker, repo, package): ...@@ -677,8 +680,8 @@ def test_run_installs_with_local_file(installer, locker, repo, package):
def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package): def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, package):
file_path = Path( file_path = (
"tests/fixtures/wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" fixtures_dir / "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl"
) )
package.add_dependency("demo", {"file": str(file_path)}) package.add_dependency("demo", {"file": str(file_path)})
...@@ -694,7 +697,7 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa ...@@ -694,7 +697,7 @@ def test_run_installs_wheel_with_no_requires_dist(installer, locker, repo, packa
def test_run_installs_with_local_poetry_directory_and_extras( def test_run_installs_with_local_poetry_directory_and_extras(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path("tests/fixtures/project_with_extras") file_path = fixtures_dir / "project_with_extras"
package.add_dependency( package.add_dependency(
"project-with-extras", {"path": str(file_path), "extras": ["extras_a"]} "project-with-extras", {"path": str(file_path), "extras": ["extras_a"]}
) )
...@@ -713,8 +716,8 @@ def test_run_installs_with_local_poetry_directory_and_extras( ...@@ -713,8 +716,8 @@ def test_run_installs_with_local_poetry_directory_and_extras(
def test_run_installs_with_local_poetry_directory_transitive( def test_run_installs_with_local_poetry_directory_transitive(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path( file_path = (
"tests/fixtures/directory/project_with_transitive_directory_dependencies/" fixtures_dir / "directory/project_with_transitive_directory_dependencies/"
) )
package.add_dependency( package.add_dependency(
"project-with-transitive-directory-dependencies", {"path": str(file_path)} "project-with-transitive-directory-dependencies", {"path": str(file_path)}
...@@ -735,9 +738,7 @@ def test_run_installs_with_local_poetry_directory_transitive( ...@@ -735,9 +738,7 @@ def test_run_installs_with_local_poetry_directory_transitive(
def test_run_installs_with_local_poetry_file_transitive( def test_run_installs_with_local_poetry_file_transitive(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path( file_path = fixtures_dir / "directory/project_with_transitive_file_dependencies/"
"tests/fixtures/directory/project_with_transitive_file_dependencies/"
)
package.add_dependency( package.add_dependency(
"project-with-transitive-file-dependencies", {"path": str(file_path)} "project-with-transitive-file-dependencies", {"path": str(file_path)}
) )
...@@ -757,7 +758,7 @@ def test_run_installs_with_local_poetry_file_transitive( ...@@ -757,7 +758,7 @@ def test_run_installs_with_local_poetry_file_transitive(
def test_run_installs_with_local_setuptools_directory( def test_run_installs_with_local_setuptools_directory(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path("tests/fixtures/project_with_setup/") file_path = fixtures_dir / "project_with_setup/"
package.add_dependency("my-package", {"path": str(file_path)}) package.add_dependency("my-package", {"path": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
...@@ -1182,8 +1183,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1182,8 +1183,8 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
"checksum": [], "checksum": [],
"dependencies": { "dependencies": {
"B": [ "B": [
{"version": "^1.0", "python": "<4.0"}, {"version": "^1.0", "python": "<2.7"},
{"version": "^2.0", "python": ">=4.0"}, {"version": "^2.0", "python": ">=2.7"},
] ]
}, },
}, },
...@@ -1196,7 +1197,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1196,7 +1197,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
"python-versions": "*", "python-versions": "*",
"checksum": [], "checksum": [],
"dependencies": {"C": "1.2"}, "dependencies": {"C": "1.2"},
"requirements": {"python": "<4.0"}, "requirements": {"python": "<2.7"},
}, },
{ {
"name": "B", "name": "B",
...@@ -1207,7 +1208,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda ...@@ -1207,7 +1208,7 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda
"python-versions": "*", "python-versions": "*",
"checksum": [], "checksum": [],
"dependencies": {"C": "1.5"}, "dependencies": {"C": "1.5"},
"requirements": {"python": ">=4.0"}, "requirements": {"python": ">=2.7"},
}, },
{ {
"name": "C", "name": "C",
...@@ -1516,7 +1517,7 @@ def test_installer_can_install_dependencies_from_forced_source( ...@@ -1516,7 +1517,7 @@ def test_installer_can_install_dependencies_from_forced_source(
def test_run_installs_with_url_file(installer, locker, repo, package): def test_run_installs_with_url_file(installer, locker, repo, package):
url = "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
package.add_dependency("demo", {"url": url}) package.add_dependency("demo", {"url": url})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -12,9 +12,9 @@ license = "MIT" ...@@ -12,9 +12,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
...@@ -50,4 +50,4 @@ my-2nd-script = "my_package:main2" ...@@ -50,4 +50,4 @@ my-2nd-script = "my_package:main2"
extra-script = {callable = "my_package.extra:main", extras = ["time"]} extra-script = {callable = "my_package.extra:main", extras = ["time"]}
[tool.poetry.urls] [tool.poetry.urls]
"Issue Tracker" = "https://github.com/sdispater/poetry/issues" "Issue Tracker" = "https://github.com/python-poetry/poetry/issues"
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -11,9 +11,9 @@ readme = "README.rst" ...@@ -11,9 +11,9 @@ readme = "README.rst"
exclude = ["my_package/data/data1.txt"] exclude = ["my_package/data/data1.txt"]
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,11 +9,12 @@ license = "MIT" ...@@ -9,11 +9,12 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
exclude = ["my_package/data/", "**/*/item*"] exclude = ["**/data/", "**/*/item*"]
include = ["my_package/data/data2.txt"]
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -9,6 +9,6 @@ license = "MIT" ...@@ -9,6 +9,6 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
build = "build.py" build = "build.py"
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
"""Test fixture for https://github.com/sdispater/poetry/issues/756""" """Test fixture for https://github.com/python-poetry/poetry/issues/756"""
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
[tool.poetry.dependencies] [tool.poetry.dependencies]
......
...@@ -9,4 +9,4 @@ license = "MIT" ...@@ -9,4 +9,4 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
[tool.poetry.dependencies] [tool.poetry.dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
[tool.poetry.dependencies] [tool.poetry.dependencies]
......
...@@ -9,7 +9,7 @@ license = "MIT" ...@@ -9,7 +9,7 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "*" python = "*"
...@@ -9,6 +9,6 @@ license = "MIT" ...@@ -9,6 +9,6 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
build = "build.py" build = "build.py"
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.rst" readme = "README.rst"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -7,9 +7,9 @@ authors = [ ...@@ -7,9 +7,9 @@ authors = [
] ]
license = "MIT" license = "MIT"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
...@@ -21,4 +21,4 @@ classifiers = [ ...@@ -21,4 +21,4 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.6" python = "^3.6"
demo = { url = "https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" } demo = { url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl" }
...@@ -7,9 +7,9 @@ authors = [ ...@@ -7,9 +7,9 @@ authors = [
] ]
license = "MIT" license = "MIT"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
...@@ -81,7 +81,7 @@ def test_get_metadata_content(): ...@@ -81,7 +81,7 @@ def test_get_metadata_content():
assert parsed["Keywords"] == "packaging,dependency,poetry" assert parsed["Keywords"] == "packaging,dependency,poetry"
assert parsed["Requires-Python"] == ">=3.6,<4.0" assert parsed["Requires-Python"] == ">=3.6,<4.0"
assert parsed["License"] == "MIT" assert parsed["License"] == "MIT"
assert parsed["Home-page"] == "https://poetry.eustace.io/" assert parsed["Home-page"] == "https://python-poetry.org/"
classifiers = parsed.get_all("Classifier") classifiers = parsed.get_all("Classifier")
assert classifiers == [ assert classifiers == [
...@@ -106,9 +106,9 @@ def test_get_metadata_content(): ...@@ -106,9 +106,9 @@ def test_get_metadata_content():
urls = parsed.get_all("Project-URL") urls = parsed.get_all("Project-URL")
assert urls == [ assert urls == [
"Documentation, https://poetry.eustace.io/docs", "Documentation, https://python-poetry.org/docs",
"Issue Tracker, https://github.com/sdispater/poetry/issues", "Issue Tracker, https://github.com/python-poetry/poetry/issues",
"Repository, https://github.com/sdispater/poetry", "Repository, https://github.com/python-poetry/poetry",
] ]
...@@ -154,6 +154,6 @@ def test_metadata_with_url_dependencies(): ...@@ -154,6 +154,6 @@ def test_metadata_with_url_dependencies():
requires_dist = metadata["Requires-Dist"] requires_dist = metadata["Requires-Dist"]
assert ( assert (
"demo @ https://poetry.eustace.io/distributions/demo-0.1.0-py2.py3-none-any.whl" "demo @ https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
== requires_dist == requires_dist
) )
...@@ -206,7 +206,7 @@ Metadata-Version: 2.1 ...@@ -206,7 +206,7 @@ Metadata-Version: 2.1
Name: my-package Name: my-package
Version: 1.2.3 Version: 1.2.3
Summary: Some description. Summary: Some description.
Home-page: https://poetry.eustace.io/ Home-page: https://python-poetry.org/
License: MIT License: MIT
Keywords: packaging,dependency,poetry Keywords: packaging,dependency,poetry
Author: Sébastien Eustace Author: Sébastien Eustace
...@@ -225,9 +225,9 @@ Provides-Extra: time ...@@ -225,9 +225,9 @@ Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time") Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://poetry.eustace.io/docs Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Issue Tracker, https://github.com/sdispater/poetry/issues Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/sdispater/poetry Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst Description-Content-Type: text/x-rst
My Package My Package
...@@ -265,6 +265,7 @@ def test_complete_no_vcs(): ...@@ -265,6 +265,7 @@ def test_complete_no_vcs():
"my_package/sub_pkg1/__init__.py", "my_package/sub_pkg1/__init__.py",
"my_package/sub_pkg2/__init__.py", "my_package/sub_pkg2/__init__.py",
"my_package/sub_pkg2/data2/data.json", "my_package/sub_pkg2/data2/data.json",
"my_package/sub_pkg3/foo.py",
"my_package-1.2.3.dist-info/entry_points.txt", "my_package-1.2.3.dist-info/entry_points.txt",
"my_package-1.2.3.dist-info/LICENSE", "my_package-1.2.3.dist-info/LICENSE",
"my_package-1.2.3.dist-info/WHEEL", "my_package-1.2.3.dist-info/WHEEL",
...@@ -309,7 +310,7 @@ Metadata-Version: 2.1 ...@@ -309,7 +310,7 @@ Metadata-Version: 2.1
Name: my-package Name: my-package
Version: 1.2.3 Version: 1.2.3
Summary: Some description. Summary: Some description.
Home-page: https://poetry.eustace.io/ Home-page: https://python-poetry.org/
License: MIT License: MIT
Keywords: packaging,dependency,poetry Keywords: packaging,dependency,poetry
Author: Sébastien Eustace Author: Sébastien Eustace
...@@ -328,9 +329,9 @@ Provides-Extra: time ...@@ -328,9 +329,9 @@ Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time") Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://poetry.eustace.io/docs Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Issue Tracker, https://github.com/sdispater/poetry/issues Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/sdispater/poetry Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst Description-Content-Type: text/x-rst
My Package My Package
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from clikit.io import NullIO from clikit.io import NullIO
from poetry.factory import Factory from poetry.factory import Factory
...@@ -15,14 +17,13 @@ fixtures_dir = Path(__file__).parent / "fixtures" ...@@ -15,14 +17,13 @@ fixtures_dir = Path(__file__).parent / "fixtures"
def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mocker): def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mocker):
move = mocker.patch("shutil.move") move = mocker.patch("shutil.move")
tmp_dir = Path(tmp_dir) tmp_dir = Path(tmp_dir)
env = MockEnv(path=tmp_dir, pip_version="18.1", execute=False) env = MockEnv(path=tmp_dir, pip_version="18.1", execute=False, sys_path=[])
env.site_packages.mkdir(parents=True)
module_path = fixtures_dir / "extended" module_path = fixtures_dir / "extended"
builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO()) builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO())
builder.build() builder.build()
expected = [["python", "-m", "pip", "install", "-e", str(module_path)]] expected = [[sys.executable, "-m", "pip", "install", "-e", str(module_path)]]
assert expected == env.executed assert expected == env.executed
assert 0 == move.call_count assert 0 == move.call_count
...@@ -31,14 +32,13 @@ def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mock ...@@ -31,14 +32,13 @@ def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mock
def test_build_should_temporarily_remove_the_pyproject_file(tmp_dir, mocker): def test_build_should_temporarily_remove_the_pyproject_file(tmp_dir, mocker):
move = mocker.patch("shutil.move") move = mocker.patch("shutil.move")
tmp_dir = Path(tmp_dir) tmp_dir = Path(tmp_dir)
env = MockEnv(path=tmp_dir, pip_version="19.1", execute=False) env = MockEnv(path=tmp_dir, pip_version="19.1", execute=False, sys_path=[])
env.site_packages.mkdir(parents=True)
module_path = fixtures_dir / "extended" module_path = fixtures_dir / "extended"
builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO()) builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO())
builder.build() builder.build()
expected = [["python", "-m", "pip", "install", "-e", str(module_path)]] expected = [[sys.executable, "-m", "pip", "install", "-e", str(module_path)]]
assert expected == env.executed assert expected == env.executed
assert 2 == move.call_count assert 2 == move.call_count
......
...@@ -124,6 +124,7 @@ def test_make_setup(): ...@@ -124,6 +124,7 @@ def test_make_setup():
"my_package", "my_package",
"my_package.sub_pkg1", "my_package.sub_pkg1",
"my_package.sub_pkg2", "my_package.sub_pkg2",
"my_package.sub_pkg3",
] ]
assert ns["install_requires"] == ["cachy[msgpack]>=0.2.0,<0.3.0", "cleo>=0.6,<0.7"] assert ns["install_requires"] == ["cachy[msgpack]>=0.2.0,<0.3.0", "cleo>=0.6,<0.7"]
assert ns["entry_points"] == { assert ns["entry_points"] == {
...@@ -178,6 +179,7 @@ def test_find_files_to_add(): ...@@ -178,6 +179,7 @@ def test_find_files_to_add():
Path("my_package/sub_pkg1/__init__.py"), Path("my_package/sub_pkg1/__init__.py"),
Path("my_package/sub_pkg2/__init__.py"), Path("my_package/sub_pkg2/__init__.py"),
Path("my_package/sub_pkg2/data2/data.json"), Path("my_package/sub_pkg2/data2/data.json"),
Path("my_package/sub_pkg3/foo.py"),
Path("pyproject.toml"), Path("pyproject.toml"),
] ]
) )
...@@ -213,7 +215,12 @@ def test_find_packages(): ...@@ -213,7 +215,12 @@ def test_find_packages():
pkg_dir, packages, pkg_data = builder.find_packages(include) pkg_dir, packages, pkg_data = builder.find_packages(include)
assert pkg_dir is None assert pkg_dir is None
assert packages == ["my_package", "my_package.sub_pkg1", "my_package.sub_pkg2"] assert packages == [
"my_package",
"my_package.sub_pkg1",
"my_package.sub_pkg2",
"my_package.sub_pkg3",
]
assert pkg_data == { assert pkg_data == {
"": ["*"], "": ["*"],
"my_package": ["data1/*"], "my_package": ["data1/*"],
...@@ -408,6 +415,9 @@ def test_default_with_excluded_data(mocker): ...@@ -408,6 +415,9 @@ def test_default_with_excluded_data(mocker):
assert "my-package-1.2.3/pyproject.toml" in names assert "my-package-1.2.3/pyproject.toml" in names
assert "my-package-1.2.3/setup.py" in names assert "my-package-1.2.3/setup.py" in names
assert "my-package-1.2.3/PKG-INFO" in names assert "my-package-1.2.3/PKG-INFO" in names
# all last modified times should be set to a valid timestamp
for tarinfo in tar.getmembers():
assert 0 < tarinfo.mtime
def test_src_excluded_nested_data(): def test_src_excluded_nested_data():
...@@ -433,6 +443,7 @@ def test_src_excluded_nested_data(): ...@@ -433,6 +443,7 @@ def test_src_excluded_nested_data():
assert "my-package-1.2.3/my_package/data/sub_data/data2.txt" not in names assert "my-package-1.2.3/my_package/data/sub_data/data2.txt" not in names
assert "my-package-1.2.3/my_package/data/sub_data/data3.txt" not in names assert "my-package-1.2.3/my_package/data/sub_data/data3.txt" not in names
assert "my-package-1.2.3/my_package/data/data1.txt" not in names assert "my-package-1.2.3/my_package/data/data1.txt" not in names
assert "my-package-1.2.3/my_package/data/data2.txt" in names
assert "my-package-1.2.3/my_package/puplic/publicdata.txt" in names assert "my-package-1.2.3/my_package/puplic/publicdata.txt" in names
assert "my-package-1.2.3/my_package/public/item1/itemdata1.txt" not in names assert "my-package-1.2.3/my_package/public/item1/itemdata1.txt" not in names
assert ( assert (
......
...@@ -93,6 +93,7 @@ def test_wheel_excluded_nested_data(): ...@@ -93,6 +93,7 @@ def test_wheel_excluded_nested_data():
assert "my_package/data/sub_data/data2.txt" not in z.namelist() assert "my_package/data/sub_data/data2.txt" not in z.namelist()
assert "my_package/data/sub_data/data3.txt" not in z.namelist() assert "my_package/data/sub_data/data3.txt" not in z.namelist()
assert "my_package/data/data1.txt" not in z.namelist() assert "my_package/data/data1.txt" not in z.namelist()
assert "my_package/data/data2.txt" in z.namelist()
assert "my_package/puplic/publicdata.txt" in z.namelist() assert "my_package/puplic/publicdata.txt" in z.namelist()
assert "my_package/public/item1/itemdata1.txt" not in z.namelist() assert "my_package/public/item1/itemdata1.txt" not in z.namelist()
assert "my_package/public/item1/subitem/subitemdata.txt" not in z.namelist() assert "my_package/public/item1/subitem/subitemdata.txt" not in z.namelist()
......
...@@ -82,7 +82,7 @@ Metadata-Version: 2.1 ...@@ -82,7 +82,7 @@ Metadata-Version: 2.1
Name: my-package Name: my-package
Version: 1.2.3 Version: 1.2.3
Summary: Some description. Summary: Some description.
Home-page: https://poetry.eustace.io/ Home-page: https://python-poetry.org/
License: MIT License: MIT
Keywords: packaging,dependency,poetry Keywords: packaging,dependency,poetry
Author: Sébastien Eustace Author: Sébastien Eustace
...@@ -101,9 +101,9 @@ Provides-Extra: time ...@@ -101,9 +101,9 @@ Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time") Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://poetry.eustace.io/docs Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Issue Tracker, https://github.com/sdispater/poetry/issues Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/sdispater/poetry Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst Description-Content-Type: text/x-rst
My Package My Package
......
import pytest
from poetry.masonry.utils.package_include import PackageInclude
from poetry.utils._compat import Path
fixtures_dir = Path(__file__).parent / "fixtures"
with_includes = fixtures_dir / "with_includes"
def test_package_include_with_multiple_dirs():
pkg_include = PackageInclude(base=fixtures_dir, include="with_includes")
assert pkg_include.elements == [
with_includes / "__init__.py",
with_includes / "bar",
with_includes / "bar/baz.py",
with_includes / "extra_package",
with_includes / "extra_package/some_dir",
with_includes / "extra_package/some_dir/foo.py",
with_includes / "extra_package/some_dir/quux.py",
with_includes / "not_a_python_pkg",
with_includes / "not_a_python_pkg/baz.txt",
]
def test_package_include_with_simple_dir():
pkg_include = PackageInclude(base=with_includes, include="bar")
assert pkg_include.elements == [with_includes / "bar/baz.py"]
def test_package_include_with_nested_dir():
pkg_include = PackageInclude(base=with_includes, include="extra_package/**/*.py")
assert pkg_include.elements == [
with_includes / "extra_package/some_dir/foo.py",
with_includes / "extra_package/some_dir/quux.py",
]
def test_package_include_with_no_python_files_in_dir():
with pytest.raises(ValueError) as e:
PackageInclude(base=with_includes, include="not_a_python_pkg")
assert str(e.value) == "not_a_python_pkg is not a package."
...@@ -8,7 +8,9 @@ def test_dependency_does_not_match_root_python_constraint(root, provider, repo): ...@@ -8,7 +8,9 @@ def test_dependency_does_not_match_root_python_constraint(root, provider, repo):
add_to_repo(repo, "foo", "1.0.0", python="<3.5") add_to_repo(repo, "foo", "1.0.0", python="<3.5")
error = """The current project must support the following Python versions: ^3.6 error = """The current project's Python requirement (^3.6) \
is not compatible with some of the required packages Python requirement:
- foo requires Python <3.5
Because no versions of foo match !=1.0.0 Because no versions of foo match !=1.0.0
and foo (1.0.0) requires Python <3.5, foo is forbidden. and foo (1.0.0) requires Python <3.5, foo is forbidden.
......
...@@ -8,12 +8,24 @@ from poetry.utils.env import EnvCommandError ...@@ -8,12 +8,24 @@ from poetry.utils.env import EnvCommandError
from poetry.utils.env import MockEnv as BaseMockEnv from poetry.utils.env import MockEnv as BaseMockEnv
fixtures_dir = Path(__file__).parent.parent / "fixtures"
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "git" / "github.com" / "demo"
class MockEnv(BaseMockEnv): class MockEnv(BaseMockEnv):
def run(self, bin, *args): def run(self, bin, *args):
raise EnvCommandError(CalledProcessError(1, "python", output="")) raise EnvCommandError(CalledProcessError(1, "python", output=""))
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "git" / "github.com" / "demo" def test_directory_dependency():
dependency = DirectoryDependency("simple_project", fixtures_dir / "simple_project")
assert dependency.pretty_name == "simple_project"
assert dependency.develop
assert dependency.path == fixtures_dir / "simple_project"
assert dependency.base_pep_508_name == "simple_project @ {}".format(
fixtures_dir / "simple_project"
)
def test_directory_dependency_must_exist(): def test_directory_dependency_must_exist():
......
...@@ -7,6 +7,16 @@ from poetry.utils._compat import Path ...@@ -7,6 +7,16 @@ from poetry.utils._compat import Path
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "distributions" DIST_PATH = Path(__file__).parent.parent / "fixtures" / "distributions"
def test_file_dependency():
dependency = FileDependency("demo", DIST_PATH / "demo-0.1.0.tar.gz")
assert dependency.pretty_name == "demo"
assert dependency.path == DIST_PATH / "demo-0.1.0.tar.gz"
assert dependency.base_pep_508_name == "demo @ {}".format(
DIST_PATH / "demo-0.1.0.tar.gz"
)
def test_file_dependency_wrong_path(): def test_file_dependency_wrong_path():
with pytest.raises(ValueError): with pytest.raises(ValueError):
FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz") FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz")
......
...@@ -181,3 +181,68 @@ A = [] ...@@ -181,3 +181,68 @@ A = []
content = f.read() content = f.read()
assert expected == content assert expected == content
def test_reading_lock_file_should_raise_an_error_on_invalid_data(locker):
content = u"""[[package]]
category = "main"
description = ""
name = "A"
optional = false
python-versions = "*"
version = "1.0.0"
[package.extras]
foo = ["bar"]
[package.extras]
foo = ["bar"]
[metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.files]
A = []
"""
with locker.lock.open("w", encoding="utf-8") as f:
f.write(content)
with pytest.raises(RuntimeError) as e:
_ = locker.lock_data
assert "Unable to read the lock file" in str(e.value)
def test_locking_legacy_repository_package_should_include_source_section(root, locker):
package_a = get_package("A", "1.0.0")
package_a.source_url = "https://foo.bar"
package_a.source_reference = "legacy"
packages = [package_a]
locker.set_lock_data(root, packages)
with locker.lock.open(encoding="utf-8") as f:
content = f.read()
expected = """[[package]]
category = "main"
description = ""
name = "A"
optional = false
python-versions = "*"
version = "1.0.0"
[package.source]
reference = "legacy"
url = "https://foo.bar"
[metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.files]
A = []
"""
assert expected == content
...@@ -169,3 +169,25 @@ def test_dependency_from_pep_508_with_not_in_op_marker(): ...@@ -169,3 +169,25 @@ def test_dependency_from_pep_508_with_not_in_op_marker():
assert ( assert (
str(dep.marker) == 'python_version not in "3.0,3.1,3.2" and extra == "export"' str(dep.marker) == 'python_version not in "3.0,3.1,3.2" and extra == "export"'
) )
def test_dependency_from_pep_508_with_git_url():
name = "django-utils @ git+ssh://git@corp-gitlab.com/corp-utils.git@1.2"
dep = dependency_from_pep_508(name)
assert "django-utils" == dep.name
assert dep.is_vcs()
assert "git" == dep.vcs
assert "ssh://git@corp-gitlab.com/corp-utils.git" == dep.source
assert "1.2" == dep.reference
def test_dependency_from_pep_508_with_url():
name = "django-utils @ https://example.com/django-utils-1.0.0.tar.gz"
dep = dependency_from_pep_508(name)
assert "django-utils" == dep.name
assert dep.is_url()
assert "https://example.com/django-utils-1.0.0.tar.gz" == dep.url
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import pytest
from poetry.packages import Package from poetry.packages import Package
...@@ -14,3 +16,24 @@ def test_package_authors(): ...@@ -14,3 +16,24 @@ def test_package_authors():
package.authors.insert(0, "John Doe") package.authors.insert(0, "John Doe")
assert package.author_name == "John Doe" assert package.author_name == "John Doe"
assert package.author_email is None assert package.author_email is None
@pytest.mark.parametrize("category", ["main", "dev"])
def test_package_add_dependency_vcs_category(category):
package = Package("foo", "0.1.0")
dependency = package.add_dependency(
"poetry",
constraint={"git": "https://github.com/python-poetry/poetry.git"},
category=category,
)
assert dependency.category == category
def test_package_add_dependency_vcs_category_default_main():
package = Package("foo", "0.1.0")
dependency = package.add_dependency(
"poetry", constraint={"git": "https://github.com/python-poetry/poetry.git"}
)
assert dependency.category == "main"
import pytest
from poetry.packages.vcs_dependency import VCSDependency from poetry.packages.vcs_dependency import VCSDependency
def test_to_pep_508(): def test_to_pep_508():
dependency = VCSDependency( dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git" "poetry", "git", "https://github.com/python-poetry/poetry.git"
) )
expected = "poetry @ git+https://github.com/sdispater/poetry.git@master" expected = "poetry @ git+https://github.com/python-poetry/poetry.git@master"
assert expected == dependency.to_pep_508()
def test_to_pep_508_ssh():
dependency = VCSDependency("poetry", "git", "git@github.com:sdispater/poetry.git")
expected = "poetry @ git+ssh://git@github.com/sdispater/poetry.git@master"
assert expected == dependency.to_pep_508() assert expected == dependency.to_pep_508()
def test_to_pep_508_with_extras(): def test_to_pep_508_with_extras():
dependency = VCSDependency( dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git" "poetry", "git", "https://github.com/python-poetry/poetry.git"
) )
dependency.extras.append("foo") dependency.extras.append("foo")
expected = "poetry[foo] @ git+https://github.com/sdispater/poetry.git@master" expected = "poetry[foo] @ git+https://github.com/python-poetry/poetry.git@master"
assert expected == dependency.to_pep_508() assert expected == dependency.to_pep_508()
def test_to_pep_508_in_extras(): def test_to_pep_508_in_extras():
dependency = VCSDependency( dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git" "poetry", "git", "https://github.com/python-poetry/poetry.git"
) )
dependency.in_extras.append("foo") dependency.in_extras.append("foo")
expected = ( expected = 'poetry @ git+https://github.com/python-poetry/poetry.git@master ; extra == "foo"'
'poetry @ git+https://github.com/sdispater/poetry.git@master ; extra == "foo"'
)
assert expected == dependency.to_pep_508() assert expected == dependency.to_pep_508()
dependency = VCSDependency( dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git" "poetry", "git", "https://github.com/python-poetry/poetry.git"
) )
dependency.in_extras.append("foo") dependency.in_extras.append("foo")
dependency.extras.append("bar") dependency.extras.append("bar")
expected = 'poetry[bar] @ git+https://github.com/sdispater/poetry.git@master ; extra == "foo"' expected = 'poetry[bar] @ git+https://github.com/python-poetry/poetry.git@master ; extra == "foo"'
assert expected == dependency.to_pep_508() assert expected == dependency.to_pep_508()
dependency = VCSDependency( dependency = VCSDependency(
"poetry", "git", "https://github.com/sdispater/poetry.git", "b;ar;" "poetry", "git", "https://github.com/python-poetry/poetry.git", "b;ar;"
) )
dependency.in_extras.append("foo;") dependency.in_extras.append("foo;")
expected = ( expected = 'poetry @ git+https://github.com/python-poetry/poetry.git@b;ar; ; extra == "foo;"'
'poetry @ git+https://github.com/sdispater/poetry.git@b;ar; ; extra == "foo;"'
)
assert expected == dependency.to_pep_508() assert expected == dependency.to_pep_508()
@pytest.mark.parametrize("category", ["main", "dev"])
def test_category(category):
dependency = VCSDependency(
"poetry",
"git",
"https://github.com/python-poetry/poetry.git",
category=category,
)
assert category == dependency.category
...@@ -407,8 +407,10 @@ def test_solver_returns_extras_if_requested(solver, repo, package): ...@@ -407,8 +407,10 @@ def test_solver_returns_extras_if_requested(solver, repo, package):
package_b = get_package("B", "1.0") package_b = get_package("B", "1.0")
package_c = get_package("C", "1.0") package_c = get_package("C", "1.0")
package_b.extras = {"foo": [get_dependency("C", "^1.0")]} dep = get_dependency("C", "^1.0", optional=True)
package_b.add_dependency("C", {"version": "^1.0", "optional": True}) dep.marker = parse_marker("extra == 'foo'")
package_b.extras = {"foo": [dep]}
package_b.requires.append(dep)
repo.add_package(package_a) repo.add_package(package_a)
repo.add_package(package_b) repo.add_package(package_b)
...@@ -425,6 +427,9 @@ def test_solver_returns_extras_if_requested(solver, repo, package): ...@@ -425,6 +427,9 @@ def test_solver_returns_extras_if_requested(solver, repo, package):
], ],
) )
assert ops[-1].package.marker.is_any()
assert ops[0].package.marker.is_any()
def test_solver_returns_prereleases_if_requested(solver, repo, package): def test_solver_returns_prereleases_if_requested(solver, repo, package):
package.add_dependency("A") package.add_dependency("A")
...@@ -1148,10 +1153,7 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl ...@@ -1148,10 +1153,7 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl
], ],
) )
assert str(ops[0].package.marker) in [ assert str(ops[0].package.marker) == ""
'extra == "foo" or extra == "bar"',
'extra == "bar" or extra == "foo"',
]
assert str(ops[1].package.marker) == "" assert str(ops[1].package.marker) == ""
...@@ -1684,7 +1686,6 @@ def test_solver_chooses_from_correct_repository_if_forced( ...@@ -1684,7 +1686,6 @@ def test_solver_chooses_from_correct_repository_if_forced(
ops, [{"job": "install", "package": get_package("tomlkit", "0.5.2")}] ops, [{"job": "install", "package": get_package("tomlkit", "0.5.2")}]
) )
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url assert "http://foo.bar" == ops[0].package.source_url
...@@ -1713,7 +1714,6 @@ def test_solver_chooses_from_correct_repository_if_forced_and_transitive_depende ...@@ -1713,7 +1714,6 @@ def test_solver_chooses_from_correct_repository_if_forced_and_transitive_depende
], ],
) )
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url assert "http://foo.bar" == ops[0].package.source_url
assert "" == ops[1].package.source_type assert "" == ops[1].package.source_type
...@@ -1743,11 +1743,9 @@ def test_solver_does_not_choose_from_secondary_repository_by_default( ...@@ -1743,11 +1743,9 @@ def test_solver_does_not_choose_from_secondary_repository_by_default(
], ],
) )
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url assert "http://foo.bar" == ops[0].package.source_url
assert "" == ops[1].package.source_type assert "" == ops[1].package.source_type
assert "" == ops[1].package.source_url assert "" == ops[1].package.source_url
assert "legacy" == ops[2].package.source_type
assert "http://foo.bar" == ops[2].package.source_url assert "http://foo.bar" == ops[2].package.source_url
...@@ -1772,7 +1770,6 @@ def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, i ...@@ -1772,7 +1770,6 @@ def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, i
], ],
) )
assert "legacy" == ops[0].package.source_type
assert "http://foo.bar" == ops[0].package.source_url assert "http://foo.bar" == ops[0].package.source_url
assert "" == ops[1].package.source_type assert "" == ops[1].package.source_type
assert "" == ops[1].package.source_url assert "" == ops[1].package.source_url
...@@ -1834,3 +1831,100 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( ...@@ -1834,3 +1831,100 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
{"job": "install", "package": package_a200}, {"job": "install", "package": package_a200},
], ],
) )
def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.5"
package.add_dependency("requests", {"version": "^2.22.0", "extras": ["security"]})
requests = get_package("requests", "2.22.0")
requests.add_dependency("idna", ">=2.5,<2.9")
requests.add_dependency(
"idna", {"version": ">=2.0.0", "markers": "extra == 'security'"}
)
requests.extras["security"] = [get_dependency("idna", ">=2.0.0")]
idna = get_package("idna", "2.8")
repo.add_package(requests)
repo.add_package(idna)
ops = solver.solve()
check_solver_result(
ops,
[{"job": "install", "package": idna}, {"job": "install", "package": requests}],
)
def test_solver_does_not_fail_with_locked_git_and_non_git_dependencies(
solver, repo, package, locked, pool, installed, io
):
package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})
package.add_dependency("a", "^1.2.3")
git_package = get_package("demo", "0.1.2")
git_package.source_type = "git"
git_package.source_url = "https://github.com/demo/demo.git"
git_package.source_reference = "commit"
installed.add_package(git_package)
locked.add_package(get_package("a", "1.2.3"))
locked.add_package(git_package)
repo.add_package(get_package("a", "1.2.3"))
solver = Solver(package, pool, installed, locked, io)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": get_package("a", "1.2.3")},
{"job": "install", "package": git_package, "skipped": True},
],
)
def test_ignore_python_constraint_no_overlap_dependencies(solver, repo, package):
pytest = get_package("demo", "1.0.0")
pytest.add_dependency("configparser", {"version": "^1.2.3", "python": "<3.2"})
package.add_dependency("demo", {"version": "^1.0.0", "python": "^3.6"})
repo.add_package(pytest)
repo.add_package(get_package("configparser", "1.2.3"))
ops = solver.solve()
check_solver_result(
ops, [{"job": "install", "package": pytest}],
)
def test_solver_properly_propagates_markers(solver, repo, package):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency(
"A",
{
"version": "^1.0",
"markers": "python_version >= '3.6' and implementation_name != 'pypy'",
},
)
package_a = get_package("A", "1.0.0")
package_a.python_versions = ">=3.6"
repo.add_package(package_a)
ops = solver.solve()
check_solver_result(ops, [{"job": "install", "package": package_a}])
assert (
str(ops[0].package.marker)
== 'python_version >= "3.6" and implementation_name != "pypy"'
)
Metadata-Version: 2.1
Name: cleo
Version: 0.7.6
Summary: Cleo allows you to create beautiful and testable command-line interfaces.
Home-page: https://github.com/sdispater/cleo
License: MIT
Keywords: cli,commands
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Requires-Dist: clikit (>=0.4.0,<0.5.0)
Description-Content-Type: text/x-rst
Cleo
####
.. image:: https://travis-ci.org/sdispater/cleo.png
:alt: Cleo Build status
:target: https://travis-ci.org/sdispater/cleo
Create beautiful and testable command-line interfaces.
Cleo is mostly a higher level wrapper for `CliKit <https://github.com/sdispater/clikit>`_, so
a lot of the components and utilities comes from it. Refer to its documentation for more
information.
Resources
=========
* `Documentation <http://cleo.readthedocs.io>`_
* `Issue Tracker <https://github.com/sdispater/cleo/issues>`_
Usage
=====
To make a command that greets you from the command line,
create ``greet_command.py`` and add the following to it:
.. code-block:: python
from cleo import Command
class GreetCommand(Command):
"""
Greets someone
greet
{name? : Who do you want to greet?}
{--y|yell : If set, the task will yell in uppercase letters}
"""
def handle(self):
name = self.argument('name')
if name:
text = 'Hello {}'.format(name)
else:
text = 'Hello'
if self.option('yell'):
text = text.upper()
self.line(text)
You also need to create the file to run at the command line which creates
an ``Application`` and adds commands to it:
.. code-block:: python
#!/usr/bin/env python
from greet_command import GreetCommand
from cleo import Application
application = Application()
application.add(GreetCommand())
if __name__ == '__main__':
application.run()
Test the new command by running the following
.. code-block:: bash
$ python application.py greet John
This will print the following to the command line:
.. code-block:: text
Hello John
You can also use the ``--yell`` option to make everything uppercase:
.. code-block:: bash
$ python application.py greet John --yell
This prints:
.. code-block:: text
HELLO JOHN
As you may have already seen, Cleo uses the command docstring to determine
the command definition.
The docstring must be in the following form :
.. code-block:: python
"""
Command description
Command signature
"""
The signature being in the following form:
.. code-block:: python
"""
command:name {argument : Argument description} {--option : Option description}
"""
The signature can span multiple lines.
.. code-block:: python
"""
command:name
{argument : Argument description}
{--option : Option description}
"""
Coloring the Output
-------------------
Whenever you output text, you can surround the text with tags to color its
output. For example:
.. code-block:: python
# green text
self.line('<info>foo</info>')
# yellow text
self.line('<comment>foo</comment>')
# black text on a cyan background
self.line('<question>foo</question>')
# white text on a red background
self.line('<error>foo</error>')
The closing tag can be replaced by ``</>``, which revokes all formatting options established by the last opened tag.
It is possible to define your own styles using the ``add_style()`` method:
.. code-block:: python
self.add_style('fire', fg='red', bg='yellow', options=['bold', 'blink'])
self.line('<fire>foo</fire>')
Available foreground and background colors are: ``black``, ``red``, ``green``,
``yellow``, ``blue``, ``magenta``, ``cyan`` and ``white``.
And available options are: ``bold``, ``underscore``, ``blink``, ``reverse`` and ``conceal``.
You can also set these colors and options inside the tag name:
.. code-block:: python
# green text
self.line('<fg=green>foo</>')
# black text on a cyan background
self.line('<fg=black;bg=cyan>foo</>')
# bold text on a yellow background
self.line('<bg=yellow;options=bold>foo</>')
Verbosity Levels
----------------
Cleo has four verbosity levels. These are defined in the ``Output`` class:
======================================= ================================== ======================
Mode Meaning Console option
======================================= ================================== ======================
``NA`` Do not output any messages ``-q`` or ``--quiet``
``clikit.VERBOSITY_NORMAL`` The default verbosity level (none)
``clikit.VERBOSITY_VERBOSE`` Increased verbosity of messages ``-v``
``clikit.VERBOSITY_VERY_VERBOSE`` Informative non essential messages ``-vv``
``clikit.VERBOSITY_DEBUG`` Debug messages ``-vvv``
======================================= ================================== ======================
It is possible to print a message in a command for only a specific verbosity
level. For example:
.. code-block:: python
if clikit.VERBOSITY_VERBOSE <= self.io.verbosity:
self.line(...)
There are also more semantic methods you can use to test for each of the
verbosity levels:
.. code-block:: python
if self.output.is_quiet():
# ...
if self.output.is_verbose():
# ...
You can also pass the verbosity flag directly to `line()`.
.. code-block:: python
self.line("", verbosity=clikit.VERBOSITY_VERBOSE)
When the quiet level is used, all output is suppressed.
Using Arguments
---------------
The most interesting part of the commands are the arguments and options that
you can make available. Arguments are the strings - separated by spaces - that
come after the command name itself. They are ordered, and can be optional
or required. For example, add an optional ``last_name`` argument to the command
and make the ``name`` argument required:
.. code-block:: python
class GreetCommand(Command):
"""
Greets someone
greet
{name : Who do you want to greet?}
{last_name? : Your last name?}
{--y|yell : If set, the task will yell in uppercase letters}
"""
You now have access to a ``last_name`` argument in your command:
.. code-block:: python
last_name = self.argument('last_name')
if last_name:
text += ' {}'.format(last_name)
The command can now be used in either of the following ways:
.. code-block:: bash
$ python application.py greet John
$ python application.py greet John Doe
It is also possible to let an argument take a list of values (imagine you want
to greet all your friends). For this it must be specified at the end of the
argument list:
.. code-block:: python
class GreetCommand(Command):
"""
Greets someone
greet
{names* : Who do you want to greet?}
{--y|yell : If set, the task will yell in uppercase letters}
"""
To use this, just specify as many names as you want:
.. code-block:: bash
$ python application.py demo:greet John Jane
You can access the ``names`` argument as a list:
.. code-block:: python
names = self.argument('names')
if names:
text += ' {}'.format(', '.join(names))
There are 3 argument variants you can use:
================================ ==================================== ===============================================================================================================
Mode Notation Value
================================ ==================================== ===============================================================================================================
``clikit.ARGUMENT_REQUIRED`` none (just write the argument name) The argument is required
``clikit.ARGUMENT_OPTIONAL`` ``argument?`` The argument is optional and therefore can be omitted
``clikit.ARGUMENT_MULTI_VALUED`` ``argument*`` The argument can contain an indefinite number of arguments and must be used at the end of the argument list
================================ ==================================== ===============================================================================================================
You can combine them like this:
.. code-block:: python
class GreetCommand(Command):
"""
Greets someone
greet
{names?* : Who do you want to greet?}
{--y|yell : If set, the task will yell in uppercase letters}
"""
If you want to set a default value, you can it like so:
.. code-block:: text
argument=default
The argument will then be considered optional.
Using Options
-------------
Unlike arguments, options are not ordered (meaning you can specify them in any
order) and are specified with two dashes (e.g. ``--yell`` - you can also
declare a one-letter shortcut that you can call with a single dash like
``-y``). Options are *always* optional, and can be setup to accept a value
(e.g. ``--dir=src``) or simply as a boolean flag without a value (e.g.
``--yell``).
.. tip::
It is also possible to make an option *optionally* accept a value (so that
``--yell`` or ``--yell=loud`` work). Options can also be configured to
accept a list of values.
For example, add a new option to the command that can be used to specify
how many times in a row the message should be printed:
.. code-block:: python
class GreetCommand(Command):
"""
Greets someone
greet
{name? : Who do you want to greet?}
{--y|yell : If set, the task will yell in uppercase letters}
{--iterations=1 : How many times should the message be printed?}
"""
Next, use this in the command to print the message multiple times:
.. code-block:: python
for _ in range(0, self.option('iterations')):
self.line(text)
Now, when you run the task, you can optionally specify a ``--iterations``
flag:
.. code-block:: bash
$ python application.py demo:greet John
$ python application.py demo:greet John --iterations=5
The first example will only print once, since ``iterations`` is empty and
defaults to ``1``. The second example will print five times.
Recall that options don't care about their order. So, either of the following
will work:
.. code-block:: bash
$ python application.py demo:greet John --iterations=5 --yell
$ python application.py demo:greet John --yell --iterations=5
There are 4 option variants you can use:
================================ =================================== ======================================================================================
Option Notation Value
================================ =================================== ======================================================================================
``clikit.OPTION_MULTI_VALUED`` ``--option=*`` This option accepts multiple values (e.g. ``--dir=/foo --dir=/bar``)
``clikit.OPTION_NO_VALUE`` ``--option`` Do not accept input for this option (e.g. ``--yell``)
``clikit.OPTION_REQUIRED_VALUE`` ``--option=`` This value is required (e.g. ``--iterations=5``), the option itself is still optional
``clikit.OPTION_OPTIONAL_VALUE`` ``--option=?`` This option may or may not have a value (e.g. ``--yell`` or ``--yell=loud``)
================================ =================================== ======================================================================================
You can combine them like this:
.. code-block:: python
class GreetCommand(Command):
"""
Greets someone
greet
{name? : Who do you want to greet?}
{--y|yell : If set, the task will yell in uppercase letters}
{--iterations=?*1 : How many times should the message be printed?}
"""
Testing Commands
----------------
Cleo provides several tools to help you test your commands. The most
useful one is the ``CommandTester`` class.
It uses a special IO class to ease testing without a real
console:
.. code-block:: python
import pytest
from cleo import Application
from cleo import CommandTester
def test_execute(self):
application = Application()
application.add(GreetCommand())
command = application.find('demo:greet')
command_tester = CommandTester(command)
command_tester.execute()
assert "..." == tester.io.fetch_output()
The ``CommandTester.io.fetch_output()`` method returns what would have been displayed
during a normal call from the console. ``CommandTester.io.fetch_error()`` is also available
to get what you have been written to the stderr.
You can test sending arguments and options to the command by passing them
as a string to the ``CommandTester.execute()`` method:
.. code-block:: python
import pytest
from cleo import Application
from cleo import CommandTester
def test_execute(self):
application = Application()
application.add(GreetCommand())
command = application.find('demo:greet')
command_tester = CommandTester(command)
command_tester.execute("John")
assert "John" in tester.io.fetch_output()
You can also test a whole console application by using the ``ApplicationTester`` class.
Calling an existing Command
---------------------------
If a command depends on another one being run before it, instead of asking the
user to remember the order of execution, you can call it directly yourself.
This is also useful if you want to create a "meta" command that just runs a
bunch of other commands.
Calling a command from another one is straightforward:
.. code-block:: python
def handle(self):
return_code = self.call('demo:greet', "John --yell")
# ...
If you want to suppress the output of the executed command,
you can use the ``call_silent()`` method instead.
Autocompletion
--------------
Cleo supports automatic (tab) completion in ``bash``, ``zsh`` and ``fish``.
To activate support for autocompletion, pass a ``complete`` keyword when initializing
your application:
.. code-block:: python
application = Application('My Application', '0.1', complete=True)
Now, register completion for your application by running one of the following in a terminal,
replacing ``[program]`` with the command you use to run your application:
.. code-block:: bash
# BASH - Ubuntu / Debian
[program] completions bash | sudo tee /etc/bash_completion.d/[program].bash-completion
# BASH - Mac OSX (with Homebrew "bash-completion")
[program] completions bash > $(brew --prefix)/etc/bash_completion.d/[program].bash-completion
# ZSH - Config file
mkdir ~/.zfunc
echo "fpath+=~/.zfunc" >> ~/.zshrc
[program] completions zsh > ~/.zfunc/_test
# FISH
[program] completions fish > ~/.config/fish/completions/[program].fish
Metadata-Version: 1.2
Name: pendulum
Version: 2.0.5
Summary: Python datetimes made easy
Home-page: https://pendulum.eustace.io
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
License: UNKNOWN
Description: Pendulum
########
.. image:: https://img.shields.io/pypi/v/pendulum.svg
:target: https://pypi.python.org/pypi/pendulum
.. image:: https://img.shields.io/pypi/l/pendulum.svg
:target: https://pypi.python.org/pypi/pendulum
.. image:: https://img.shields.io/codecov/c/github/sdispater/pendulum/master.svg
:target: https://codecov.io/gh/sdispater/pendulum/branch/master
.. image:: https://travis-ci.org/sdispater/pendulum.svg
:alt: Pendulum Build status
:target: https://travis-ci.org/sdispater/pendulum
Python datetimes made easy.
Supports Python **2.7** and **3.4+**.
.. code-block:: python
>>> import pendulum
>>> now_in_paris = pendulum.now('Europe/Paris')
>>> now_in_paris
'2016-07-04T00:49:58.502116+02:00'
# Seamless timezone switching
>>> now_in_paris.in_timezone('UTC')
'2016-07-03T22:49:58.502116+00:00'
>>> tomorrow = pendulum.now().add(days=1)
>>> last_week = pendulum.now().subtract(weeks=1)
>>> past = pendulum.now().subtract(minutes=2)
>>> past.diff_for_humans()
>>> '2 minutes ago'
>>> delta = past - last_week
>>> delta.hours
23
>>> delta.in_words(locale='en')
'6 days 23 hours 58 minutes'
# Proper handling of datetime normalization
>>> pendulum.datetime(2013, 3, 31, 2, 30, tz='Europe/Paris')
'2013-03-31T03:30:00+02:00' # 2:30 does not exist (Skipped time)
# Proper handling of dst transitions
>>> just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, tz='Europe/Paris')
'2013-03-31T01:59:59.999999+01:00'
>>> just_before.add(microseconds=1)
'2013-03-31T03:00:00+02:00'
Why Pendulum?
=============
Native ``datetime`` instances are enough for basic cases but when you face more complex use-cases
they often show limitations and are not so intuitive to work with.
``Pendulum`` provides a cleaner and more easy to use API while still relying on the standard library.
So it's still ``datetime`` but better.
Unlike other datetime libraries for Python, Pendulum is a drop-in replacement
for the standard ``datetime`` class (it inherits from it), so, basically, you can replace all your ``datetime``
instances by ``DateTime`` instances in you code (exceptions exist for libraries that check
the type of the objects by using the ``type`` function like ``sqlite3`` or ``PyMySQL`` for instance).
It also removes the notion of naive datetimes: each ``Pendulum`` instance is timezone-aware
and by default in ``UTC`` for ease of use.
Pendulum also improves the standard ``timedelta`` class by providing more intuitive methods and properties.
Why not Arrow?
==============
Arrow is the most popular datetime library for Python right now, however its behavior
and API can be erratic and unpredictable. The ``get()`` method can receive pretty much anything
and it will try its best to return something while silently failing to handle some cases:
.. code-block:: python
arrow.get('2016-1-17')
# <Arrow [2016-01-01T00:00:00+00:00]>
pendulum.parse('2016-1-17')
# <Pendulum [2016-01-17T00:00:00+00:00]>
arrow.get('20160413')
# <Arrow [1970-08-22T08:06:53+00:00]>
pendulum.parse('20160413')
# <Pendulum [2016-04-13T00:00:00+00:00]>
arrow.get('2016-W07-5')
# <Arrow [2016-01-01T00:00:00+00:00]>
pendulum.parse('2016-W07-5')
# <Pendulum [2016-02-19T00:00:00+00:00]>
# Working with DST
just_before = arrow.Arrow(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris')
just_after = just_before.replace(microseconds=1)
'2013-03-31T02:00:00+02:00'
# Should be 2013-03-31T03:00:00+02:00
(just_after.to('utc') - just_before.to('utc')).total_seconds()
-3599.999999
# Should be 1e-06
just_before = pendulum.datetime(2013, 3, 31, 1, 59, 59, 999999, 'Europe/Paris')
just_after = just_before.add(microseconds=1)
'2013-03-31T03:00:00+02:00'
(just_after.in_timezone('utc') - just_before.in_timezone('utc')).total_seconds()
1e-06
Those are a few examples showing that Arrow cannot always be trusted to have a consistent
behavior with the data you are passing to it.
Limitations
===========
Even though the ``DateTime`` class is a subclass of ``datetime`` there are some rare cases where
it can't replace the native class directly. Here is a list (non-exhaustive) of the reported cases with
a possible solution, if any:
* ``sqlite3`` will use the ``type()`` function to determine the type of the object by default. To work around it you can register a new adapter:
.. code-block:: python
from pendulum import DateTime
from sqlite3 import register_adapter
register_adapter(DateTime, lambda val: val.isoformat(' '))
* ``mysqlclient`` (former ``MySQLdb``) and ``PyMySQL`` will use the ``type()`` function to determine the type of the object by default. To work around it you can register a new adapter:
.. code-block:: python
import MySQLdb.converters
import pymysql.converters
from pendulum import DateTime
MySQLdb.converters.conversions[DateTime] = MySQLdb.converters.DateTime2literal
pymysql.converters.conversions[DateTime] = pymysql.converters.escape_datetime
* ``django`` will use the ``isoformat()`` method to store datetimes in the database. However since ``pendulum`` is always timezone aware the offset information will always be returned by ``isoformat()`` raising an error, at least for MySQL databases. To work around it you can either create your own ``DateTimeField`` or use the previous workaround for ``MySQLdb``:
.. code-block:: python
from django.db.models import DateTimeField as BaseDateTimeField
from pendulum import DateTime
class DateTimeField(BaseDateTimeField):
def value_to_string(self, obj):
val = self.value_from_object(obj)
if isinstance(value, DateTime):
return value.to_datetime_string()
return '' if val is None else val.isoformat()
Resources
=========
* `Official Website <https://pendulum.eustace.io>`_
* `Documentation <https://pendulum.eustace.io/docs/>`_
* `Issue Tracker <https://github.com/sdispater/pendulum/issues>`_
Contributing
============
Contributions are welcome, especially with localization.
Getting started
---------------
To work on the Pendulum codebase, you'll want to clone the project locally
and install the required depedendencies via `poetry <https://python-poetry.org>`_.
.. code-block:: bash
$ git clone git@github.com:sdispater/pendulum.git
$ poetry install
Localization
------------
If you want to help with localization, there are two different cases: the locale already exists
or not.
If the locale does not exist you will need to create it by using the ``clock`` utility:
.. code-block:: bash
./clock locale create <your-locale>
It will generate a directory in ``pendulum/locales`` named after your locale, with the following
structure:
.. code-block:: text
<your-locale>/
- custom.py
- locale.py
The ``locale.py`` file must not be modified. It contains the translations provided by
the CLDR database.
The ``custom.py`` file is the one you want to modify. It contains the data needed
by Pendulum that are not provided by the CLDR database. You can take the `en <https://github.com/sdispater/pendulum/tree/master/pendulum/locales/en/custom.py>`_
data as a reference to see which data is needed.
You should also add tests for the created or modified locale.
Platform: UNKNOWN
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
python-dateutil<3.0,>=2.6
pytzdata>=2018.3
[:python_version < "3.5"]
typing<4.0,>=3.6
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"last_month": -1, "last_month": -1,
"last_week": -1 "last_week": -1
}, },
"home_page": "https://poetry.eustace.io/", "home_page": "https://python-poetry.org/",
"keywords": "packaging,dependency,poetry", "keywords": "packaging,dependency,poetry",
"license": "MIT", "license": "MIT",
"maintainer": "Sébastien Eustace", "maintainer": "Sébastien Eustace",
...@@ -34,9 +34,9 @@ ...@@ -34,9 +34,9 @@
"platform": "", "platform": "",
"project_url": "https://pypi.org/project/poetry/", "project_url": "https://pypi.org/project/poetry/",
"project_urls": { "project_urls": {
"Documentation": "https://poetry.eustace.io/docs", "Documentation": "https://python-poetry.org/docs",
"Homepage": "https://poetry.eustace.io/", "Homepage": "https://python-poetry.org/",
"Repository": "https://github.com/sdispater/poetry" "Repository": "https://github.com/python-poetry/poetry"
}, },
"release_url": "https://pypi.org/project/poetry/0.12.4/", "release_url": "https://pypi.org/project/poetry/0.12.4/",
"requires_dist": [ "requires_dist": [
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"last_month": -1, "last_month": -1,
"last_week": -1 "last_week": -1
}, },
"home_page": "https://poetry.eustace.io/", "home_page": "https://python-poetry.org/",
"keywords": "packaging,dependency,poetry", "keywords": "packaging,dependency,poetry",
"license": "MIT", "license": "MIT",
"maintainer": "Sébastien Eustace", "maintainer": "Sébastien Eustace",
...@@ -34,9 +34,9 @@ ...@@ -34,9 +34,9 @@
"platform": "", "platform": "",
"project_url": "https://pypi.org/project/poetry/", "project_url": "https://pypi.org/project/poetry/",
"project_urls": { "project_urls": {
"Documentation": "https://poetry.eustace.io/docs", "Documentation": "https://python-poetry.org/docs",
"Homepage": "https://poetry.eustace.io/", "Homepage": "https://python-poetry.org/",
"Repository": "https://github.com/sdispater/poetry" "Repository": "https://github.com/python-poetry/poetry"
}, },
"release_url": "https://pypi.org/project/poetry/0.12.4/", "release_url": "https://pypi.org/project/poetry/0.12.4/",
"requires_dist": [ "requires_dist": [
......
...@@ -8,9 +8,9 @@ from poetry.utils._compat import encode ...@@ -8,9 +8,9 @@ from poetry.utils._compat import encode
def test_auth_with_request_on_the_same_host(): def test_auth_with_request_on_the_same_host():
auth = Auth("https://poetry.eustace.io", "foo", "bar") auth = Auth("https://python-poetry.org", "foo", "bar")
request = Request("GET", "https://poetry.eustace.io/docs/") request = Request("GET", "https://python-poetry.org/docs/")
assert "Authorization" not in request.headers assert "Authorization" not in request.headers
request = auth(request) request = auth(request)
...@@ -22,9 +22,9 @@ def test_auth_with_request_on_the_same_host(): ...@@ -22,9 +22,9 @@ def test_auth_with_request_on_the_same_host():
def test_auth_with_request_with_same_authentication(): def test_auth_with_request_with_same_authentication():
auth = Auth("https://poetry.eustace.io", "foo", "bar") auth = Auth("https://python-poetry.org", "foo", "bar")
request = Request("GET", "https://foo:bar@poetry.eustace.io/docs/") request = Request("GET", "https://foo:bar@python-poetry.org/docs/")
assert "Authorization" not in request.headers assert "Authorization" not in request.headers
request = auth(request) request = auth(request)
...@@ -36,7 +36,7 @@ def test_auth_with_request_with_same_authentication(): ...@@ -36,7 +36,7 @@ def test_auth_with_request_with_same_authentication():
def test_auth_with_request_on_different_hosts(): def test_auth_with_request_on_different_hosts():
auth = Auth("https://poetry.eustace.io", "foo", "bar") auth = Auth("https://python-poetry.org", "foo", "bar")
request = Request("GET", "https://pendulum.eustace.io/docs/") request = Request("GET", "https://pendulum.eustace.io/docs/")
assert "Authorization" not in request.headers assert "Authorization" not in request.headers
......
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.utils._compat import Path
from poetry.utils._compat import metadata
from poetry.utils._compat import zipp
from poetry.utils.env import MockEnv as BaseMockEnv from poetry.utils.env import MockEnv as BaseMockEnv
FREEZE_RESULTS = """cleo==0.6.8 FIXTURES_DIR = Path(__file__).parent / "fixtures"
-e git+https://github.com/sdispater/pendulum.git@bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6#egg=pendulum ENV_DIR = (FIXTURES_DIR / "installed").resolve()
orator===0.9.8 SITE_PACKAGES = ENV_DIR / "lib" / "python3.7" / "site-packages"
""" SRC = ENV_DIR / "src"
INSTALLED_RESULTS = [
metadata.PathDistribution(SITE_PACKAGES / "cleo-0.7.6.dist-info"),
metadata.PathDistribution(SRC / "pendulum" / "pendulum.egg-info"),
metadata.PathDistribution(
zipp.Path(str(SITE_PACKAGES / "foo-0.1.0-py3.8.egg"), "EGG-INFO")
),
]
class MockEnv(BaseMockEnv): class MockEnv(BaseMockEnv):
def run(self, bin, *args): @property
if bin == "python" and args[:3] == ("-m", "pip", "freeze"): def site_packages(self): # type: () -> Path
return FREEZE_RESULTS return SITE_PACKAGES
return super(MockEnv, self).run(bin, *args)
def test_load(mocker):
def test_load(): mocker.patch(
repository = InstalledRepository.load(MockEnv()) "poetry.utils._compat.metadata.Distribution.discover",
return_value=INSTALLED_RESULTS,
)
mocker.patch(
"poetry.vcs.git.Git.rev_parse",
return_value="bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6",
)
mocker.patch(
"poetry.vcs.git.Git.remote_urls",
side_effect=[
{"remote.origin.url": "https://github.com/sdispater/pendulum.git"},
{"remote.origin.url": "git@github.com:sdispater/pendulum.git"},
],
)
repository = InstalledRepository.load(MockEnv(path=ENV_DIR))
assert len(repository.packages) == 3 assert len(repository.packages) == 3
cleo = repository.packages[0] cleo = repository.packages[0]
assert cleo.name == "cleo" assert cleo.name == "cleo"
assert cleo.version.text == "0.6.8" assert cleo.version.text == "0.7.6"
assert (
cleo.description
== "Cleo allows you to create beautiful and testable command-line interfaces."
)
foo = repository.packages[1]
assert foo.name == "foo"
assert foo.version.text == "0.1.0"
pendulum = repository.packages[1] pendulum = repository.packages[2]
assert pendulum.name == "pendulum" assert pendulum.name == "pendulum"
assert pendulum.version.text == "0.0.0" assert pendulum.version.text == "2.0.5"
assert pendulum.description == "Python datetimes made easy"
assert pendulum.source_type == "git" assert pendulum.source_type == "git"
assert pendulum.source_url == "https://github.com/sdispater/pendulum.git" assert pendulum.source_url == "https://github.com/sdispater/pendulum.git"
assert pendulum.source_reference == "bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6" assert pendulum.source_reference == "bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6"
orator = repository.packages[2]
assert orator.name == "orator"
assert orator.version.text == "0.9.8"
import json import json
import shutil import shutil
from io import BytesIO
import pytest import pytest
from requests.exceptions import TooManyRedirects
from requests.models import Response
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
from poetry.utils._compat import PY35 from poetry.utils._compat import PY35
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import encode
class MockRepository(PyPiRepository): class MockRepository(PyPiRepository):
...@@ -186,3 +192,19 @@ def test_invalid_versions_ignored(): ...@@ -186,3 +192,19 @@ def test_invalid_versions_ignored():
# and a correct one. # and a correct one.
packages = repo.find_packages("pygame-music-grid") packages = repo.find_packages("pygame-music-grid")
assert len(packages) == 1 assert len(packages) == 1
def test_get_should_invalid_cache_on_too_many_redirects_error(mocker):
delete_cache = mocker.patch("cachecontrol.caches.file_cache.FileCache.delete")
response = Response()
response.encoding = "utf-8"
response.raw = BytesIO(encode('{"foo": "bar"}'))
mocker.patch(
"cachecontrol.adapter.CacheControlAdapter.send",
side_effect=[TooManyRedirects(), response],
)
repository = PyPiRepository()
repository._get("https://pypi.org/pypi/async-timeout/json")
assert delete_cache.called
...@@ -68,6 +68,10 @@ def test_parse_constraint_wildcard(input, constraint): ...@@ -68,6 +68,10 @@ def test_parse_constraint_wildcard(input, constraint):
("~3.5", VersionRange(Version(3, 5, 0), Version(3, 6, 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", VersionRange(Version(3, 5, 0), Version(4, 0, 0), True)), # PEP 440
("~=3.5.3", VersionRange(Version(3, 5, 3), Version(3, 6, 0), True)), # PEP 440 ("~=3.5.3", VersionRange(Version(3, 5, 3), Version(3, 6, 0), True)), # PEP 440
(
"~=3.5.3rc1",
VersionRange(Version(3, 5, 3, pre="rc1"), Version(3, 6, 0), True),
), # PEP 440
], ],
) )
def test_parse_constraint_tilde(input, constraint): def test_parse_constraint_tilde(input, constraint):
......
...@@ -27,8 +27,8 @@ def test_create_poetry(): ...@@ -27,8 +27,8 @@ def test_create_poetry():
package.readme.relative_to(fixtures_dir).as_posix() package.readme.relative_to(fixtures_dir).as_posix()
== "sample_project/README.rst" == "sample_project/README.rst"
) )
assert package.homepage == "https://poetry.eustace.io" assert package.homepage == "https://python-poetry.org"
assert package.repository_url == "https://github.com/sdispater/poetry" assert package.repository_url == "https://github.com/python-poetry/poetry"
assert package.keywords == ["packaging", "dependency", "poetry"] assert package.keywords == ["packaging", "dependency", "poetry"]
assert package.python_versions == "~2.7 || ^3.6" assert package.python_versions == "~2.7 || ^3.6"
......
...@@ -9,9 +9,9 @@ license = "MIT" ...@@ -9,9 +9,9 @@ license = "MIT"
readme = "README.md" readme = "README.md"
homepage = "https://poetry.eustace.io/" homepage = "https://python-poetry.org/"
repository = "https://github.com/sdispater/poetry" repository = "https://github.com/python-poetry/poetry"
documentation = "https://poetry.eustace.io/docs" documentation = "https://python-poetry.org/docs"
keywords = ["packaging", "dependency", "poetry"] keywords = ["packaging", "dependency", "poetry"]
......
import setuptools
setuptools.setup(
name="my_package",
version="0.1.2",
author="John Doe",
author_email="john@example.orh",
description="Just a description",
url="https://example.org",
packages=setuptools.find_packages(),
)
...@@ -9,6 +9,7 @@ from clikit.io import NullIO ...@@ -9,6 +9,7 @@ from clikit.io import NullIO
from poetry.factory import Factory from poetry.factory import Factory
from poetry.semver import Version from poetry.semver import Version
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvCommandError
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
...@@ -30,6 +31,20 @@ print("nullpackage loaded"), ...@@ -30,6 +31,20 @@ print("nullpackage loaded"),
""" """
class MockVirtualEnv(VirtualEnv):
def __init__(self, path, base=None, sys_path=None):
super(MockVirtualEnv, self).__init__(path, base=base)
self._sys_path = sys_path
@property
def sys_path(self):
if self._sys_path is not None:
return self._sys_path
return super(MockVirtualEnv, self).sys_path
@pytest.fixture() @pytest.fixture()
def poetry(config): def poetry(config):
poetry = Factory().create_poetry( poetry = Factory().create_poetry(
...@@ -661,3 +676,170 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable( ...@@ -661,3 +676,170 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable(
assert expected_message == str(e.value) assert expected_message == str(e.value)
assert 0 == m.call_count assert 0 == m.call_count
def test_create_venv_uses_patch_version_to_detect_compatibility(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
version = Version.parse(".".join(str(c) for c in sys.version_info[:3]))
poetry.package.python_versions = "^{}".format(
".".join(str(c) for c in sys.version_info[:3])
)
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
mocker.patch("sys.version_info", (version.major, version.minor, version.patch + 1))
check_output = mocker.patch(
"poetry.utils._compat.subprocess.check_output",
side_effect=check_output_wrapper(Version.parse("3.6.9")),
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
manager.create_venv(NullIO())
assert not check_output.called
m.assert_called_with(
str(
Path(
"/foo/virtualenvs/{}-py{}.{}".format(
venv_name, version.major, version.minor
)
)
),
executable=None,
)
def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable(
manager, poetry, config, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
version = Version.parse(".".join(str(c) for c in sys.version_info[:3]))
poetry.package.python_versions = "~{}".format(
".".join(str(c) for c in (version.major, version.minor - 1, 0))
)
venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent))
check_output = mocker.patch(
"poetry.utils._compat.subprocess.check_output",
side_effect=check_output_wrapper(
Version.parse("{}.{}.0".format(version.major, version.minor - 1))
),
)
m = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)
manager.create_venv(
NullIO(), executable="python{}.{}".format(version.major, version.minor - 1)
)
assert check_output.called
m.assert_called_with(
str(
Path(
"/foo/virtualenvs/{}-py{}.{}".format(
venv_name, version.major, version.minor - 1
)
)
),
executable="python{}.{}".format(version.major, version.minor - 1),
)
def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir(
manager, poetry, config, tmp_dir, mocker
):
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]
config.merge(
{
"virtualenvs": {
"path": str(Path(tmp_dir) / "virtualenvs"),
"in-project": True,
}
}
)
mocker.patch(
"poetry.utils._compat.subprocess.check_output",
side_effect=check_output_wrapper(),
)
mocker.patch(
"poetry.utils._compat.subprocess.Popen.communicate",
side_effect=[("/prefix", None), ("/prefix", None)],
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv")
manager.activate("python3.7", NullIO())
m.assert_called_with(
os.path.join(str(poetry.file.parent), ".venv"), executable="python3.7"
)
envs_file = TomlFile(Path(tmp_dir) / "virtualenvs" / "envs.toml")
assert not envs_file.exists()
def test_env_site_packages_should_find_the_site_packages_directory_if_standard(tmp_dir):
if WINDOWS:
site_packages = Path(tmp_dir).joinpath("Lib/site-packages")
else:
site_packages = Path(tmp_dir).joinpath(
"lib/python{}/site-packages".format(
".".join(str(v) for v in sys.version_info[:2])
)
)
site_packages.mkdir(parents=True)
env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[str(site_packages)])
assert site_packages == env.site_packages
def test_env_site_packages_should_find_the_site_packages_directory_if_root(tmp_dir):
site_packages = Path(tmp_dir).joinpath("site-packages")
site_packages.mkdir(parents=True)
env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[str(site_packages)])
assert site_packages == env.site_packages
def test_env_site_packages_should_find_the_dist_packages_directory_if_necessary(
tmp_dir,
):
site_packages = Path(tmp_dir).joinpath("dist-packages")
site_packages.mkdir(parents=True)
env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[str(site_packages)])
assert site_packages == env.site_packages
def test_env_site_packages_should_prefer_site_packages_over_dist_packages(tmp_dir):
dist_packages = Path(tmp_dir).joinpath("dist-packages")
dist_packages.mkdir(parents=True)
site_packages = Path(tmp_dir).joinpath("site-packages")
site_packages.mkdir(parents=True)
env = MockVirtualEnv(
Path(tmp_dir), Path(tmp_dir), sys_path=[str(dist_packages), str(site_packages)]
)
assert site_packages == env.site_packages
def test_env_site_packages_should_raise_an_error_if_no_site_packages(tmp_dir):
env = MockVirtualEnv(Path(tmp_dir), Path(tmp_dir), sys_path=[])
with pytest.raises(RuntimeError):
env.site_packages
...@@ -603,7 +603,7 @@ def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry ...@@ -603,7 +603,7 @@ def test_exporter_can_export_requirements_txt_with_file_packages(tmp_dir, poetry
content = f.read() content = f.read()
expected = """\ expected = """\
-e tests/fixtures/distributions/demo-0.1.0.tar.gz tests/fixtures/distributions/demo-0.1.0.tar.gz
""" """
assert expected == content assert expected == content
...@@ -644,7 +644,7 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers( ...@@ -644,7 +644,7 @@ def test_exporter_can_export_requirements_txt_with_file_packages_and_markers(
content = f.read() content = f.read()
expected = """\ expected = """\
-e tests/fixtures/distributions/demo-0.1.0.tar.gz; python_version < "3.7" tests/fixtures/distributions/demo-0.1.0.tar.gz; python_version < "3.7"
""" """
assert expected == content assert expected == content
...@@ -707,6 +707,86 @@ foo==1.2.3 \\ ...@@ -707,6 +707,86 @@ foo==1.2.3 \\
assert expected == content assert expected == content
def test_exporter_exports_requirements_txt_with_legacy_packages_and_duplicate_sources(
tmp_dir, poetry
):
poetry.pool.add_repository(
LegacyRepository(
"custom",
"https://example.com/simple",
auth=Auth("https://example.com/simple", "foo", "bar"),
)
)
poetry.pool.add_repository(LegacyRepository("custom", "https://foobaz.com/simple",))
poetry.locker.mock_lock_data(
{
"package": [
{
"name": "foo",
"version": "1.2.3",
"category": "main",
"optional": False,
"python-versions": "*",
"source": {
"type": "legacy",
"url": "https://example.com/simple",
"reference": "",
},
},
{
"name": "bar",
"version": "4.5.6",
"category": "dev",
"optional": False,
"python-versions": "*",
"source": {
"type": "legacy",
"url": "https://example.com/simple",
"reference": "",
},
},
{
"name": "baz",
"version": "7.8.9",
"category": "dev",
"optional": False,
"python-versions": "*",
"source": {
"type": "legacy",
"url": "https://foobaz.com/simple",
"reference": "",
},
},
],
"metadata": {
"python-versions": "*",
"content-hash": "123456789",
"hashes": {"foo": ["12345"], "bar": ["67890"], "baz": ["24680"]},
},
}
)
exporter = Exporter(poetry)
exporter.export("requirements.txt", Path(tmp_dir), "requirements.txt", dev=True)
with (Path(tmp_dir) / "requirements.txt").open(encoding="utf-8") as f:
content = f.read()
expected = """\
--extra-index-url https://example.com/simple
--extra-index-url https://foobaz.com/simple
bar==4.5.6 \\
--hash=sha256:67890
baz==7.8.9 \\
--hash=sha256:24680
foo==1.2.3 \\
--hash=sha256:12345
"""
assert expected == content
def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials( def test_exporter_exports_requirements_txt_with_legacy_packages_and_credentials(
tmp_dir, poetry, config tmp_dir, poetry, config
): ):
......
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_cert
from poetry.utils.helpers import get_client_cert from poetry.utils.helpers import get_client_cert
from poetry.utils.helpers import get_http_basic_auth
from poetry.utils.helpers import parse_requires from poetry.utils.helpers import parse_requires
...@@ -54,22 +53,6 @@ zipfile36>=0.1.0.0,<0.2.0.0 ...@@ -54,22 +53,6 @@ zipfile36>=0.1.0.0,<0.2.0.0
assert result == expected assert result == expected
def test_get_http_basic_auth(config):
config.merge({"http-basic": {"foo": {"username": "foo", "password": "bar"}}})
assert get_http_basic_auth(config, "foo") == ("foo", "bar")
def test_get_http_basic_auth_without_password(config):
config.merge({"http-basic": {"foo": {"username": "foo"}}})
assert get_http_basic_auth(config, "foo") == ("foo", None)
def test_get_http_basic_auth_missing(config):
assert get_http_basic_auth(config, "foo") is None
def test_get_cert(config): def test_get_cert(config):
ca_cert = "path/to/ca.pem" ca_cert = "path/to/ca.pem"
config.merge({"certificates": {"foo": {"cert": ca_cert}}}) config.merge({"certificates": {"foo": {"cert": ca_cert}}})
......
from uuid import uuid4
import pytest
from keyring import get_keyring
from keyring import set_keyring
from keyring.backend import KeyringBackend
from keyring.errors import KeyringError
from poetry.utils.helpers import keyring_repository_password_del
from poetry.utils.helpers import keyring_repository_password_get
from poetry.utils.helpers import keyring_repository_password_set
from poetry.utils.helpers import keyring_service_name
class DictKeyring(KeyringBackend):
priority = 1
def __init__(self):
self._storage = dict()
def set_password(self, servicename, username, password):
if servicename not in self._storage:
self._storage[servicename] = dict()
self._storage[servicename][username] = password
def get_password(self, servicename, username):
if servicename in self._storage:
return self._storage[servicename].get(username)
def delete_password(self, servicename, username):
if servicename in self._storage:
if username in self._storage[servicename]:
del self._storage[servicename][username]
if not self._storage[servicename]:
del self._storage[servicename]
class BrokenKeyring(KeyringBackend):
priority = 1
def set_password(self, servicename, username, password):
raise KeyringError()
def get_password(self, servicename, username):
raise KeyringError()
def delete_password(self, servicename, username):
raise KeyringError()
@pytest.fixture
def keyring(): # type: () -> KeyringBackend
k = DictKeyring()
set_keyring(k)
return k
@pytest.fixture
def broken_keyring(): # type: () -> KeyringBackend
k = BrokenKeyring()
set_keyring(k)
return k
@pytest.fixture
def repository(): # type: () -> str
return "test"
@pytest.fixture
def username(): # type: () -> str
return "username"
@pytest.fixture
def password(): # type: () -> str
return str(uuid4())
def test_keyring_repository_password_get(keyring, repository, username, password):
keyring.set_password(keyring_service_name(repository), username, password)
assert keyring_repository_password_get(repository, username) == password
def test_keyring_repository_password_get_not_set(keyring, repository, username):
assert keyring.get_password(keyring_service_name(repository), username) is None
assert keyring_repository_password_get(repository, username) is None
def test_keyring_repository_password_get_broken(broken_keyring):
assert get_keyring() == broken_keyring
assert keyring_repository_password_get("repository", "username") is None
def test_keyring_repository_password_set(keyring, repository, username, password):
keyring_repository_password_set(repository, username, password)
assert keyring.get_password(keyring_service_name(repository), username) == password
def test_keyring_repository_password_set_broken(broken_keyring):
assert get_keyring() == broken_keyring
with pytest.raises(RuntimeError):
keyring_repository_password_set(repository, "username", "password")
def test_keyring_repository_password_del(
keyring, config, repository, username, password
):
keyring.set_password(keyring_service_name(repository), username, password)
config.merge({"http-basic": {repository: {"username": username}}})
keyring_repository_password_del(config, repository)
assert keyring.get_password(keyring_service_name(repository), username) is None
def test_keyring_repository_password_del_not_set(keyring, config, repository, username):
config.merge({"http-basic": {repository: {"username": username}}})
keyring_repository_password_del(config, repository)
assert keyring.get_password(keyring_service_name(repository), username) is None
def test_keyring_repository_password_del_broken(broken_keyring, config):
assert get_keyring() == broken_keyring
keyring_repository_password_del(config, "repository")
import pytest
from keyring.backend import KeyringBackend
from poetry.utils.password_manager import KeyRing
from poetry.utils.password_manager import KeyRingError
from poetry.utils.password_manager import PasswordManager
class DummyBackend(KeyringBackend):
def __init__(self):
self._passwords = {}
@classmethod
def priority(cls):
return 42
def set_password(self, service, username, password):
self._passwords[service] = {username: password}
def get_password(self, service, username):
return self._passwords.get(service, {}).get(username)
def delete_password(self, service, username):
if service in self._passwords and username in self._passwords[service]:
del self._passwords[service][username]
@pytest.fixture()
def backend():
return DummyBackend()
@pytest.fixture()
def mock_available_backend(backend):
import keyring
keyring.set_keyring(backend)
@pytest.fixture()
def mock_unavailable_backend():
import keyring
from keyring.backends.fail import Keyring
keyring.set_keyring(Keyring())
@pytest.fixture()
def mock_chainer_backend(mocker):
from keyring.backends.fail import Keyring
mocker.patch("keyring.backend.get_all_keyring", [Keyring()])
import keyring
from keyring.backends.chainer import ChainerBackend
keyring.set_keyring(ChainerBackend())
def test_set_http_password(config, mock_available_backend, backend):
manager = PasswordManager(config)
assert manager.keyring.is_available()
manager.set_http_password("foo", "bar", "baz")
assert "baz" == backend.get_password("poetry-repository-foo", "bar")
auth = config.get("http-basic.foo")
assert "bar" == auth["username"]
assert "password" not in auth
def test_get_http_auth(config, mock_available_backend, backend):
backend.set_password("poetry-repository-foo", "bar", "baz")
config.auth_config_source.add_property("http-basic.foo", {"username": "bar"})
manager = PasswordManager(config)
assert manager.keyring.is_available()
auth = manager.get_http_auth("foo")
assert "bar" == auth["username"]
assert "baz" == auth["password"]
def test_delete_http_password(config, mock_available_backend, backend):
backend.set_password("poetry-repository-foo", "bar", "baz")
config.auth_config_source.add_property("http-basic.foo", {"username": "bar"})
manager = PasswordManager(config)
assert manager.keyring.is_available()
manager.delete_http_password("foo")
assert backend.get_password("poetry-repository-foo", "bar") is None
assert config.get("http-basic.foo") is None
def test_set_pypi_token(config, mock_available_backend, backend):
manager = PasswordManager(config)
assert manager.keyring.is_available()
manager.set_pypi_token("foo", "baz")
assert config.get("pypi-token.foo") is None
assert "baz" == backend.get_password("poetry-repository-foo", "__token__")
def test_get_pypi_token(config, mock_available_backend, backend):
backend.set_password("poetry-repository-foo", "__token__", "baz")
manager = PasswordManager(config)
assert manager.keyring.is_available()
assert "baz" == manager.get_pypi_token("foo")
def test_delete_pypi_token(config, mock_available_backend, backend):
backend.set_password("poetry-repository-foo", "__token__", "baz")
manager = PasswordManager(config)
assert manager.keyring.is_available()
manager.delete_pypi_token("foo")
assert backend.get_password("poetry-repository-foo", "__token__") is None
def test_set_http_password_with_unavailable_backend(config, mock_unavailable_backend):
manager = PasswordManager(config)
assert not manager.keyring.is_available()
manager.set_http_password("foo", "bar", "baz")
auth = config.get("http-basic.foo")
assert "bar" == auth["username"]
assert "baz" == auth["password"]
def test_get_http_auth_with_unavailable_backend(config, mock_unavailable_backend):
config.auth_config_source.add_property(
"http-basic.foo", {"username": "bar", "password": "baz"}
)
manager = PasswordManager(config)
assert not manager.keyring.is_available()
auth = manager.get_http_auth("foo")
assert "bar" == auth["username"]
assert "baz" == auth["password"]
def test_delete_http_password_with_unavailable_backend(
config, mock_unavailable_backend
):
config.auth_config_source.add_property(
"http-basic.foo", {"username": "bar", "password": "baz"}
)
manager = PasswordManager(config)
assert not manager.keyring.is_available()
manager.delete_http_password("foo")
assert config.get("http-basic.foo") is None
def test_set_pypi_token_with_unavailable_backend(config, mock_unavailable_backend):
manager = PasswordManager(config)
assert not manager.keyring.is_available()
manager.set_pypi_token("foo", "baz")
assert "baz" == config.get("pypi-token.foo")
def test_get_pypi_token_with_unavailable_backend(config, mock_unavailable_backend):
config.auth_config_source.add_property("pypi-token.foo", "baz")
manager = PasswordManager(config)
assert not manager.keyring.is_available()
assert "baz" == manager.get_pypi_token("foo")
def test_delete_pypi_token_with_unavailable_backend(config, mock_unavailable_backend):
config.auth_config_source.add_property("pypi-token.foo", "baz")
manager = PasswordManager(config)
assert not manager.keyring.is_available()
manager.delete_pypi_token("foo")
assert config.get("pypi-token.foo") is None
def test_keyring_raises_errors_on_keyring_errors(mocker, mock_unavailable_backend):
mocker.patch("poetry.utils.password_manager.KeyRing._check")
key_ring = KeyRing("poetry")
with pytest.raises(KeyRingError):
key_ring.set_password("foo", "bar", "baz")
with pytest.raises(KeyRingError):
key_ring.get_password("foo", "bar")
with pytest.raises(KeyRingError):
key_ring.delete_password("foo", "bar")
def test_keyring_with_chainer_backend_and_not_compatible_only_should_be_unavailable(
mock_chainer_backend,
):
key_ring = KeyRing("poetry")
assert not key_ring.is_available()
...@@ -166,3 +166,14 @@ def test_setup_reader_read_extras_require_with_variables(setup): ...@@ -166,3 +166,14 @@ def test_setup_reader_read_extras_require_with_variables(setup):
assert expected_install_requires == result["install_requires"] assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"] assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"] assert expected_python_requires == result["python_requires"]
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_setuptools(setup):
result = SetupReader.read_from_directory(setup("setuptools_setup"))
expected_name = "my_package"
expected_version = "0.1.2"
assert expected_name == result["name"]
assert expected_version == result["version"]
import pytest
from poetry.vcs.git import Git
from poetry.vcs.git import GitUrl
@pytest.mark.parametrize(
"url, normalized",
[
(
"git+ssh://user@hostname:project.git#commit",
GitUrl("user@hostname:project.git", "commit"),
),
(
"git+http://user@hostname/project/blah.git@commit",
GitUrl("http://user@hostname/project/blah.git", "commit"),
),
(
"git+https://user@hostname/project/blah.git",
GitUrl("https://user@hostname/project/blah.git", None),
),
(
"git+https://user@hostname:project/blah.git",
GitUrl("https://user@hostname/project/blah.git", None),
),
(
"git+ssh://git@github.com:sdispater/poetry.git#v1.0.27",
GitUrl("git@github.com:sdispater/poetry.git", "v1.0.27"),
),
(
"git+ssh://git@github.com:/sdispater/poetry.git",
GitUrl("git@github.com:/sdispater/poetry.git", None),
),
("git+ssh://git@github.com:org/repo", GitUrl("git@github.com:org/repo", None),),
(
"git+ssh://git@github.com/org/repo",
GitUrl("ssh://git@github.com/org/repo", None),
),
("git+ssh://foo:22/some/path", GitUrl("ssh://foo:22/some/path", None)),
("git@github.com:org/repo", GitUrl("git@github.com:org/repo", None)),
(
"git+https://github.com/sdispater/pendulum",
GitUrl("https://github.com/sdispater/pendulum", None),
),
(
"git+https://github.com/sdispater/pendulum#7a018f2d075b03a73409e8356f9b29c9ad4ea2c5",
GitUrl(
"https://github.com/sdispater/pendulum",
"7a018f2d075b03a73409e8356f9b29c9ad4ea2c5",
),
),
(
"git+ssh://git@git.example.com:b/b.git#v1.0.0",
GitUrl("git@git.example.com:b/b.git", "v1.0.0"),
),
(
"git+ssh://git@github.com:sdispater/pendulum.git#foo/bar",
GitUrl("git@github.com:sdispater/pendulum.git", "foo/bar"),
),
("git+file:///foo/bar.git", GitUrl("file:///foo/bar.git", None)),
(
"git+file://C:\\Users\\hello\\testing.git#zkat/windows-files",
GitUrl("file://C:\\Users\\hello\\testing.git", "zkat/windows-files"),
),
(
"git+https://git.example.com/sdispater/project/my_repo.git",
GitUrl("https://git.example.com/sdispater/project/my_repo.git", None),
),
(
"git+ssh://git@git.example.com:sdispater/project/my_repo.git",
GitUrl("git@git.example.com:sdispater/project/my_repo.git", None),
),
],
)
def test_normalize_url(url, normalized):
assert normalized == Git.normalize_url(url)
...@@ -310,6 +310,24 @@ def test_marker_union_intersect_marker_union(): ...@@ -310,6 +310,24 @@ def test_marker_union_intersect_marker_union():
) )
def test_marker_union_intersect_marker_union_drops_unnecessary_markers():
m = parse_marker(
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
m2 = parse_marker(
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
intersection = m.intersect(m2)
expected = (
'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "4.0"'
)
assert expected == str(intersection)
def test_marker_union_intersect_multi_marker(): def test_marker_union_intersect_multi_marker():
m = parse_marker('sys_platform == "darwin" or python_version < "3.4"') m = parse_marker('sys_platform == "darwin" or python_version < "3.4"')
...@@ -354,6 +372,46 @@ def test_marker_union_union_duplicates(): ...@@ -354,6 +372,46 @@ def test_marker_union_union_duplicates():
) )
def test_marker_union_all_any():
union = MarkerUnion(parse_marker(""), parse_marker(""))
assert union.is_any()
def test_marker_union_not_all_any():
union = MarkerUnion(parse_marker(""), parse_marker(""), parse_marker("<empty>"))
assert union.is_any()
def test_marker_union_all_empty():
union = MarkerUnion(parse_marker("<empty>"), parse_marker("<empty>"))
assert union.is_empty()
def test_marker_union_not_all_empty():
union = MarkerUnion(
parse_marker("<empty>"), parse_marker("<empty>"), parse_marker("")
)
assert not union.is_empty()
def test_marker_str_conversion_skips_empty_and_any():
union = MarkerUnion(
parse_marker("<empty>"),
parse_marker(
'sys_platform == "darwin" or python_version <= "3.6" or os_name == "Windows"'
),
parse_marker(""),
)
assert str(union) == (
'sys_platform == "darwin" or python_version <= "3.6" or os_name == "Windows"'
)
def test_intersect_compacts_constraints(): def test_intersect_compacts_constraints():
m = parse_marker('python_version < "4.0"') m = parse_marker('python_version < "4.0"')
...@@ -439,3 +497,110 @@ def test_parse_version_like_markers(marker, env): ...@@ -439,3 +497,110 @@ def test_parse_version_like_markers(marker, env):
m = parse_marker(marker) m = parse_marker(marker)
assert m.validate(env) assert m.validate(env)
@pytest.mark.parametrize(
"marker, expected",
[
('python_version >= "3.6"', 'python_version >= "3.6"'),
('python_version >= "3.6" and extra == "foo"', 'python_version >= "3.6"'),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar")',
'python_version >= "3.6"',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar") or implementation_name == "pypy"',
'python_version >= "3.6" or implementation_name == "pypy"',
),
(
'python_version >= "3.6" and extra == "foo" or implementation_name == "pypy" and extra == "bar"',
'python_version >= "3.6" or implementation_name == "pypy"',
),
(
'python_version >= "3.6" or extra == "foo" and implementation_name == "pypy" or extra == "bar"',
'python_version >= "3.6" or implementation_name == "pypy"',
),
],
)
def test_without_extras(marker, expected):
m = parse_marker(marker)
assert expected == str(m.without_extras())
@pytest.mark.parametrize(
"marker, excluded, expected",
[
('python_version >= "3.6"', "implementation_name", 'python_version >= "3.6"'),
('python_version >= "3.6"', "python_version", "*"),
(
'python_version >= "3.6" and extra == "foo"',
"extra",
'python_version >= "3.6"',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar")',
"python_version",
'(extra == "foo" or extra == "bar")',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar") or implementation_name == "pypy"',
"python_version",
'(extra == "foo" or extra == "bar") or implementation_name == "pypy"',
),
(
'python_version >= "3.6" and extra == "foo" or implementation_name == "pypy" and extra == "bar"',
"implementation_name",
'python_version >= "3.6" and extra == "foo" or extra == "bar"',
),
(
'python_version >= "3.6" or extra == "foo" and implementation_name == "pypy" or extra == "bar"',
"implementation_name",
'python_version >= "3.6" or extra == "foo" or extra == "bar"',
),
],
)
def test_exclude(marker, excluded, expected):
m = parse_marker(marker)
if expected == "*":
assert m.exclude(excluded).is_any()
else:
assert expected == str(m.exclude(excluded))
@pytest.mark.parametrize(
"marker, only, expected",
[
('python_version >= "3.6"', "python_version", 'python_version >= "3.6"'),
(
'python_version >= "3.6" and extra == "foo"',
"python_version",
'python_version >= "3.6"',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar")',
"extra",
'(extra == "foo" or extra == "bar")',
),
(
'python_version >= "3.6" and (extra == "foo" or extra == "bar") or implementation_name == "pypy"',
"implementation_name",
'implementation_name == "pypy"',
),
(
'python_version >= "3.6" and extra == "foo" or implementation_name == "pypy" and extra == "bar"',
"implementation_name",
'implementation_name == "pypy"',
),
(
'python_version >= "3.6" or extra == "foo" and implementation_name == "pypy" or extra == "bar"',
"implementation_name",
'implementation_name == "pypy"',
),
],
)
def test_only(marker, only, expected):
m = parse_marker(marker)
assert expected == str(m.only(only))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment