Commit 38949f82 by Josh Guice

fix(solver): merge upstream changes

parents 5b4a3842 cdfcd146
freebsd_instance:
image_family: freebsd-12-1-snap
image_family: freebsd-12-2
test_task:
name: "Tests / FreeBSD / "
only_if: $CIRRUS_TAG == ''
skip: "!changesInclude('.cirrus.yml', 'poetry.lock', 'pyproject.toml', '**.json','**.py')"
env:
matrix:
- PYTHON: python2.7
- PYTHON: python3.7
- PYTHON: python3.6
- PYTHON: python3.8
python_script:
- PYPACKAGE=$(printf '%s' $PYTHON | tr -d '.')
- SQLPACKAGE=$(printf '%s-sqlite3' $PYPACKAGE | sed 's/thon//')
......@@ -28,25 +29,44 @@ release_task:
name: "Release / FreeBSD"
only_if: $CIRRUS_TAG != ''
env:
GITHUB_TOKEN: ENCRYPTED[e637a1887c028ea4e44867715a8a4b7c5e27568559a15c45a3e33739b0d97fc050394c728a44e9bfe7c246179810cad7]
GITHUB_TOKEN: ENCRYPTED[2b573a2d28a03523ac6fb5b3c2f513a41c0a98db81e40e50e1d103b171f85c57e58ae38d957499dbf7fd7635cfcfd7be]
PYTHON: python3.8
PYTHON36: python3.6
PYTHON37: python3.7
PYTHON38: python3.8
freebsd_instance:
matrix:
- image_family: freebsd-12-1-snap
- image_family: freebsd-11-3-snap
python_script: pkg install -y curl python3 python27 python35 python36 python37 python38
- image_family: freebsd-13-0-snap
- image_family: freebsd-11-4-snap
python_script: pkg install -y curl bash jq python3 python36 python37 python38
pip_script:
- python2.7 -m ensurepip
- python3.5 -m ensurepip
- python3.6 -m ensurepip
- python3.7 -m ensurepip
- python3.8 -m ensurepip
build_script: ./make-nix-release.sh
build_script: bash ./make-nix-release.sh
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/*
do
echo "Uploading $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 \
--data-binary @$fpath \
--header "Authorization: token $GITHUB_TOKEN" \
......
[flake8]
max-line-length = 88
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 =
.git
__pycache__
......
......@@ -16,6 +16,6 @@ jobs:
name: Linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: pre-commit/action@v2.0.0
......@@ -22,12 +22,13 @@ jobs:
strategy:
matrix:
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:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
......@@ -36,15 +37,16 @@ jobs:
shell: bash
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
run: |
python get-poetry.py -y --preview
echo "::set-env name=PATH::$HOME/.poetry/bin:$PATH"
python -m ensurepip
python -m pip install --upgrade pip
python -m pip install .
- name: Configure poetry
shell: bash
run: poetry config virtualenvs.in-project true
run: python -m poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v2
......@@ -56,16 +58,12 @@ jobs:
- name: Ensure cache is healthy
if: steps.cache.outputs.cache-hit == 'true'
shell: bash
run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv
- name: Upgrade pip
shell: bash
run: poetry run python -m pip install pip -U
run: timeout 10s python -m poetry run pip --version || rm -rf .venv
- name: Install dependencies
shell: bash
run: poetry install
run: python -m poetry install
- name: Run pytest
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:
poetry install --no-dev
- name: Preparing Python executables
run: |
curl -L https://github.com/sdispater/python-binaries/releases/download/2.7.17/python-2.7.17.macos.tar.xz -o python-2.7.17.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.5.9/python-3.5.9.macos.tar.xz -o python-3.5.9.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.macos.tar.xz -o python-3.6.8.tar.xz
curl -L https://github.com/sdispater/python-binaries/releases/download/3.7.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.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.7.6.tar.xz
tar -zxf python-3.8.3.tar.xz
......@@ -68,7 +64,7 @@ jobs:
- name: Build specific release
run: |
source $HOME/.poetry/env
poetry run python sonnet make release --ansi -P "2.7:python-2.7.17/bin/python" -P "3.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
uses: actions/upload-artifact@v1
with:
......@@ -104,21 +100,14 @@ jobs:
poetry install --no-dev
- name: Preparing Python executables
run: |
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/2.7.17/python-2.7.17.windows.tar.xz -O python-2.7.17.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.5.4/python-3.5.4.windows.tar.xz -O python-3.5.4.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.6.8/python-3.6.8.windows.tar.xz -O python-3.6.8.tar.xz
Invoke-WebRequest https://github.com/sdispater/python-binaries/releases/download/3.7.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.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.7.6.tar.xz
7z x python-3.8.3.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.7.6.tar
7z x python-3.8.3.tar
......@@ -126,7 +115,7 @@ jobs:
- name: Build specific release
run: |
$env:Path += ";$env:Userprofile\.poetry\bin"
poetry run python sonnet make release --ansi -P "2.7:python-2.7.17\python.exe" -P "3.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
uses: actions/upload-artifact@v1
with:
......
......@@ -32,6 +32,6 @@ jobs:
strategy:
matrix:
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:
- run: exit 0
repos:
- repo: https://github.com/psf/black
rev: 19.10b0
rev: 20.8b1
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
rev: 3.8.4
hooks:
- id: flake8
- repo: https://github.com/timothycrosley/isort
rev: 5.4.2
rev: 5.7.0
hooks:
- id: isort
additional_dependencies: [toml]
exclude: ^.*/?setup\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v3.4.0
hooks:
- id: trailing-whitespace
exclude: ^tests/.*/fixtures/.*
exclude: |
(?x)(
^tests/.*/fixtures/.*
| ^tests/console/commands/debug/test_resolve.py
)
- id: end-of-file-fixer
exclude: ^tests/.*/fixtures/.*
- id: debug-statements
# 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
### Changed
......@@ -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.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
......
......@@ -10,7 +10,10 @@ The following is a set of guidelines for contributing to Poetry on GitHub. These
* [Reporting bugs](#reporting-bugs)
* [Suggesting enhancements](#suggesting-enhancements)
* [Contributing to documentation](#contributing-to-documentation)
* [Contributing to code](#contributing-to-code)
* [Issue triage](#issue-triage)
* [Git workflow](#git-workflow)
## How to contribute
......@@ -76,9 +79,27 @@ Enhancement suggestions are tracked on the [official issue tracker](https://gith
* **Provide specific examples to demonstrate the steps**..
* **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
#### 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
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
$ 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
tests are passing on your machine:
......@@ -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)
* 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.
> **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
poetry completions zsh > $(brew --prefix)/share/zsh/site-functions/_poetry
# Zsh (Oh-My-Zsh)
mkdir $ZSH/plugins/poetry
poetry completions zsh > $ZSH/plugins/poetry/_poetry
mkdir $ZSH_CUSTOM/plugins/poetry
poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry
# Zsh (prezto)
poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry
......@@ -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).
And, finally, there is no reliable tool to properly resolve dependencies in Python, so I started `poetry`
to bring an exhaustive dependency resolver to the Python community.
And, finally, I started `poetry` to bring another exhaustive dependency resolver to the Python community apart from
[Conda's](https://conda.io).
### What about Pipenv?
......
......@@ -41,6 +41,16 @@ python = "*"
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
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`
that an activated virtual environment remains active after the Poetry command
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.
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`.
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`.
| | POSIX Shell | Windows | Exit/Deactivate |
|-------------------|------------------------------------------------|---------------------------------------------|-----------------|
|-------------------|---------------------------------------------------|---------------------------------------------|-----------------|
| New Shell | `poetry shell` | `poetry shell` | `exit` |
| Manual Activation | `source {path_to_venv}/bin/activate` | `source {path_to_venv}\Scripts\activate.bat`| `deactivate` |
| One-liner | ```source`poetry env info --path`/bin/activate```| | `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` |
### Version constraints
......
......@@ -14,6 +14,7 @@ then `--help` combined with any of those can give you more information.
* `--ansi`: Force ANSI output.
* `--no-ansi`: Disable ANSI output.
* `--version (-V)`: Display this application version.
* `--no-interaction (-n)`: Do not ask any interactive question.
## new
......@@ -108,6 +109,13 @@ the `--no-dev` option.
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
`--remove-untracked` option.
......@@ -116,7 +124,7 @@ poetry install --remove-untracked
```
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
poetry install --extras "mysql pgsql"
......@@ -141,10 +149,16 @@ If you want to skip this installation, use the `--no-root` option.
poetry install --no-root
```
Installation of your project's package is also skipped when the `--dev-only`
option is passed.
### Options
* `--no-dev`: Do not install dev dependencies.
* `--dev-only`: Only install dev dependencies.
* `--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).
## update
......@@ -212,6 +226,10 @@ or use ssh instead of https:
```bash
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,
......@@ -220,6 +238,11 @@ you can specify it when using `add`:
```bash
poetry add git+https://github.com/sdispater/pendulum.git#develop
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:
......@@ -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
```
Path dependencies pointing to a local directory will be installed in editable mode (i.e. setuptools "develop mode").
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:
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.
```toml
[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
when adding the package:
......@@ -252,10 +277,14 @@ poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]"
### Options
* `--dev (-D)`: Add package as development dependency.
* `--path`: The path to a dependency.
* `--optional` : Add as an optional dependency.
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
* `--lock` : Do not perform install (only update the lockfile).
* `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed)
* `--optional`: Add as an optional dependency.
* `--python`: Python version for which the dependency must be installed.
* `--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
......@@ -421,6 +450,10 @@ This command locks (without installing) the dependencies specified in `pyproject
poetry lock
```
### Options
* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`
## version
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.
| prerelease | 1.0.3-alpha.0 | 1.0.3-alpha.1 |
| prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 |
## Options
### Options
* `--short (-s)`: Output the version number only.
......@@ -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.
```bash
poetry export -f requirements.txt > requirements.txt
poetry export -f requirements.txt --output requirements.txt
```
!!!note
......@@ -489,3 +522,19 @@ The `cache list` command lists Poetry's available caches.
```bash
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:
```toml
cache-dir = "/path/to/cache/directory"
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
```
......@@ -89,7 +91,6 @@ This also works for secret settings, like credentials:
export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret
```
## Available settings
### `cache-dir`: string
......@@ -102,21 +103,55 @@ Defaults to one of the following directories:
- Windows: `C:\Users\<username>\AppData\Local\pypoetry\Cache`
- 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
Create a new virtual environment if one doesn't already exist.
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
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
Directory where virtual environments will be created.
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
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
### Caret requirements
**Caret requirements** allow SemVer compatible updates to a specified version.
An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping.
In this case, if we ran `poetry update requests`, poetry would update us to version `2.14.0` if it was available,
but would not update us to `3.0.0`.
If instead we had specified the version string as `^0.1.13`, poetry would update to `0.1.14` but not `0.2.0`.
`0.0.x` is not considered compatible with any other version.
**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.
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" }
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
To depend on a library located in a local directory or file,
......@@ -111,12 +113,16 @@ you can use the `path` property:
```toml
[tool.poetry.dependencies]
# directory
my-package = { path = "../my-package/" }
my-package = { path = "../my-package/", develop = false }
# file
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
......@@ -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
In the case of more complex dependency specifications, you may find that you
......@@ -206,8 +217,3 @@ markers = "platform_python_implementation == 'CPython'"
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,
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`.
### osx / linux / bashonwindows install instructions
```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
```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
......@@ -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.
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,
which means you can run them from the shell without further configuration.
Open a new shell and type the following:
This directory will be automatically added to your `$PATH` environment variable,
by appending a statement to your `$HOME/.profile` configuration (or equivalent files).
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
poetry --version
......@@ -117,7 +120,7 @@ pip install --user poetry
#### 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
pipx install poetry
......@@ -188,8 +191,8 @@ poetry completions fish > (brew --prefix)/share/fish/vendor_completions.d/poetry
poetry completions zsh > ~/.zfunc/_poetry
# Oh-My-Zsh
mkdir $ZSH/plugins/poetry
poetry completions zsh > $ZSH/plugins/poetry/_poetry
mkdir $ZSH_CUSTOM/plugins/poetry
poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry
# prezto
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
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
exclude = ["my_package/excluded.py"]
```
......@@ -207,7 +220,7 @@ url = 'http://example.com/simple'
## `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
[tool.poetry.scripts]
......@@ -243,15 +256,32 @@ mysqlclient = { version = "^1.3", optional = true }
[tool.poetry.extras]
mysql = ["mysqlclient"]
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
poetry install --extras "mysql 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`
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:
```toml
[build-system]
requires = ["poetry_core>=1.0.0"]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```
......@@ -300,4 +330,4 @@ build-backend = "poetry.core.masonry.api"
!!!note
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):
def temporary_directory(*args, **kwargs):
try:
from tempfile import TemporaryDirectory
with TemporaryDirectory(*args, **kwargs) as name:
yield name
except ImportError:
name = tempfile.mkdtemp(*args, **kwargs)
yield name
shutil.rmtree(name)
else:
with TemporaryDirectory(*args, **kwargs) as name:
yield name
def string_to_bool(value):
......@@ -317,7 +317,7 @@ class Installer:
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)?"
")?"
r"(?:\+[^\s]+)?"
......@@ -903,7 +903,7 @@ class Installer:
if "zsh" in SHELL:
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")
if os.path.exists(bash_profile):
......
#!/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"
$PYTHON get-poetry.py -y --preview
$PYTHON $HOME/.poetry/bin/poetry config virtualenvs.create false
$PYTHON $HOME/.poetry/bin/poetry install --no-dev
$PYTHON $HOME/.poetry/bin/poetry run python sonnet make release \
${PYTHON27:+-P "2.7:$PYTHON27"} \
${PYTHON35:+-P "3.5:$PYTHON35"} \
${PYTHON36:+-P "3.6:$PYTHON36"} \
${PYTHON37:+-P "3.7:$PYTHON37"} \
${PYTHON38:+-P "3.8:$PYTHON38"} \
${PYTHON39:+-P "3.9:$PYTHON39"}
if [ "$OSTYPE" == "linux-gnu" ]; then
$PYTHON get-poetry.py -y --preview
POETRY="$PYTHON $HOME/.poetry/bin/poetry"
RUNTIMES[5]="${PYTHON39:+-P "3.9:$PYTHON39"}"
else
$PYTHON -m pip install poetry -U --pre
POETRY="$PYTHON -m poetry"
fi
$POETRY config virtualenvs.in-project true
$POETRY install --no-dev
$POETRY run python sonnet make release ${RUNTIMES[@]}
......@@ -2,6 +2,6 @@ import sys
if __name__ == "__main__":
from .console import main
from .console.application import main
sys.exit(main())
__version__ = "1.1.0b2"
__version__ = "1.2.0a0"
......@@ -4,14 +4,13 @@ import os
import re
from copy import deepcopy
from pathlib import Path
from typing import Any
from typing import Callable
from typing import Dict
from typing import Optional
from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from .config_source import ConfigSource
from .dict_config_source import DictConfigSource
......@@ -20,11 +19,11 @@ from .dict_config_source import DictConfigSource
_NOT_SET = object()
def boolean_validator(val):
def boolean_validator(val: str) -> bool:
return val in {"true", "false", "1", "0"}
def boolean_normalizer(val):
def boolean_normalizer(val: str) -> bool:
return val in ["true", "1"]
......@@ -34,15 +33,17 @@ class Config(object):
"cache-dir": str(CACHE_DIR),
"virtualenvs": {
"create": True,
"in-project": False,
"in-project": None,
"path": os.path.join("{cache-dir}", "virtualenvs"),
"options": {"always-copy": False, "system-site-packages": False},
},
"experimental": {"new-installer": True},
"installer": {"parallel": True},
}
def __init__(
self, use_environment=True, base_dir=None
): # type: (bool, Optional[Path]) -> None
self, use_environment: bool = True, base_dir: Optional[Path] = None
) -> None:
self._config = deepcopy(self.default_config)
self._use_environment = use_environment
self._base_dir = base_dir
......@@ -50,44 +51,48 @@ class Config(object):
self._auth_config_source = DictConfigSource()
@property
def name(self):
def name(self) -> str:
return str(self._file.path)
@property
def config(self):
def config(self) -> Dict:
return self._config
@property
def config_source(self): # type: () -> ConfigSource
def config_source(self) -> ConfigSource:
return self._config_source
@property
def auth_config_source(self): # type: () -> ConfigSource
def auth_config_source(self) -> ConfigSource:
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
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
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
merge_dicts(self._config, config)
def all(self): # type: () -> Dict[str, Any]
def _all(config, parent_key=""):
def all(self) -> Dict[str, Any]:
def _all(config: Dict, parent_key: str = "") -> Dict:
all_ = {}
for key in config:
value = self.get(parent_key + key)
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
all_[key] = value
......@@ -96,10 +101,10 @@ class Config(object):
return _all(self.config)
def raw(self): # type: () -> Dict[str, Any]
def raw(self) -> Dict[str, Any]:
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.
"""
......@@ -124,21 +129,20 @@ class Config(object):
return self.process(value)
def process(self, value): # type: (Any) -> Any
if not isinstance(value, basestring):
def process(self, value: Any) -> Any:
if not isinstance(value, str):
return value
return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value)
def _get_validator(self, name): # type: (str) -> Callable
if name in {"virtualenvs.create", "virtualenvs.in-project"}:
return boolean_validator
if name == "virtualenvs.path":
return str
def _get_normalizer(self, name): # type: (str) -> Callable
if name in {"virtualenvs.create", "virtualenvs.in-project"}:
def _get_normalizer(self, name: str) -> Callable:
if name in {
"virtualenvs.create",
"virtualenvs.in-project",
"virtualenvs.options.always-copy",
"virtualenvs.options.system-site-packages",
"installer.parallel",
}:
return boolean_normalizer
if name == "virtualenvs.path":
......
......@@ -2,8 +2,8 @@ from typing import Any
class ConfigSource(object):
def add_property(self, key, value): # type: (str, Any) -> None
def add_property(self, key: str, value: Any) -> None:
raise NotImplementedError()
def remove_property(self, key): # type: (str) -> None
def remove_property(self, key: str) -> None:
raise NotImplementedError()
......@@ -5,14 +5,14 @@ from .config_source import ConfigSource
class DictConfigSource(ConfigSource):
def __init__(self): # type: () -> None
def __init__(self) -> None:
self._config = {}
@property
def config(self): # type: () -> Dict[str, Any]
def config(self) -> Dict[str, Any]:
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(".")
config = self._config
......@@ -26,7 +26,7 @@ class DictConfigSource(ConfigSource):
config = config[key]
def remove_property(self, key): # type: (str) -> None
def remove_property(self, key: str) -> None:
keys = key.split(".")
config = self._config
......
from contextlib import contextmanager
from typing import TYPE_CHECKING
from typing import Any
from typing import Iterator
from tomlkit import document
from tomlkit import table
from poetry.utils.toml_file import TomlFile
from .config_source import ConfigSource
if TYPE_CHECKING:
from tomlkit.toml_document import TOMLDocument
from poetry.core.toml.file import TOMLFile
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._auth_config = auth_config
@property
def name(self): # type: () -> str
def name(self) -> str:
return str(self._file.path)
@property
def file(self): # type: () -> TomlFile
def file(self) -> "TOMLFile":
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:
keys = key.split(".")
......@@ -36,7 +42,7 @@ class FileConfigSource(ConfigSource):
config = config[key]
def remove_property(self, key): # type: (str) -> None
def remove_property(self, key: str) -> None:
with self.secure() as config:
keys = key.split(".")
......@@ -53,7 +59,7 @@ class FileConfigSource(ConfigSource):
current_config = current_config[key]
@contextmanager
def secure(self):
def secure(self) -> Iterator["TOMLDocument"]:
if self.file.exists():
initial_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):
description = "Shows information about Poetry."
def handle(self):
def handle(self) -> None:
self.line(
"""<info>Poetry - Package Management for Python</info>
......
from cleo import argument
from cleo import option
# -*- coding: utf-8 -*-
from typing import Dict
from typing import List
from cleo.helpers import argument
from cleo.helpers import option
from .init import InitCommand
from .installer_command import InstallerCommand
......@@ -55,6 +59,8 @@ class AddCommand(InstallerCommand, InitCommand):
" - 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 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 directory (<b>../my-package/</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):
loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self):
def handle(self) -> int:
from tomlkit import inline_table
from poetry.core.semver import parse_constraint
from poetry.core.semver.helpers import parse_constraint
packages = self.argument("name")
is_dev = self.option("dev")
......@@ -86,18 +92,18 @@ class AddCommand(InstallerCommand, InitCommand):
if section not in poetry_content:
poetry_content[section] = {}
for name in packages:
for key in poetry_content[section]:
if key.lower() == name.lower():
pair = self._parse_requirements([name])[0]
if (
"git" in pair
or "url" in pair
or pair.get("version") == "latest"
):
continue
existing_packages = self.get_existing_packages_from_input(
packages, poetry_content, section
)
raise ValueError("Package {} is already present".format(name))
if existing_packages:
self.notify_about_existing_packages(existing_packages)
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(
packages,
......@@ -147,6 +153,7 @@ class AddCommand(InstallerCommand, InitCommand):
poetry_content[section][_constraint["name"]] = constraint
try:
# Write new content
self.poetry.file.write(content)
......@@ -165,11 +172,10 @@ class AddCommand(InstallerCommand, InitCommand):
self._installer.whitelist([r["name"] for r in requirements])
try:
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)
raise
if status != 0 or self.option("dry-run"):
......@@ -184,3 +190,26 @@ class AddCommand(InstallerCommand, InitCommand):
self.poetry.file.write(original_content)
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
......@@ -18,8 +18,8 @@ class BuildCommand(EnvCommand):
"poetry.core.masonry.builders.wheel",
]
def handle(self):
from poetry.core.masonry import Builder
def handle(self) -> None:
from poetry.core.masonry.builder import Builder
fmt = "all"
if self.option("format"):
......@@ -33,4 +33,4 @@ class BuildCommand(EnvCommand):
)
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
from cleo import argument
from cleo import option
from cleo.helpers import argument
from cleo.helpers import option
from ..command import Command
class CacheClearCommand(Command):
name = "clear"
name = "cache clear"
description = "Clears Poetry's cache."
arguments = [argument("cache", description="The name of the cache to clear.")]
options = [option("all", description="Clear all entries in the cache.")]
def handle(self):
def handle(self) -> int:
from cachy import CacheManager
from poetry.locations import REPOSITORY_CACHE_DIR
......
import os
from typing import Optional
from ..command import Command
class CacheListCommand(Command):
name = "list"
name = "cache list"
description = "List Poetry's caches."
def handle(self):
def handle(self) -> Optional[int]:
from poetry.locations import 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.utils._compat import Path
from poetry.utils.toml_file import TomlFile
from .command import Command
......@@ -10,10 +11,10 @@ class CheckCommand(Command):
name = "check"
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
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)
if not check_result["errors"] and not check_result["warnings"]:
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 = []
@property
def poetry(self):
return self.application.poetry
def poetry(self) -> "Poetry":
return self.get_application().poetry
def get_application(self) -> "Application":
return self.application
def reset_poetry(self): # type: () -> None
self.application.reset_poetry()
def reset_poetry(self) -> None:
self.get_application().reset_poetry()
import json
import re
from cleo import argument
from cleo import option
from typing import TYPE_CHECKING
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
if TYPE_CHECKING:
from poetry.config.config_source import ConfigSource
class ConfigCommand(Command):
name = "config"
......@@ -38,11 +47,12 @@ To remove a repository (repo is a short alias for repositories):
LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"}
@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_validator
from poetry.locations import CACHE_DIR
from poetry.utils._compat import Path
unique_config_values = {
"cache-dir": (
......@@ -52,6 +62,16 @@ To remove a repository (repo is a short alias for repositories):
),
"virtualenvs.create": (boolean_validator, boolean_normalizer, True),
"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": (
str,
lambda val: str(Path(val)),
......@@ -62,26 +82,33 @@ To remove a repository (repo is a short alias for repositories):
boolean_normalizer,
True,
),
"installer.parallel": (
boolean_validator,
boolean_normalizer,
True,
),
}
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.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.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils.toml_file import TomlFile
config = Factory.create_config(self.io)
config_file = TomlFile(Path(CONFIG_DIR) / "config.toml")
config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml")
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():
config.merge(local_config_file.read())
except RuntimeError:
local_config_file = TomlFile(Path.cwd() / "poetry.toml")
except (RuntimeError, PyProjectException):
local_config_file = TOMLFile(Path.cwd() / "poetry.toml")
if self.option("local"):
config.set_config_source(FileConfigSource(local_config_file))
......@@ -127,7 +154,7 @@ To remove a repository (repo is a short alias for repositories):
value = config.get(setting_key)
if not isinstance(value, basestring):
if not isinstance(value, str):
value = json.dumps(value)
self.line(value)
......@@ -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")))
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
if len(values) > 1:
......@@ -259,9 +292,7 @@ To remove a repository (repo is a short alias for repositories):
return 0
def _list_configuration(self, config, raw, k=""):
from poetry.utils._compat import basestring
def _list_configuration(self, config: Dict, raw: Dict, k: str = "") -> None:
orig_k = k
for key, value in sorted(config.items()):
if k + key in self.LIST_PROHIBITED_SETTINGS:
......@@ -286,7 +317,7 @@ To remove a repository (repo is a short alias for repositories):
message = "<c1>{}</c1> = <c2>{}</c2>".format(
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(
k + key, json.dumps(raw_val), value
)
......@@ -295,15 +326,13 @@ To remove a repository (repo is a short alias for repositories):
self.line(message)
def _list_setting(self, contents, setting=None, k=None, default=None):
values = self._get_setting(contents, setting, k, default)
for value in values:
self.line(
"<comment>{}</comment> = <info>{}</info>".format(value[0], value[1])
)
def _get_setting(self, contents, setting=None, k=None, default=None):
def _get_setting(
self,
contents: Dict,
setting: Optional[str] = None,
k: Optional[str] = None,
default: Optional[Any] = None,
) -> List[Tuple[str, str]]:
orig_k = k
if setting and setting.split(".")[0] not in contents:
......@@ -344,11 +373,3 @@ To remove a repository (repo is a short alias for repositories):
values.append(((k or "") + key, value))
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
from clikit.args import StringArgs
from ..command import Command
class DebugInfoCommand(Command):
name = "info"
name = "debug info"
description = "Shows debug information."
def handle(self):
def handle(self) -> int:
poetry_python_version = ".".join(str(s) for s in sys.version_info[:3])
self.line("")
......@@ -25,7 +23,6 @@ class DebugInfoCommand(Command):
]
)
)
args = StringArgs("")
command = self.application.get_command("env").get_sub_command("info")
command = self.application.get("env info")
return command.run(args, self._io)
return command.run(self._io)
from cleo import argument
from cleo import option
from typing import TYPE_CHECKING
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
if TYPE_CHECKING:
from poetry.console.commands.show import ShowCommand
class DebugResolveCommand(InitCommand):
name = "resolve"
name = "debug resolve"
description = "Debugs dependency resolution."
arguments = [
......@@ -27,9 +35,11 @@ class DebugResolveCommand(InitCommand):
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.io.null_io import NullIO
from poetry.factory import Factory
from poetry.puzzle import Solver
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository
......@@ -48,18 +58,15 @@ class DebugResolveCommand(InitCommand):
)
# Silencing output
is_quiet = self.io.output.is_quiet()
if not is_quiet:
self.io.output.set_quiet(True)
verbosity = self.io.output.verbosity
self.io.output.set_verbosity(Verbosity.QUIET)
requirements = self._determine_requirements(packages)
if not is_quiet:
self.io.output.set_quiet(False)
self.io.output.set_verbosity(verbosity)
for constraint in requirements:
name = constraint.pop("name")
dep = package.add_dependency(name, constraint)
extras = []
for extra in self.option("extras"):
if " " in extra:
......@@ -67,8 +74,9 @@ class DebugResolveCommand(InitCommand):
else:
extras.append(extra)
for ex in extras:
dep.extras.append(ex)
constraint["extras"] = extras
package.add_dependency(Factory.create_dependency(name, constraint))
package.python_versions = self.option("python") or (
self.poetry.package.python_versions
......@@ -85,7 +93,7 @@ class DebugResolveCommand(InitCommand):
self.line("")
if self.option("tree"):
show_command = self.application.find("show")
show_command: ShowCommand = self.application.find("show")
show_command.init_styles(self.io)
packages = [op.package for op in ops]
......@@ -100,7 +108,8 @@ class DebugResolveCommand(InitCommand):
return 0
table = self.table([], style="borderless")
table = self.table([], style="compact")
table.style.set_vertical_border_chars("", " ")
rows = []
if self.option("install"):
......@@ -122,7 +131,7 @@ class DebugResolveCommand(InitCommand):
pkg = op.package
row = [
"<c1>{}</c1>".format(pkg.name),
"<c1>{}</c1>".format(pkg.complete_name),
"<b>{}</b>".format(pkg.version),
"",
]
......@@ -133,4 +142,4 @@ class DebugResolveCommand(InitCommand):
rows.append(row)
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
if TYPE_CHECKING:
from poetry.utils.env import Env
class EnvInfoCommand(Command):
name = "info"
name = "env info"
description = "Displays information about the current environment."
options = [option("path", "p", "Only display the environment's path.")]
def handle(self):
def handle(self) -> Optional[int]:
from poetry.utils.env import EnvManager
env = EnvManager(self.poetry).get()
......@@ -25,7 +32,7 @@ class EnvInfoCommand(Command):
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])
self.line("")
self.line("<b>Virtualenv</b>")
......
from cleo import option
from cleo.helpers import option
from ..command import Command
class EnvListCommand(Command):
name = "list"
name = "env list"
description = "Lists all virtualenvs associated with the current project."
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
manager = EnvManager(self.poetry)
......
from cleo import argument
from cleo.helpers import argument
from ..command import Command
class EnvRemoveCommand(Command):
name = "remove"
name = "env remove"
description = "Removes a specific virtualenv associated with the project."
arguments = [
argument("python", "The python executable to remove the virtualenv for.")
]
def handle(self):
def handle(self) -> None:
from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry)
......
from cleo import argument
from cleo.helpers import argument
from ..command import Command
class EnvUseCommand(Command):
name = "use"
name = "env use"
description = "Activates or creates a new virtualenv for the current project."
arguments = [argument("python", "The python executable to use.")]
def handle(self):
def handle(self) -> None:
from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry)
......
from typing import TYPE_CHECKING
from .command import Command
if TYPE_CHECKING:
from poetry.utils.env import VirtualEnv
class EnvCommand(Command):
def __init__(self):
def __init__(self) -> None:
self._env = None
super(EnvCommand, self).__init__()
@property
def env(self):
def env(self) -> "VirtualEnv":
return self._env
def set_env(self, env):
def set_env(self, env: "VirtualEnv") -> None:
self._env = env
from cleo import option
from cleo.helpers import option
from poetry.utils.exporter import Exporter
......@@ -31,7 +31,7 @@ class ExportCommand(Command):
option("with-credentials", None, "Include credentials for extra indices."),
]
def handle(self):
def handle(self) -> None:
fmt = self.option("format")
if fmt not in Exporter.ACCEPTED_FORMATS:
......@@ -50,7 +50,7 @@ class ExportCommand(Command):
elif self.io.is_verbose():
options.append(("-v", None))
self.call("lock", options)
self.call("lock", " ".join(options))
if not locker.is_fresh():
self.line(
......
from cleo import option
from cleo.helpers import option
from .installer_command import InstallerCommand
......@@ -10,6 +10,7 @@ class InstallCommand(InstallerCommand):
options = [
option("no-dev", None, "Do not install the development dependencies."),
option("dev-only", None, "Only install the development dependencies."),
option(
"no-root", None, "Do not install the root package (the current project)."
),
......@@ -20,7 +21,9 @@ class InstallCommand(InstallerCommand):
"(implicitly enables --verbose).",
),
option(
"remove-untracked", None, "Removes packages not present in the lock file.",
"remove-untracked",
None,
"Removes packages not present in the lock file.",
),
option(
"extras",
......@@ -47,7 +50,7 @@ dependencies and not including the current project, run the command with the
_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.masonry.builders import EditableBuilder
......@@ -64,6 +67,7 @@ dependencies and not including the current project, run the command with the
self._installer.extras(extras)
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.remove_untracked(self.option("remove-untracked"))
self._installer.verbose(self._io.is_verbose())
......@@ -73,7 +77,7 @@ dependencies and not including the current project, run the command with the
if return_code != 0:
return return_code
if self.option("no-root"):
if self.option("no-root") or self.option("dev-only"):
return 0
try:
......@@ -85,7 +89,7 @@ dependencies and not including the current project, run the command with the
return 0
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(
"<b>Installing</> the current project: <c1>{}</c1> (<c2>{}</c2>)".format(
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
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(
"<b>Installing</> the current project: <c1>{}</c1> (<success>{}</success>)".format(
self.poetry.package.pretty_name, self.poetry.package.pretty_version
......
......@@ -9,20 +9,20 @@ if TYPE_CHECKING:
class InstallerCommand(EnvCommand):
def __init__(self):
self._installer = None # type: Optional[Installer]
def __init__(self) -> None:
self._installer: Optional["Installer"] = None
super(InstallerCommand, self).__init__()
def reset_poetry(self):
def reset_poetry(self) -> None:
super(InstallerCommand, self).reset_poetry()
self._installer.set_package(self.poetry.package)
self._installer.set_locker(self.poetry.locker)
@property
def installer(self): # type: () -> Installer
def installer(self) -> "Installer":
return self._installer
def set_installer(self, installer): # type: (Installer) -> None
def set_installer(self, installer: "Installer") -> None:
self._installer = installer
from cleo.helpers import option
from .installer_command import InstallerCommand
......@@ -6,6 +8,18 @@ class LockCommand(InstallerCommand):
name = "lock"
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 = """
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</>
......@@ -16,11 +30,18 @@ file.
loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
def handle(self) -> int:
self._installer.use_executor(
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()
import sys
from cleo import argument
from cleo import option
from cleo.helpers import argument
from cleo.helpers import option
from poetry.utils.helpers import module_name
......@@ -19,11 +19,12 @@ class NewCommand(Command):
option("src", None, "Use the src layout for the project."),
]
def handle(self):
from poetry.core.semver import parse_constraint
def handle(self) -> None:
from pathlib import Path
from poetry.core.semver.helpers import parse_constraint
from poetry.core.vcs.git import GitConfig
from poetry.layouts import layout
from poetry.utils._compat import Path
from poetry.utils.env import SystemEnv
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
......@@ -40,7 +41,7 @@ the config command.
loggers = ["poetry.masonry.publishing.publisher"]
def handle(self):
def handle(self) -> Optional[int]:
from poetry.publishing.publisher import Publisher
publisher = Publisher(self.poetry, self.io)
......
from cleo import argument
from cleo import option
from cleo.helpers import argument
from cleo.helpers import option
from .installer_command import InstallerCommand
......@@ -27,7 +27,7 @@ list of installed packages
loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self):
def handle(self) -> int:
packages = self.argument("packages")
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
if TYPE_CHECKING:
from poetry.core.masonry.utils.module import Module
class RunCommand(EnvCommand):
name = "run"
......@@ -12,14 +20,7 @@ class RunCommand(EnvCommand):
argument("args", "The command and arguments/options to run.", multiple=True)
]
def __init__(self): # type: () -> None
from poetry.console.args.run_args_parser import RunArgsParser
super(RunCommand, self).__init__()
self.config.set_args_parser(RunArgsParser())
def handle(self):
def handle(self) -> Any:
args = self.argument("args")
script = args[0]
scripts = self.poetry.local_config.get("scripts")
......@@ -29,7 +30,18 @@ class RunCommand(EnvCommand):
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):
script = script["callable"]
......@@ -47,14 +59,3 @@ class RunCommand(EnvCommand):
]
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
......@@ -10,7 +10,7 @@ class SearchCommand(Command):
arguments = [argument("tokens", "The tokens to search for.", multiple=True)]
def handle(self):
def handle(self) -> None:
from poetry.repositories.pypi_repository import PyPiRepository
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
from functools import cmp_to_key
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 import option
from cleo.helpers import argument
from cleo.helpers import option
from ..command import Command
try:
from urllib.error import HTTPError
from urllib.request import urlopen
except ImportError:
from urllib2 import HTTPError
from urllib2 import urlopen
if TYPE_CHECKING:
from poetry.core.packages.package import Package
from poetry.core.semver.version import Version
BIN = """# -*- coding: utf-8 -*-
......@@ -49,7 +51,7 @@ BAT = '@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n'
class SelfUpdateCommand(Command):
name = "update"
name = "self update"
description = "Updates Poetry to the latest version."
arguments = [argument("version", "The version to update to.", optional=True)]
......@@ -59,26 +61,27 @@ class SelfUpdateCommand(Command):
BASE_URL = REPOSITORY_URL + "/releases/download"
@property
def home(self):
from poetry.utils._compat import Path
def home(self) -> Path:
from pathlib import Path
return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser()
@property
def bin(self):
def bin(self) -> Path:
return self.home / "bin"
@property
def lib(self):
def lib(self) -> Path:
return self.home / "lib"
@property
def lib_backup(self):
def lib_backup(self) -> Path:
return self.home / "lib-backup"
def handle(self):
def handle(self) -> None:
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
self._check_recommended_installation()
......@@ -89,7 +92,7 @@ class SelfUpdateCommand(Command):
repo = PyPiRepository(fallback=False)
packages = repo.find_packages(
"poetry", version, allow_prereleases=self.option("preview")
Dependency("poetry", version, allows_prereleases=self.option("preview"))
)
if not packages:
self.line("No release found for the specified version")
......@@ -127,7 +130,7 @@ class SelfUpdateCommand(Command):
self.update(release)
def update(self, release):
def update(self, release: "Package") -> None:
version = release.version
self.line("Updating to <info>{}</info>".format(version))
......@@ -163,7 +166,7 @@ class SelfUpdateCommand(Command):
)
)
def _update(self, version):
def _update(self, version: "Version") -> None:
from poetry.utils.helpers import temporary_directory
release_name = self._get_release_name(version)
......@@ -233,37 +236,31 @@ class SelfUpdateCommand(Command):
finally:
gz.close()
def process(self, *args):
def process(self, *args: Any) -> str:
return subprocess.check_output(list(args), stderr=subprocess.STDOUT)
def _check_recommended_installation(self):
from poetry.utils._compat import Path
def _check_recommended_installation(self) -> None:
from pathlib import Path
from poetry.console.exceptions import PoetrySimpleConsoleException
current = Path(__file__)
try:
current.relative_to(self.home)
except ValueError:
raise RuntimeError(
"Poetry was not installed with the recommended installer. "
"Cannot update automatically."
raise PoetrySimpleConsoleException(
"Poetry was not installed with the recommended installer, "
"so it cannot be updated automatically."
)
def _get_release_name(self, version):
def _get_release_name(self, version: "Version") -> str:
platform = sys.platform
if platform == "linux2":
platform = "linux"
return "poetry-{}-{}".format(version, platform)
def _bin_path(self, base_path, bin):
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):
def make_bin(self) -> None:
from poetry.utils._compat import WINDOWS
self.bin.mkdir(0o755, parents=True, exist_ok=True)
......@@ -292,7 +289,7 @@ class SelfUpdateCommand(Command):
st = os.stat(str(self.bin.joinpath("poetry")))
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.
"""
......
......@@ -16,7 +16,7 @@ class ShellCommand(EnvCommand):
If one doesn't exist yet, it will be created.
"""
def handle(self):
def handle(self) -> None:
from poetry.utils.shell import Shell
# Check if it's already activated or doesn't exist and won't be created
......
from cleo import argument
from cleo import option
from cleo.helpers import argument
from cleo.helpers import option
from .installer_command import InstallerCommand
......@@ -27,7 +27,7 @@ class UpdateCommand(InstallerCommand):
loggers = ["poetry.repositories.pypi_repository"]
def handle(self):
def handle(self) -> int:
packages = self.argument("packages")
self._installer.use_executor(
......
from cleo import argument
from cleo import option
from typing import TYPE_CHECKING
from cleo.helpers import argument
from cleo.helpers import option
from .command import Command
if TYPE_CHECKING:
from poetry.core.semver.version import Version
class VersionCommand(Command):
name = "version"
......@@ -40,7 +46,7 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
"prerelease",
}
def handle(self):
def handle(self) -> None:
version = self.argument("version")
if version:
......@@ -48,6 +54,9 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
self.poetry.package.pretty_version, version
)
if self.option("short"):
self.line("{}".format(version))
else:
self.line(
"Bumping version from <b>{}</> to <fg=green>{}</>".format(
self.poetry.package.pretty_version, version
......@@ -69,8 +78,8 @@ patch, minor, major, prepatch, preminor, premajor, prerelease.
)
)
def increment_version(self, version, rule):
from poetry.core.semver import Version
def increment_version(self, version: str, rule: str) -> "Version":
from poetry.core.semver.version import Version
try:
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
class BuilderLogFormatter(Formatter):
def format(self, msg): # type: (str) -> str
def format(self, msg: str) -> str:
if msg.startswith("Building "):
msg = re.sub("Building (.+)", " - Building <info>\\1</info>", msg)
elif msg.startswith("Built "):
......
......@@ -2,5 +2,5 @@ import logging
class Formatter(object):
def format(self, record): # type: (logging.LogRecord) -> str
def format(self, record: logging.LogRecord) -> str:
raise NotImplementedError()
import logging
from typing import TYPE_CHECKING
from .formatters import FORMATTERS
if TYPE_CHECKING:
from logging import LogRecord
class IOFormatter(logging.Formatter):
_colors = {
......@@ -12,7 +18,7 @@ class IOFormatter(logging.Formatter):
"info": "fg=blue",
}
def format(self, record):
def format(self, record: "LogRecord") -> str:
if not record.exc_info:
level = record.levelname.lower()
msg = record.msg
......
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):
def __init__(self, io):
def __init__(self, io: "IO") -> None:
self._io = io
super(IOHandler, self).__init__()
def emit(self, record):
def emit(self, record: "LogRecord") -> None:
try:
msg = self.format(record)
level = record.levelname.lower()
err = level in ("warning", "error", "exception", "critical")
if err:
self._io.error_line(msg)
self._io.write_error_line(msg)
else:
self._io.write_line(msg)
except Exception:
......
from __future__ import absolute_import
from __future__ import unicode_literals
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Dict
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.utils.toml_file import TomlFile
from poetry.core.toml.file import TOMLFile
from .config.config import Config
from .config.file_config_source import FileConfigSource
from .io.null_io import NullIO
from .locations import CONFIG_DIR
from .packages.locker import Locker
from .poetry import Poetry
from .repositories.pypi_repository import PyPiRepository
from .utils._compat import Path
if TYPE_CHECKING:
from .repositories.legacy_repository import LegacyRepository
class Factory(BaseFactory):
......@@ -25,8 +30,8 @@ class Factory(BaseFactory):
"""
def create_poetry(
self, cwd=None, io=None
): # type: (Optional[Path], Optional[IO]) -> Poetry
self, cwd: Optional[Path] = None, io: Optional[IO] = None
) -> Poetry:
if io is None:
io = NullIO()
......@@ -40,7 +45,7 @@ class Factory(BaseFactory):
config = self.create_config(io)
# 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 io.is_debug():
io.write_line(
......@@ -49,6 +54,18 @@ class Factory(BaseFactory):
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(
base_poetry.file.path,
base_poetry.local_config,
......@@ -58,7 +75,8 @@ class Factory(BaseFactory):
)
# 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)
is_default = source.get("default", False)
is_secondary = source.get("secondary", False)
......@@ -78,7 +96,8 @@ class Factory(BaseFactory):
# Always put PyPI last to prefer private repositories
# but only if we have no other default source
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:
if io.is_debug():
io.write_line("Deactivating the PyPI repository")
......@@ -86,13 +105,13 @@ class Factory(BaseFactory):
return poetry
@classmethod
def create_config(cls, io=None): # type: (Optional[IO]) -> Config
def create_config(cls, io: Optional[IO] = None) -> Config:
if io is None:
io = NullIO()
config = 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 io.is_debug():
io.write_line(
......@@ -106,7 +125,7 @@ class Factory(BaseFactory):
config.set_config_source(FileConfigSource(config_file))
# 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 io.is_debug():
io.write_line(
......@@ -122,13 +141,11 @@ class Factory(BaseFactory):
return config
def create_legacy_repository(
self, source, auth_config
): # type: (Dict[str, str], Config) -> LegacyRepository
from .repositories.auth import Auth
self, source: Dict[str, str], auth_config: Config
) -> "LegacyRepository":
from .repositories.legacy_repository import LegacyRepository
from .utils.helpers import get_cert
from .utils.helpers import get_client_cert
from .utils.password_manager import PasswordManager
if "url" in source:
# PyPI-like repository
......@@ -137,19 +154,13 @@ class Factory(BaseFactory):
else:
raise RuntimeError("Unsupported source specified")
password_manager = PasswordManager(auth_config)
name = source["name"]
url = source["url"]
credentials = password_manager.get_http_auth(name)
if credentials:
auth = Auth(url, credentials["username"], credentials["password"])
else:
auth = None
return LegacyRepository(
name,
url,
auth=auth,
config=auth_config,
cert=get_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 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
if TYPE_CHECKING:
from typing import Any
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 cleo.io.io import IO
from poetry.config.config import Config
logger = logging.getLogger()
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._io = io
self._session = None
self._credentials = {}
self._password_manager = PasswordManager(self._config)
@property
def session(self): # type: () -> Session
from requests import Session # noqa
def _log(self, message: str, level: str = "debug") -> None:
if self._io is not None:
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:
self._session = Session()
self._session = requests.Session()
return self._session
def request(self, method, url, **kwargs): # type: (str, str, Any) -> Response
from requests import Request # noqa
from requests.auth import HTTPBasicAuth
request = Request(method, url)
username, password = self._get_credentials_for_url(url)
def request(self, method: str, url: str, **kwargs: Any) -> requests.Response:
request = requests.Request(method, url)
username, password = self.get_credentials_for_url(url)
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
prepared_request = session.prepare_request(request)
......@@ -63,16 +74,35 @@ class Authenticator(object):
"allow_redirects": kwargs.get("allow_redirects", True),
}
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
def _get_credentials_for_url(
self, url
): # type: (str) -> Tuple[Optional[str], Optional[str]]
parsed_url = urlparse.urlsplit(url)
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
# this should never really be hit under any sane circumstance
raise PoetryException("Failed HTTP {} request", method.upper())
def get_credentials_for_url(self, url: str) -> Tuple[Optional[str], Optional[str]]:
parsed_url = urllib.parse.urlsplit(url)
netloc = parsed_url.netloc
......@@ -95,7 +125,7 @@ class Authenticator(object):
credentials = auth, None
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:
......@@ -106,10 +136,11 @@ class Authenticator(object):
return credentials[0], credentials[1]
def _get_credentials_for_netloc_from_config(
self, netloc
): # type: (str) -> Tuple[Optional[str], Optional[str]]
self, netloc: str
) -> Tuple[Optional[str], Optional[str]]:
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(
"repositories.{}".format(repository_name)
)
......@@ -120,7 +151,7 @@ class Authenticator(object):
if not url:
continue
parsed_url = urlparse.urlsplit(url)
parsed_url = urllib.parse.urlsplit(url)
if netloc == parsed_url.netloc:
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:
def install(self, package):
def install(self, package: "Package") -> None:
raise NotImplementedError
def update(self, source, target):
def update(self, source: "Package", target: "Package") -> None:
raise NotImplementedError
def remove(self, package):
def remove(self, package: "Package") -> None:
raise NotImplementedError
import hashlib
import json
from pathlib import Path
from typing import TYPE_CHECKING
from typing import List
from typing import Optional
from poetry.core.packages.utils.link import Link
from poetry.utils._compat import Path
from .chooser import InvalidWheelName
from .chooser import Wheel
if TYPE_CHECKING:
from typing import List
from typing import Optional
from poetry.config.config import Config
from poetry.utils.env import Env
class Chef:
def __init__(self, config, env): # type: (Config, Env) -> None
def __init__(self, config: "Config", env: "Env") -> None:
self._config = config
self._env = env
self._cache_dir = (
Path(config.get("cache-dir")).expanduser().joinpath("artifacts")
)
def prepare(self, archive): # type: (Path) -> Path
def prepare(self, archive: Path) -> Path:
return archive
def prepare_sdist(self, archive): # type: (Path) -> Path
def prepare_sdist(self, archive: Path) -> Path:
return archive
def prepare_wheel(self, archive): # type: (Path) -> Path
def prepare_wheel(self, archive: Path) -> Path:
return archive
def should_prepare(self, archive): # type: (Path) -> bool
def should_prepare(self, archive: Path) -> bool:
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"
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 link.is_wheel:
pass
......@@ -74,7 +74,7 @@ class Chef:
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)
archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"]
......@@ -85,7 +85,7 @@ class Chef:
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}
if link.hash_name is not None and link.hash is not None:
......
import re
from typing import List
from typing import Optional
from typing import Tuple
from packaging.tags import Tag
......@@ -17,7 +18,7 @@ class InvalidWheelName(Exception):
class Wheel(object):
def __init__(self, filename): # type: (str) -> None
def __init__(self, filename: str) -> None:
wheel_info = wheel_file_re.match(filename)
if not wheel_info:
raise InvalidWheelName("{} is not a valid wheel filename.".format(filename))
......@@ -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
}
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]
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))
......@@ -48,11 +49,11 @@ class Chooser:
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._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.
"""
......@@ -63,7 +64,7 @@ class Chooser:
):
continue
if link.ext == ".egg":
if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}:
continue
links.append(link)
......@@ -82,7 +83,7 @@ class Chooser:
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 self._pool.has_repository("pypi"):
repository = self._pool.repositories[0]
......@@ -111,7 +112,7 @@ class Chooser:
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
InstallationCandidates by preference.
......@@ -169,9 +170,7 @@ class Chooser:
pri,
)
def _is_link_hash_allowed_for_package(
self, link, package
): # type: (Link, Package) -> bool
def _is_link_hash_allowed_for_package(self, link: Link, package: Package) -> bool:
if not link.hash:
return True
......
from typing import TYPE_CHECKING
from typing import List
from .base_installer import BaseInstaller
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class NoopInstaller(BaseInstaller):
def __init__(self):
def __init__(self) -> None:
self._installs = []
self._updates = []
self._removals = []
@property
def installs(self):
def installs(self) -> List["Package"]:
return self._installs
@property
def updates(self):
def updates(self) -> List["Package"]:
return self._updates
@property
def removals(self):
def removals(self) -> List["Package"]:
return self._removals
def install(self, package):
def install(self, package: "Package") -> None:
self._installs.append(package)
def update(self, source, target):
def update(self, source: "Package", target: "Package") -> None:
self._updates.append((source, target))
def remove(self, package):
def remove(self, package: "Package") -> None:
self._removals.append(package)
from typing import Union
from .install import Install
from .uninstall import Uninstall
from .update import Update
OperationTypes = Union[Install, Uninstall, Update]
from typing import TYPE_CHECKING
from typing import Optional
from .operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
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)
self._package = package
@property
def package(self):
def package(self) -> "Package":
return self._package
@property
def job_type(self):
def job_type(self) -> str:
return "install"
def __str__(self):
def __str__(self) -> str:
return "Installing {} ({})".format(
self.package.pretty_name, self.format_version(self.package)
)
def __repr__(self):
def __repr__(self) -> str:
return "<Install {} ({})>".format(
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):
def __init__(
self, reason=None, priority=0
): # type: (Union[str, None], int) -> None
def __init__(self, reason: Optional[str] = None, priority: int = 0) -> None:
self._reason = reason
self._skipped = False
......@@ -14,39 +15,39 @@ class Operation(object):
self._priority = priority
@property
def job_type(self): # type: () -> str
def job_type(self) -> str:
raise NotImplementedError
@property
def reason(self): # type: () -> str
def reason(self) -> str:
return self._reason
@property
def skipped(self): # type: () -> bool
def skipped(self) -> bool:
return self._skipped
@property
def skip_reason(self): # type: () -> Union[str, None]
def skip_reason(self) -> Optional[str]:
return self._skip_reason
@property
def priority(self): # type: () -> int
def priority(self) -> int:
return self._priority
@property
def package(self):
def package(self) -> "Package":
raise NotImplementedError()
def format_version(self, package): # type: (...) -> str
def format_version(self, package: "Package") -> str:
return package.full_pretty_version
def skip(self, reason): # type: (str) -> Operation
def skip(self, reason: str) -> "Operation":
self._skipped = True
self._skip_reason = reason
return self
def unskip(self): # type: () -> Operation
def unskip(self) -> "Operation":
self._skipped = False
self._skip_reason = None
......
from typing import TYPE_CHECKING
from typing import Optional
from .operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
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)
self._package = package
@property
def package(self):
def package(self) -> "Package":
return self._package
@property
def job_type(self):
def job_type(self) -> str:
return "uninstall"
def __str__(self):
def __str__(self) -> str:
return "Uninstalling {} ({})".format(
self.package.pretty_name, self.format_version(self._package)
)
def __repr__(self):
def __repr__(self) -> str:
return "<Uninstall {} ({})>".format(
self.package.pretty_name, self.format_version(self.package)
)
from typing import TYPE_CHECKING
from typing import Optional
from .operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
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._target_package = target
super(Update, self).__init__(reason, priority=priority)
@property
def initial_package(self):
def initial_package(self) -> "Package":
return self._initial_package
@property
def target_package(self):
def target_package(self) -> "Package":
return self._target_package
@property
def package(self):
def package(self) -> "Package":
return self._target_package
@property
def job_type(self):
def job_type(self) -> str:
return "update"
def __str__(self):
def __str__(self) -> str:
return "Updating {} ({}) to {} ({})".format(
self.initial_package.pretty_name,
self.format_version(self.initial_package),
......@@ -32,7 +45,7 @@ class Update(Operation):
self.format_version(self.target_package),
)
def __repr__(self):
def __repr__(self) -> str:
return "<Update {} ({}) to {} ({})>".format(
self.initial_package.pretty_name,
self.format_version(self.initial_package),
......
import os
import tempfile
import urllib.parse
from pathlib import Path
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.utils._compat import encode
from poetry.utils.env import Env
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
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
if TYPE_CHECKING:
from poetry.core.packages.package import Package
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._io = io
self._pool = pool
def install(self, package, update=False):
def install(self, package: "Package", update: bool = False) -> None:
if package.source_type == "directory":
self.install_directory(package)
......@@ -43,9 +48,9 @@ class PipInstaller(BaseInstaller):
and package.source_url
):
repository = self._pool.repository(package.source_reference)
parsed = urlparse.urlparse(package.source_url)
parsed = urllib.parse.urlparse(package.source_url)
if parsed.scheme == "http":
self._io.error(
self._io.write_error(
" <warning>Installing from unsecure host: {}</warning>".format(
parsed.hostname
)
......@@ -94,7 +99,7 @@ class PipInstaller(BaseInstaller):
self.run(*args)
def update(self, package, target):
def update(self, package: "Package", target: "Package") -> None:
if package.source_type != target.source_type:
# If the source type has changed, we remove the current
# package to avoid perpetual updates in some cases
......@@ -102,7 +107,7 @@ class PipInstaller(BaseInstaller):
self.install(target, update=True)
def remove(self, package):
def remove(self, package: "Package") -> None:
try:
self.run("uninstall", package.name, "-y")
except CalledProcessError as e:
......@@ -112,7 +117,9 @@ class PipInstaller(BaseInstaller):
raise
# 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():
nspkg_pth_file.unlink()
......@@ -122,10 +129,10 @@ class PipInstaller(BaseInstaller):
if src_dir.exists():
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)
def requirement(self, package, formatted=False):
def requirement(self, package: "Package", formatted: bool = False) -> str:
if formatted and not package.source_type:
req = "{}=={}".format(package.name, package.version)
for f in package.files:
......@@ -166,7 +173,7 @@ class PipInstaller(BaseInstaller):
return "{}=={}".format(package.name, package.version)
def create_temporary_requirement(self, package):
def create_temporary_requirement(self, package: "Package") -> str:
fd, name = tempfile.mkstemp(
"reqs.txt", "{}-{}".format(package.name, package.version)
)
......@@ -178,50 +185,41 @@ class PipInstaller(BaseInstaller):
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.io.null_io import NullIO
from poetry.masonry.builders.editable import EditableBuilder
from poetry.utils.toml_file import TomlFile
req: Path
if package.root_dir:
req = (package.root_dir / package.source_url).as_posix()
else:
req = os.path.realpath(package.source_url)
args = ["install", "--no-deps", "-U"]
req = Path(package.source_url).resolve(strict=False)
pyproject = TomlFile(os.path.join(req, "pyproject.toml"))
pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))
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"]
)
if pyproject.is_poetry_project():
# Even if there is a build system specified
# some versions of pip (< 19.0.0) don't understand it
# so we need to check the version of pip to know
# if we can rely on the build system
pip_version = self._env.pip_version
pip_version_with_build_system_support = pip_version.__class__(19, 0, 0)
has_build_system = (
"build-system" in pyproject_content
and pip_version >= pip_version_with_build_system_support
legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
19, 0, 0
)
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:
from poetry.masonry.builders.editable import EditableBuilder
# This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip
# to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build()
return
elif not has_build_system or package_poetry.package.build_script:
return 0
elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder
# We need to rely on creating a temporary setup.py
......@@ -232,22 +230,20 @@ class PipInstaller(BaseInstaller):
with builder.setup_py():
if package.develop:
args.append("-e")
args.append(req)
return self.run(*args)
return pip_editable_install(
directory=req, environment=self._env
)
return pip_install(
path=req, environment=self._env, deps=False, upgrade=True
)
if package.develop:
args.append("-e")
args.append(req)
return self.run(*args)
return pip_editable_install(directory=req, environment=self._env)
return pip_install(path=req, environment=self._env, deps=False, upgrade=True)
def install_git(self, package):
from poetry.core.packages import Package
from poetry.core.vcs import Git
def install_git(self, package: "Package") -> None:
from poetry.core.packages.package import Package
from poetry.core.vcs.git import Git
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
......@@ -261,8 +257,8 @@ class PipInstaller(BaseInstaller):
# Now we just need to install from the source directory
pkg = Package(package.name, package.version)
pkg.source_type = "directory"
pkg.source_url = str(src_dir)
pkg._source_type = "directory"
pkg._source_url = str(src_dir)
pkg.develop = package.develop
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