Commit 38949f82 by Josh Guice

fix(solver): merge upstream changes

parents 5b4a3842 cdfcd146
freebsd_instance: freebsd_instance:
image_family: freebsd-12-1-snap image_family: freebsd-12-2
test_task: test_task:
name: "Tests / FreeBSD / " name: "Tests / FreeBSD / "
only_if: $CIRRUS_TAG == ''
skip: "!changesInclude('.cirrus.yml', 'poetry.lock', 'pyproject.toml', '**.json','**.py')" skip: "!changesInclude('.cirrus.yml', 'poetry.lock', 'pyproject.toml', '**.json','**.py')"
env: env:
matrix: matrix:
- PYTHON: python2.7 - PYTHON: python3.6
- PYTHON: python3.7 - PYTHON: python3.8
python_script: python_script:
- PYPACKAGE=$(printf '%s' $PYTHON | tr -d '.') - PYPACKAGE=$(printf '%s' $PYTHON | tr -d '.')
- SQLPACKAGE=$(printf '%s-sqlite3' $PYPACKAGE | sed 's/thon//') - SQLPACKAGE=$(printf '%s-sqlite3' $PYPACKAGE | sed 's/thon//')
...@@ -28,25 +29,44 @@ release_task: ...@@ -28,25 +29,44 @@ release_task:
name: "Release / FreeBSD" name: "Release / FreeBSD"
only_if: $CIRRUS_TAG != '' only_if: $CIRRUS_TAG != ''
env: env:
GITHUB_TOKEN: ENCRYPTED[e637a1887c028ea4e44867715a8a4b7c5e27568559a15c45a3e33739b0d97fc050394c728a44e9bfe7c246179810cad7] GITHUB_TOKEN: ENCRYPTED[2b573a2d28a03523ac6fb5b3c2f513a41c0a98db81e40e50e1d103b171f85c57e58ae38d957499dbf7fd7635cfcfd7be]
PYTHON: python3.8
PYTHON36: python3.6
PYTHON37: python3.7
PYTHON38: python3.8
freebsd_instance: freebsd_instance:
matrix: matrix:
- image_family: freebsd-12-1-snap - image_family: freebsd-12-1-snap
- image_family: freebsd-11-3-snap - image_family: freebsd-13-0-snap
python_script: pkg install -y curl python3 python27 python35 python36 python37 python38 - image_family: freebsd-11-4-snap
python_script: pkg install -y curl bash jq python3 python36 python37 python38
pip_script: pip_script:
- python2.7 -m ensurepip
- python3.5 -m ensurepip
- python3.6 -m ensurepip - python3.6 -m ensurepip
- python3.7 -m ensurepip - python3.7 -m ensurepip
- python3.8 -m ensurepip - python3.8 -m ensurepip
build_script: ./make-nix-release.sh build_script: bash ./make-nix-release.sh
upload_script: | upload_script: |
#!/usr/bin/env bash
if [[ "$CIRRUS_RELEASE" == "" ]]; then
CIRRUS_RELEASE=$(curl -sL https://api.github.com/repos/$CIRRUS_REPO_FULL_NAME/releases/tags/$CIRRUS_TAG | jq -r '.id')
if [[ "$CIRRUS_RELEASE" == "null" ]]; then
echo "Failed to find a release associated with this tag!"
exit 0
fi
fi
if [[ "$GITHUB_TOKEN" == "" ]]; then
echo "Please provide GitHub access token via GITHUB_TOKEN environment variable!"
exit 1
fi
for fpath in releases/* for fpath in releases/*
do do
echo "Uploading $fpath..." echo "Uploading $fpath..."
name=$(basename "$fpath") name=$(basename "$fpath")
url_to_upload="https://uploads.github.com/repos/$CIRRUS_REPO_FULL_NAME/releases/$CIRRUS_TAG/assets?name=$name" url_to_upload="https://uploads.github.com/repos/$CIRRUS_REPO_FULL_NAME/releases/$CIRRUS_RELEASE/assets?name=$name"
echo "Uploading to $url_to_upload"
curl -X POST \ curl -X POST \
--data-binary @$fpath \ --data-binary @$fpath \
--header "Authorization: token $GITHUB_TOKEN" \ --header "Authorization: token $GITHUB_TOKEN" \
......
[flake8] [flake8]
max-line-length = 88 max-line-length = 88
ignore = E501, E203, W503 ignore = E501, E203, W503
per-file-ignores = __init__.py:F401 per-file-ignores =
__init__.py:F401
tests/console/commands/debug/test_resolve.py:W291
exclude = exclude =
.git .git
__pycache__ __pycache__
......
...@@ -16,6 +16,6 @@ jobs: ...@@ -16,6 +16,6 @@ jobs:
name: Linting name: Linting
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- uses: actions/setup-python@v1 - uses: actions/setup-python@v2
- uses: pre-commit/action@v2.0.0 - uses: pre-commit/action@v2.0.0
...@@ -22,12 +22,13 @@ jobs: ...@@ -22,12 +22,13 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [Ubuntu, MacOS, Windows] os: [Ubuntu, MacOS, Windows]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8] python-version: [3.6, 3.7, 3.8, 3.9]
fail-fast: false
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
...@@ -36,15 +37,16 @@ jobs: ...@@ -36,15 +37,16 @@ jobs:
shell: bash shell: bash
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
- name: Install poetry - name: Bootstrap poetry
shell: bash shell: bash
run: | run: |
python get-poetry.py -y --preview python -m ensurepip
echo "::set-env name=PATH::$HOME/.poetry/bin:$PATH" python -m pip install --upgrade pip
python -m pip install .
- name: Configure poetry - name: Configure poetry
shell: bash shell: bash
run: poetry config virtualenvs.in-project true run: python -m poetry config virtualenvs.in-project true
- name: Set up cache - name: Set up cache
uses: actions/cache@v2 uses: actions/cache@v2
...@@ -56,16 +58,12 @@ jobs: ...@@ -56,16 +58,12 @@ jobs:
- name: Ensure cache is healthy - name: Ensure cache is healthy
if: steps.cache.outputs.cache-hit == 'true' if: steps.cache.outputs.cache-hit == 'true'
shell: bash shell: bash
run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv run: timeout 10s python -m poetry run pip --version || rm -rf .venv
- name: Upgrade pip
shell: bash
run: poetry run python -m pip install pip -U
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
run: poetry install run: python -m poetry install
- name: Run pytest - name: Run pytest
shell: bash shell: bash
run: poetry run python -m pytest -v tests run: python -m poetry run python -m pytest -p no:sugar -q tests/
...@@ -53,14 +53,10 @@ jobs: ...@@ -53,14 +53,10 @@ jobs:
poetry install --no-dev poetry install --no-dev
- name: Preparing Python executables - name: Preparing Python executables
run: | 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.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.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.6/python-3.7.6.macos.tar.xz -o python-3.7.6.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.7.6/python-3.7.6.macos.tar.xz -o python-3.7.6.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.macos.tar.xz -o python-3.8.3.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.macos.tar.xz -o python-3.8.3.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.macos.tar.xz -o python-3.9.0b4.tar.xz curl -L https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.macos.tar.xz -o python-3.9.0b4.tar.xz
tar -zxf python-2.7.17.tar.xz
tar -zxf python-3.5.9.tar.xz
tar -zxf python-3.6.8.tar.xz tar -zxf python-3.6.8.tar.xz
tar -zxf python-3.7.6.tar.xz tar -zxf python-3.7.6.tar.xz
tar -zxf python-3.8.3.tar.xz tar -zxf python-3.8.3.tar.xz
...@@ -68,7 +64,7 @@ jobs: ...@@ -68,7 +64,7 @@ jobs:
- name: Build specific release - name: Build specific release
run: | run: |
source $HOME/.poetry/env source $HOME/.poetry/env
poetry run python sonnet make release --ansi -P "2.7:python-2.7.17/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.6/bin/python" -P "3.8:python-3.8.3/bin/python" -P "3.9:python-3.9.0b4/bin/python" poetry run python sonnet make release --ansi -P "3.6:python-3.6.8/bin/python" -P "3.7:python-3.7.6/bin/python" -P "3.8:python-3.8.3/bin/python" -P "3.9:python-3.9.0b4/bin/python"
- name: Upload release file - name: Upload release file
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
with: with:
...@@ -104,21 +100,14 @@ jobs: ...@@ -104,21 +100,14 @@ jobs:
poetry install --no-dev poetry install --no-dev
- name: Preparing Python executables - name: Preparing Python executables
run: | 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.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.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.6/python-3.7.6.windows.tar.xz -O python-3.7.6.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.7.6/python-3.7.6.windows.tar.xz -O python-3.7.6.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.windows.tar.xz -O python-3.8.3.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.8.3/python-3.8.3.windows.tar.xz -O python-3.8.3.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.windows.tar.xz -O python-3.9.0b4.tar.xz Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.9.0b4/python-3.9.0b4.windows.tar.xz -O python-3.9.0b4.tar.xz
7z x python-2.7.17.tar.xz
7z x python-3.5.4.tar.xz
7z x python-3.6.8.tar.xz 7z x python-3.6.8.tar.xz
7z x python-3.7.6.tar.xz 7z x python-3.7.6.tar.xz
7z x python-3.8.3.tar.xz 7z x python-3.8.3.tar.xz
7z x python-3.9.0b4.tar.xz 7z x python-3.9.0b4.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.6.8.tar
7z x python-3.7.6.tar 7z x python-3.7.6.tar
7z x python-3.8.3.tar 7z x python-3.8.3.tar
...@@ -126,7 +115,7 @@ jobs: ...@@ -126,7 +115,7 @@ jobs:
- name: Build specific release - name: Build specific release
run: | run: |
$env:Path += ";$env:Userprofile\.poetry\bin" $env:Path += ";$env:Userprofile\.poetry\bin"
poetry run python sonnet make release --ansi -P "2.7:python-2.7.17\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.6\python.exe" -P "3.8:python-3.8.3\python.exe" -P "3.9:python-3.9.0b4\python.exe" poetry run python sonnet make release --ansi -P "3.6:python-3.6.8\python.exe" -P "3.7:python-3.7.6\python.exe" -P "3.8:python-3.8.3\python.exe" -P "3.9:python-3.9.0b4\python.exe"
- name: Upload release file - name: Upload release file
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
with: with:
......
...@@ -32,6 +32,6 @@ jobs: ...@@ -32,6 +32,6 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [Ubuntu, MacOS, Windows] os: [Ubuntu, MacOS, Windows]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8] python-version: [3.6, 3.7, 3.8, 3.9]
steps: steps:
- run: exit 0 - run: exit 0
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 19.10b0 rev: 20.8b1
hooks: hooks:
- id: black - id: black
- repo: https://gitlab.com/pycqa/flake8 - repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3 rev: 3.8.4
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/timothycrosley/isort - repo: https://github.com/timothycrosley/isort
rev: 5.4.2 rev: 5.7.0
hooks: hooks:
- id: isort - id: isort
additional_dependencies: [toml] additional_dependencies: [toml]
exclude: ^.*/?setup\.py$ exclude: ^.*/?setup\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0 rev: v3.4.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
exclude: ^tests/.*/fixtures/.* exclude: |
(?x)(
^tests/.*/fixtures/.*
| ^tests/console/commands/debug/test_resolve.py
)
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: ^tests/.*/fixtures/.* exclude: ^tests/.*/fixtures/.*
- id: debug-statements - id: debug-statements
# Change Log # Change Log
## [1.1.4] - 2020-10-23
### Added
- Added `installer.parallel` boolean flag (defaults to `true`) configuration to enable/disable parallel execution of operations when using the new installer. ([#3088](https://github.com/python-poetry/poetry/pull/3088))
### Changed
- When using system environments as an unprivileged user, user site and bin directories are created if they do not already exist. ([#3107](https://github.com/python-poetry/poetry/pull/3107))
### Fixed
- Fixed editable installation of poetry projects when using system environments. ([#3107](https://github.com/python-poetry/poetry/pull/3107))
- Fixed locking of nested extra activations. If you were affected by this issue, you will need to regenerate the lock file using `poetry lock --no-update`. ([#3229](https://github.com/python-poetry/poetry/pull/3229))
- Fixed prioritisation of non-default custom package sources. ([#3251](https://github.com/python-poetry/poetry/pull/3251))
- Fixed detection of installed editable packages when non-poetry managed `.pth` file exists. ([#3210](https://github.com/python-poetry/poetry/pull/3210))
- Fixed scripts generated by editable builder to use valid import statements. ([#3214](https://github.com/python-poetry/poetry/pull/3214))
- Fixed recursion error when locked dependencies contain cyclic dependencies. ([#3237](https://github.com/python-poetry/poetry/pull/3237))
- Fixed propagation of editable flag for VCS dependencies. ([#3264](https://github.com/python-poetry/poetry/pull/3264))
## [1.1.3] - 2020-10-14
### Changed
- Python version support deprecation warning is now written to `stderr`. ([#3131](https://github.com/python-poetry/poetry/pull/3131))
### Fixed
- Fixed `KeyError` when `PATH` is not defined in environment variables. ([#3159](https://github.com/python-poetry/poetry/pull/3159))
- Fixed error when using `config` command in a directory with an existing `pyproject.toml` without any Poetry configuration. ([#3172](https://github.com/python-poetry/poetry/pull/3172))
- Fixed incorrect inspection of package requirements when same dependency is specified multiple times with unique markers. ([#3147](https://github.com/python-poetry/poetry/pull/3147))
- Fixed `show` command to use already resolved package metadata. ([#3117](https://github.com/python-poetry/poetry/pull/3117))
- Fixed multiple issues with `export` command output when using `requirements.txt` format. ([#3119](https://github.com/python-poetry/poetry/pull/3119))
## [1.1.2] - 2020-10-06
### Changed
- Dependency installation of editable packages and all uninstall operations are now performed serially within their corresponding priority groups. ([#3099](https://github.com/python-poetry/poetry/pull/3099))
- Improved package metadata inspection of nested poetry projects within project path dependencies. ([#3105](https://github.com/python-poetry/poetry/pull/3105))
### Fixed
- Fixed export of `requirements.txt` when project dependency contains git dependencies. ([#3100](https://github.com/python-poetry/poetry/pull/3100))
## [1.1.1] - 2020-10-05
### Added
- Added `--no-update` option to `lock` command. ([#3034](https://github.com/python-poetry/poetry/pull/3034))
### Fixed
- Fixed resolution of packages with missing required extras. ([#3035](https://github.com/python-poetry/poetry/pull/3035))
- Fixed export of `requirements.txt` dependencies to include development dependencies. ([#3024](https://github.com/python-poetry/poetry/pull/3024))
- Fixed incorrect selection of unsupported binary distribution formats when selecting a package artifact to install. ([#3058](https://github.com/python-poetry/poetry/pull/3058))
- Fixed incorrect use of system executable when building package distributions via `build` command. ([#3056](https://github.com/python-poetry/poetry/pull/3056))
- Fixed errors in `init` command when specifying `--dependency` in non-interactive mode when a `pyproject.toml` file already exists. ([#3076](https://github.com/python-poetry/poetry/pull/3076))
- Fixed incorrect selection of configured source url when a publish repository url configuration with the same name already exists. ([#3047](https://github.com/python-poetry/poetry/pull/3047))
- Fixed dependency resolution issues when the same package is specified in multiple dependency extras. ([#3046](https://github.com/python-poetry/poetry/pull/3046))
## [1.1.0] - 2020-10-01
### Changed
- The `init` command will now use existing `pyproject.toml` if possible ([#2448](https://github.com/python-poetry/poetry/pull/2448)).
- Error messages when metadata information retrieval fails have been improved ([#2997](https://github.com/python-poetry/poetry/pull/2997)).
### Fixed
- Fixed parsing of version constraint for `rc` prereleases ([#2978](https://github.com/python-poetry/poetry/pull/2978)).
- Fixed how some metadata information are extracted from `setup.cfg` files ([#2957](https://github.com/python-poetry/poetry/pull/2957)).
- Fixed return codes returned by the executor ([#2981](https://github.com/python-poetry/poetry/pull/2981)).
- Fixed whitespaces not being accepted for the list of extras when adding packages ([#2985](https://github.com/python-poetry/poetry/pull/2985)).
- Fixed repositories specified in the `pyproject.toml` file not being taken into account for authentication when downloading packages ([#2990](https://github.com/python-poetry/poetry/pull/2990)).
- Fixed permission errors when installing the root project if the `site-packages` directory is not writeable ([#3002](https://github.com/python-poetry/poetry/pull/3002)).
- Fixed environment marker propagation when exporting to the `requirements.txt` format ([#3002](https://github.com/python-poetry/poetry/pull/3002)).
- Fixed errors when paths in run command contained spaces ([#3015](https://github.com/python-poetry/poetry/pull/3015)).
## [1.1.0rc1] - 2020-09-25
### Changed
- The `virtualenvs.in-project` setting will now always be honored, if set explicitly, regardless of the presence of a `.venv` directory ([#2771](https://github.com/python-poetry/poetry/pull/2771)).
- Adding packages already present in the `pyproject.toml` file will no longer raise an error ([#2886](https://github.com/python-poetry/poetry/pull/2886)).
- Errors when authenticating against custom repositories will now be logged ([#2577](https://github.com/python-poetry/poetry/pull/2577)).
### Fixed
- Fixed an error on Python 3.5 when resolving URL dependencies ([#2954](https://github.com/python-poetry/poetry/pull/2954)).
- Fixed the `dependency` option of the `init` command being ignored ([#2587](https://github.com/python-poetry/poetry/pull/2587)).
- Fixed the `show` command displaying erroneous information following the changes in the lock file format ([#2967](https://github.com/python-poetry/poetry/pull/2967)).
- Fixed dependency resolution errors due to invalid python constraints propagation ([#2968](https://github.com/python-poetry/poetry/pull/2968)).
## [1.1.0b4] - 2020-09-23
### Changed
- When running under Python 2.7 on Windows, install command will be limited to one worker to mitigate threading issue ([#2941](https://github.com/python-poetry/poetry/pull/2941)).
## [1.1.0b3] - 2020-09-18
### Changed
- Improved the error reporting when HTTP error are encountered for legacy repositories ([#2459](https://github.com/python-poetry/poetry/pull/2459)).
- When displaying the name of packages retrieved from remote repositories, the original name will now be used ([#2305](https://github.com/python-poetry/poetry/pull/2305)).
- Failed package downloads will now be retried on connection errors ([#2813](https://github.com/python-poetry/poetry/pull/2813)).
- Path dependencies will now be installed as editable only when `develop` option is set to `true` ([#2887](https://github.com/python-poetry/poetry/pull/2887)).
### Fixed
- Fixed the detection of the type of installed packages ([#2722](https://github.com/python-poetry/poetry/pull/2722)).
- Fixed deadlocks when installing packages on systems not supporting non-ascii characters ([#2721](https://github.com/python-poetry/poetry/pull/2721)).
- Fixed handling of wildcard constraints for packages with prereleases only ([#2821](https://github.com/python-poetry/poetry/pull/2821)).
- Fixed dependencies of some packages not being discovered by ensuring we use the PEP-516 backend if specified ([#2810](https://github.com/python-poetry/poetry/pull/2810)).
- Fixed recursion errors when retrieving extras ([#2787](https://github.com/python-poetry/poetry/pull/2787)).
- Fixed `PyPI` always being displayed when publishing even for custom repositories ([#2905](https://github.com/python-poetry/poetry/pull/2905)).
- Fixed handling of packages extras when resolving dependencies ([#2887](https://github.com/python-poetry/poetry/pull/2887)).
## [1.1.0b2] - 2020-07-24 ## [1.1.0b2] - 2020-07-24
### Changed ### Changed
...@@ -961,7 +1083,15 @@ Initial release ...@@ -961,7 +1083,15 @@ Initial release
[Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.0b2...master [Unreleased]: https://github.com/python-poetry/poetry/compare/1.1.4...master
[1.1.4]: https://github.com/python-poetry/poetry/compare/1.1.4
[1.1.3]: https://github.com/python-poetry/poetry/compare/1.1.3
[1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2
[1.1.1]: https://github.com/python-poetry/poetry/releases/tag/1.1.1
[1.1.0]: https://github.com/python-poetry/poetry/releases/tag/1.1.0
[1.1.0rc1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0rc1
[1.1.0b4]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b4
[1.1.0b3]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b3
[1.1.0b2]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b2 [1.1.0b2]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b2
[1.1.0b1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b1 [1.1.0b1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b1
[1.1.0a3]: https://github.com/python-poetry/poetry/releases/tag/1.1.0a3 [1.1.0a3]: https://github.com/python-poetry/poetry/releases/tag/1.1.0a3
......
...@@ -10,7 +10,10 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These ...@@ -10,7 +10,10 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These
* [Reporting bugs](#reporting-bugs) * [Reporting bugs](#reporting-bugs)
* [Suggesting enhancements](#suggesting-enhancements) * [Suggesting enhancements](#suggesting-enhancements)
* [Contributing to documentation](#contributing-to-documentation)
* [Contributing to code](#contributing-to-code) * [Contributing to code](#contributing-to-code)
* [Issue triage](#issue-triage)
* [Git workflow](#git-workflow)
## How to contribute ## How to contribute
...@@ -76,9 +79,27 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith ...@@ -76,9 +79,27 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith
* **Provide specific examples to demonstrate the steps**.. * **Provide specific examples to demonstrate the steps**..
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. * **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
### Contributing to documentation
One of the simplest ways to get started contributing to a project is through improving documentation. Poetry is constantly evolving, this means that sometimes our documentation has gaps. You can help by
adding missing sections, editing the existing content so it is more accessible or creating new content (tutorials, FAQs, etc).
> **Note:** A great way to understand Poetry's design and how it all fits together, is to add FAQ entries for commonly
> asked questions. Poetry members usually mark issues with [candidate/faq](https://github.com/python-poetry/poetry/issues?q=is%3Aissue+label%3Acandidate%2Ffaq+) to indicate that the issue either contains a response
> that explains how something works or might benefit from an entry in the FAQ.
Issues pertaining to the documentation are usually marked with the [Documentation](https://github.com/python-poetry/poetry/labels/Documentation) label.
### Contributing to code ### Contributing to code
#### Picking an issue
> **Note:** If you are a first time contributor, and are looking for an issue to take on, you might want to look for [Good First Issue](https://github.com/python-poetry/poetry/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22)
> labelled issues. We do our best to label such issues, however we might fall behind at times. So, ask us.
If you would like to take on an issue, feel free to comment on the issue tagging `@python-poetry/triage`. We are more than happy to discuss solutions on the issue. If you would like help with navigating
the code base, join us on our [Discord Server](https://discordapp.com/invite/awxPgve).
#### Local development #### Local development
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 need Poetry to start contributing on the Poetry codebase. Refer to the [documentation](https://python-poetry.org/docs/#introduction) to start using Poetry.
...@@ -90,6 +111,9 @@ $ git clone git@github.com:python-poetry/poetry.git ...@@ -90,6 +111,9 @@ $ git clone git@github.com:python-poetry/poetry.git
$ cd poetry $ cd poetry
``` ```
> **Note:** We recommend that you use a personal [fork](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) for this step. If you are new to GitHub collaboration,
> you can refer to the [Forking Projects Guide](https://guides.github.com/activities/forking/).
Now, you will need to install the required dependency for Poetry and be sure that the current Now, you will need to install the required dependency for Poetry and be sure that the current
tests are passing on your machine: tests are passing on your machine:
...@@ -125,3 +149,90 @@ will not be merged. ...@@ -125,3 +149,90 @@ will not be merged.
* Fill in [the required template](https://github.com/python-poetry/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.
> **Note:** Make sure your branch is [rebased](https://docs.github.com/en/free-pro-team@latest/github/using-git/about-git-rebase) against the latest main branch. A maintainer might ask you to ensure the branch is
> up-to-date prior to merging your Pull Request if changes have conflicts.
All pull requests, unless otherwise instructed, need to be first accepted into the main branch (`master`).
### Issue triage
> **Note:** If you have an issue that hasn't had any attention, you can ping us `@python-poetry/triage` on the issue. Please, give us reasonable time to get to your issue first, spamming us with messages
> does not help anyone.
If you are helping with the triage of reported issues, this section provides some useful information to assist you in your contribution.
#### Triage steps
1. If `pyproject.toml` is missing or `-vvv` debug logs (with stack trace) is not provided and required, request that the issue author provides it.
1. Attempt to reproduce the issue with the reported Poetry version or request further clarification from the issue author.
1. Ensure the issue is not already resolved. You can attempt to reproduce using the latest preview release and/or poetry from the main branch.
1. If the issue cannot be reproduced,
1. clarify with the issue's author,
1. close the issue or notify `@python-poetry/triage`.
1. If the issue can be reproduced,
1. comment on the issue confirming so
1. notify `@python-poetry/triage`.
1. if possible, identify the root cause of the issue.
1. if interested, attempt to fix it via a pull request.
#### Multiple versions
Often times you would want to attempt to reproduce issues with multiple versions of `poetry` at the same time. For these use cases, the [pipx project](https://pipxproject.github.io/pipx/) is useful.
You can set your environment up like so.
```sh
pipx install --suffix @1.0.10 'poetry==1.0.10'
pipx install --suffix @1.1.0rc1 'poetry==1.1.0rc1'
pipx install --suffix @master 'poetry @ git+https://github.com/python-poetry/poetry'
```
> **Hint:** Do not forget to update your `poetry@master` installation in sync with upstream.
For `@local` it is recommended that you do something similar to the following as editable installs are not supported for PEP 517 projects.
```sh
# note this will not work for Windows, and we assume you have already run `poetry install`
cd /path/to/python-poetry/poetry
ln -sf $(poetry run which poetry) ~/.local/bin/poetry@local
```
> **Hint:** This mechanism can also be used to test pull requests.
### Git Workflow
All development work is performed against Poetry's main branch (`master`). All changes are expected to be submitted and accepted to this
branch.
#### Release branch
When a release is ready, the following are required before a release is tagged.
1. A release branch with the prefix `release-`, eg: `release-1.1.0rc1`.
1. A pull request from the release branch to the main branch (`master`) if it's a minor or major release. Otherwise, to the bug fix branch (eg: `1.0`).
1. The pull request description MUST include the change log corresponding to the release (eg: [#2971](https://github.com/python-poetry/poetry/pull/2971)).
1. The pull request must contain a commit that updates [CHANGELOG.md](CHANGELOG.md) and bumps the project version (eg: [#2971](https://github.com/python-poetry/poetry/pull/2971/commits/824e7b79defca435cf1d765bb633030b71b9a780)).
1. The pull request must have the `Release` label specified.
Once the branch pull-request is ready and approved, a member of `@python-poetry/core` will,
1. Tag the branch with the version identifier (eg: `1.1.0rc1`).
2. Merge the pull request once the release is created and assets are uploaded by the CI.
> **Note:** In this case, we prefer a merge commit instead of squash or rebase merge.
#### Bug fix branch
Once a minor version (eg: `1.1.0`) is released, a new branch for the minor version (eg: `1.1`) is created for the bug fix releases. Changes identified
or acknowledged by the Poetry team as requiring a bug fix can be submitted as a pull requests against this branch.
At the time of writing only issues meeting the following criteria may be accepted into a bug fix branch. Trivial fixes may be accepted on a
case-by-case basis.
1. The issue breaks a core functionality and/or is a critical regression.
1. The change set does not introduce a new feature or changes an existing functionality.
1. A new minor release is not expected within a reasonable time frame.
1. If the issue affects the next minor/major release, a corresponding fix has been accepted into the main branch.
> **Note:** This is subject to the interpretation of a maintainer within the context of the issue.
...@@ -105,8 +105,8 @@ poetry completions zsh > ~/.zfunc/_poetry ...@@ -105,8 +105,8 @@ poetry completions zsh > ~/.zfunc/_poetry
poetry completions zsh > $(brew --prefix)/share/zsh/site-functions/_poetry poetry completions zsh > $(brew --prefix)/share/zsh/site-functions/_poetry
# Zsh (Oh-My-Zsh) # Zsh (Oh-My-Zsh)
mkdir $ZSH/plugins/poetry mkdir $ZSH_CUSTOM/plugins/poetry
poetry completions zsh > $ZSH/plugins/poetry/_poetry poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry
# Zsh (prezto) # Zsh (prezto)
poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry
...@@ -195,8 +195,8 @@ dependency management, packaging and publishing. ...@@ -195,8 +195,8 @@ dependency management, packaging and publishing.
It takes inspiration in tools that exist in other languages, like `composer` (PHP) or `cargo` (Rust). It takes inspiration in tools that exist in other languages, like `composer` (PHP) or `cargo` (Rust).
And, finally, there is no reliable tool to properly resolve dependencies in Python, so I started `poetry` And, finally, I started `poetry` to bring another exhaustive dependency resolver to the Python community apart from
to bring an exhaustive dependency resolver to the Python community. [Conda's](https://conda.io).
### What about Pipenv? ### What about Pipenv?
......
...@@ -41,6 +41,16 @@ python = "*" ...@@ -41,6 +41,16 @@ python = "*"
pytest = "^3.4" pytest = "^3.4"
``` ```
### Initialising a pre-existing project
Instead of creating a new project, Poetry can be used to 'initialise' a pre-populated
directory. To interactively create a `pyproject.toml` file in directory `pre-existing-project`:
```bash
cd pre-existing-project
poetry init
```
### Specifying dependencies ### Specifying dependencies
If you want to add dependencies to your project, you can specify them in the `tool.poetry.dependencies` section. If you want to add dependencies to your project, you can specify them in the `tool.poetry.dependencies` section.
...@@ -96,21 +106,21 @@ To deactivate the virtual environment without leaving the shell use `deactivate` ...@@ -96,21 +106,21 @@ To deactivate the virtual environment without leaving the shell use `deactivate`
that an activated virtual environment remains active after the Poetry command that an activated virtual environment remains active after the Poetry command
has completed execution. has completed execution.
Therefore, Poetry has to create a sub-shell with the virtual envrionment activated Therefore, Poetry has to create a sub-shell with the virtual environment activated
in order for the subsequent commands to run from within the virtual environment. in order for the subsequent commands to run from within the virtual environment.
Alternatively, to avoid creating a new shell, you can manually activate the Alternatively, to avoid creating a new shell, you can manually activate the
virtual environment by running `source {path_to_venv}/bin/activate` (`source {path_to_venv}\Scripts\activate.bat` on Windows). virtual environment by running `source {path_to_venv}/bin/activate` (`{path_to_venv}\Scripts\activate.bat` on Windows).
To get the path to your virtual environment run `poetry env info --path`. To get the path to your virtual environment run `poetry env info --path`.
You can also combine these into a nice one-liner, `source $(poetry env info --path)/bin/activate` You can also combine these into a nice one-liner, `source $(poetry env info --path)/bin/activate`
To deactivate this virtual environment simply use `deactivate`. To deactivate this virtual environment simply use `deactivate`.
| | POSIX Shell | Windows | Exit/Deactivate | | | POSIX Shell | Windows | Exit/Deactivate |
|-------------------|------------------------------------------------|---------------------------------------------|-----------------| |-------------------|---------------------------------------------------|---------------------------------------------|-----------------|
| New Shell | `poetry shell` | `poetry shell` | `exit` | | New Shell | `poetry shell` | `poetry shell` | `exit` |
| Manual Activation | `source {path_to_venv}/bin/activate` | `source {path_to_venv}\Scripts\activate.bat`| `deactivate` | | Manual Activation | `source {path_to_venv}/bin/activate` | `{path_to_venv}\Scripts\activate.bat` | `deactivate` |
| One-liner | ```source`poetry env info --path`/bin/activate```| | `deactivate` | | One-liner | ```source `poetry env info --path`/bin/activate```| | `deactivate` |
### Version constraints ### Version constraints
......
...@@ -14,6 +14,7 @@ then `--help` combined with any of those can give you more information. ...@@ -14,6 +14,7 @@ then `--help` combined with any of those can give you more information.
* `--ansi`: Force ANSI output. * `--ansi`: Force ANSI output.
* `--no-ansi`: Disable ANSI output. * `--no-ansi`: Disable ANSI output.
* `--version (-V)`: Display this application version. * `--version (-V)`: Display this application version.
* `--no-interaction (-n)`: Do not ask any interactive question.
## new ## new
...@@ -108,6 +109,13 @@ the `--no-dev` option. ...@@ -108,6 +109,13 @@ the `--no-dev` option.
poetry install --no-dev poetry install --no-dev
``` ```
Conversely, you can specify to the command that you only want to install the development dependencies
by passing the `--dev-only` option. Note that `--no-dev` takes priority if both options are passed.
```bash
poetry install --dev-only
```
If you want to remove old dependencies no longer present in the lock file, use the If you want to remove old dependencies no longer present in the lock file, use the
`--remove-untracked` option. `--remove-untracked` option.
...@@ -116,7 +124,7 @@ poetry install --remove-untracked ...@@ -116,7 +124,7 @@ poetry install --remove-untracked
``` ```
You can also specify the extras you want installed You can also specify the extras you want installed
by passing the `--E|--extras` option (See [Extras](/docs/pyproject/#extras) for more info) by passing the `-E|--extras` option (See [Extras](/docs/pyproject/#extras) for more info)
```bash ```bash
poetry install --extras "mysql pgsql" poetry install --extras "mysql pgsql"
...@@ -141,10 +149,16 @@ If you want to skip this installation, use the `--no-root` option. ...@@ -141,10 +149,16 @@ If you want to skip this installation, use the `--no-root` option.
poetry install --no-root poetry install --no-root
``` ```
Installation of your project's package is also skipped when the `--dev-only`
option is passed.
### Options ### Options
* `--no-dev`: Do not install dev dependencies. * `--no-dev`: Do not install dev dependencies.
* `--dev-only`: Only install dev dependencies.
* `--no-root`: Do not install the root package (your project). * `--no-root`: Do not install the root package (your project).
* `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose).
* `--remove-untracked`: Remove dependencies not presented in the lock file
* `--extras (-E)`: Features to install (multiple values allowed). * `--extras (-E)`: Features to install (multiple values allowed).
## update ## update
...@@ -212,6 +226,10 @@ or use ssh instead of https: ...@@ -212,6 +226,10 @@ or use ssh instead of https:
```bash ```bash
poetry add git+ssh://git@github.com/sdispater/pendulum.git poetry add git+ssh://git@github.com/sdispater/pendulum.git
or alternatively:
poetry add git+ssh://git@github.com:sdispater/pendulum.git
``` ```
If you need to checkout a specific branch, tag or revision, If you need to checkout a specific branch, tag or revision,
...@@ -220,6 +238,11 @@ you can specify it when using `add`: ...@@ -220,6 +238,11 @@ 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 using SSH instead:
poetry add git+ssh://github.com/sdispater/pendulum.git#develop
poetry add git+ssh://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:
...@@ -230,16 +253,18 @@ poetry add ../my-package/dist/my-package-0.1.0.tar.gz ...@@ -230,16 +253,18 @@ poetry add ../my-package/dist/my-package-0.1.0.tar.gz
poetry add ../my-package/dist/my_package-0.1.0.whl poetry add ../my-package/dist/my_package-0.1.0.whl
``` ```
Path dependencies pointing to a local directory will be installed in editable mode (i.e. setuptools "develop mode"). If you want the dependency to be installed in editable mode you can specify it in the `pyproject.toml` file. It means that changes in the local directory will be reflected directly in environment.
It means that changes in the local directory will be reflected directly in environment.
If you don't want the dependency to be installed in editable mode you can specify it in the `pyproject.toml` file:
```toml ```toml
[tool.poetry.dependencies] [tool.poetry.dependencies]
my-package = {path = "../my/path", develop = false} my-package = {path = "../my/path", develop = true}
``` ```
!!!note
Before poetry 1.1 path dependencies were installed in editable mode by default. You should always set the `develop` attribute explicit,
to make sure the behavior is the same for all poetry versions.
If the package(s) you want to install provide extras, you can specify them If the package(s) you want to install provide extras, you can specify them
when adding the package: when adding the package:
...@@ -252,10 +277,14 @@ poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]" ...@@ -252,10 +277,14 @@ poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]"
### Options ### Options
* `--dev (-D)`: Add package as development dependency. * `--dev (-D)`: Add package as development dependency.
* `--path`: The path to a dependency. * `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed)
* `--optional` : Add as an optional dependency. * `--optional`: Add as an optional dependency.
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose). * `--python`: Python version for which the dependency must be installed.
* `--lock` : Do not perform install (only update the lockfile). * `--platform`: Platforms for which the dependency must be installed.
* `--source`: Name of the source to use to install the package.
* `---allow-prereleases`: Accept prereleases.
* `--dry-run`: Outputs the operations but will not execute anything (implicitly enables --verbose).
* `--lock`: Do not perform install (only update the lockfile).
## remove ## remove
...@@ -421,6 +450,10 @@ This command locks (without installing) the dependencies specified in `pyproject ...@@ -421,6 +450,10 @@ This command locks (without installing) the dependencies specified in `pyproject
poetry lock poetry lock
``` ```
### Options
* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`
## version ## version
This command shows the current version of the project or bumps the version of This command shows the current version of the project or bumps the version of
...@@ -444,7 +477,7 @@ The table below illustrates the effect of these rules with concrete examples. ...@@ -444,7 +477,7 @@ The table below illustrates the effect of these rules with concrete examples.
| prerelease | 1.0.3-alpha.0 | 1.0.3-alpha.1 | | prerelease | 1.0.3-alpha.0 | 1.0.3-alpha.1 |
| prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 | | prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 |
## Options ### Options
* `--short (-s)`: Output the version number only. * `--short (-s)`: Output the version number only.
...@@ -453,7 +486,7 @@ The table below illustrates the effect of these rules with concrete examples. ...@@ -453,7 +486,7 @@ The table below illustrates the effect of these rules with concrete examples.
This command exports the lock file to other formats. This command exports the lock file to other formats.
```bash ```bash
poetry export -f requirements.txt > requirements.txt poetry export -f requirements.txt --output requirements.txt
``` ```
!!!note !!!note
...@@ -489,3 +522,19 @@ The `cache list` command lists Poetry's available caches. ...@@ -489,3 +522,19 @@ The `cache list` command lists Poetry's available caches.
```bash ```bash
poetry cache list poetry cache list
``` ```
### cache clear
The `cache clear` command removes packages from a cached repository.
For example, to clear the whole cache of packages from the `pypi` repository, run:
```bash
poetry cache clear pypi --all
```
To only remove a specific package from a cache, you have to specify the cache entry in the following form `cache:package:version`:
```bash
poetry cache clear pypi:requests:2.24.0
```
...@@ -33,7 +33,9 @@ which will give you something similar to this: ...@@ -33,7 +33,9 @@ which will give you something similar to this:
```toml ```toml
cache-dir = "/path/to/cache/directory" cache-dir = "/path/to/cache/directory"
virtualenvs.create = true virtualenvs.create = true
virtualenvs.in-project = false virtualenvs.in-project = null
virtualenvs.options.always-copy = true
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs
``` ```
...@@ -89,7 +91,6 @@ This also works for secret settings, like credentials: ...@@ -89,7 +91,6 @@ This also works for secret settings, like credentials:
export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret
``` ```
## Available settings ## Available settings
### `cache-dir`: string ### `cache-dir`: string
...@@ -102,21 +103,55 @@ Defaults to one of the following directories: ...@@ -102,21 +103,55 @@ Defaults to one of the following directories:
- Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache` - Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache`
- Unix: `~/.cache/pypoetry` - Unix: `~/.cache/pypoetry`
### `installer.parallel`: boolean
Use parallel execution when using the new (`>=1.1.0`) installer.
Defaults to `true`.
!!!note:
This configuration will be ignored, and parallel execution disabled when running
Python 2.7 under Windows.
### `virtualenvs.create`: boolean ### `virtualenvs.create`: boolean
Create a new virtual environment if one doesn't already exist. Create a new virtual environment if one doesn't already exist.
Defaults to `true`. Defaults to `true`.
If set to `false`, poetry will install dependencies into the current python environment.
!!!note
When setting this configuration to `false`, the Python environment used must have `pip`
installed and available.
### `virtualenvs.in-project`: boolean ### `virtualenvs.in-project`: boolean
Create the virtualenv inside the project's root directory. Create the virtualenv inside the project's root directory.
Defaults to `false`. Defaults to `None`.
If set to `true`, the virtualenv will be created and expected in a folder named
`.venv` within the root directory of the project.
If not set explicitly (default), `poetry` will use the virtualenv from the `.venv`
directory when one is available. If set to `false`, `poetry` will ignore any
existing `.venv` directory.
### `virtualenvs.path`: string ### `virtualenvs.path`: string
Directory where virtual environments 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).
### `virtualenvs.options.always-copy`: boolean
If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked.
Defaults to `false`.
### `virtualenvs.options.system-site-packages`: boolean
Give the virtual environment access to the system site-packages directory.
Applies on virtualenv creation.
Defaults to `false`.
### `repositories.<name>`: string ### `repositories.<name>`: string
Set a new alternative repository. See [Repositories](/docs/repositories/) for more information. Set a new alternative repository. See [Repositories](/docs/repositories/) for more information.
...@@ -7,12 +7,7 @@ of the dependency and on the optional constraints that might be needed for it to ...@@ -7,12 +7,7 @@ of the dependency and on the optional constraints that might be needed for it to
### Caret requirements ### Caret requirements
**Caret requirements** allow SemVer compatible updates to a specified version. **Caret requirements** allow [SemVer](https://semver.org/) 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. For instance, if we previously ran `poetry add requests@^2.13.0` and wanted to update the library and 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.
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: Here are some more examples of caret requirements and the versions that would be allowed with them:
...@@ -103,6 +98,13 @@ flask = { git = "https://github.com/pallets/flask.git", rev = "38eb5d3b" } ...@@ -103,6 +98,13 @@ flask = { git = "https://github.com/pallets/flask.git", rev = "38eb5d3b" }
numpy = { git = "https://github.com/numpy/numpy.git", tag = "v0.13.2" } numpy = { git = "https://github.com/numpy/numpy.git", tag = "v0.13.2" }
``` ```
To use an SSH connection, for example in the case of private repositories, use the following example syntax:
```toml
[tool.poetry.dependencies]
requests = { git = "git@github.com:requests/requests.git" }
```
## `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,
...@@ -111,12 +113,16 @@ you can use the `path` property: ...@@ -111,12 +113,16 @@ you can use the `path` property:
```toml ```toml
[tool.poetry.dependencies] [tool.poetry.dependencies]
# directory # directory
my-package = { path = "../my-package/" } my-package = { path = "../my-package/", develop = false }
# file # file
my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" } my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" }
``` ```
!!!note
Before poetry 1.1 directory path dependencies were installed in editable mode by default. You should set the `develop` attribute explicitly,
to make sure the behavior is the same for all poetry versions.
## `url` dependencies ## `url` dependencies
...@@ -179,6 +185,11 @@ foo = [ ...@@ -179,6 +185,11 @@ foo = [
] ]
``` ```
!!!note
The constraints **must** have different requirements (like `python`)
otherwise it will cause an error when resolving dependencies.
## Expanded dependency specification syntax ## Expanded dependency specification syntax
In the case of more complex dependency specifications, you may find that you In the case of more complex dependency specifications, you may find that you
...@@ -206,8 +217,3 @@ markers = "platform_python_implementation == 'CPython'" ...@@ -206,8 +217,3 @@ markers = "platform_python_implementation == 'CPython'"
All of the same information is still present, and ends up providing the exact All of the same information is still present, and ends up providing the exact
same specification. It's simply split into multiple, slightly more readable, same specification. It's simply split into multiple, slightly more readable,
lines. lines.
!!!note
The constraints **must** have different requirements (like `python`)
otherwise it will cause an error when resolving dependencies.
...@@ -23,11 +23,11 @@ recommended way of installing `poetry`. ...@@ -23,11 +23,11 @@ recommended way of installing `poetry`.
### osx / linux / bashonwindows install instructions ### osx / linux / bashonwindows install instructions
```bash ```bash
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
``` ```
### windows powershell install instructions ### windows powershell install instructions
```powershell ```powershell
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -
``` ```
!!! note !!! note
...@@ -38,9 +38,12 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poet ...@@ -38,9 +38,12 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poet
The installer installs the `poetry` tool to Poetry's `bin` directory. The installer installs the `poetry` tool to Poetry's `bin` directory.
On Unix it is located at `$HOME/.poetry/bin` and on Windows at `%USERPROFILE%\.poetry\bin`. On Unix it is located at `$HOME/.poetry/bin` and on Windows at `%USERPROFILE%\.poetry\bin`.
This directory will be in your `$PATH` environment variable, This directory will be automatically added to your `$PATH` environment variable,
which means you can run them from the shell without further configuration. by appending a statement to your `$HOME/.profile` configuration (or equivalent files).
Open a new shell and type the following: If you do not feel comfortable with this, please pass the `--no-modify-path` flag to
the installer and manually add the Poetry's `bin` directory to your path.
Finally, open a new shell and type the following:
```bash ```bash
poetry --version poetry --version
...@@ -117,7 +120,7 @@ pip install --user poetry ...@@ -117,7 +120,7 @@ pip install --user poetry
#### Installing with `pipx` #### Installing with `pipx`
Using [`pipx`](https://github.com/cs01/pipx) to install Poetry is also possible. [pipx] is used to install Python CLI applications globally while still isolating them in virtual environments. This allows for clean upgrades and uninstalls. pipx supports Python 3.6 and later. If using an earlier version of Python, consider [pipsi](https://github.com/mitsuhiko/pipsi). Using [`pipx`](https://github.com/cs01/pipx) to install Poetry is also possible. `pipx` is used to install Python CLI applications globally while still isolating them in virtual environments. This allows for clean upgrades and uninstalls. `pipx` supports Python 3.6 and later. If using an earlier version of Python, consider [`pipsi`](https://github.com/mitsuhiko/pipsi).
```bash ```bash
pipx install poetry pipx install poetry
...@@ -188,8 +191,8 @@ poetry completions fish > (brew --prefix)/share/fish/vendor_completions.d/poetry ...@@ -188,8 +191,8 @@ poetry completions fish > (brew --prefix)/share/fish/vendor_completions.d/poetry
poetry completions zsh > ~/.zfunc/_poetry poetry completions zsh > ~/.zfunc/_poetry
# Oh-My-Zsh # Oh-My-Zsh
mkdir $ZSH/plugins/poetry mkdir $ZSH_CUSTOM/plugins/poetry
poetry completions zsh > $ZSH/plugins/poetry/_poetry poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry
# prezto # prezto
poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry
......
...@@ -173,6 +173,19 @@ If a VCS is being used for a package, the exclude field will be seeded with the ...@@ -173,6 +173,19 @@ If a VCS is being used for a package, the exclude field will be seeded with the
include = ["CHANGELOG.md"] include = ["CHANGELOG.md"]
``` ```
You can also specify the formats for which these patterns have to be included, as shown here:
```toml
[tool.poetry]
# ...
include = [
{ path = "tests", format = "sdist" },
{ path = "for_wheel.txt", format = ["sdist", "wheel"] }
]
```
If no format is specified, it will default to include both `sdist` and `wheel`.
```toml ```toml
exclude = ["my_package/excluded.py"] exclude = ["my_package/excluded.py"]
``` ```
...@@ -207,7 +220,7 @@ url = 'http://example.com/simple' ...@@ -207,7 +220,7 @@ url = 'http://example.com/simple'
## `scripts` ## `scripts`
This section describe the scripts or executable that will be installed when installing the package This section describes the scripts or executables that will be installed when installing the package
```toml ```toml
[tool.poetry.scripts] [tool.poetry.scripts]
...@@ -243,15 +256,32 @@ mysqlclient = { version = "^1.3", optional = true } ...@@ -243,15 +256,32 @@ mysqlclient = { version = "^1.3", optional = true }
[tool.poetry.extras] [tool.poetry.extras]
mysql = ["mysqlclient"] mysql = ["mysqlclient"]
pgsql = ["psycopg2"] pgsql = ["psycopg2"]
databases = ["mysqlclient", "psycopg2"]
``` ```
When installing packages, you can specify extras by using the `-E|--extras` option: When installing packages with Poetry, you can specify extras by using the `-E|--extras` option:
```bash ```bash
poetry install --extras "mysql pgsql" poetry install --extras "mysql pgsql"
poetry install -E mysql -E pgsql poetry install -E mysql -E pgsql
``` ```
When installing or specifying Poetry-built packages, the extras defined in this section can be activated
as described in [PEP 508](https://www.python.org/dev/peps/pep-0508/#extras).
For example, when installing the package using `pip`, the dependencies required by
the `databases` extra can be installed as shown below.
```bash
pip install awesome[databases]
```
!!!note
The dependencies specified for each `extra` must already be defined as project dependencies.
Dependencies listed in the `dev-dependencies` section cannot be specified as extras.
## `plugins` ## `plugins`
Poetry supports arbitrary plugins which work similarly to Poetry supports arbitrary plugins which work similarly to
...@@ -288,7 +318,7 @@ it in the `build-system` section of the `pyproject.toml` file like so: ...@@ -288,7 +318,7 @@ it in the `build-system` section of the `pyproject.toml` file like so:
```toml ```toml
[build-system] [build-system]
requires = ["poetry_core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
``` ```
...@@ -300,4 +330,4 @@ build-backend = "poetry.core.masonry.api" ...@@ -300,4 +330,4 @@ build-backend = "poetry.core.masonry.api"
!!!note !!!note
If your `pyproject.toml` file still references `poetry` directly as a build backend, If your `pyproject.toml` file still references `poetry` directly as a build backend,
you should update it to reference `poetry_core` instead. you should update it to reference `poetry-core` instead.
...@@ -159,15 +159,15 @@ def colorize(style, text): ...@@ -159,15 +159,15 @@ def colorize(style, text):
def temporary_directory(*args, **kwargs): def temporary_directory(*args, **kwargs):
try: try:
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
with TemporaryDirectory(*args, **kwargs) as name:
yield name
except ImportError: except ImportError:
name = tempfile.mkdtemp(*args, **kwargs) name = tempfile.mkdtemp(*args, **kwargs)
yield name yield name
shutil.rmtree(name) shutil.rmtree(name)
else:
with TemporaryDirectory(*args, **kwargs) as name:
yield name
def string_to_bool(value): def string_to_bool(value):
...@@ -317,7 +317,7 @@ class Installer: ...@@ -317,7 +317,7 @@ class Installer:
r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?" r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?"
"(" "("
"[._-]?" "[._-]?"
r"(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?" r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?"
"([.-]?dev)?" "([.-]?dev)?"
")?" ")?"
r"(?:\+[^\s]+)?" r"(?:\+[^\s]+)?"
...@@ -903,7 +903,7 @@ class Installer: ...@@ -903,7 +903,7 @@ class Installer:
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, ".zshrc"))
bash_profile = os.path.join(HOME, ".bash_profile") bash_profile = os.path.join(HOME, ".bash_profile")
if os.path.exists(bash_profile): if os.path.exists(bash_profile):
......
#!/bin/sh #!/bin/sh
set -e set -ex
RUNTIMES[0]="${PYTHON27:+-P "2.7:$PYTHON27"}"
RUNTIMES[1]="${PYTHON35:+-P "3.5:$PYTHON35"}"
RUNTIMES[2]="${PYTHON36:+-P "3.6:$PYTHON36"}"
RUNTIMES[3]="${PYTHON37:+-P "3.7:$PYTHON37"}"
RUNTIMES[4]="${PYTHON38:+-P "3.8:$PYTHON38"}"
test -n "$PYTHON" || PYTHON="python3" test -n "$PYTHON" || PYTHON="python3"
$PYTHON get-poetry.py -y --preview
$PYTHON $HOME/.poetry/bin/poetry config virtualenvs.create false if [ "$OSTYPE" == "linux-gnu" ]; then
$PYTHON $HOME/.poetry/bin/poetry install --no-dev $PYTHON get-poetry.py -y --preview
$PYTHON $HOME/.poetry/bin/poetry run python sonnet make release \ POETRY="$PYTHON $HOME/.poetry/bin/poetry"
${PYTHON27:+-P "2.7:$PYTHON27"} \ RUNTIMES[5]="${PYTHON39:+-P "3.9:$PYTHON39"}"
${PYTHON35:+-P "3.5:$PYTHON35"} \ else
${PYTHON36:+-P "3.6:$PYTHON36"} \ $PYTHON -m pip install poetry -U --pre
${PYTHON37:+-P "3.7:$PYTHON37"} \ POETRY="$PYTHON -m poetry"
${PYTHON38:+-P "3.8:$PYTHON38"} \ fi
${PYTHON39:+-P "3.9:$PYTHON39"}
$POETRY config virtualenvs.in-project true
$POETRY install --no-dev
$POETRY run python sonnet make release ${RUNTIMES[@]}
...@@ -2,6 +2,6 @@ import sys ...@@ -2,6 +2,6 @@ import sys
if __name__ == "__main__": if __name__ == "__main__":
from .console import main from .console.application import main
sys.exit(main()) sys.exit(main())
__version__ = "1.1.0b2" __version__ = "1.2.0a0"
...@@ -4,14 +4,13 @@ import os ...@@ -4,14 +4,13 @@ import os
import re import re
from copy import deepcopy from copy import deepcopy
from pathlib import Path
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from .config_source import ConfigSource from .config_source import ConfigSource
from .dict_config_source import DictConfigSource from .dict_config_source import DictConfigSource
...@@ -20,11 +19,11 @@ from .dict_config_source import DictConfigSource ...@@ -20,11 +19,11 @@ from .dict_config_source import DictConfigSource
_NOT_SET = object() _NOT_SET = object()
def boolean_validator(val): def boolean_validator(val: str) -> bool:
return val in {"true", "false", "1", "0"} return val in {"true", "false", "1", "0"}
def boolean_normalizer(val): def boolean_normalizer(val: str) -> bool:
return val in ["true", "1"] return val in ["true", "1"]
...@@ -34,15 +33,17 @@ class Config(object): ...@@ -34,15 +33,17 @@ class Config(object):
"cache-dir": str(CACHE_DIR), "cache-dir": str(CACHE_DIR),
"virtualenvs": { "virtualenvs": {
"create": True, "create": True,
"in-project": False, "in-project": None,
"path": os.path.join("{cache-dir}", "virtualenvs"), "path": os.path.join("{cache-dir}", "virtualenvs"),
"options": {"always-copy": False, "system-site-packages": False},
}, },
"experimental": {"new-installer": True}, "experimental": {"new-installer": True},
"installer": {"parallel": True},
} }
def __init__( def __init__(
self, use_environment=True, base_dir=None self, use_environment: bool = True, base_dir: Optional[Path] = None
): # type: (bool, Optional[Path]) -> None ) -> None:
self._config = deepcopy(self.default_config) self._config = deepcopy(self.default_config)
self._use_environment = use_environment self._use_environment = use_environment
self._base_dir = base_dir self._base_dir = base_dir
...@@ -50,44 +51,48 @@ class Config(object): ...@@ -50,44 +51,48 @@ class Config(object):
self._auth_config_source = DictConfigSource() self._auth_config_source = DictConfigSource()
@property @property
def name(self): def name(self) -> str:
return str(self._file.path) return str(self._file.path)
@property @property
def config(self): def config(self) -> Dict:
return self._config return self._config
@property @property
def config_source(self): # type: () -> ConfigSource def config_source(self) -> ConfigSource:
return self._config_source return self._config_source
@property @property
def auth_config_source(self): # type: () -> ConfigSource def auth_config_source(self) -> ConfigSource:
return self._auth_config_source return self._auth_config_source
def set_config_source(self, config_source): # type: (ConfigSource) -> Config def set_config_source(self, config_source: ConfigSource) -> "Config":
self._config_source = config_source self._config_source = config_source
return self return self
def set_auth_config_source(self, config_source): # type: (ConfigSource) -> Config def set_auth_config_source(self, config_source: ConfigSource) -> "Config":
self._auth_config_source = config_source self._auth_config_source = config_source
return self return self
def merge(self, config): # type: (Dict[str, Any]) -> None def merge(self, config: Dict[str, Any]) -> None:
from poetry.utils.helpers import merge_dicts from poetry.utils.helpers import merge_dicts
merge_dicts(self._config, config) merge_dicts(self._config, config)
def all(self): # type: () -> Dict[str, Any] def all(self) -> Dict[str, Any]:
def _all(config, parent_key=""): def _all(config: Dict, parent_key: str = "") -> Dict:
all_ = {} all_ = {}
for key in config: for key in config:
value = self.get(parent_key + key) value = self.get(parent_key + key)
if isinstance(value, dict): if isinstance(value, dict):
all_[key] = _all(config[key], parent_key=key + ".") if parent_key != "":
current_parent = parent_key + key + "."
else:
current_parent = key + "."
all_[key] = _all(config[key], parent_key=current_parent)
continue continue
all_[key] = value all_[key] = value
...@@ -96,10 +101,10 @@ class Config(object): ...@@ -96,10 +101,10 @@ class Config(object):
return _all(self.config) return _all(self.config)
def raw(self): # type: () -> Dict[str, Any] def raw(self) -> Dict[str, Any]:
return self._config return self._config
def get(self, setting_name, default=None): # type: (str, Any) -> Any def get(self, setting_name: str, default: Any = None) -> Any:
""" """
Retrieve a setting value. Retrieve a setting value.
""" """
...@@ -124,21 +129,20 @@ class Config(object): ...@@ -124,21 +129,20 @@ class Config(object):
return self.process(value) return self.process(value)
def process(self, value): # type: (Any) -> Any def process(self, value: Any) -> Any:
if not isinstance(value, basestring): if not isinstance(value, str):
return value return value
return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value)
def _get_validator(self, name): # type: (str) -> Callable def _get_normalizer(self, name: str) -> Callable:
if name in {"virtualenvs.create", "virtualenvs.in-project"}: if name in {
return boolean_validator "virtualenvs.create",
"virtualenvs.in-project",
if name == "virtualenvs.path": "virtualenvs.options.always-copy",
return str "virtualenvs.options.system-site-packages",
"installer.parallel",
def _get_normalizer(self, name): # type: (str) -> Callable }:
if name in {"virtualenvs.create", "virtualenvs.in-project"}:
return boolean_normalizer return boolean_normalizer
if name == "virtualenvs.path": if name == "virtualenvs.path":
......
...@@ -2,8 +2,8 @@ from typing import Any ...@@ -2,8 +2,8 @@ from typing import Any
class ConfigSource(object): class ConfigSource(object):
def add_property(self, key, value): # type: (str, Any) -> None def add_property(self, key: str, value: Any) -> None:
raise NotImplementedError() raise NotImplementedError()
def remove_property(self, key): # type: (str) -> None def remove_property(self, key: str) -> None:
raise NotImplementedError() raise NotImplementedError()
...@@ -5,14 +5,14 @@ from .config_source import ConfigSource ...@@ -5,14 +5,14 @@ from .config_source import ConfigSource
class DictConfigSource(ConfigSource): class DictConfigSource(ConfigSource):
def __init__(self): # type: () -> None def __init__(self) -> None:
self._config = {} self._config = {}
@property @property
def config(self): # type: () -> Dict[str, Any] def config(self) -> Dict[str, Any]:
return self._config return self._config
def add_property(self, key, value): # type: (str, Any) -> None def add_property(self, key: str, value: Any) -> None:
keys = key.split(".") keys = key.split(".")
config = self._config config = self._config
...@@ -26,7 +26,7 @@ class DictConfigSource(ConfigSource): ...@@ -26,7 +26,7 @@ class DictConfigSource(ConfigSource):
config = config[key] config = config[key]
def remove_property(self, key): # type: (str) -> None def remove_property(self, key: str) -> None:
keys = key.split(".") keys = key.split(".")
config = self._config config = self._config
......
from contextlib import contextmanager from contextlib import contextmanager
from typing import TYPE_CHECKING
from typing import Any from typing import Any
from typing import Iterator
from tomlkit import document from tomlkit import document
from tomlkit import table from tomlkit import table
from poetry.utils.toml_file import TomlFile
from .config_source import ConfigSource from .config_source import ConfigSource
if TYPE_CHECKING:
from tomlkit.toml_document import TOMLDocument
from poetry.core.toml.file import TOMLFile
class FileConfigSource(ConfigSource): class FileConfigSource(ConfigSource):
def __init__(self, file, auth_config=False): # type: (TomlFile, bool) -> None def __init__(self, file: "TOMLFile", auth_config: bool = False) -> None:
self._file = file self._file = file
self._auth_config = auth_config self._auth_config = auth_config
@property @property
def name(self): # type: () -> str def name(self) -> str:
return str(self._file.path) return str(self._file.path)
@property @property
def file(self): # type: () -> TomlFile def file(self) -> "TOMLFile":
return self._file return self._file
def add_property(self, key, value): # type: (str, Any) -> None def add_property(self, key: str, value: Any) -> None:
with self.secure() as config: with self.secure() as config:
keys = key.split(".") keys = key.split(".")
...@@ -36,7 +42,7 @@ class FileConfigSource(ConfigSource): ...@@ -36,7 +42,7 @@ class FileConfigSource(ConfigSource):
config = config[key] config = config[key]
def remove_property(self, key): # type: (str) -> None def remove_property(self, key: str) -> None:
with self.secure() as config: with self.secure() as config:
keys = key.split(".") keys = key.split(".")
...@@ -53,7 +59,7 @@ class FileConfigSource(ConfigSource): ...@@ -53,7 +59,7 @@ class FileConfigSource(ConfigSource):
current_config = current_config[key] current_config = current_config[key]
@contextmanager @contextmanager
def secure(self): def secure(self) -> Iterator["TOMLDocument"]:
if self.file.exists(): if self.file.exists():
initial_config = self.file.read() initial_config = self.file.read()
config = self.file.read() config = self.file.read()
......
from .application import Application
def main():
return Application().run()
from clikit.api.args import Args
from clikit.api.args import RawArgs
from clikit.api.args.format import ArgsFormat
from clikit.api.args.format import ArgsFormatBuilder
from clikit.args import DefaultArgsParser
class RunArgsParser(DefaultArgsParser):
"""
Parser that just parses command names and leave the rest
alone to be passed to the command.
"""
def parse(
self, args, fmt, lenient=False
): # type: (RawArgs, ArgsFormat, bool) -> Args
builder = ArgsFormatBuilder()
builder.set_command_names(*fmt.get_command_names())
builder.set_arguments(*fmt.get_arguments().values())
fmt = builder.format
return super(RunArgsParser, self).parse(args, fmt, True)
def _parse(
self, raw_args, fmt, lenient
): # type: (RawArgs, ArgsFormat, bool) -> None
"""
Parse everything as a single, multi-valued argument.
"""
tokens = raw_args.tokens[:]
last_arg = list(fmt.get_arguments().values())[-1]
self._arguments[last_arg.name] = []
while True:
try:
token = tokens.pop(0)
except IndexError:
break
self._arguments[last_arg.name].append(token)
from .about import AboutCommand
from .add import AddCommand
from .build import BuildCommand
from .check import CheckCommand
from .config import ConfigCommand
from .export import ExportCommand
from .init import InitCommand
from .install import InstallCommand
from .lock import LockCommand
from .new import NewCommand
from .publish import PublishCommand
from .remove import RemoveCommand
from .run import RunCommand
from .search import SearchCommand
from .shell import ShellCommand
from .show import ShowCommand
from .update import UpdateCommand
from .version import VersionCommand
...@@ -7,7 +7,7 @@ class AboutCommand(Command): ...@@ -7,7 +7,7 @@ class AboutCommand(Command):
description = "Shows information about Poetry." description = "Shows information about Poetry."
def handle(self): def handle(self) -> None:
self.line( self.line(
"""<info>Poetry - Package Management for Python</info> """<info>Poetry - Package Management for Python</info>
......
from cleo import argument # -*- coding: utf-8 -*-
from cleo import option from typing import Dict
from typing import List
from cleo.helpers import argument
from cleo.helpers import option
from .init import InitCommand from .init import InitCommand
from .installer_command import InstallerCommand from .installer_command import InstallerCommand
...@@ -55,6 +59,8 @@ class AddCommand(InstallerCommand, InitCommand): ...@@ -55,6 +59,8 @@ class AddCommand(InstallerCommand, InitCommand):
" - 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>git+https://github.com/python-poetry/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>git+https://github.com/python-poetry/poetry.git#develop</b>)\n" " - A git url with a revision (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)\n"
" - A git SSH url (<b>git+ssh://github.com/python-poetry/poetry.git</b>)\n"
" - A git SSH url with a revision (<b>git+ssh://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"
" - A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n" " - A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n"
...@@ -62,10 +68,10 @@ class AddCommand(InstallerCommand, InitCommand): ...@@ -62,10 +68,10 @@ class AddCommand(InstallerCommand, InitCommand):
loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self): def handle(self) -> int:
from tomlkit import inline_table from tomlkit import inline_table
from poetry.core.semver import parse_constraint from poetry.core.semver.helpers import parse_constraint
packages = self.argument("name") packages = self.argument("name")
is_dev = self.option("dev") is_dev = self.option("dev")
...@@ -86,18 +92,18 @@ class AddCommand(InstallerCommand, InitCommand): ...@@ -86,18 +92,18 @@ class AddCommand(InstallerCommand, InitCommand):
if section not in poetry_content: if section not in poetry_content:
poetry_content[section] = {} poetry_content[section] = {}
for name in packages: existing_packages = self.get_existing_packages_from_input(
for key in poetry_content[section]: packages, poetry_content, section
if key.lower() == name.lower(): )
pair = self._parse_requirements([name])[0]
if ( if existing_packages:
"git" in pair self.notify_about_existing_packages(existing_packages)
or "url" in pair
or pair.get("version") == "latest"
):
continue
raise ValueError("Package {} is already present".format(name)) packages = [name for name in packages if name not in existing_packages]
if not packages:
self.line("Nothing to add.")
return 0
requirements = self._determine_requirements( requirements = self._determine_requirements(
packages, packages,
...@@ -147,29 +153,29 @@ class AddCommand(InstallerCommand, InitCommand): ...@@ -147,29 +153,29 @@ class AddCommand(InstallerCommand, InitCommand):
poetry_content[section][_constraint["name"]] = constraint poetry_content[section][_constraint["name"]] = constraint
# Write new content try:
self.poetry.file.write(content) # Write new content
self.poetry.file.write(content)
# Cosmetic new line # Cosmetic new line
self.line("") self.line("")
# Update packages # Update packages
self.reset_poetry() self.reset_poetry()
self._installer.set_package(self.poetry.package) self._installer.set_package(self.poetry.package)
self._installer.dry_run(self.option("dry-run")) self._installer.dry_run(self.option("dry-run"))
self._installer.verbose(self._io.is_verbose()) self._installer.verbose(self._io.is_verbose())
self._installer.update(True) self._installer.update(True)
if self.option("lock"): if self.option("lock"):
self._installer.lock() self._installer.lock()
self._installer.whitelist([r["name"] for r in requirements]) self._installer.whitelist([r["name"] for r in requirements])
try:
status = self._installer.run() status = self._installer.run()
except Exception: except BaseException:
# Using BaseException here as some exceptions, eg: KeyboardInterrupt, do not inherit from Exception
self.poetry.file.write(original_content) self.poetry.file.write(original_content)
raise raise
if status != 0 or self.option("dry-run"): if status != 0 or self.option("dry-run"):
...@@ -184,3 +190,26 @@ class AddCommand(InstallerCommand, InitCommand): ...@@ -184,3 +190,26 @@ class AddCommand(InstallerCommand, InitCommand):
self.poetry.file.write(original_content) self.poetry.file.write(original_content)
return status return status
def get_existing_packages_from_input(
self, packages: List[str], poetry_content: Dict, target_section: str
) -> List[str]:
existing_packages = []
for name in packages:
for key in poetry_content[target_section]:
if key.lower() == name.lower():
existing_packages.append(name)
return existing_packages
def notify_about_existing_packages(self, existing_packages: List[str]) -> None:
self.line(
"The following packages are already present in the pyproject.toml and will be skipped:\n"
)
for name in existing_packages:
self.line(" • <c1>{name}</c1>".format(name=name))
self.line(
"\nIf you want to update it to the latest compatible version, you can use `poetry update package`.\n"
"If you prefer to upgrade it to the latest available version, you can use `poetry add package@latest`.\n"
)
from cleo import option from cleo.helpers import option
from .env_command import EnvCommand from .env_command import EnvCommand
...@@ -18,8 +18,8 @@ class BuildCommand(EnvCommand): ...@@ -18,8 +18,8 @@ class BuildCommand(EnvCommand):
"poetry.core.masonry.builders.wheel", "poetry.core.masonry.builders.wheel",
] ]
def handle(self): def handle(self) -> None:
from poetry.core.masonry import Builder from poetry.core.masonry.builder import Builder
fmt = "all" fmt = "all"
if self.option("format"): if self.option("format"):
...@@ -33,4 +33,4 @@ class BuildCommand(EnvCommand): ...@@ -33,4 +33,4 @@ class BuildCommand(EnvCommand):
) )
builder = Builder(self.poetry) builder = Builder(self.poetry)
builder.build(fmt) builder.build(fmt, executable=self.env.python)
from poetry.console.commands.cache.list import CacheListCommand
from ..command import Command
from .clear import CacheClearCommand
class CacheCommand(Command):
name = "cache"
description = "Interact with Poetry's cache"
commands = [CacheClearCommand(), CacheListCommand()]
def handle(self):
return self.call("help", self._config.name)
import os import os
from cleo import argument from cleo.helpers import argument
from cleo import option from cleo.helpers import option
from ..command import Command from ..command import Command
class CacheClearCommand(Command): class CacheClearCommand(Command):
name = "clear" name = "cache clear"
description = "Clears Poetry's cache." description = "Clears Poetry's cache."
arguments = [argument("cache", description="The name of the cache to clear.")] arguments = [argument("cache", description="The name of the cache to clear.")]
options = [option("all", description="Clear all entries in the cache.")] options = [option("all", description="Clear all entries in the cache.")]
def handle(self): def handle(self) -> int:
from cachy import CacheManager from cachy import CacheManager
from poetry.locations import REPOSITORY_CACHE_DIR from poetry.locations import REPOSITORY_CACHE_DIR
......
import os import os
from typing import Optional
from ..command import Command from ..command import Command
class CacheListCommand(Command): class CacheListCommand(Command):
name = "list" name = "cache list"
description = "List Poetry's caches." description = "List Poetry's caches."
def handle(self): def handle(self) -> Optional[int]:
from poetry.locations import REPOSITORY_CACHE_DIR from poetry.locations import REPOSITORY_CACHE_DIR
if os.path.exists(str(REPOSITORY_CACHE_DIR)): if os.path.exists(str(REPOSITORY_CACHE_DIR)):
......
from pathlib import Path
from poetry.core.pyproject.toml import PyProjectTOML
from poetry.factory import Factory from poetry.factory import Factory
from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile
from .command import Command from .command import Command
...@@ -10,10 +11,10 @@ class CheckCommand(Command): ...@@ -10,10 +11,10 @@ class CheckCommand(Command):
name = "check" name = "check"
description = "Checks the validity of the <comment>pyproject.toml</comment> file." description = "Checks the validity of the <comment>pyproject.toml</comment> file."
def handle(self): def handle(self) -> int:
# Load poetry config and display errors, if any # Load poetry config and display errors, if any
poetry_file = Factory.locate(Path.cwd()) poetry_file = Factory.locate(Path.cwd())
config = TomlFile(str(poetry_file)).read()["tool"]["poetry"] config = PyProjectTOML(poetry_file).poetry_config
check_result = Factory.validate(config, strict=True) check_result = Factory.validate(config, strict=True)
if not check_result["errors"] and not check_result["warnings"]: if not check_result["errors"] and not check_result["warnings"]:
self.info("All set!") self.info("All set!")
......
from cleo import Command as BaseCommand from typing import TYPE_CHECKING
from cleo.commands.command import Command as BaseCommand
if TYPE_CHECKING:
from poetry.console.application import Application
from poetry.poetry import Poetry
class Command(BaseCommand):
class Command(BaseCommand):
loggers = [] loggers = []
@property @property
def poetry(self): def poetry(self) -> "Poetry":
return self.application.poetry return self.get_application().poetry
def get_application(self) -> "Application":
return self.application
def reset_poetry(self): # type: () -> None def reset_poetry(self) -> None:
self.application.reset_poetry() self.get_application().reset_poetry()
import json import json
import re import re
from cleo import argument from typing import TYPE_CHECKING
from cleo import option from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from poetry.factory import Factory from cleo.helpers import argument
from cleo.helpers import option
from .command import Command from .command import Command
if TYPE_CHECKING:
from poetry.config.config_source import ConfigSource
class ConfigCommand(Command): class ConfigCommand(Command):
name = "config" name = "config"
...@@ -38,11 +47,12 @@ To remove a repository (repo is a short alias for repositories): ...@@ -38,11 +47,12 @@ To remove a repository (repo is a short alias for repositories):
LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"}
@property @property
def unique_config_values(self): def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]:
from pathlib import Path
from poetry.config.config import boolean_normalizer from poetry.config.config import boolean_normalizer
from poetry.config.config import boolean_validator from poetry.config.config import boolean_validator
from poetry.locations import CACHE_DIR from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path
unique_config_values = { unique_config_values = {
"cache-dir": ( "cache-dir": (
...@@ -52,6 +62,16 @@ To remove a repository (repo is a short alias for repositories): ...@@ -52,6 +62,16 @@ To remove a repository (repo is a short alias for repositories):
), ),
"virtualenvs.create": (boolean_validator, boolean_normalizer, True), "virtualenvs.create": (boolean_validator, boolean_normalizer, True),
"virtualenvs.in-project": (boolean_validator, boolean_normalizer, False), "virtualenvs.in-project": (boolean_validator, boolean_normalizer, False),
"virtualenvs.options.always-copy": (
boolean_validator,
boolean_normalizer,
False,
),
"virtualenvs.options.system-site-packages": (
boolean_validator,
boolean_normalizer,
False,
),
"virtualenvs.path": ( "virtualenvs.path": (
str, str,
lambda val: str(Path(val)), lambda val: str(Path(val)),
...@@ -62,26 +82,33 @@ To remove a repository (repo is a short alias for repositories): ...@@ -62,26 +82,33 @@ To remove a repository (repo is a short alias for repositories):
boolean_normalizer, boolean_normalizer,
True, True,
), ),
"installer.parallel": (
boolean_validator,
boolean_normalizer,
True,
),
} }
return unique_config_values return unique_config_values
def handle(self): def handle(self) -> Optional[int]:
from pathlib import Path
from poetry.config.file_config_source import FileConfigSource from poetry.config.file_config_source import FileConfigSource
from poetry.core.pyproject.exceptions import PyProjectException
from poetry.core.toml.file import TOMLFile
from poetry.factory import Factory
from poetry.locations import CONFIG_DIR from poetry.locations import CONFIG_DIR
from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils.toml_file import TomlFile
config = Factory.create_config(self.io) config = Factory.create_config(self.io)
config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml")
try: try:
local_config_file = TomlFile(self.poetry.file.parent / "poetry.toml") local_config_file = TOMLFile(self.poetry.file.parent / "poetry.toml")
if local_config_file.exists(): if local_config_file.exists():
config.merge(local_config_file.read()) config.merge(local_config_file.read())
except RuntimeError: except (RuntimeError, PyProjectException):
local_config_file = TomlFile(Path.cwd() / "poetry.toml") local_config_file = TOMLFile(Path.cwd() / "poetry.toml")
if self.option("local"): if self.option("local"):
config.set_config_source(FileConfigSource(local_config_file)) config.set_config_source(FileConfigSource(local_config_file))
...@@ -127,7 +154,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -127,7 +154,7 @@ To remove a repository (repo is a short alias for repositories):
value = config.get(setting_key) value = config.get(setting_key)
if not isinstance(value, basestring): if not isinstance(value, str):
value = json.dumps(value) value = json.dumps(value)
self.line(value) self.line(value)
...@@ -245,7 +272,13 @@ To remove a repository (repo is a short alias for repositories): ...@@ -245,7 +272,13 @@ To remove a repository (repo is a short alias for repositories):
raise ValueError("Setting {} does not exist".format(self.argument("key"))) raise ValueError("Setting {} does not exist".format(self.argument("key")))
def _handle_single_value(self, source, key, callbacks, values): def _handle_single_value(
self,
source: "ConfigSource",
key: str,
callbacks: Tuple[Any, Any, Any],
values: List[Any],
) -> int:
validator, normalizer, _ = callbacks validator, normalizer, _ = callbacks
if len(values) > 1: if len(values) > 1:
...@@ -259,9 +292,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -259,9 +292,7 @@ To remove a repository (repo is a short alias for repositories):
return 0 return 0
def _list_configuration(self, config, raw, k=""): def _list_configuration(self, config: Dict, raw: Dict, k: str = "") -> None:
from poetry.utils._compat import basestring
orig_k = k orig_k = k
for key, value in sorted(config.items()): for key, value in sorted(config.items()):
if k + key in self.LIST_PROHIBITED_SETTINGS: if k + key in self.LIST_PROHIBITED_SETTINGS:
...@@ -286,7 +317,7 @@ To remove a repository (repo is a short alias for repositories): ...@@ -286,7 +317,7 @@ To remove a repository (repo is a short alias for repositories):
message = "<c1>{}</c1> = <c2>{}</c2>".format( message = "<c1>{}</c1> = <c2>{}</c2>".format(
k + key, json.dumps(raw_val) k + key, json.dumps(raw_val)
) )
elif isinstance(raw_val, basestring) and raw_val != value: elif isinstance(raw_val, str) and raw_val != value:
message = "<c1>{}</c1> = <c2>{}</c2> # {}".format( message = "<c1>{}</c1> = <c2>{}</c2> # {}".format(
k + key, json.dumps(raw_val), value k + key, json.dumps(raw_val), value
) )
...@@ -295,15 +326,13 @@ To remove a repository (repo is a short alias for repositories): ...@@ -295,15 +326,13 @@ To remove a repository (repo is a short alias for repositories):
self.line(message) self.line(message)
def _list_setting(self, contents, setting=None, k=None, default=None): def _get_setting(
values = self._get_setting(contents, setting, k, default) self,
contents: Dict,
for value in values: setting: Optional[str] = None,
self.line( k: Optional[str] = None,
"<comment>{}</comment> = <info>{}</info>".format(value[0], value[1]) default: Optional[Any] = None,
) ) -> List[Tuple[str, str]]:
def _get_setting(self, contents, setting=None, k=None, default=None):
orig_k = k orig_k = k
if setting and setting.split(".")[0] not in contents: if setting and setting.split(".")[0] not in contents:
...@@ -344,11 +373,3 @@ To remove a repository (repo is a short alias for repositories): ...@@ -344,11 +373,3 @@ To remove a repository (repo is a short alias for repositories):
values.append(((k or "") + key, value)) values.append(((k or "") + key, value))
return values return values
def _get_formatted_value(self, value):
if isinstance(value, list):
value = [json.dumps(val) if isinstance(val, list) else val for val in value]
value = "[{}]".format(", ".join(value))
return json.dumps(value)
from ..command import Command
from .info import DebugInfoCommand
from .resolve import DebugResolveCommand
class DebugCommand(Command):
name = "debug"
description = "Debug various elements of Poetry."
commands = [DebugInfoCommand().default(), DebugResolveCommand()]
import sys import sys
from clikit.args import StringArgs
from ..command import Command from ..command import Command
class DebugInfoCommand(Command): class DebugInfoCommand(Command):
name = "info" name = "debug info"
description = "Shows debug information." description = "Shows debug information."
def handle(self): def handle(self) -> int:
poetry_python_version = ".".join(str(s) for s in sys.version_info[:3]) poetry_python_version = ".".join(str(s) for s in sys.version_info[:3])
self.line("") self.line("")
...@@ -25,7 +23,6 @@ class DebugInfoCommand(Command): ...@@ -25,7 +23,6 @@ class DebugInfoCommand(Command):
] ]
) )
) )
args = StringArgs("") command = self.application.get("env info")
command = self.application.get_command("env").get_sub_command("info")
return command.run(args, self._io) return command.run(self._io)
from cleo import argument from typing import TYPE_CHECKING
from cleo import option from typing import Optional
from cleo.helpers import argument
from cleo.helpers import option
from cleo.io.outputs.output import Verbosity
from ..init import InitCommand from ..init import InitCommand
if TYPE_CHECKING:
from poetry.console.commands.show import ShowCommand
class DebugResolveCommand(InitCommand): class DebugResolveCommand(InitCommand):
name = "resolve" name = "debug resolve"
description = "Debugs dependency resolution." description = "Debugs dependency resolution."
arguments = [ arguments = [
...@@ -27,9 +35,11 @@ class DebugResolveCommand(InitCommand): ...@@ -27,9 +35,11 @@ class DebugResolveCommand(InitCommand):
loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self): def handle(self) -> Optional[int]:
from cleo.io.null_io import NullIO
from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.project_package import ProjectPackage
from poetry.io.null_io import NullIO from poetry.factory import Factory
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
...@@ -48,18 +58,15 @@ class DebugResolveCommand(InitCommand): ...@@ -48,18 +58,15 @@ class DebugResolveCommand(InitCommand):
) )
# Silencing output # Silencing output
is_quiet = self.io.output.is_quiet() verbosity = self.io.output.verbosity
if not is_quiet: self.io.output.set_verbosity(Verbosity.QUIET)
self.io.output.set_quiet(True)
requirements = self._determine_requirements(packages) requirements = self._determine_requirements(packages)
if not is_quiet: self.io.output.set_verbosity(verbosity)
self.io.output.set_quiet(False)
for constraint in requirements: for constraint in requirements:
name = constraint.pop("name") name = constraint.pop("name")
dep = package.add_dependency(name, constraint)
extras = [] extras = []
for extra in self.option("extras"): for extra in self.option("extras"):
if " " in extra: if " " in extra:
...@@ -67,8 +74,9 @@ class DebugResolveCommand(InitCommand): ...@@ -67,8 +74,9 @@ class DebugResolveCommand(InitCommand):
else: else:
extras.append(extra) extras.append(extra)
for ex in extras: constraint["extras"] = extras
dep.extras.append(ex)
package.add_dependency(Factory.create_dependency(name, constraint))
package.python_versions = self.option("python") or ( package.python_versions = self.option("python") or (
self.poetry.package.python_versions self.poetry.package.python_versions
...@@ -85,7 +93,7 @@ class DebugResolveCommand(InitCommand): ...@@ -85,7 +93,7 @@ class DebugResolveCommand(InitCommand):
self.line("") self.line("")
if self.option("tree"): if self.option("tree"):
show_command = self.application.find("show") show_command: ShowCommand = self.application.find("show")
show_command.init_styles(self.io) show_command.init_styles(self.io)
packages = [op.package for op in ops] packages = [op.package for op in ops]
...@@ -100,7 +108,8 @@ class DebugResolveCommand(InitCommand): ...@@ -100,7 +108,8 @@ class DebugResolveCommand(InitCommand):
return 0 return 0
table = self.table([], style="borderless") table = self.table([], style="compact")
table.style.set_vertical_border_chars("", " ")
rows = [] rows = []
if self.option("install"): if self.option("install"):
...@@ -122,7 +131,7 @@ class DebugResolveCommand(InitCommand): ...@@ -122,7 +131,7 @@ class DebugResolveCommand(InitCommand):
pkg = op.package pkg = op.package
row = [ row = [
"<c1>{}</c1>".format(pkg.name), "<c1>{}</c1>".format(pkg.complete_name),
"<b>{}</b>".format(pkg.version), "<b>{}</b>".format(pkg.version),
"", "",
] ]
...@@ -133,4 +142,4 @@ class DebugResolveCommand(InitCommand): ...@@ -133,4 +142,4 @@ class DebugResolveCommand(InitCommand):
rows.append(row) rows.append(row)
table.set_rows(rows) table.set_rows(rows)
table.render(self.io) table.render()
from ..command import Command
from .info import EnvInfoCommand
from .list import EnvListCommand
from .remove import EnvRemoveCommand
from .use import EnvUseCommand
class EnvCommand(Command):
name = "env"
description = "Interact with Poetry's project environments."
commands = [EnvInfoCommand(), EnvListCommand(), EnvRemoveCommand(), EnvUseCommand()]
def handle(self): # type: () -> int
return self.call("help", self._config.name)
from cleo import option from typing import TYPE_CHECKING
from typing import Optional
from cleo.helpers import option
from ..command import Command from ..command import Command
if TYPE_CHECKING:
from poetry.utils.env import Env
class EnvInfoCommand(Command): class EnvInfoCommand(Command):
name = "info" name = "env info"
description = "Displays information about the current environment." description = "Displays information about the current environment."
options = [option("path", "p", "Only display the environment's path.")] options = [option("path", "p", "Only display the environment's path.")]
def handle(self): def handle(self) -> Optional[int]:
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
env = EnvManager(self.poetry).get() env = EnvManager(self.poetry).get()
...@@ -25,7 +32,7 @@ class EnvInfoCommand(Command): ...@@ -25,7 +32,7 @@ class EnvInfoCommand(Command):
self._display_complete_info(env) self._display_complete_info(env)
def _display_complete_info(self, env): def _display_complete_info(self, env: "Env") -> None:
env_python_version = ".".join(str(s) for s in env.version_info[:3]) env_python_version = ".".join(str(s) for s in env.version_info[:3])
self.line("") self.line("")
self.line("<b>Virtualenv</b>") self.line("<b>Virtualenv</b>")
......
from cleo import option from cleo.helpers import option
from ..command import Command from ..command import Command
class EnvListCommand(Command): class EnvListCommand(Command):
name = "list" name = "env list"
description = "Lists all virtualenvs associated with the current project." description = "Lists all virtualenvs associated with the current project."
options = [option("full-path", None, "Output the full paths of the virtualenvs.")] options = [option("full-path", None, "Output the full paths of the virtualenvs.")]
def handle(self): def handle(self) -> None:
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry) manager = EnvManager(self.poetry)
......
from cleo import argument from cleo.helpers import argument
from ..command import Command from ..command import Command
class EnvRemoveCommand(Command): class EnvRemoveCommand(Command):
name = "remove" name = "env remove"
description = "Removes a specific virtualenv associated with the project." description = "Removes a specific virtualenv associated with the project."
arguments = [ arguments = [
argument("python", "The python executable to remove the virtualenv for.") argument("python", "The python executable to remove the virtualenv for.")
] ]
def handle(self): def handle(self) -> None:
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry) manager = EnvManager(self.poetry)
......
from cleo import argument from cleo.helpers import argument
from ..command import Command from ..command import Command
class EnvUseCommand(Command): class EnvUseCommand(Command):
name = "use" name = "env use"
description = "Activates or creates a new virtualenv for the current project." description = "Activates or creates a new virtualenv for the current project."
arguments = [argument("python", "The python executable to use.")] arguments = [argument("python", "The python executable to use.")]
def handle(self): def handle(self) -> None:
from poetry.utils.env import EnvManager from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry) manager = EnvManager(self.poetry)
......
from typing import TYPE_CHECKING
from .command import Command from .command import Command
if TYPE_CHECKING:
from poetry.utils.env import VirtualEnv
class EnvCommand(Command): class EnvCommand(Command):
def __init__(self): def __init__(self) -> None:
self._env = None self._env = None
super(EnvCommand, self).__init__() super(EnvCommand, self).__init__()
@property @property
def env(self): def env(self) -> "VirtualEnv":
return self._env return self._env
def set_env(self, env): def set_env(self, env: "VirtualEnv") -> None:
self._env = env self._env = env
from cleo import option from cleo.helpers import option
from poetry.utils.exporter import Exporter from poetry.utils.exporter import Exporter
...@@ -31,7 +31,7 @@ class ExportCommand(Command): ...@@ -31,7 +31,7 @@ class ExportCommand(Command):
option("with-credentials", None, "Include credentials for extra indices."), option("with-credentials", None, "Include credentials for extra indices."),
] ]
def handle(self): def handle(self) -> None:
fmt = self.option("format") fmt = self.option("format")
if fmt not in Exporter.ACCEPTED_FORMATS: if fmt not in Exporter.ACCEPTED_FORMATS:
...@@ -50,7 +50,7 @@ class ExportCommand(Command): ...@@ -50,7 +50,7 @@ class ExportCommand(Command):
elif self.io.is_verbose(): elif self.io.is_verbose():
options.append(("-v", None)) options.append(("-v", None))
self.call("lock", options) self.call("lock", " ".join(options))
if not locker.is_fresh(): if not locker.is_fresh():
self.line( self.line(
......
from cleo import option from cleo.helpers import option
from .installer_command import InstallerCommand from .installer_command import InstallerCommand
...@@ -10,6 +10,7 @@ class InstallCommand(InstallerCommand): ...@@ -10,6 +10,7 @@ class InstallCommand(InstallerCommand):
options = [ options = [
option("no-dev", None, "Do not install the development dependencies."), option("no-dev", None, "Do not install the development dependencies."),
option("dev-only", None, "Only install the development dependencies."),
option( option(
"no-root", None, "Do not install the root package (the current project)." "no-root", None, "Do not install the root package (the current project)."
), ),
...@@ -20,7 +21,9 @@ class InstallCommand(InstallerCommand): ...@@ -20,7 +21,9 @@ class InstallCommand(InstallerCommand):
"(implicitly enables --verbose).", "(implicitly enables --verbose).",
), ),
option( option(
"remove-untracked", None, "Removes packages not present in the lock file.", "remove-untracked",
None,
"Removes packages not present in the lock file.",
), ),
option( option(
"extras", "extras",
...@@ -47,7 +50,7 @@ dependencies and not including the current project, run the command with the ...@@ -47,7 +50,7 @@ dependencies and not including the current project, run the command with the
_loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] _loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self): def handle(self) -> int:
from poetry.core.masonry.utils.module import ModuleOrPackageNotFound from poetry.core.masonry.utils.module import ModuleOrPackageNotFound
from poetry.masonry.builders import EditableBuilder from poetry.masonry.builders import EditableBuilder
...@@ -64,6 +67,7 @@ dependencies and not including the current project, run the command with the ...@@ -64,6 +67,7 @@ dependencies and not including the current project, run the command with the
self._installer.extras(extras) self._installer.extras(extras)
self._installer.dev_mode(not self.option("no-dev")) self._installer.dev_mode(not self.option("no-dev"))
self._installer.dev_only(self.option("dev-only"))
self._installer.dry_run(self.option("dry-run")) self._installer.dry_run(self.option("dry-run"))
self._installer.remove_untracked(self.option("remove-untracked")) self._installer.remove_untracked(self.option("remove-untracked"))
self._installer.verbose(self._io.is_verbose()) self._installer.verbose(self._io.is_verbose())
...@@ -73,7 +77,7 @@ dependencies and not including the current project, run the command with the ...@@ -73,7 +77,7 @@ dependencies and not including the current project, run the command with the
if return_code != 0: if return_code != 0:
return return_code return return_code
if self.option("no-root"): if self.option("no-root") or self.option("dev-only"):
return 0 return 0
try: try:
...@@ -85,7 +89,7 @@ dependencies and not including the current project, run the command with the ...@@ -85,7 +89,7 @@ dependencies and not including the current project, run the command with the
return 0 return 0
self.line("") self.line("")
if not self._io.supports_ansi() or self.io.is_debug(): if not self._io.output.is_decorated() or self.io.is_debug():
self.line( self.line(
"<b>Installing</> the current project: <c1>{}</c1> (<c2>{}</c2>)".format( "<b>Installing</> the current project: <c1>{}</c1> (<c2>{}</c2>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version self.poetry.package.pretty_name, self.poetry.package.pretty_version
...@@ -104,7 +108,7 @@ dependencies and not including the current project, run the command with the ...@@ -104,7 +108,7 @@ dependencies and not including the current project, run the command with the
builder.build() builder.build()
if self._io.supports_ansi() and not self.io.is_debug(): if self._io.output.is_decorated() and not self.io.is_debug():
self.overwrite( self.overwrite(
"<b>Installing</> the current project: <c1>{}</c1> (<success>{}</success>)".format( "<b>Installing</> the current project: <c1>{}</c1> (<success>{}</success>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version self.poetry.package.pretty_name, self.poetry.package.pretty_version
......
...@@ -9,20 +9,20 @@ if TYPE_CHECKING: ...@@ -9,20 +9,20 @@ if TYPE_CHECKING:
class InstallerCommand(EnvCommand): class InstallerCommand(EnvCommand):
def __init__(self): def __init__(self) -> None:
self._installer = None # type: Optional[Installer] self._installer: Optional["Installer"] = None
super(InstallerCommand, self).__init__() super(InstallerCommand, self).__init__()
def reset_poetry(self): def reset_poetry(self) -> None:
super(InstallerCommand, self).reset_poetry() super(InstallerCommand, self).reset_poetry()
self._installer.set_package(self.poetry.package) self._installer.set_package(self.poetry.package)
self._installer.set_locker(self.poetry.locker) self._installer.set_locker(self.poetry.locker)
@property @property
def installer(self): # type: () -> Installer def installer(self) -> "Installer":
return self._installer return self._installer
def set_installer(self, installer): # type: (Installer) -> None def set_installer(self, installer: "Installer") -> None:
self._installer = installer self._installer = installer
from cleo.helpers import option
from .installer_command import InstallerCommand from .installer_command import InstallerCommand
...@@ -6,6 +8,18 @@ class LockCommand(InstallerCommand): ...@@ -6,6 +8,18 @@ class LockCommand(InstallerCommand):
name = "lock" name = "lock"
description = "Locks the project dependencies." description = "Locks the project dependencies."
options = [
option(
"no-update", None, "Do not update locked versions, only refresh lock file."
),
option(
"check",
None,
"Check that the <comment>poetry.lock</> file corresponds to the current version "
"of <comment>pyproject.toml</>.",
),
]
help = """ help = """
The <info>lock</info> command reads the <comment>pyproject.toml</> file from the 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</> current directory, processes it, and locks the dependencies in the <comment>poetry.lock</>
...@@ -16,11 +30,18 @@ file. ...@@ -16,11 +30,18 @@ file.
loggers = ["poetry.repositories.pypi_repository"] loggers = ["poetry.repositories.pypi_repository"]
def handle(self): def handle(self) -> int:
self._installer.use_executor( self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False) self.poetry.config.get("experimental.new-installer", False)
) )
self._installer.lock() if self.option("check"):
return (
0
if self.poetry.locker.is_locked() and self.poetry.locker.is_fresh()
else 1
)
self._installer.lock(update=not self.option("no-update"))
return self._installer.run() return self._installer.run()
import sys import sys
from cleo import argument from cleo.helpers import argument
from cleo import option from cleo.helpers import option
from poetry.utils.helpers import module_name from poetry.utils.helpers import module_name
...@@ -19,11 +19,12 @@ class NewCommand(Command): ...@@ -19,11 +19,12 @@ class NewCommand(Command):
option("src", None, "Use the src layout for the project."), option("src", None, "Use the src layout for the project."),
] ]
def handle(self): def handle(self) -> None:
from poetry.core.semver import parse_constraint from pathlib import Path
from poetry.core.semver.helpers import parse_constraint
from poetry.core.vcs.git import GitConfig from poetry.core.vcs.git import GitConfig
from poetry.layouts import layout from poetry.layouts import layout
from poetry.utils._compat import Path
from poetry.utils.env import SystemEnv from poetry.utils.env import SystemEnv
if self.option("src"): if self.option("src"):
......
from cleo import option from pathlib import Path
from typing import Optional
from poetry.utils._compat import Path from cleo.helpers import option
from .command import Command from .command import Command
...@@ -40,7 +41,7 @@ the config command. ...@@ -40,7 +41,7 @@ the config command.
loggers = ["poetry.masonry.publishing.publisher"] loggers = ["poetry.masonry.publishing.publisher"]
def handle(self): def handle(self) -> Optional[int]:
from poetry.publishing.publisher import Publisher from poetry.publishing.publisher import Publisher
publisher = Publisher(self.poetry, self.io) publisher = Publisher(self.poetry, self.io)
......
from cleo import argument from cleo.helpers import argument
from cleo import option from cleo.helpers import option
from .installer_command import InstallerCommand from .installer_command import InstallerCommand
...@@ -27,7 +27,7 @@ list of installed packages ...@@ -27,7 +27,7 @@ list of installed packages
loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"] loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self): def handle(self) -> int:
packages = self.argument("packages") packages = self.argument("packages")
is_dev = self.option("dev") is_dev = self.option("dev")
......
from cleo import argument from typing import TYPE_CHECKING
from typing import Any
from typing import Union
from cleo.helpers import argument
from .env_command import EnvCommand from .env_command import EnvCommand
if TYPE_CHECKING:
from poetry.core.masonry.utils.module import Module
class RunCommand(EnvCommand): class RunCommand(EnvCommand):
name = "run" name = "run"
...@@ -12,14 +20,7 @@ class RunCommand(EnvCommand): ...@@ -12,14 +20,7 @@ class RunCommand(EnvCommand):
argument("args", "The command and arguments/options to run.", multiple=True) argument("args", "The command and arguments/options to run.", multiple=True)
] ]
def __init__(self): # type: () -> None def handle(self) -> Any:
from poetry.console.args.run_args_parser import RunArgsParser
super(RunCommand, self).__init__()
self.config.set_args_parser(RunArgsParser())
def handle(self):
args = self.argument("args") args = self.argument("args")
script = args[0] script = args[0]
scripts = self.poetry.local_config.get("scripts") scripts = self.poetry.local_config.get("scripts")
...@@ -29,7 +30,18 @@ class RunCommand(EnvCommand): ...@@ -29,7 +30,18 @@ class RunCommand(EnvCommand):
return self.env.execute(*args) return self.env.execute(*args)
def run_script(self, script, args): @property
def _module(self) -> "Module":
from poetry.core.masonry.utils.module import Module
poetry = self.poetry
package = poetry.package
path = poetry.file.parent
module = Module(package.name, path.as_posix(), package.packages)
return module
def run_script(self, script: Union[str, dict], args: str) -> Any:
if isinstance(script, dict): if isinstance(script, dict):
script = script["callable"] script = script["callable"]
...@@ -47,14 +59,3 @@ class RunCommand(EnvCommand): ...@@ -47,14 +59,3 @@ class RunCommand(EnvCommand):
] ]
return self.env.execute(*cmd) return self.env.execute(*cmd)
@property
def _module(self):
from poetry.core.masonry.utils.module import Module
poetry = self.poetry
package = poetry.package
path = poetry.file.parent
module = Module(package.name, path.as_posix(), package.packages)
return module
from cleo import argument from cleo.helpers import argument
from .command import Command from .command import Command
...@@ -10,7 +10,7 @@ class SearchCommand(Command): ...@@ -10,7 +10,7 @@ class SearchCommand(Command):
arguments = [argument("tokens", "The tokens to search for.", multiple=True)] arguments = [argument("tokens", "The tokens to search for.", multiple=True)]
def handle(self): def handle(self) -> None:
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
results = PyPiRepository().search(self.argument("tokens")) results = PyPiRepository().search(self.argument("tokens"))
......
from ..command import Command
from .update import SelfUpdateCommand
class SelfCommand(Command):
name = "self"
description = "Interact with Poetry directly."
commands = [SelfUpdateCommand()]
def handle(self):
return self.call("help", self._config.name)
...@@ -11,19 +11,21 @@ import tarfile ...@@ -11,19 +11,21 @@ import tarfile
from functools import cmp_to_key from functools import cmp_to_key
from gzip import GzipFile from gzip import GzipFile
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from urllib.error import HTTPError
from urllib.request import urlopen
from cleo import argument from cleo.helpers import argument
from cleo import option from cleo.helpers import option
from ..command import Command from ..command import Command
try: if TYPE_CHECKING:
from urllib.error import HTTPError from poetry.core.packages.package import Package
from urllib.request import urlopen from poetry.core.semver.version import Version
except ImportError:
from urllib2 import HTTPError
from urllib2 import urlopen
BIN = """# -*- coding: utf-8 -*- BIN = """# -*- coding: utf-8 -*-
...@@ -49,7 +51,7 @@ BAT = '@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n' ...@@ -49,7 +51,7 @@ BAT = '@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n'
class SelfUpdateCommand(Command): class SelfUpdateCommand(Command):
name = "update" name = "self update"
description = "Updates Poetry to the latest version." description = "Updates Poetry to the latest version."
arguments = [argument("version", "The version to update to.", optional=True)] arguments = [argument("version", "The version to update to.", optional=True)]
...@@ -59,26 +61,27 @@ class SelfUpdateCommand(Command): ...@@ -59,26 +61,27 @@ class SelfUpdateCommand(Command):
BASE_URL = REPOSITORY_URL + "/releases/download" BASE_URL = REPOSITORY_URL + "/releases/download"
@property @property
def home(self): def home(self) -> Path:
from poetry.utils._compat import Path from pathlib import Path
return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser() return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser()
@property @property
def bin(self): def bin(self) -> Path:
return self.home / "bin" return self.home / "bin"
@property @property
def lib(self): def lib(self) -> Path:
return self.home / "lib" return self.home / "lib"
@property @property
def lib_backup(self): def lib_backup(self) -> Path:
return self.home / "lib-backup" return self.home / "lib-backup"
def handle(self): def handle(self) -> None:
from poetry.__version__ import __version__ from poetry.__version__ import __version__
from poetry.core.semver import Version from poetry.core.packages.dependency import Dependency
from poetry.core.semver.version import Version
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
self._check_recommended_installation() self._check_recommended_installation()
...@@ -89,7 +92,7 @@ class SelfUpdateCommand(Command): ...@@ -89,7 +92,7 @@ class SelfUpdateCommand(Command):
repo = PyPiRepository(fallback=False) repo = PyPiRepository(fallback=False)
packages = repo.find_packages( packages = repo.find_packages(
"poetry", version, allow_prereleases=self.option("preview") Dependency("poetry", version, allows_prereleases=self.option("preview"))
) )
if not packages: if not packages:
self.line("No release found for the specified version") self.line("No release found for the specified version")
...@@ -127,7 +130,7 @@ class SelfUpdateCommand(Command): ...@@ -127,7 +130,7 @@ class SelfUpdateCommand(Command):
self.update(release) self.update(release)
def update(self, release): def update(self, release: "Package") -> None:
version = release.version version = release.version
self.line("Updating to <info>{}</info>".format(version)) self.line("Updating to <info>{}</info>".format(version))
...@@ -163,7 +166,7 @@ class SelfUpdateCommand(Command): ...@@ -163,7 +166,7 @@ class SelfUpdateCommand(Command):
) )
) )
def _update(self, version): def _update(self, version: "Version") -> None:
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
release_name = self._get_release_name(version) release_name = self._get_release_name(version)
...@@ -233,37 +236,31 @@ class SelfUpdateCommand(Command): ...@@ -233,37 +236,31 @@ class SelfUpdateCommand(Command):
finally: finally:
gz.close() gz.close()
def process(self, *args): def process(self, *args: Any) -> str:
return subprocess.check_output(list(args), stderr=subprocess.STDOUT) return subprocess.check_output(list(args), stderr=subprocess.STDOUT)
def _check_recommended_installation(self): def _check_recommended_installation(self) -> None:
from poetry.utils._compat import Path from pathlib import Path
from poetry.console.exceptions import PoetrySimpleConsoleException
current = Path(__file__) current = Path(__file__)
try: try:
current.relative_to(self.home) current.relative_to(self.home)
except ValueError: except ValueError:
raise RuntimeError( raise PoetrySimpleConsoleException(
"Poetry was not installed with the recommended installer. " "Poetry was not installed with the recommended installer, "
"Cannot update automatically." "so it cannot be updated automatically."
) )
def _get_release_name(self, version): def _get_release_name(self, version: "Version") -> str:
platform = sys.platform platform = sys.platform
if platform == "linux2": if platform == "linux2":
platform = "linux" platform = "linux"
return "poetry-{}-{}".format(version, platform) return "poetry-{}-{}".format(version, platform)
def _bin_path(self, base_path, bin): def make_bin(self) -> None:
from poetry.utils._compat import WINDOWS
if WINDOWS:
return (base_path / "Scripts" / bin).with_suffix(".exe")
return base_path / "bin" / bin
def make_bin(self):
from poetry.utils._compat import WINDOWS from poetry.utils._compat import WINDOWS
self.bin.mkdir(0o755, parents=True, exist_ok=True) self.bin.mkdir(0o755, parents=True, exist_ok=True)
...@@ -292,7 +289,7 @@ class SelfUpdateCommand(Command): ...@@ -292,7 +289,7 @@ class SelfUpdateCommand(Command):
st = os.stat(str(self.bin.joinpath("poetry"))) st = os.stat(str(self.bin.joinpath("poetry")))
os.chmod(str(self.bin.joinpath("poetry")), st.st_mode | stat.S_IEXEC) os.chmod(str(self.bin.joinpath("poetry")), st.st_mode | stat.S_IEXEC)
def _which_python(self): def _which_python(self) -> str:
""" """
Decides which python executable we'll embed in the launcher script. Decides which python executable we'll embed in the launcher script.
""" """
......
...@@ -16,7 +16,7 @@ class ShellCommand(EnvCommand): ...@@ -16,7 +16,7 @@ class ShellCommand(EnvCommand):
If one doesn't exist yet, it will be created. If one doesn't exist yet, it will be created.
""" """
def handle(self): def handle(self) -> None:
from poetry.utils.shell import Shell from poetry.utils.shell import Shell
# Check if it's already activated or doesn't exist and won't be created # Check if it's already activated or doesn't exist and won't be created
......
from cleo import argument from cleo.helpers import argument
from cleo import option from cleo.helpers import option
from .installer_command import InstallerCommand from .installer_command import InstallerCommand
...@@ -27,7 +27,7 @@ class UpdateCommand(InstallerCommand): ...@@ -27,7 +27,7 @@ class UpdateCommand(InstallerCommand):
loggers = ["poetry.repositories.pypi_repository"] loggers = ["poetry.repositories.pypi_repository"]
def handle(self): def handle(self) -> int:
packages = self.argument("packages") packages = self.argument("packages")
self._installer.use_executor( self._installer.use_executor(
......
from cleo import argument from typing import TYPE_CHECKING
from cleo import option
from cleo.helpers import argument
from cleo.helpers import option
from .command import Command from .command import Command
if TYPE_CHECKING:
from poetry.core.semver.version import Version
class VersionCommand(Command): class VersionCommand(Command):
name = "version" name = "version"
...@@ -40,7 +46,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -40,7 +46,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
"prerelease", "prerelease",
} }
def handle(self): def handle(self) -> None:
version = self.argument("version") version = self.argument("version")
if version: if version:
...@@ -48,11 +54,14 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -48,11 +54,14 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
self.poetry.package.pretty_version, version self.poetry.package.pretty_version, version
) )
self.line( if self.option("short"):
"Bumping version from <b>{}</> to <fg=green>{}</>".format( self.line("{}".format(version))
self.poetry.package.pretty_version, version else:
self.line(
"Bumping version from <b>{}</> to <fg=green>{}</>".format(
self.poetry.package.pretty_version, version
)
) )
)
content = self.poetry.file.read() content = self.poetry.file.read()
poetry_content = content["tool"]["poetry"] poetry_content = content["tool"]["poetry"]
...@@ -69,8 +78,8 @@ patch, minor, major, prepatch, preminor, premajor, prerelease. ...@@ -69,8 +78,8 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
) )
) )
def increment_version(self, version, rule): def increment_version(self, version: str, rule: str) -> "Version":
from poetry.core.semver import Version from poetry.core.semver.version import Version
try: try:
version = Version.parse(version) version = Version.parse(version)
......
from .application_config import ApplicationConfig
import logging
from typing import Any
from cleo.config import ApplicationConfig as BaseApplicationConfig
from clikit.api.application.application import Application
from clikit.api.args.raw_args import RawArgs
from clikit.api.event import PRE_HANDLE
from clikit.api.event import PreHandleEvent
from clikit.api.event import PreResolveEvent
from clikit.api.event.event_dispatcher import EventDispatcher
from clikit.api.exceptions import CliKitException
from clikit.api.formatter import Style
from clikit.api.io import Input
from clikit.api.io import InputStream
from clikit.api.io import Output
from clikit.api.io import OutputStream
from clikit.api.io.flags import DEBUG
from clikit.api.io.flags import VERBOSE
from clikit.api.io.flags import VERY_VERBOSE
from clikit.api.io.io import IO
from clikit.formatter import AnsiFormatter
from clikit.formatter import PlainFormatter
from clikit.io.input_stream import StandardInputStream
from clikit.io.output_stream import ErrorOutputStream
from clikit.io.output_stream import StandardOutputStream
from poetry.console.commands.command import Command
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.installer_command import InstallerCommand
from poetry.console.logging.io_formatter import IOFormatter
from poetry.console.logging.io_handler import IOHandler
from poetry.utils._compat import PY36
class ApplicationConfig(BaseApplicationConfig):
def configure(self):
super(ApplicationConfig, self).configure()
self.add_style(Style("c1").fg("cyan"))
self.add_style(Style("c2").fg("default").bold())
self.add_style(Style("info").fg("blue"))
self.add_style(Style("comment").fg("green"))
self.add_style(Style("error").fg("red").bold())
self.add_style(Style("warning").fg("yellow").bold())
self.add_style(Style("debug").fg("default").dark())
self.add_style(Style("success").fg("green"))
# Dark variants
self.add_style(Style("c1_dark").fg("cyan").dark())
self.add_style(Style("c2_dark").fg("default").bold().dark())
self.add_style(Style("success_dark").fg("green").dark())
self.add_event_listener(PRE_HANDLE, self.register_command_loggers)
self.add_event_listener(PRE_HANDLE, self.set_env)
self.add_event_listener(PRE_HANDLE, self.set_installer)
if PY36:
from poetry.mixology.solutions.providers import (
PythonRequirementSolutionProvider,
)
self._solution_provider_repository.register_solution_providers(
[PythonRequirementSolutionProvider]
)
def register_command_loggers(
self, event, event_name, _
): # type: (PreHandleEvent, str, Any) -> None
command = event.command.config.handler
if not isinstance(command, Command):
return
io = event.io
loggers = [
"poetry.packages.locker",
"poetry.packages.package",
"poetry.utils.password_manager",
]
loggers += command.loggers
handler = IOHandler(io)
handler.setFormatter(IOFormatter())
for logger in loggers:
logger = logging.getLogger(logger)
logger.handlers = [handler]
level = logging.WARNING
# The builders loggers are special and we can actually
# start at the INFO level.
if logger.name.startswith("poetry.core.masonry.builders"):
level = logging.INFO
if io.is_debug():
level = logging.DEBUG
elif io.is_very_verbose() or io.is_verbose():
level = logging.INFO
logger.setLevel(level)
def set_env(self, event, event_name, _): # type: (PreHandleEvent, str, Any) -> None
from poetry.utils.env import EnvManager
command = event.command.config.handler # type: EnvCommand
if not isinstance(command, EnvCommand):
return
if command.env is not None:
return
io = event.io
poetry = command.poetry
env_manager = EnvManager(poetry)
env = env_manager.create_venv(io)
if env.is_venv() and io.is_verbose():
io.write_line("Using virtualenv: <comment>{}</>".format(env.path))
command.set_env(env)
def set_installer(
self, event, event_name, _
): # type: (PreHandleEvent, str, Any) -> None
command = event.command.config.handler # type: InstallerCommand
if not isinstance(command, InstallerCommand):
return
# If the command already has an installer
# we skip this step
if command.installer is not None:
return
from poetry.installation.installer import Installer
poetry = command.poetry
installer = Installer(
event.io,
command.env,
poetry.package,
poetry.locker,
poetry.pool,
poetry.config,
)
installer.use_executor(poetry.config.get("experimental.new-installer", False))
command.set_installer(installer)
def resolve_help_command(
self, event, event_name, dispatcher
): # type: (PreResolveEvent, str, EventDispatcher) -> None
args = event.raw_args
application = event.application
if args.has_option_token("-h") or args.has_option_token("--help"):
from clikit.api.resolver import ResolvedCommand
try:
resolved_command = self.command_resolver.resolve(args, application)
except CliKitException:
# We weren't able to resolve the command,
# due to a parse error most likely,
# so we fall back on the default behavior
return super(ApplicationConfig, self).resolve_help_command(
event, event_name, dispatcher
)
# If the current command is the run one, skip option
# check and interpret them as part of the executed command
if resolved_command.command.name == "run":
event.set_resolved_command(resolved_command)
return event.stop_propagation()
command = application.get_command("help")
# Enable lenient parsing
parsed_args = command.parse(args, True)
event.set_resolved_command(ResolvedCommand(command, parsed_args))
event.stop_propagation()
def create_io(
self,
application,
args,
input_stream=None,
output_stream=None,
error_stream=None,
): # type: (Application, RawArgs, InputStream, OutputStream, OutputStream) -> IO
if input_stream is None:
input_stream = StandardInputStream()
if output_stream is None:
output_stream = StandardOutputStream()
if error_stream is None:
error_stream = ErrorOutputStream()
style_set = application.config.style_set
if output_stream.supports_ansi():
output_formatter = AnsiFormatter(style_set)
else:
output_formatter = PlainFormatter(style_set)
if error_stream.supports_ansi():
error_formatter = AnsiFormatter(style_set)
else:
error_formatter = PlainFormatter(style_set)
io = self.io_class(
Input(input_stream),
Output(output_stream, output_formatter),
Output(error_stream, error_formatter),
)
resolved_command = application.resolve_command(args)
# If the current command is the run one, skip option
# check and interpret them as part of the executed command
if resolved_command.command.name == "run":
return io
if args.has_option_token("--no-ansi"):
formatter = PlainFormatter(style_set)
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
elif args.has_option_token("--ansi"):
formatter = AnsiFormatter(style_set, True)
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
if args.has_option_token("-vvv") or self.is_debug():
io.set_verbosity(DEBUG)
elif args.has_option_token("-vv"):
io.set_verbosity(VERY_VERBOSE)
elif args.has_option_token("-v"):
io.set_verbosity(VERBOSE)
if args.has_option_token("--quiet") or args.has_option_token("-q"):
io.set_quiet(True)
if args.has_option_token("--no-interaction") or args.has_option_token("-n"):
io.set_interactive(False)
return io
from cleo.exceptions import CleoSimpleException
class PoetrySimpleConsoleException(CleoSimpleException):
pass
from typing import List
from typing import Optional
from typing import Union
from cleo.io.inputs.argv_input import ArgvInput
from cleo.io.inputs.definition import Definition
class RunArgvInput(ArgvInput):
def __init__(
self, argv: Optional[List[str]] = None, definition: Optional[Definition] = None
) -> None:
super().__init__(argv, definition=definition)
self._parameter_options = []
@property
def first_argument(self) -> Optional[str]:
return "run"
def add_parameter_option(self, name: str) -> None:
self._parameter_options.append(name)
def has_parameter_option(
self, values: Union[str, List[str]], only_params: bool = False
) -> bool:
if not isinstance(values, list):
values = [values]
for token in self._tokens:
if only_params and token == "--":
return False
for value in values:
if value not in self._parameter_options:
continue
# Options with values:
# For long options, test for '--option=' at beginning
# For short options, test for '-o' at beginning
if value.find("--") == 0:
leading = value + "="
else:
leading = value
if token == value or leading != "" and token.find(leading) == 0:
return True
return False
def _parse(self) -> None:
parse_options = True
self._parsed = self._tokens[:]
try:
token = self._parsed.pop(0)
except IndexError:
token = None
while token is not None:
if parse_options and token == "":
self._parse_argument(token)
elif parse_options and token == "--":
parse_options = False
elif parse_options and token.find("--") == 0:
if token in self._parameter_options:
self._parse_long_option(token)
else:
self._parse_argument(token)
elif parse_options and token[0] == "-" and token != "-":
if token in self._parameter_options:
self._parse_short_option(token)
else:
self._parse_argument(token)
else:
self._parse_argument(token)
try:
token = self._parsed.pop(0)
except IndexError:
token = None
...@@ -4,7 +4,7 @@ from .formatter import Formatter ...@@ -4,7 +4,7 @@ from .formatter import Formatter
class BuilderLogFormatter(Formatter): class BuilderLogFormatter(Formatter):
def format(self, msg): # type: (str) -> str def format(self, msg: str) -> str:
if msg.startswith("Building "): if msg.startswith("Building "):
msg = re.sub("Building (.+)", " - Building <info>\\1</info>", msg) msg = re.sub("Building (.+)", " - Building <info>\\1</info>", msg)
elif msg.startswith("Built "): elif msg.startswith("Built "):
......
...@@ -2,5 +2,5 @@ import logging ...@@ -2,5 +2,5 @@ import logging
class Formatter(object): class Formatter(object):
def format(self, record): # type: (logging.LogRecord) -> str def format(self, record: logging.LogRecord) -> str:
raise NotImplementedError() raise NotImplementedError()
import logging import logging
from typing import TYPE_CHECKING
from .formatters import FORMATTERS from .formatters import FORMATTERS
if TYPE_CHECKING:
from logging import LogRecord
class IOFormatter(logging.Formatter): class IOFormatter(logging.Formatter):
_colors = { _colors = {
...@@ -12,7 +18,7 @@ class IOFormatter(logging.Formatter): ...@@ -12,7 +18,7 @@ class IOFormatter(logging.Formatter):
"info": "fg=blue", "info": "fg=blue",
} }
def format(self, record): def format(self, record: "LogRecord") -> str:
if not record.exc_info: if not record.exc_info:
level = record.levelname.lower() level = record.levelname.lower()
msg = record.msg msg = record.msg
......
import logging import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from logging import LogRecord
from cleo.io.io import IO # noqa
class IOHandler(logging.Handler): class IOHandler(logging.Handler):
def __init__(self, io): def __init__(self, io: "IO") -> None:
self._io = io self._io = io
super(IOHandler, self).__init__() super(IOHandler, self).__init__()
def emit(self, record): def emit(self, record: "LogRecord") -> None:
try: try:
msg = self.format(record) msg = self.format(record)
level = record.levelname.lower() level = record.levelname.lower()
err = level in ("warning", "error", "exception", "critical") err = level in ("warning", "error", "exception", "critical")
if err: if err:
self._io.error_line(msg) self._io.write_error_line(msg)
else: else:
self._io.write_line(msg) self._io.write_line(msg)
except Exception: except Exception:
......
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from clikit.api.io.io import IO from cleo.io.io import IO
from cleo.io.null_io import NullIO
from poetry.core.factory import Factory as BaseFactory from poetry.core.factory import Factory as BaseFactory
from poetry.core.utils.toml_file import TomlFile from poetry.core.toml.file import TOMLFile
from .config.config import Config from .config.config import Config
from .config.file_config_source import FileConfigSource from .config.file_config_source import FileConfigSource
from .io.null_io import NullIO
from .locations import CONFIG_DIR from .locations import CONFIG_DIR
from .packages.locker import Locker from .packages.locker import Locker
from .poetry import Poetry from .poetry import Poetry
from .repositories.pypi_repository import PyPiRepository from .repositories.pypi_repository import PyPiRepository
from .utils._compat import Path
if TYPE_CHECKING:
from .repositories.legacy_repository import LegacyRepository
class Factory(BaseFactory): class Factory(BaseFactory):
...@@ -25,8 +30,8 @@ class Factory(BaseFactory): ...@@ -25,8 +30,8 @@ class Factory(BaseFactory):
""" """
def create_poetry( def create_poetry(
self, cwd=None, io=None self, cwd: Optional[Path] = None, io: Optional[IO] = None
): # type: (Optional[Path], Optional[IO]) -> Poetry ) -> Poetry:
if io is None: if io is None:
io = NullIO() io = NullIO()
...@@ -40,7 +45,7 @@ class Factory(BaseFactory): ...@@ -40,7 +45,7 @@ class Factory(BaseFactory):
config = self.create_config(io) config = self.create_config(io)
# Loading local configuration # Loading local configuration
local_config_file = TomlFile(base_poetry.file.parent / "poetry.toml") local_config_file = TOMLFile(base_poetry.file.parent / "poetry.toml")
if local_config_file.exists(): if local_config_file.exists():
if io.is_debug(): if io.is_debug():
io.write_line( io.write_line(
...@@ -49,6 +54,18 @@ class Factory(BaseFactory): ...@@ -49,6 +54,18 @@ class Factory(BaseFactory):
config.merge(local_config_file.read()) config.merge(local_config_file.read())
# Load local sources
repositories = {}
existing_repositories = config.get("repositories", {})
for source in base_poetry.pyproject.poetry_config.get("source", []):
name = source.get("name")
url = source.get("url")
if name and url:
if name not in existing_repositories:
repositories[name] = {"url": url}
config.merge({"repositories": repositories})
poetry = Poetry( poetry = Poetry(
base_poetry.file.path, base_poetry.file.path,
base_poetry.local_config, base_poetry.local_config,
...@@ -58,7 +75,8 @@ class Factory(BaseFactory): ...@@ -58,7 +75,8 @@ class Factory(BaseFactory):
) )
# Configuring sources # Configuring sources
for source in poetry.local_config.get("source", []): sources = poetry.local_config.get("source", [])
for source in sources:
repository = self.create_legacy_repository(source, config) repository = self.create_legacy_repository(source, config)
is_default = source.get("default", False) is_default = source.get("default", False)
is_secondary = source.get("secondary", False) is_secondary = source.get("secondary", False)
...@@ -78,7 +96,8 @@ class Factory(BaseFactory): ...@@ -78,7 +96,8 @@ class Factory(BaseFactory):
# Always put PyPI last to prefer private repositories # Always put PyPI last to prefer private repositories
# but only if we have no other default source # but only if we have no other default source
if not poetry.pool.has_default(): if not poetry.pool.has_default():
poetry.pool.add_repository(PyPiRepository(), True) has_sources = bool(sources)
poetry.pool.add_repository(PyPiRepository(), not has_sources, has_sources)
else: else:
if io.is_debug(): if io.is_debug():
io.write_line("Deactivating the PyPI repository") io.write_line("Deactivating the PyPI repository")
...@@ -86,13 +105,13 @@ class Factory(BaseFactory): ...@@ -86,13 +105,13 @@ class Factory(BaseFactory):
return poetry return poetry
@classmethod @classmethod
def create_config(cls, io=None): # type: (Optional[IO]) -> Config def create_config(cls, io: Optional[IO] = None) -> Config:
if io is None: if io is None:
io = NullIO() io = NullIO()
config = Config() config = Config()
# Load global config # Load global config
config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml")
if config_file.exists(): if config_file.exists():
if io.is_debug(): if io.is_debug():
io.write_line( io.write_line(
...@@ -106,7 +125,7 @@ class Factory(BaseFactory): ...@@ -106,7 +125,7 @@ class Factory(BaseFactory):
config.set_config_source(FileConfigSource(config_file)) config.set_config_source(FileConfigSource(config_file))
# Load global auth config # Load global auth config
auth_config_file = TomlFile(Path(CONFIG_DIR) / "auth.toml") auth_config_file = TOMLFile(Path(CONFIG_DIR) / "auth.toml")
if auth_config_file.exists(): if auth_config_file.exists():
if io.is_debug(): if io.is_debug():
io.write_line( io.write_line(
...@@ -122,13 +141,11 @@ class Factory(BaseFactory): ...@@ -122,13 +141,11 @@ class Factory(BaseFactory):
return config return config
def create_legacy_repository( def create_legacy_repository(
self, source, auth_config self, source: Dict[str, str], auth_config: Config
): # type: (Dict[str, str], Config) -> LegacyRepository ) -> "LegacyRepository":
from .repositories.auth import Auth
from .repositories.legacy_repository import LegacyRepository from .repositories.legacy_repository import LegacyRepository
from .utils.helpers import get_cert from .utils.helpers import get_cert
from .utils.helpers import get_client_cert from .utils.helpers import get_client_cert
from .utils.password_manager import PasswordManager
if "url" in source: if "url" in source:
# PyPI-like repository # PyPI-like repository
...@@ -137,19 +154,13 @@ class Factory(BaseFactory): ...@@ -137,19 +154,13 @@ class Factory(BaseFactory):
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 = password_manager.get_http_auth(name)
if credentials:
auth = Auth(url, credentials["username"], credentials["password"])
else:
auth = None
return LegacyRepository( return LegacyRepository(
name, name,
url, url,
auth=auth, config=auth_config,
cert=get_cert(auth_config, name), cert=get_cert(auth_config, name),
client_cert=get_client_cert(auth_config, name), client_cert=get_client_cert(auth_config, name),
) )
import logging
import time
import urllib.parse
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Any
from typing import Optional
from typing import Tuple
import requests
import requests.auth
import requests.exceptions
from poetry.utils._compat import urlparse from poetry.exceptions import PoetryException
from poetry.utils.password_manager import PasswordManager from poetry.utils.password_manager import PasswordManager
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any from cleo.io.io import IO
from typing import Optional
from typing import Tuple
from clikit.api.io import IO
from requests import Request # noqa
from requests import Response # noqa
from requests import Session # noqa
from poetry.config.config import Config from poetry.config.config import Config
logger = logging.getLogger()
class Authenticator(object): class Authenticator(object):
def __init__(self, config, io): # type: (Config, IO) -> None def __init__(self, config: "Config", io: Optional["IO"] = None) -> None:
self._config = config self._config = config
self._io = io self._io = io
self._session = None self._session = None
self._credentials = {} self._credentials = {}
self._password_manager = PasswordManager(self._config) self._password_manager = PasswordManager(self._config)
@property def _log(self, message: str, level: str = "debug") -> None:
def session(self): # type: () -> Session if self._io is not None:
from requests import Session # noqa self._io.write_line(
"<{level:s}>{message:s}</{level:s}>".format(
message=message, level=level
)
)
else:
getattr(logger, level, logger.debug)(message)
@property
def session(self) -> requests.Session:
if self._session is None: if self._session is None:
self._session = Session() self._session = requests.Session()
return self._session return self._session
def request(self, method, url, **kwargs): # type: (str, str, Any) -> Response def request(self, method: str, url: str, **kwargs: Any) -> requests.Response:
from requests import Request # noqa request = requests.Request(method, url)
from requests.auth import HTTPBasicAuth username, password = self.get_credentials_for_url(url)
request = Request(method, url)
username, password = self._get_credentials_for_url(url)
if username is not None and password is not None: if username is not None and password is not None:
request = HTTPBasicAuth(username, password)(request) request = requests.auth.HTTPBasicAuth(username, password)(request)
session = self.session session = self.session
prepared_request = session.prepare_request(request) prepared_request = session.prepare_request(request)
...@@ -63,16 +74,35 @@ class Authenticator(object): ...@@ -63,16 +74,35 @@ class Authenticator(object):
"allow_redirects": kwargs.get("allow_redirects", True), "allow_redirects": kwargs.get("allow_redirects", True),
} }
send_kwargs.update(settings) send_kwargs.update(settings)
resp = session.send(prepared_request, **send_kwargs)
resp.raise_for_status() attempt = 0
while True:
is_last_attempt = attempt >= 5
try:
resp = session.send(prepared_request, **send_kwargs)
except (requests.exceptions.ConnectionError, OSError) as e:
if is_last_attempt:
raise e
else:
if resp.status_code not in [502, 503, 504] or is_last_attempt:
resp.raise_for_status()
return resp
if not is_last_attempt:
attempt += 1
delay = 0.5 * attempt
self._log(
"Retrying HTTP request in {} seconds.".format(delay), level="debug"
)
time.sleep(delay)
continue
return resp # this should never really be hit under any sane circumstance
raise PoetryException("Failed HTTP {} request", method.upper())
def _get_credentials_for_url( def get_credentials_for_url(self, url: str) -> Tuple[Optional[str], Optional[str]]:
self, url parsed_url = urllib.parse.urlsplit(url)
): # type: (str) -> Tuple[Optional[str], Optional[str]]
parsed_url = urlparse.urlsplit(url)
netloc = parsed_url.netloc netloc = parsed_url.netloc
...@@ -95,7 +125,7 @@ class Authenticator(object): ...@@ -95,7 +125,7 @@ class Authenticator(object):
credentials = auth, None credentials = auth, None
credentials = tuple( credentials = tuple(
None if x is None else urlparse.unquote(x) for x in credentials None if x is None else urllib.parse.unquote(x) for x in credentials
) )
if credentials[0] is not None or credentials[1] is not None: if credentials[0] is not None or credentials[1] is not None:
...@@ -106,10 +136,11 @@ class Authenticator(object): ...@@ -106,10 +136,11 @@ class Authenticator(object):
return credentials[0], credentials[1] return credentials[0], credentials[1]
def _get_credentials_for_netloc_from_config( def _get_credentials_for_netloc_from_config(
self, netloc self, netloc: str
): # type: (str) -> Tuple[Optional[str], Optional[str]] ) -> Tuple[Optional[str], Optional[str]]:
credentials = (None, None) credentials = (None, None)
for repository_name in self._config.get("http-basic", {}):
for repository_name in self._config.get("repositories", []):
repository_config = self._config.get( repository_config = self._config.get(
"repositories.{}".format(repository_name) "repositories.{}".format(repository_name)
) )
...@@ -120,7 +151,7 @@ class Authenticator(object): ...@@ -120,7 +151,7 @@ class Authenticator(object):
if not url: if not url:
continue continue
parsed_url = urlparse.urlsplit(url) parsed_url = urllib.parse.urlsplit(url)
if netloc == parsed_url.netloc: if netloc == parsed_url.netloc:
auth = self._password_manager.get_http_auth(repository_name) auth = self._password_manager.get_http_auth(repository_name)
......
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class BaseInstaller: class BaseInstaller:
def install(self, package): def install(self, package: "Package") -> None:
raise NotImplementedError raise NotImplementedError
def update(self, source, target): def update(self, source: "Package", target: "Package") -> None:
raise NotImplementedError raise NotImplementedError
def remove(self, package): def remove(self, package: "Package") -> None:
raise NotImplementedError raise NotImplementedError
import hashlib import hashlib
import json import json
from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import List
from typing import Optional
from poetry.core.packages.utils.link import Link from poetry.core.packages.utils.link import Link
from poetry.utils._compat import Path
from .chooser import InvalidWheelName from .chooser import InvalidWheelName
from .chooser import Wheel from .chooser import Wheel
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import List
from typing import Optional
from poetry.config.config import Config from poetry.config.config import Config
from poetry.utils.env import Env from poetry.utils.env import Env
class Chef: class Chef:
def __init__(self, config, env): # type: (Config, Env) -> None def __init__(self, config: "Config", env: "Env") -> None:
self._config = config self._config = config
self._env = env self._env = env
self._cache_dir = ( self._cache_dir = (
Path(config.get("cache-dir")).expanduser().joinpath("artifacts") Path(config.get("cache-dir")).expanduser().joinpath("artifacts")
) )
def prepare(self, archive): # type: (Path) -> Path def prepare(self, archive: Path) -> Path:
return archive return archive
def prepare_sdist(self, archive): # type: (Path) -> Path def prepare_sdist(self, archive: Path) -> Path:
return archive return archive
def prepare_wheel(self, archive): # type: (Path) -> Path def prepare_wheel(self, archive: Path) -> Path:
return archive return archive
def should_prepare(self, archive): # type: (Path) -> bool def should_prepare(self, archive: Path) -> bool:
return not self.is_wheel(archive) return not self.is_wheel(archive)
def is_wheel(self, archive): # type: (Path) -> bool def is_wheel(self, archive: Path) -> bool:
return archive.suffix == ".whl" return archive.suffix == ".whl"
def get_cached_archive_for_link(self, link): # type: (Link) -> Optional[Link] def get_cached_archive_for_link(self, link: Link) -> Optional[Link]:
# If the archive is already a wheel, there is no need to cache it. # If the archive is already a wheel, there is no need to cache it.
if link.is_wheel: if link.is_wheel:
pass pass
...@@ -74,7 +74,7 @@ class Chef: ...@@ -74,7 +74,7 @@ class Chef:
return min(candidates)[1] return min(candidates)[1]
def get_cached_archives_for_link(self, link): # type: (Link) -> List[Link] def get_cached_archives_for_link(self, link: Link) -> List[Link]:
cache_dir = self.get_cache_directory_for_link(link) cache_dir = self.get_cache_directory_for_link(link)
archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"] archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"]
...@@ -85,7 +85,7 @@ class Chef: ...@@ -85,7 +85,7 @@ class Chef:
return links return links
def get_cache_directory_for_link(self, link): # type: (Link) -> Path def get_cache_directory_for_link(self, link: Link) -> Path:
key_parts = {"url": link.url_without_fragment} key_parts = {"url": link.url_without_fragment}
if link.hash_name is not None and link.hash is not None: if link.hash_name is not None and link.hash is not None:
......
import re import re
from typing import List from typing import List
from typing import Optional
from typing import Tuple from typing import Tuple
from packaging.tags import Tag from packaging.tags import Tag
...@@ -17,7 +18,7 @@ class InvalidWheelName(Exception): ...@@ -17,7 +18,7 @@ class InvalidWheelName(Exception):
class Wheel(object): class Wheel(object):
def __init__(self, filename): # type: (str) -> None def __init__(self, filename: str) -> None:
wheel_info = wheel_file_re.match(filename) wheel_info = wheel_file_re.match(filename)
if not wheel_info: if not wheel_info:
raise InvalidWheelName("{} is not a valid wheel filename.".format(filename)) raise InvalidWheelName("{} is not a valid wheel filename.".format(filename))
...@@ -34,12 +35,12 @@ class Wheel(object): ...@@ -34,12 +35,12 @@ class Wheel(object):
Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
} }
def get_minimum_supported_index(self, tags): def get_minimum_supported_index(self, tags: List[Tag]) -> Optional[int]:
indexes = [tags.index(t) for t in self.tags if t in tags] indexes = [tags.index(t) for t in self.tags if t in tags]
return min(indexes) if indexes else None return min(indexes) if indexes else None
def is_supported_by_environment(self, env): def is_supported_by_environment(self, env: Env) -> bool:
return bool(set(env.supported_tags).intersection(self.tags)) return bool(set(env.supported_tags).intersection(self.tags))
...@@ -48,11 +49,11 @@ class Chooser: ...@@ -48,11 +49,11 @@ class Chooser:
A Chooser chooses an appropriate release archive for packages. A Chooser chooses an appropriate release archive for packages.
""" """
def __init__(self, pool, env): # type: (Pool, Env) -> None def __init__(self, pool: Pool, env: Env) -> None:
self._pool = pool self._pool = pool
self._env = env self._env = env
def choose_for(self, package): # type: (Package) -> Link def choose_for(self, package: Package) -> Link:
""" """
Return the url of the selected archive for a given package. Return the url of the selected archive for a given package.
""" """
...@@ -63,7 +64,7 @@ class Chooser: ...@@ -63,7 +64,7 @@ class Chooser:
): ):
continue continue
if link.ext == ".egg": if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}:
continue continue
links.append(link) links.append(link)
...@@ -82,7 +83,7 @@ class Chooser: ...@@ -82,7 +83,7 @@ class Chooser:
return chosen return chosen
def _get_links(self, package): # type: (Package) -> List[Link] def _get_links(self, package: Package) -> List[Link]:
if not package.source_type: if not package.source_type:
if not self._pool.has_repository("pypi"): if not self._pool.has_repository("pypi"):
repository = self._pool.repositories[0] repository = self._pool.repositories[0]
...@@ -111,7 +112,7 @@ class Chooser: ...@@ -111,7 +112,7 @@ class Chooser:
return selected_links return selected_links
def _sort_key(self, package, link): # type: (Package, Link) -> Tuple def _sort_key(self, package: Package, link: Link) -> Tuple:
""" """
Function to pass as the `key` argument to a call to sorted() to sort Function to pass as the `key` argument to a call to sorted() to sort
InstallationCandidates by preference. InstallationCandidates by preference.
...@@ -169,9 +170,7 @@ class Chooser: ...@@ -169,9 +170,7 @@ class Chooser:
pri, pri,
) )
def _is_link_hash_allowed_for_package( def _is_link_hash_allowed_for_package(self, link: Link, package: Package) -> bool:
self, link, package
): # type: (Link, Package) -> bool
if not link.hash: if not link.hash:
return True return True
......
from typing import TYPE_CHECKING
from typing import List
from .base_installer import BaseInstaller from .base_installer import BaseInstaller
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class NoopInstaller(BaseInstaller): class NoopInstaller(BaseInstaller):
def __init__(self): def __init__(self) -> None:
self._installs = [] self._installs = []
self._updates = [] self._updates = []
self._removals = [] self._removals = []
@property @property
def installs(self): def installs(self) -> List["Package"]:
return self._installs return self._installs
@property @property
def updates(self): def updates(self) -> List["Package"]:
return self._updates return self._updates
@property @property
def removals(self): def removals(self) -> List["Package"]:
return self._removals return self._removals
def install(self, package): def install(self, package: "Package") -> None:
self._installs.append(package) self._installs.append(package)
def update(self, source, target): def update(self, source: "Package", target: "Package") -> None:
self._updates.append((source, target)) self._updates.append((source, target))
def remove(self, package): def remove(self, package: "Package") -> None:
self._removals.append(package) self._removals.append(package)
from typing import Union
from .install import Install from .install import Install
from .uninstall import Uninstall from .uninstall import Uninstall
from .update import Update from .update import Update
OperationTypes = Union[Install, Uninstall, Update]
from typing import TYPE_CHECKING
from typing import Optional
from .operation import Operation from .operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Install(Operation): class Install(Operation):
def __init__(self, package, reason=None, priority=0): def __init__(
self, package: "Package", reason: Optional[str] = None, priority: int = 0
) -> None:
super(Install, self).__init__(reason, priority=priority) super(Install, self).__init__(reason, priority=priority)
self._package = package self._package = package
@property @property
def package(self): def package(self) -> "Package":
return self._package return self._package
@property @property
def job_type(self): def job_type(self) -> str:
return "install" return "install"
def __str__(self): def __str__(self) -> str:
return "Installing {} ({})".format( return "Installing {} ({})".format(
self.package.pretty_name, self.format_version(self.package) self.package.pretty_name, self.format_version(self.package)
) )
def __repr__(self): def __repr__(self) -> str:
return "<Install {} ({})>".format( return "<Install {} ({})>".format(
self.package.pretty_name, self.format_version(self.package) self.package.pretty_name, self.format_version(self.package)
) )
# -*- coding: utf-8 -*- from typing import TYPE_CHECKING
from typing import Optional
from typing import Union
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Operation(object): class Operation(object):
def __init__( def __init__(self, reason: Optional[str] = None, priority: int = 0) -> None:
self, reason=None, priority=0
): # type: (Union[str, None], int) -> None
self._reason = reason self._reason = reason
self._skipped = False self._skipped = False
...@@ -14,39 +15,39 @@ class Operation(object): ...@@ -14,39 +15,39 @@ class Operation(object):
self._priority = priority self._priority = priority
@property @property
def job_type(self): # type: () -> str def job_type(self) -> str:
raise NotImplementedError raise NotImplementedError
@property @property
def reason(self): # type: () -> str def reason(self) -> str:
return self._reason return self._reason
@property @property
def skipped(self): # type: () -> bool def skipped(self) -> bool:
return self._skipped return self._skipped
@property @property
def skip_reason(self): # type: () -> Union[str, None] def skip_reason(self) -> Optional[str]:
return self._skip_reason return self._skip_reason
@property @property
def priority(self): # type: () -> int def priority(self) -> int:
return self._priority return self._priority
@property @property
def package(self): def package(self) -> "Package":
raise NotImplementedError() raise NotImplementedError()
def format_version(self, package): # type: (...) -> str def format_version(self, package: "Package") -> str:
return package.full_pretty_version return package.full_pretty_version
def skip(self, reason): # type: (str) -> Operation def skip(self, reason: str) -> "Operation":
self._skipped = True self._skipped = True
self._skip_reason = reason self._skip_reason = reason
return self return self
def unskip(self): # type: () -> Operation def unskip(self) -> "Operation":
self._skipped = False self._skipped = False
self._skip_reason = None self._skip_reason = None
......
from typing import TYPE_CHECKING
from typing import Optional
from .operation import Operation from .operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Uninstall(Operation): class Uninstall(Operation):
def __init__(self, package, reason=None, priority=float("inf")): def __init__(
self,
package: "Package",
reason: Optional[str] = None,
priority: int = float("inf"),
) -> None:
super(Uninstall, self).__init__(reason, priority=priority) super(Uninstall, self).__init__(reason, priority=priority)
self._package = package self._package = package
@property @property
def package(self): def package(self) -> "Package":
return self._package return self._package
@property @property
def job_type(self): def job_type(self) -> str:
return "uninstall" return "uninstall"
def __str__(self): def __str__(self) -> str:
return "Uninstalling {} ({})".format( return "Uninstalling {} ({})".format(
self.package.pretty_name, self.format_version(self._package) self.package.pretty_name, self.format_version(self._package)
) )
def __repr__(self): def __repr__(self) -> str:
return "<Uninstall {} ({})>".format( return "<Uninstall {} ({})>".format(
self.package.pretty_name, self.format_version(self.package) self.package.pretty_name, self.format_version(self.package)
) )
from typing import TYPE_CHECKING
from typing import Optional
from .operation import Operation from .operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Update(Operation): class Update(Operation):
def __init__(self, initial, target, reason=None, priority=0): def __init__(
self,
initial: "Package",
target: "Package",
reason: Optional[str] = None,
priority: int = 0,
) -> None:
self._initial_package = initial self._initial_package = initial
self._target_package = target self._target_package = target
super(Update, self).__init__(reason, priority=priority) super(Update, self).__init__(reason, priority=priority)
@property @property
def initial_package(self): def initial_package(self) -> "Package":
return self._initial_package return self._initial_package
@property @property
def target_package(self): def target_package(self) -> "Package":
return self._target_package return self._target_package
@property @property
def package(self): def package(self) -> "Package":
return self._target_package return self._target_package
@property @property
def job_type(self): def job_type(self) -> str:
return "update" return "update"
def __str__(self): def __str__(self) -> str:
return "Updating {} ({}) to {} ({})".format( return "Updating {} ({}) to {} ({})".format(
self.initial_package.pretty_name, self.initial_package.pretty_name,
self.format_version(self.initial_package), self.format_version(self.initial_package),
...@@ -32,7 +45,7 @@ class Update(Operation): ...@@ -32,7 +45,7 @@ class Update(Operation):
self.format_version(self.target_package), self.format_version(self.target_package),
) )
def __repr__(self): def __repr__(self) -> str:
return "<Update {} ({}) to {} ({})>".format( return "<Update {} ({}) to {} ({})>".format(
self.initial_package.pretty_name, self.initial_package.pretty_name,
self.format_version(self.initial_package), self.format_version(self.initial_package),
......
import os import os
import tempfile import tempfile
import urllib.parse
from pathlib import Path
from subprocess import CalledProcessError from subprocess import CalledProcessError
from typing import TYPE_CHECKING
from typing import Any
from typing import Union
from clikit.api.io import IO from cleo.io.io import IO
from poetry.core.pyproject.toml import PyProjectTOML
from poetry.installation.base_installer import BaseInstaller
from poetry.repositories.pool import Pool from poetry.repositories.pool import Pool
from poetry.utils._compat import encode from poetry.utils._compat import encode
from poetry.utils.env import Env from poetry.utils.env import Env
from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import safe_rmtree
from poetry.utils.pip import pip_editable_install
from poetry.utils.pip import pip_install
from .base_installer import BaseInstaller
if TYPE_CHECKING:
try: from poetry.core.packages.package import Package
import urllib.parse as urlparse
except ImportError:
import urlparse
class PipInstaller(BaseInstaller): class PipInstaller(BaseInstaller):
def __init__(self, env, io, pool): # type: (Env, IO, Pool) -> None def __init__(self, env: Env, io: IO, pool: Pool) -> None:
self._env = env self._env = env
self._io = io self._io = io
self._pool = pool self._pool = pool
def install(self, package, update=False): def install(self, package: "Package", update: bool = False) -> None:
if package.source_type == "directory": if package.source_type == "directory":
self.install_directory(package) self.install_directory(package)
...@@ -43,9 +48,9 @@ class PipInstaller(BaseInstaller): ...@@ -43,9 +48,9 @@ class PipInstaller(BaseInstaller):
and package.source_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 = urllib.parse.urlparse(package.source_url)
if parsed.scheme == "http": if parsed.scheme == "http":
self._io.error( self._io.write_error(
" <warning>Installing from unsecure host: {}</warning>".format( " <warning>Installing from unsecure host: {}</warning>".format(
parsed.hostname parsed.hostname
) )
...@@ -94,7 +99,7 @@ class PipInstaller(BaseInstaller): ...@@ -94,7 +99,7 @@ class PipInstaller(BaseInstaller):
self.run(*args) self.run(*args)
def update(self, package, target): def update(self, package: "Package", target: "Package") -> None:
if package.source_type != target.source_type: if package.source_type != target.source_type:
# If the source type has changed, we remove the current # If the source type has changed, we remove the current
# package to avoid perpetual updates in some cases # package to avoid perpetual updates in some cases
...@@ -102,7 +107,7 @@ class PipInstaller(BaseInstaller): ...@@ -102,7 +107,7 @@ class PipInstaller(BaseInstaller):
self.install(target, update=True) self.install(target, update=True)
def remove(self, package): def remove(self, package: "Package") -> None:
try: try:
self.run("uninstall", package.name, "-y") self.run("uninstall", package.name, "-y")
except CalledProcessError as e: except CalledProcessError as e:
...@@ -112,7 +117,9 @@ class PipInstaller(BaseInstaller): ...@@ -112,7 +117,9 @@ class PipInstaller(BaseInstaller):
raise raise
# This is a workaround for https://github.com/pypa/pip/issues/4176 # This is a workaround for https://github.com/pypa/pip/issues/4176
nspkg_pth_file = self._env.site_packages / "{}-nspkg.pth".format(package.name) nspkg_pth_file = self._env.site_packages.path / "{}-nspkg.pth".format(
package.name
)
if nspkg_pth_file.exists(): if nspkg_pth_file.exists():
nspkg_pth_file.unlink() nspkg_pth_file.unlink()
...@@ -122,10 +129,10 @@ class PipInstaller(BaseInstaller): ...@@ -122,10 +129,10 @@ class PipInstaller(BaseInstaller):
if src_dir.exists(): if src_dir.exists():
safe_rmtree(str(src_dir)) safe_rmtree(str(src_dir))
def run(self, *args, **kwargs): # type: (...) -> str def run(self, *args: Any, **kwargs: Any) -> str:
return self._env.run_pip(*args, **kwargs) return self._env.run_pip(*args, **kwargs)
def requirement(self, package, formatted=False): def requirement(self, package: "Package", formatted: bool = False) -> str:
if formatted and not package.source_type: if formatted and not package.source_type:
req = "{}=={}".format(package.name, package.version) req = "{}=={}".format(package.name, package.version)
for f in package.files: for f in package.files:
...@@ -166,7 +173,7 @@ class PipInstaller(BaseInstaller): ...@@ -166,7 +173,7 @@ class PipInstaller(BaseInstaller):
return "{}=={}".format(package.name, package.version) return "{}=={}".format(package.name, package.version)
def create_temporary_requirement(self, package): def create_temporary_requirement(self, package: "Package") -> str:
fd, name = tempfile.mkstemp( fd, name = tempfile.mkstemp(
"reqs.txt", "{}-{}".format(package.name, package.version) "reqs.txt", "{}-{}".format(package.name, package.version)
) )
...@@ -178,50 +185,41 @@ class PipInstaller(BaseInstaller): ...@@ -178,50 +185,41 @@ class PipInstaller(BaseInstaller):
return name return name
def install_directory(self, package): def install_directory(self, package: "Package") -> Union[str, int]:
from cleo.io.null_io import NullIO
from poetry.factory import Factory from poetry.factory import Factory
from poetry.io.null_io import NullIO
from poetry.masonry.builders.editable import EditableBuilder req: Path
from poetry.utils.toml_file import TomlFile
if package.root_dir: if package.root_dir:
req = (package.root_dir / package.source_url).as_posix() req = (package.root_dir / package.source_url).as_posix()
else: else:
req = os.path.realpath(package.source_url) req = Path(package.source_url).resolve(strict=False)
args = ["install", "--no-deps", "-U"] pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))
pyproject = TomlFile(os.path.join(req, "pyproject.toml")) if pyproject.is_poetry_project():
has_poetry = False
has_build_system = False
if pyproject.exists():
pyproject_content = pyproject.read()
has_poetry = (
"tool" in pyproject_content and "poetry" in pyproject_content["tool"]
)
# Even if there is a build system specified # Even if there is a build system specified
# some versions of pip (< 19.0.0) don't understand it # some versions of pip (< 19.0.0) don't understand it
# so we need to check the version of pip to know # so we need to check the version of pip to know
# if we can rely on the build system # if we can rely on the build system
pip_version = self._env.pip_version legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
pip_version_with_build_system_support = pip_version.__class__(19, 0, 0) 19, 0, 0
has_build_system = (
"build-system" in pyproject_content
and pip_version >= pip_version_with_build_system_support
) )
package_poetry = Factory().create_poetry(pyproject.file.path.parent)
if has_poetry:
package_poetry = Factory().create_poetry(pyproject.parent)
if package.develop and not package_poetry.package.build_script: if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder
# This is a Poetry package in editable mode # This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip # we can use the EditableBuilder without going through pip
# to install it, unless it has a build script. # to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO()) builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build() builder.build()
return return 0
elif not has_build_system or package_poetry.package.build_script: elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder from poetry.core.masonry.builders.sdist import SdistBuilder
# We need to rely on creating a temporary setup.py # We need to rely on creating a temporary setup.py
...@@ -232,22 +230,20 @@ class PipInstaller(BaseInstaller): ...@@ -232,22 +230,20 @@ class PipInstaller(BaseInstaller):
with builder.setup_py(): with builder.setup_py():
if package.develop: if package.develop:
args.append("-e") return pip_editable_install(
directory=req, environment=self._env
args.append(req) )
return pip_install(
return self.run(*args) path=req, environment=self._env, deps=False, upgrade=True
)
if package.develop: if package.develop:
args.append("-e") return pip_editable_install(directory=req, environment=self._env)
return pip_install(path=req, environment=self._env, deps=False, upgrade=True)
args.append(req)
return self.run(*args)
def install_git(self, package): def install_git(self, package: "Package") -> None:
from poetry.core.packages import Package from poetry.core.packages.package import Package
from poetry.core.vcs import Git from poetry.core.vcs.git import Git
src_dir = self._env.path / "src" / package.name src_dir = self._env.path / "src" / package.name
if src_dir.exists(): if src_dir.exists():
...@@ -261,8 +257,8 @@ class PipInstaller(BaseInstaller): ...@@ -261,8 +257,8 @@ class PipInstaller(BaseInstaller):
# Now we just need to install from the source directory # Now we just need to install from the source directory
pkg = Package(package.name, package.version) pkg = Package(package.name, package.version)
pkg.source_type = "directory" pkg._source_type = "directory"
pkg.source_url = str(src_dir) pkg._source_url = str(src_dir)
pkg.develop = package.develop pkg.develop = package.develop
self.install_directory(pkg) self.install_directory(pkg)
This diff is collapsed. Click to expand it.
File mode changed from 100755 to 100644
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