diff --git a/.cirrus.yml b/.cirrus.yml index c8ccc47cf40..2a1c443a7bc 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,24 +1,29 @@ freebsd_instance: - image_family: freebsd-12-2 + image_family: freebsd-13-0 test_task: name: "Tests / FreeBSD / " only_if: $CIRRUS_TAG == '' skip: "!changesInclude('.cirrus.yml', 'poetry.lock', 'pyproject.toml', '**.json','**.py')" env: + PATH: /.local/bin:${PATH} matrix: - - PYTHON: python3.6 - PYTHON: python3.8 + - PYTHON: python3.9 python_script: - PYPACKAGE=$(printf '%s' $PYTHON | tr -d '.') - SQLPACKAGE=$(printf '%s-sqlite3' $PYPACKAGE | sed 's/thon//') - - pkg install -y git-lite $PYPACKAGE $SQLPACKAGE + - pkg install -y git-lite curl $PYPACKAGE $SQLPACKAGE pip_script: - $PYTHON -m ensurepip - - $PYTHON -m pip install -U pip tox - - $PYTHON -m pip install -U --pre poetry + - $PYTHON -m pip --disable-pip-version-check install -U pip + poetry_script: + - curl -sL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py \ + | $PYTHON - -y - poetry config virtualenvs.in-project true - tox_script: $PYTHON -m tox -e py -- -q --junitxml=junit.xml tests + test_script: | + poetry install + poetry run pytest -q --junitxml=junit.xml tests on_failure: annotate_failure_artifacts: path: junit.xml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..f4369a4054e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 + +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + # keep dependency updates manual for now + open-pull-requests-limit: 0 + reviewers: + - "python-poetry/triage" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index b14884c8fa0..00000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Documentation - -on: - push: - paths: - - 'docs/**' - - '.github/workflows/docs.yml' - branches: - - master - pull_request: - paths: - - 'docs/**' - - '.github/workflows/docs.yml' - branches: - - '**' - -jobs: - docs: - name: Documentation Build - runs-on: Ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install tox - run: pip install tox - - name: Build documentation - run: tox -e doc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9377e9a6992..b6a282e11fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,7 @@ repos: rev: 3.8.4 hooks: - id: flake8 + additional_dependencies: [flake8-bugbear] - repo: https://github.com/timothycrosley/isort rev: 5.7.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 35c22a27f68..afa70117a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,65 @@ # Change Log +## [1.1.10] - 2021-09-21 + +### Fixed + +- Fixed an issue where non-sha256 hashes were not checked. ([#4529](https://github.com/python-poetry/poetry/pull/4529)) + +## [1.1.9] - 2021-09-18 + +### Fixed + +- Fixed a security issue where file hashes were not checked prior to installation. ([#4420](https://github.com/python-poetry/poetry/pull/4420), [#4444](https://github.com/python-poetry/poetry/pull/4444), [python-poetry/poetry-core#193](https://github.com/python-poetry/poetry-core/pull/193)) +- Fixed the detection of the system environment when the setting `virtualenvs.create` is deactivated. ([#4507](https://github.com/python-poetry/poetry/pull/4507)) +- Fixed an issue where unsafe parameters could be passed to `git` commands. ([python-poetry/poetry-core#203](https://github.com/python-poetry/poetry-core/pull/203)) +- Fixed an issue where the wrong `git` executable could be used on Windows. ([python-poetry/poetry-core#205](https://github.com/python-poetry/poetry-core/pull/205)) +## [1.1.8] - 2021-08-19 + +### Fixed + +- Fixed an error with repository prioritization when specifying secondary repositories. ([#4241](https://github.com/python-poetry/poetry/pull/4241)) +- Fixed the detection of the system environment when the setting `virtualenvs.create` is deactivated. ([#4330](https://github.com/python-poetry/poetry/pull/4330), [#4407](https://github.com/python-poetry/poetry/pull/4407)) +- Fixed the evaluation of relative path dependencies. ([#4246](https://github.com/python-poetry/poetry/pull/4246)) +- Fixed environment detection for Python 3.10 environments. ([#4387](https://github.com/python-poetry/poetry/pull/4387)) +- Fixed an error in the evaluation of `in/not in` markers ([python-poetry/poetry-core#189](https://github.com/python-poetry/poetry-core/pull/189)) + +## [1.2.0a2] - 2021-08-01 + +### Added + +- Poetry now supports dependency groups. ([#4260](https://github.com/python-poetry/poetry/pull/4260)) +- The `install` command now supports a `--sync` option to synchronize the environment with the lock file. ([#4336](https://github.com/python-poetry/poetry/pull/4336)) + +### Changed + +- Improved the way credentials are retrieved to better support keyring backends. ([#4086](https://github.com/python-poetry/poetry/pull/4086)) +- The `--remove-untracked` option of the `install` command is now deprecated in favor of the new `--sync` option. ([#4336](https://github.com/python-poetry/poetry/pull/4336)) +- The user experience when installing dependency groups has been improved. ([#4336](https://github.com/python-poetry/poetry/pull/4336)) + +### Fixed + +- Fixed performance issues when resolving dependencies. ([#3839](https://github.com/python-poetry/poetry/pull/3839)) +- Fixed an issue where transitive dependencies of directory or VCS dependencies were not installed or otherwise removed. ([#4202](https://github.com/python-poetry/poetry/pull/4202)) +- Fixed the behavior of the `init` command in non-interactive mode. ([#2899](https://github.com/python-poetry/poetry/pull/2899)) +- Fixed the detection of the system environment when the setting `virtualenvs.create` is deactivated. ([#4329](https://github.com/python-poetry/poetry/pull/4329)) +- Fixed the display of possible solutions for some common errors. ([#4332](https://github.com/python-poetry/poetry/pull/4332)) + + +## [1.1.7] - 2021-06-25 + +Note: Lock files might need to be regenerated for the first fix below to take effect.\ +You can use `poetry lock` to do so without the `--no-update` option. + +### Changed + +- This release is compatible with the `install-poetry.py` installation script to ease the migration path from `1.1` releases to `1.2` releases. ([#4192](https://github.com/python-poetry/poetry/pull/4192)) + +### Fixed + +- Fixed an issue where transitive dependencies of directory or VCS dependencies were not installed or otherwise removed. ([#4203](https://github.com/python-poetry/poetry/pull/4203)) +- Fixed an issue where the combination of the `--tree` and `--no-dev` options for the show command was still displaying development dependencies. ([#3992](https://github.com/python-poetry/poetry/pull/3992)) + ## [1.2.0a1] - 2021-05-21 This release is the first testing release of the upcoming 1.2.0 version. @@ -29,6 +89,16 @@ It **drops** support for Python 2.7 and 3.5. - Fixed an error where command line options were not taken into account when using the `run` command. ([#3618](https://github.com/python-poetry/poetry/pull/3618)) - Fixed an error in the way custom repositories were resolved. ([#3406](https://github.com/python-poetry/poetry/pull/3406)) +## [1.1.6] - 2021-04-14 + +### Fixed +- Fixed export format for path dependencies. ([#3121](https://github.com/python-poetry/poetry/pull/3121)) +- Fixed errors caused by environment modification when executing some commands. ([#3253](https://github.com/python-poetry/poetry/pull/3253)) +- Fixed handling of wheel files with single-digit versions. ([#3338](https://github.com/python-poetry/poetry/pull/3338)) +- Fixed an error when handling single-digit Python markers. ([poetry-core#156](https://github.com/python-poetry/poetry-core/pull/156)) +- Fixed dependency markers not being properly copied when changing the constraint leading to resolution errors. ([poetry-core#163](https://github.com/python-poetry/poetry-core/pull/163)) +- Fixed an error where VCS dependencies were always updated. ([#3947](https://github.com/python-poetry/poetry/pull/3947)) +- Fixed an error where the incorrect version of a package was locked when using environment markers. ([#3945](https://github.com/python-poetry/poetry/pull/3945)) ## [1.1.5] - 2021-03-04 @@ -949,7 +1019,7 @@ commands in project subdirectories. - Improved dependency resolution to avoid unnecessary operations. - Improved dependency resolution speed. - Improved CLI reactivity by deferring imports. -- License classifer is not automatically added to classifers. +- License classifier is not automatically added to classifiers. ### Fixed @@ -994,7 +1064,7 @@ commands in project subdirectories. ### Changed -- Changed how wilcard constraints are handled. +- Changed how wildcard constraints are handled. ### Fixed @@ -1123,10 +1193,17 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.2.0a1...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.2.0a2...master +[1.2.0a2]: https://github.com/python-poetry/poetry/compare/1.2.0a2 [1.2.0a1]: https://github.com/python-poetry/poetry/compare/1.2.0a1 -[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.10]: https://github.com/python-poetry/poetry/releases/tag/1.1.10 +[1.1.9]: https://github.com/python-poetry/poetry/releases/tag/1.1.9 +[1.1.8]: https://github.com/python-poetry/poetry/releases/tag/1.1.8 +[1.1.7]: https://github.com/python-poetry/poetry/releases/tag/1.1.7 +[1.1.6]: https://github.com/python-poetry/poetry/releases/tag/1.1.6 +[1.1.5]: https://github.com/python-poetry/poetry/releases/tag/1.1.5 +[1.1.4]: https://github.com/python-poetry/poetry/releases/tag/1.1.4 +[1.1.3]: https://github.com/python-poetry/poetry/releases/tag/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 diff --git a/README.md b/README.md index 1f43666a795..e4ea02d9cad 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,9 @@ python install-poetry.py --git https://github.com/python-poetry/poetry.git@maste Updating poetry to the latest stable version is as simple as calling the `self update` command. +**Warning**: Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this +command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. + ```bash poetry self update ``` @@ -205,7 +208,7 @@ There are some things we can notice here: * It will try to enforce [semantic versioning]() as the best practice in version naming. * You can specify the readme, included and excluded files: no more `MANIFEST.in`. `poetry` will also use VCS ignore files (like `.gitignore`) to populate the `exclude` section. -* Keywords (up to 5) can be specified and will act as tags on the packaging site. +* Keywords can be specified and will act as tags on the packaging site. * The dependencies sections support caret, tilde, wildcard, inequality and multiple requirements. * You must specify the python versions for which your package is compatible. diff --git a/docs/docs/index.md b/docs/_index.md similarity index 80% rename from docs/docs/index.md rename to docs/_index.md index dc0d9295309..d349033d651 100644 --- a/docs/docs/index.md +++ b/docs/_index.md @@ -1,6 +1,17 @@ +--- +title: "Introduction" +draft: false +type: docs +layout: "single" + +menu: + docs: + weight: 0 +--- + # Introduction -Poetry is a tool for dependency management and packaging in Python. +Poetry is a tool for **dependency management** and **packaging** in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. @@ -9,11 +20,10 @@ It allows you to declare the libraries your project depends on and it will manag Poetry requires Python 2.7 or 3.5+. It is multi-platform and the goal is to make it work equally well on Windows, Linux and OSX. -!!! note - - Python 2.7 and 3.5 will no longer be supported in the next feature release (1.2). - You should consider updating your Python version to a supported one. - +{{% note %}} +Python 2.7 and 3.5 will no longer be supported in the next feature release (1.2). +You should consider updating your Python version to a supported one. +{{% /note %}} ## Installation @@ -29,10 +39,10 @@ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install- (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - ``` -!!!warning - - The previous `get-poetry.py` installer is now deprecated, if you are currently using it - you should migrate to the new, supported, `install-poetry.py` installer. +{{% warning %}} +The previous `get-poetry.py` installer is now deprecated, if you are currently using it +you should migrate to the new, supported, `install-poetry.py` installer. +{{% /warning %}} The installer installs the `poetry` tool to Poetry's `bin` directory. This location depends on your system: @@ -89,9 +99,9 @@ You can also install Poetry for a `git` repository by using the `--git` option: python install-poetry.py --git https://github.com/python-poetry/poetry.git@master ```` -!!!note - - Note that the installer does not support Python < 3.6. +{{% note %}} +Note that the installer does not support Python < 3.6. +{{% /note %}} ### Alternative installation methods @@ -123,11 +133,10 @@ Using `pip` to install Poetry is possible. pip install --user poetry ``` -!!!warning - - Be aware that it will also install Poetry's dependencies - which might cause conflicts with other packages. - +{{% warning %}} +Be aware that it will also install Poetry's dependencies +which might cause conflicts with other packages. +{{% /warning %}} ## Updating `poetry` @@ -137,6 +146,11 @@ Updating Poetry to the latest stable version is as simple as calling the `self u poetry self update ``` +{{% warning %}} +Poetry versions installed using the now deprecated `get-poetry.py` installer will not be able to use this +command to update to 1.2 releases or later. Migrate to using the `install-poetry.py` installer or `pipx`. +{{% /warning %}} + If you want to install pre-release versions, you can use the `--preview` option. ```bash @@ -182,9 +196,9 @@ poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry ``` -!!! note - - You may need to restart your shell in order for the changes to take effect. +{{% note %}} +You may need to restart your shell in order for the changes to take effect. +{{% /note %}} For `zsh`, you must then add the following line in your `~/.zshrc` before `compinit`: @@ -194,7 +208,7 @@ fpath+=~/.zfunc For `oh-my-zsh`, you must then enable poetry in your `~/.zshrc` plugins -``` +```text plugins( poetry ... diff --git a/docs/docs/basic-usage.md b/docs/basic-usage.md similarity index 66% rename from docs/docs/basic-usage.md rename to docs/basic-usage.md index c9805bb3996..fb20dee502b 100644 --- a/docs/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -1,7 +1,18 @@ +--- +title: "Basic usage" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 10 +--- + # Basic usage For the basic usage introduction we will be installing `pendulum`, a datetime library. -If you have not yet installed Poetry, refer to the [Introduction](/docs/) chapter. +If you have not yet installed Poetry, refer to the [Introduction]({{< relref "docs" >}} "Introduction") chapter. ## Project setup @@ -37,8 +48,8 @@ authors = ["Sébastien Eustace "] [tool.poetry.dependencies] python = "*" -[tool.poetry.dev-dependencies] -pytest = "^3.4" +[tool.poetry.group.dev.dependencies] +pytest = "^6.0" ``` ### Initialising a pre-existing project @@ -57,13 +68,13 @@ If you want to add dependencies to your project, you can specify them in the `to ```toml [tool.poetry.dependencies] -pendulum = "^1.4" +pendulum = "^2.1" ``` As you can see, it takes a mapping of **package names** and **version constraints**. Poetry uses this information to search for the right set of files in package "repositories" that you register -in the `tool.poetry.repositories` section, or on [PyPI](https://pypi.org) by default. +in the `tool.poetry.source` section, or on [PyPI](https://pypi.org) by default. Also, instead of modifying the `pyproject.toml` file by hand, you can use the `add` command. @@ -71,14 +82,14 @@ Also, instead of modifying the `pyproject.toml` file by hand, you can use the `a $ poetry add pendulum ``` -It will automatically find a suitable version constraint **and install** the package and subdependencies. +It will automatically find a suitable version constraint **and install** the package and sub-dependencies. ## Using your virtual environment By default, poetry creates a virtual environment in `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). -You can change the [`cache-dir`](/docs/configuration/#cache-dir) value by editing the poetry config. -Additionally, you can use the [`virtualenvs.in-project`](/docs/configuration/#virtualenvs.in-project) configuration variable +You can change the [`cache-dir`]({{< relref "configuration#cache-dir" >}} "cache-dir configuration documentation") value by editing the poetry config. +Additionally, you can use the [`virtualenvs.in-project`]({{< relref "configuration#virtualenvsin-project" >}} "#virtualenvs.in-project configuration documentation") configuration variable to create virtual environment within your project directory. @@ -96,18 +107,19 @@ The easiest way to activate the virtual environment is to create a new shell wit To deactivate the virtual environment and exit this new shell type `exit`. To deactivate the virtual environment without leaving the shell use `deactivate`. -!!!note +{{% note %}} +**Why a new shell?** - **Why a new shell?** - Child processes inherit their environment from their parents, but do not share - them. As such, any modifications made by a child process, is not persisted after - the child process exits. A Python application (Poetry), being a child process, - cannot modify the environment of the shell that it has been called from such - that an activated virtual environment remains active after the Poetry command - has completed execution. +Child processes inherit their environment from their parents, but do not share +them. As such, any modifications made by a child process, is not persisted after +the child process exits. A Python application (Poetry), being a child process, +cannot modify the environment of the shell that it has been called from such +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 environment activated - in order for the subsequent commands to run from within the virtual environment. +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. +{{% /note %}} Alternatively, to avoid creating a new shell, you can manually activate the @@ -116,38 +128,38 @@ 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` | `{path_to_venv}\Scripts\activate.bat` | `deactivate` | -| One-liner | ```source `poetry env info --path`/bin/activate```| | `deactivate` | +| | POSIX Shell | Windows | Exit/Deactivate | +| ----------------- | ----------------------------------------------- | ------------------------------------- | --------------- | +| New Shell | `poetry shell` | `poetry shell` | `exit` | +| 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 -In our example, we are requesting the `pendulum` package with the version constraint `^1.4`. -This means any version greater or equal to 1.4.0 and less than 2.0.0 (`>=1.4.0 <2.0.0`). +In our example, we are requesting the `pendulum` package with the version constraint `^2.1`. +This means any version greater or equal to 2.1.0 and less than 3.0.0 (`>=2.1.0 <3.0.0`). -Please read [Dependency specification](/docs/dependency-specification) for more in-depth information on versions, +Please read [Dependency specification]({{< relref "dependency-specification" >}} "Dependency specification documentation") for more in-depth information on versions, how versions relate to each other, and on the different ways you can specify dependencies. -!!!note - - **How does Poetry download the right files?** +{{% note %}} +**How does Poetry download the right files?** - When you specify a dependency in `pyproject.toml`, Poetry first takes the name of the package - that you have requested and searches for it in any repository you have registered using the `repositories` key. - If you have not registered any extra repositories, or it does not find a package with that name in the - repositories you have specified, it falls back on PyPI. +When you specify a dependency in `pyproject.toml`, Poetry first takes the name of the package +that you have requested and searches for it in any repository you have registered using the `repositories` key. +If you have not registered any extra repositories, or it does not find a package with that name in the +repositories you have specified, it falls back on PyPI. - When Poetry finds the right package, it then attempts to find the best match - for the version constraint you have specified. +When Poetry finds the right package, it then attempts to find the best match +for the version constraint you have specified. +{{% /note %}} ## Installing dependencies -To install the defined dependencies for your project, just run the `install` command. +To install the defined dependencies for your project, just run the [`install`]({{< relref "cli#install" >}}) command. ```bash poetry install @@ -160,7 +172,7 @@ When you run this command, one of two things may happen: If you have never run the command before and there is also no `poetry.lock` file present, Poetry simply resolves all dependencies listed in your `pyproject.toml` file and downloads the latest version of their files. -When Poetry has finished installing, it writes all of the packages and the exact versions of them that it downloaded to the `poetry.lock` file, +When Poetry has finished installing, it writes all the packages and their exact versions that it downloaded to the `poetry.lock` file, locking the project to those specific versions. You should commit the `poetry.lock` file to your project repo so that all people working on the project are locked to the same versions of dependencies (more below). @@ -175,7 +187,7 @@ Either way, running `install` when a `poetry.lock` file is present resolves and but Poetry uses the exact versions listed in `poetry.lock` to ensure that the package versions are consistent for everyone working on your project. As a result you will have all dependencies requested by your `pyproject.toml` file, but they may not all be at the very latest available versions -(some of the dependencies listed in the `poetry.lock` file may have released newer versions since the file was created). +(some dependencies listed in the `poetry.lock` file may have released newer versions since the file was created). This is by design, it ensures that your project does not break because of unexpected changes in dependencies. ### Commit your `poetry.lock` file to version control @@ -189,13 +201,13 @@ Even if you develop alone, in six months when reinstalling the project you can f the dependencies installed are still working even if your dependencies released many new versions since then. (See note below about using the update command.) -!!!note - - For libraries it is not necessary to commit the lock file. +{{% note %}} +For libraries it is not necessary to commit the lock file. +{{% /note %}} ### Installing dependencies only -The current project is installed in [editable](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs) mode by default. +The current project is installed in [editable](https://pip.pypa.io/en/stable/cli/pip_install/#install-editable) mode by default. If you want to install the dependencies only, run the `install` command with the `--no-root` flag: @@ -212,7 +224,7 @@ This will fetch the latest matching versions (according to your `pyproject.toml` and update the lock file with the new versions. (This is equivalent to deleting the `poetry.lock` file and running `install` again.) -!!!note - - Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml` - are not synchronized. +{{% note %}} +Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml` +are not synchronized. +{{% /note %}} diff --git a/docs/docs/cli.md b/docs/cli.md similarity index 75% rename from docs/docs/cli.md rename to docs/cli.md index 45881bec80a..6abd517c270 100644 --- a/docs/docs/cli.md +++ b/docs/cli.md @@ -1,3 +1,15 @@ +--- +title: "Commands" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 30 +--- + + # Commands You've already learned how to use the command-line interface to do some things. @@ -121,36 +133,60 @@ This ensures that everyone using the library will get the same versions of the d If there is no `poetry.lock` file, Poetry will create one after dependency resolution. -You can specify to the command that you do not want the development dependencies installed by passing -the `--no-dev` option. +If you want to exclude one or more dependency group for the installation, you can use +the `--without` option. ```bash -poetry install --no-dev +poetry install --without test,docs ``` -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. +{{% note %}} +The `--no-dev` option is now deprecated. You should use the `--without dev` notation instead. +{{% /note %}} + +You can also select optional dependency groups with the `--with` option. ```bash -poetry install --dev-only +poetry install --with test,docs ``` -If you want to remove old dependencies no longer present in the lock file, use the -`--remove-untracked` option. +It's also possible to only install specific dependency groups by using the `only` option. ```bash -poetry install --remove-untracked +poetry install --only test,docs +``` + +{{% note %}} +The `--dev-only` option is now deprecated. You should use the `--only dev` notation instead. +{{% /note %}} + +See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information +about dependency groups. + +If you want to synchronize your environment – and ensure it matches the lock file – use the +`--sync` option. + +```bash +poetry install --sync +``` + +The `--sync` can be combined with group-related options: + +```bash +poetry install --without dev --sync +poetry install --with docs --sync +poetry install --only dev ``` 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]({{< relref "pyproject#extras" >}}) for more info) ```bash poetry install --extras "mysql pgsql" poetry install -E mysql -E pgsql ``` -By default `poetry` will install your project's package everytime you run `install`: +By default `poetry` will install your project's package every time you run `install`: ```bash $ poetry install @@ -159,7 +195,6 @@ Installing dependencies from lock file No dependencies to install or update - Installing (x.x.x) - ``` If you want to skip this installation, use the `--no-root` option. @@ -168,17 +203,23 @@ 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. +Installation of your project's package is also skipped when the `--only` +option is used. ### Options -* `--no-dev`: Do not install dev dependencies. -* `--dev-only`: Only install dev dependencies. +* `--without`: The dependency groups to ignore for installation. +* `--with`: The optional dependency groups to include for installation. +* `--only`: The only dependency groups to install. +* `--default`: Only install the default dependencies. +* `--sync`: Synchronize the environment with the locked packages and the specified groups. * `--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). +* `--no-dev`: Do not install dev dependencies. (**Deprecated**) +* `--dev-only`: Only install dev dependencies. (**Deprecated**) +* `--remove-untracked`: Remove dependencies not presented in the lock file. (**Deprecated**) + ## update @@ -286,10 +327,10 @@ Alternatively, you can specify it in the `pyproject.toml` file. It means that ch 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. +{{% 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. +{{% /note %}} If the package(s) you want to install provide extras, you can specify them when adding the package: @@ -300,9 +341,19 @@ poetry add "requests[security,socks]~=2.22.0" poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]" ``` +If you want to add a package to a specific group of dependencies, you can use the `--group (-G)` option: + +```bash +poetry add mkdocs --group docs +``` + +See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information +about dependency groups. + ### Options -* `--dev (-D)`: Add package as development dependency. +* `--group (-D)`: The group to add the dependency to. +* `--dev (-D)`: Add package as development dependency. (**Deprecated**) * `--editable (-e)`: Add vcs/path dependencies as editable. * `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed) * `--optional`: Add as an optional dependency. @@ -323,9 +374,19 @@ list of installed packages. poetry remove pendulum ``` +If you want to remove a package from a specific group of dependencies, you can use the `--group (-G)` option: + +```bash +poetry remove mkdocs --group docs +``` + +See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information +about dependency groups. + ### Options -* `--dev (-D)`: Removes a package from the development dependencies. +* `--group (-D)`: The group to remove the dependency from. +* `--dev (-D)`: Removes a package from the development dependencies. (**Deprecated**) * `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose). @@ -346,14 +407,21 @@ name : pendulum version : 1.4.2 description : Python datetimes made easy -dependencies: +dependencies - python-dateutil >=2.6.1 - tzlocal >=1.4 - pytzdata >=2017.2.2 + +required by + - calendar >=1.4.0 ``` ### Options +* `--without`: Do not show the information of the specified groups' dependencies. +* `--with`: Show the information of the specified optional groups' dependencies as well. +* `--only`: Only show the information of dependencies belonging to the specified groups. +* `--default`: Only show the information of the default dependencies. * `--no-dev`: Do not list the dev dependencies. * `--tree`: List the dependencies as a tree. * `--latest (-l)`: Show the latest version. @@ -409,7 +477,7 @@ poetry config [options] [setting-key] [setting-value1] ... [setting-valueN] ```` `setting-key` is a configuration option name and `setting-value1` is a configuration value. -See [Configuration](/docs/configuration/) for all available settings. +See [Configuration]({{< relref "configuration" >}}) for all available settings. ### Options @@ -473,9 +541,9 @@ poetry search requests pendulum This command locks (without installing) the dependencies specified in `pyproject.toml`. -!!!note - - By default, this will lock all dependencies to the latest available compatible versions. To only refresh the lock file, use the `--no-update` option. +{{% note %}} +By default, this will lock all dependencies to the latest available compatible versions. To only refresh the lock file, use the `--no-update` option. +{{% /note %}} ```bash poetry lock @@ -497,17 +565,17 @@ The new version should ideally be a valid [semver](https://semver.org/) string o The table below illustrates the effect of these rules with concrete examples. -| rule | before | after | -|------------|---------------|---------------| -| major | 1.3.0 | 2.0.0 | -| minor | 2.1.4 | 2.2.0 | -| patch | 4.1.1 | 4.1.2 | -| premajor | 1.0.2 | 2.0.0-alpha.0 | -| preminor | 1.0.2 | 1.1.0-alpha.0 | -| prepatch | 1.0.2 | 1.0.3-alpha.0 | -| prerelease | 1.0.2 | 1.0.3-alpha.0 | +| rule | before | after | +| ---------- | ------------- | ------------- | +| major | 1.3.0 | 2.0.0 | +| minor | 2.1.4 | 2.2.0 | +| patch | 4.1.1 | 4.1.2 | +| premajor | 1.0.2 | 2.0.0-alpha.0 | +| preminor | 1.0.2 | 1.1.0-alpha.0 | +| prepatch | 1.0.2 | 1.0.3-alpha.0 | +| prerelease | 1.0.2 | 1.0.3-alpha.0 | | prerelease | 1.0.3-alpha.0 | 1.0.3-alpha.1 | -| prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 | +| prerelease | 1.0.3-beta.0 | 1.0.3-beta.1 | ### Options @@ -521,9 +589,9 @@ This command exports the lock file to other formats. poetry export -f requirements.txt --output requirements.txt ``` -!!!note - - Only the `requirements.txt` format is currently supported. +{{% note %}} +Only the `requirements.txt` format is currently supported. +{{% /note %}} ### Options @@ -541,7 +609,7 @@ poetry export -f requirements.txt --output requirements.txt The `env` command regroups sub commands to interact with the virtualenvs associated with a specific project. -See [Managing environments](/docs/managing-environments/) for more information about these commands. +See [Managing environments]({{< relref "managing-environments" >}}) for more information about these commands. ## cache @@ -628,18 +696,18 @@ For example, to add the `pypi-test` source, you can run: poetry source add pypi-test https://test.pypi.org/simple/ ``` -!!!note - - You cannot use the name `pypi` as it is reserved for use by the default PyPI source. +{{% note %}} +You cannot use the name `pypi` as it is reserved for use by the default PyPI source. +{{% /note %}} #### Options -* `--default`: Set this source as the [default](/docs/repositories/#disabling-the-pypi-repository) (disable PyPI). -* `--secondary`: Set this source as a [secondary](/docs/repositories/#install-dependencies-from-a-private-repository) source. +* `--default`: Set this source as the [default]({{< relref "repositories#disabling-the-pypi-repository" >}}) (disable PyPI). +* `--secondary`: Set this source as a [secondary]({{< relref "repositories#install-dependencies-from-a-private-repository" >}}) source. -!!!note - - You cannot set a source as both `default` and `secondary`. +{{% note %}} +You cannot set a source as both `default` and `secondary`. +{{% /note %}} ### `source show` @@ -655,9 +723,9 @@ Optionally, you can show information of one or more sources by specifying their poetry source show pypi-test ``` -!!!note - - This command will only show sources configured via the `pyproject.toml` and does not include PyPI. +{{% note %}} +This command will only show sources configured via the `pyproject.toml` and does not include PyPI. +{{% /note %}} ### `source remove` diff --git a/docs/docs/configuration.md b/docs/configuration.md similarity index 81% rename from docs/docs/configuration.md rename to docs/configuration.md index 4743515dd43..f1669d76f62 100644 --- a/docs/docs/configuration.md +++ b/docs/configuration.md @@ -1,6 +1,17 @@ +--- +title: "Configuration" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 40 +--- + # Configuration -Poetry can be configured via the `config` command ([see more about its usage here](/docs/cli/#config)) +Poetry can be configured via the `config` command ([see more about its usage here]({{< relref "cli#config" >}} "config command documentation")) or directly in the `config.toml` file that will be automatically be created when you first run that command. This file can typically be found in one of the following directories: @@ -93,7 +104,9 @@ export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret ## Available settings -### `cache-dir`: string +### `cache-dir` + +**Type**: string The path to the cache directory used by Poetry. @@ -103,28 +116,35 @@ Defaults to one of the following directories: - Windows: `C:\Users\\AppData\Local\pypoetry\Cache` - Unix: `~/.cache/pypoetry` -### `installer.parallel`: boolean +### `installer.parallel` + +**Type**: 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. +{{% note %}} +This configuration will be ignored, and parallel execution disabled when running +Python 2.7 under Windows. +{{% /note %}} + +### `virtualenvs.create` -### `virtualenvs.create`: boolean +**Type**: 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 +{{% note %}} +When setting this configuration to `false`, the Python environment used must have `pip` +installed and available. +{{% /note %}} - When setting this configuration to `false`, the Python environment used must have `pip` - installed and available. +### `virtualenvs.in-project` -### `virtualenvs.in-project`: boolean +**Type**: boolean Create the virtualenv inside the project's root directory. Defaults to `None`. @@ -136,22 +156,30 @@ If not set explicitly (default), `poetry` will use the virtualenv from the `.ven directory when one is available. If set to `false`, `poetry` will ignore any existing `.venv` directory. -### `virtualenvs.path`: string +### `virtualenvs.path` + +**Type**: string Directory where virtual environments will be created. Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). -### `virtualenvs.options.always-copy`: boolean +### `virtualenvs.options.always-copy` + +**Type**: 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 +### `virtualenvs.options.system-site-packages` + +**Type**: boolean Give the virtual environment access to the system site-packages directory. Applies on virtualenv creation. Defaults to `false`. -### `repositories.`: string +### `repositories.` + +**Type**: string -Set a new alternative repository. See [Repositories](/docs/repositories/) for more information. +Set a new alternative repository. See [Repositories]({{< relref "repositories" >}}) for more information. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000000..d54359fde70 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,266 @@ +--- +title: "Contributing to Poetry" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 100 +--- + +# Contributing to Poetry + +First off, thanks for taking the time to contribute! + +The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request. + +#### Table of contents + +[How to contribute](#how-to-contribute) + + * [Reporting bugs](#reporting-bugs) + * [Suggesting enhancements](#suggesting-enhancements) + * [Contributing to documentation](#contributing-to-documentation) + * [Contributing to code](#contributing-to-code) + * [Issue triage](#issue-triage) + * [Git workflow](#git-workflow) + + +## How to contribute + +### Reporting bugs + +This section guides you through submitting a bug report for Poetry. +Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. + +Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/---bug-report.md), the information it asks helps the maintainers resolve the issue faster. + +> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +#### Before submitting a bug report + +* **Check the [FAQs on the official website](https://python-poetry.org/docs/faq)** for a list of common questions and problems. +* **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues)**. + +#### How do I submit a bug report? + +Bugs are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/---bug-report.md). + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. +* **Provide your pyproject.toml file** in a [Gist](https://gist.github.com) after removing potential private information (like private package repositories). +* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy-paste-able snippets, which you use in those examples. +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** +* **If the problem is an unexpected error being raised**, execute the corresponding command in **debug** mode (the `-vvv` option). + +Provide more context by answering these questions: + +* **Did the problem start happening recently** (e.g. after updating to a new version of Poetry) or was this always a problem? +* If the problem started happening recently, **can you reproduce the problem in an older version of Poetry?** What's the most recent version in which the problem doesn't happen? +* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Include details about your configuration and environment: + +* **Which version of Poetry are you using?** You can get the exact version by running `poetry -V` in your terminal. +* **Which Python version Poetry has been installed for?** Execute the `debug:info` to get the information. +* **What's the name and version of the OS you're using**? + + +### Suggesting enhancements + +This section guides you through submitting an enhancement suggestion for Poetry, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. + +Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/python-poetry/poetry/blob/master/.github/ISSUE_TEMPLATE/---feature-request.md), including the steps that you imagine you would take if the feature you're requesting existed. + +#### Before submitting an enhancement suggestion + +* **Check the [FAQs on the official website](https://python-poetry.org/docs/faq)** for a list of common questions and problems. +* **Check that your issue does not already exist in the [issue tracker](https://github.com/python-poetry/poetry/issues)**. + +#### How do I submit an Enhancement suggestion? + +Enhancement suggestions are tracked on the [official issue tracker](https://github.com/python-poetry/poetry/issues) where you can create a new one and provide the following information: + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. +* **Provide specific examples to demonstrate the steps**.. +* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. + +### Contributing to 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. +{{% /note %}} + +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. +{{% /note %}} + +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. + +You will first need to clone the repository using `git` and place yourself in its directory: + +```bash +$ 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/). +{{% /note %}} + +Now, you will need to install the required dependency for Poetry and be sure that the current +tests are passing on your machine: + +```bash +$ poetry install +$ poetry run pytest tests/ +``` + +Poetry uses the [black](https://github.com/psf/black) coding style and you must ensure that your +code follows it. If not, the CI will fail and your Pull Request will not be merged. + +Similarly, the import statements are sorted with [isort](https://github.com/timothycrosley/isort) +and special care must be taken to respect it. If you don't, the CI will fail as well. + +To make sure that you don't accidentally commit code that does not follow the coding style, you can +install a pre-commit hook that will check that everything is in order: + +```bash +$ poetry run pre-commit install +``` + +You can also run it anytime using: + +```bash +$ poetry run pre-commit run --all-files +``` + +Your code must always be accompanied by corresponding tests, if tests are not present your code +will not be merged. + +#### Pull requests + +* Fill in [the required template](https://github.com/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. +{{% /note %}} + +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 +{{% /note %}} + +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' +``` + +{{% note %}} +Do not forget to update your `poetry@master` installation in sync with upstream. +{{% /note %}} + +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 +``` + +{{% note %}} +This mechanism can also be used to test pull requests. +{{% /note %}} + +### 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. +{{% /note %}} + +#### 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. +{{% /note %}} diff --git a/docs/docs/dependency-specification.md b/docs/dependency-specification.md similarity index 90% rename from docs/docs/dependency-specification.md rename to docs/dependency-specification.md index 97888a02cd7..e8b865da340 100644 --- a/docs/docs/dependency-specification.md +++ b/docs/dependency-specification.md @@ -1,3 +1,14 @@ +--- +title: "Dependency specification" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 70 +--- + # Dependency specification Dependencies for a project can be specified in various forms, which depend on the type @@ -119,10 +130,10 @@ my-package = { path = "../my-package/", develop = false } 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. +{{% 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. +{{% /note %}} ## `url` dependencies @@ -185,10 +196,10 @@ foo = [ ] ``` -!!!note - - The constraints **must** have different requirements (like `python`) - otherwise it will cause an error when resolving dependencies. +{{% note %}} +The constraints **must** have different requirements (like `python`) +otherwise it will cause an error when resolving dependencies. +{{% /note %}} ## Expanded dependency specification syntax @@ -199,21 +210,21 @@ you can shift from using "inline table" syntax, to the "standard table" syntax. An example where this might be useful is the following: ```toml -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] black = {version = "19.10b0", allow-prereleases = true, python = "^3.6", markers = "platform_python_implementation == 'CPython'"} ``` -As a single line, this is a lot to digest. To make this a little bit easier to +As a single line, this is a lot to digest. To make this a bit easier to work with, you can do the following: ```toml -[tool.poetry.dev-dependencies.black] +[tool.poetry.group.dev.dependencies.black] version = "19.10b0" allow-prereleases = true python = "^3.6" markers = "platform_python_implementation == 'CPython'" ``` -All of the same information is still present, and ends up providing the exact +The same information is still present, and ends up providing the exact same specification. It's simply split into multiple, slightly more readable, lines. diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md deleted file mode 100644 index 568877b4a4f..00000000000 --- a/docs/docs/contributing.md +++ /dev/null @@ -1 +0,0 @@ -{!../CONTRIBUTING.md!} diff --git a/docs/docs/faq.md b/docs/faq.md similarity index 75% rename from docs/docs/faq.md rename to docs/faq.md index 4d464d7ebc0..1687fa2e2ff 100644 --- a/docs/docs/faq.md +++ b/docs/faq.md @@ -1,6 +1,17 @@ +--- +title: "FAQ" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 110 +--- + # FAQ -## Why is the dependency resolution process slow? +### Why is the dependency resolution process slow? While the dependency resolver at the heart of Poetry is highly optimized and should be fast enough for most cases, sometimes, with some specific set of dependencies, @@ -13,29 +24,29 @@ operation, both in bandwidth and time, which is why it seems this is a long proc At the moment there is no way around it. -!!!note - - Once Poetry has cached the releases' information, the dependency resolution process - will be much faster. +{{% note %}} +Once Poetry has cached the releases' information, the dependency resolution process +will be much faster. +{{% /note %}} -## Why are unbound version constraints a bad idea? +### Why are unbound version constraints a bad idea? A version constraint without an upper bound such as `*` or `>=3.4` will allow updates to any future version of the dependency. This includes major versions breaking backward compatibility. Once a release of your package is published, you cannot tweak its dependencies anymore in case a dependency breaks BC -- you have to do a new release but the previous one stays broken. +– you have to do a new release but the previous one stays broken. The only good alternative is to define an upper bound on your constraints, which you can increase in a new release after testing that your package is compatible with the new major version of your dependency. -For example instead of using `>=3.4` you should use `~3.4` which allows all versions `<4.0`. +For example instead of using `>=3.4` you should use `^3.4` which allows all versions `<4.0`. The `^` operator works very well with libraries following [semantic versioning](https://semver.org). -## Is tox supported? +### Is tox supported? -Yes. By using the [isolated builds](https://tox.readthedocs.io/en/latest/config.html#conf-isolated_build) `tox` provides, +**Yes**. By using the [isolated builds](https://tox.readthedocs.io/en/latest/config.html#conf-isolated_build) `tox` provides, you can use it in combination with the PEP 517 compliant build system provided by Poetry. So, in your `pyproject.toml` file, add this section if it does not already exist: @@ -60,7 +71,7 @@ commands = poetry run pytest tests/ ``` -## I don't want Poetry to manage my virtual environments. Can I disable it? +### I don't want Poetry to manage my virtual environments. Can I disable it? While Poetry automatically creates virtual environments to always work isolated from the global Python installation, there are valid reasons why it's not necessary diff --git a/docs/docs/libraries.md b/docs/libraries.md similarity index 78% rename from docs/docs/libraries.md rename to docs/libraries.md index db4822c342d..bd7d195bd6c 100644 --- a/docs/docs/libraries.md +++ b/docs/libraries.md @@ -1,3 +1,15 @@ +--- +title: "Libraries" +draft: false +type: docs +layout: "docs" + +menu: + docs: + weight: 20 +--- + + # Libraries This chapter will tell you how to make your library installable through Poetry. @@ -9,7 +21,7 @@ While Poetry does not enforce any convention regarding package versioning, it **strongly** recommends to follow [semantic versioning](https://semver.org). This has many advantages for the end users and allows them to set appropriate -[version constraints](/docs/dependency-specification/#version-constraints). +[version constraints]({{< relref "dependency-specification#version-constraints" >}}). ## Lock file @@ -49,14 +61,14 @@ poetry publish ``` This will package and publish the library to PyPI, at the condition that you are a registered user -and you have [configured your credentials](/docs/repositories/#adding-credentials) properly. - -!!!note +and you have [configured your credentials]({{< relref "repositories#configuring-credentials" >}}) properly. - The `publish` command does not execute `build` by default. +{{% note %}} +The `publish` command does not execute `build` by default. - If you want to build and publish your packages together, - just pass the `--build` option. +If you want to build and publish your packages together, +just pass the `--build` option. +{{% /note %}} Once this is done, your library will be available to anyone. @@ -68,7 +80,7 @@ Sometimes, you may want to keep your library private but also being accessible t In this case, you will need to use a private repository. In order to publish to a private repository, you will need to add it to your -global list of repositories. See [Adding a repository](/docs/repositories/#adding-a-repository) +global list of repositories. See [Adding a repository]({{< relref "repositories#adding-a-repository" >}}) for more information. Once this is done, you can actually publish to it like so: diff --git a/docs/managing-dependencies.md b/docs/managing-dependencies.md new file mode 100644 index 00000000000..d70dc1e934b --- /dev/null +++ b/docs/managing-dependencies.md @@ -0,0 +1,174 @@ +--- +draft: false +layout: single +menu: + docs: + weight: 11 +title: Managing dependencies +type: docs +--- + + +# Managing dependencies + +## Dependency groups + +Poetry provides a way to **organize** your dependencies by **groups**. For instance, you might have +dependencies that are only needed to test your project or to build the documentation. + +To declare a new dependency group, use a `tool.poetry.group.` section +where `` is the name of your dependency group (for instance, `test`): + +```toml +[tool.poetry.group.test] # This part can be left out + +[tool.poetry.group.test.dependencies] +pytest = "^6.0.0" +pytest-mock = "*" +``` + +{{% note %}} +All dependencies **must be compatible with each other** across groups since they will +be resolved regardless of whether they are required for installation or not (see [Installing group dependencies]({{< relref "#installing-group-dependencies" >}})). + +Think of dependency groups as **labels** associated with your dependencies: they don't have any bearings +on whether their dependencies will be resolved and installed **by default**, they are simply a way to organize +the dependencies logically. +{{% /note %}} + +The dependencies declared in `tool.poetry.dependencies` are part of an implicit `default` group. + +```toml +[tool.poetry.dependencies] # Default dependency group +httpx = "*" +pendulum = "*" + +[tool.poetry.group.test.dependencies] +pytest = "^6.0.0" +pytest-mock = "*" +``` + +{{% note %}} +**A note about the `dev-dependencies` section** + +Any dependency declared in the `dev-dependencies` section will automatically be added to a `dev` group. +So the two following notations are equivalent: + +```toml +[tool.poetry.dev-dependencies] +pytest = "^6.0.0" +pytest-mock = "*" +``` + +```toml +[tool.poetry.group.dev.dependencies] +pytest = "^6.0.0" +pytest-mock = "*" +``` + +Poetry will slowly transition away from the `dev-dependencies` notation which will soon be deprecated, +so it's advised to migrate your existing development dependencies to the new `group` notation. +{{% /note %}} + +### Optional groups + +A dependency group can be declared as optional. This makes sense when you have +a group of dependencies that are only required in a particular environment or for +a specific purpose. + +```toml +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +mkdocs = "*" +``` + +Optional groups can be installed in addition to the **default** dependencies by using the `--with` +option of the [`install`]({{< relref "cli#install" >}}) command. + +```bash +poetry install --with docs +``` + +{{% warning %}} +Optional group dependencies will **still** be resolved alongside other dependencies, so +special care should be taken to ensure they are compatible with each other. +{{% /warning %}} + +### Adding a dependency to a group + +The [`add`]({{< relref "cli#add" >}}) command is the preferred way to add dependencies +to a group. This is done by using the `--group (-G)` option. + +```bash +poetry add pytest --group test +``` + +If the group does not already exist, it will be created automatically. + +### Installing group dependencies + +**By default**, dependencies across **all groups** will be installed when executing `poetry install`. + +You can **exclude** one or more groups with the `--without` option: + +```bash +poetry install --without test,docs +``` + +You can also opt in [optional groups]({{< relref "#optional-groups" >}}) by using the `--with` option: + +```bash +poetry install --with docs +``` + +If you only want to install the **default**, non-grouped, dependencies, you can do so +with the `--default` option: + +```bash +poetry install --default +``` + +Finally, in some case you might want to install **only specific groups** of dependencies +without installing the default dependencies. For that purpose, you can use +the `--only` option. + +```bash +poetry install --only docs +``` + +### Removing dependencies from a group + +The [`remove`]({{< relref "cli#remove" >}}) command supports a `--group` option +to remove packages from a specific group: + +```bash +poetry remove mkdocs --group docs +``` + + +## Synchronizing dependencies + +Poetry supports what's called dependency synchronization. What this does is ensuring +that the locked dependencies in the `poetry.lock` file are the only ones present +in the environment, removing anything that's not necessary. + +This is done by using the `--sync` option of the `install` command: + +```bash +poetry install --sync +``` + +The `--sync` option can be combined with any [dependency groups]({{< relref "#dependency-groups" >}}) related options +to synchronize the environment with specific groups. + +```bash +poetry install --without dev --sync +poetry install --with docs --sync +poetry install --only dev +``` + +{{% note %}} +The `--sync` option replaces the `--remove-untracked` option which is now deprecated. +{{% /note %}} diff --git a/docs/docs/managing-environments.md b/docs/managing-environments.md similarity index 86% rename from docs/docs/managing-environments.md rename to docs/managing-environments.md index 8b626cd813a..deed3578467 100644 --- a/docs/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -1,3 +1,14 @@ +--- +title: "Managing environments" +draft: false +type: docs +layout: "docs" + +menu: + docs: + weight: 60 +--- + # Managing environments Poetry makes project environment isolation one of its core features. @@ -15,19 +26,19 @@ with the `python` requirement of the project. In this case, Poetry will try to find one that is and use it. If it's unable to do so then you will be prompted to activate one explicitly, see [Switching environments](#switching-between-environments). -!!!note - - To easily switch between Python versions, it is recommended to - use [pyenv](https://github.com/pyenv/pyenv) or similar tools. +{{% note %}} +To easily switch between Python versions, it is recommended to +use [pyenv](https://github.com/pyenv/pyenv) or similar tools. - For instance, if your project is Python 2.7 only, a standard workflow - would be: +For instance, if your project is Python 3.6 only, a standard workflow +would be: - ```bash - pyenv install 2.7.15 - pyenv local 2.7.15 # Activate Python 2.7 for the current project - poetry install - ``` +```bash +pyenv install 3.6.15 +pyenv local 3.6.15 # Activate Python 3.6 for the current project +poetry install +``` +{{% /note %}} ## Switching between environments diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml deleted file mode 100644 index e055a6b6370..00000000000 --- a/docs/mkdocs.yml +++ /dev/null @@ -1,31 +0,0 @@ -site_name: Poetry documentation - -theme: - name: null - custom_dir: theme - -extra: - version: 2.0 - -nav: - - Introduction: index.md - - Basic Usage: basic-usage.md - - Libraries: libraries.md - - Commands: cli.md - - Configuration: configuration.md - - Repositories: repositories.md - - Managing environments: managing-environments.md - - Dependency specification: dependency-specification.md - - Plugins: plugins.md - - The pyproject.toml file: pyproject.md - - Contributing: contributing.md - - FAQ: faq.md - -markdown_extensions: - - codehilite - - admonition - - pymdownx.superfences - - toc: - permalink:  - - markdown_include.include: - base_path: docs diff --git a/docs/docs/plugins.md b/docs/plugins.md similarity index 84% rename from docs/docs/plugins.md rename to docs/plugins.md index f31bc85b3de..96ca48e191b 100644 --- a/docs/docs/plugins.md +++ b/docs/plugins.md @@ -1,3 +1,14 @@ +--- +title: "Plugins" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 80 +--- + # Plugins Poetry supports using and building plugins if you wish to @@ -18,7 +29,7 @@ and may also depend on further packages. ### Plugin package The plugin package must depend on Poetry -and declare a proper [plugin](/docs/pyproject/#plugins) in the `pyproject.toml` file. +and declare a proper [plugin]({{< relref "pyproject#plugins" >}}) in the `pyproject.toml` file. ```toml [tool.poetry] @@ -96,19 +107,19 @@ class MyApplicationPlugin(ApplicationPlugin): application.command_loader.register_factory("my-command", factory) ``` -!!!note - - It's possible to do the following to register the command: +{{% note %}} +It's possible to do the following to register the command: - ```python - application.add(MyCommand()) - ``` +```python +application.add(MyCommand()) +``` - However, it is **strongly** recommended to register a new factory - in the command loader to defer the loading of the command when it's actually - called. +However, it is **strongly** recommended to register a new factory +in the command loader to defer the loading of the command when it's actually +called. - This will help keep the performances of Poetry good. +This will help keep the performances of Poetry good. +{{% /note %}} The plugin also must be declared in the `pyproject.toml` file of the plugin package as an `application.plugin` plugin: @@ -118,9 +129,9 @@ as an `application.plugin` plugin: foo-command = "poetry_demo_plugin.plugin:MyApplicationPlugin" ``` -!!!warning - - A plugin **must not** remove or modify in any way the core commands of Poetry. +{{% warning %}} +A plugin **must not** remove or modify in any way the core commands of Poetry. +{{% /warning %}} ### Event handler @@ -152,10 +163,15 @@ from poetry.plugins.application_plugin import ApplicationPlugin class MyApplicationPlugin(ApplicationPlugin): def activate(self, application: Application): - application.event_dispatcher.add_listener(COMMAND, self.load_dotenv) + application.event_dispatcher.add_listener( + COMMAND, self.load_dotenv + ) def load_dotenv( - self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher + self, + event: ConsoleCommandEvent, + event_name: str, + dispatcher: EventDispatcher ) -> None: command = event.command if not isinstance(command, EnvCommand): @@ -164,7 +180,9 @@ class MyApplicationPlugin(ApplicationPlugin): io = event.io if io.is_debug(): - io.write_line("Loading environment variables.") + io.write_line( + "Loading environment variables." + ) load_dotenv() ``` @@ -188,7 +206,7 @@ The `plugin add` command will ensure that the plugin is compatible with the curr and install the needed packages for the plugin to work. The package specification formats supported by the `plugin add` command are the same as the ones supported -by the [`add` command](/docs/cli/#add). +by the [`add` command]({{< relref "cli#add" >}}). If you no longer need a plugin and want to uninstall it, you can use the `plugin remove` command. diff --git a/docs/docs/pyproject.md b/docs/pyproject.md similarity index 72% rename from docs/docs/pyproject.md rename to docs/pyproject.md index 76cb2a86868..a0d1c896f41 100644 --- a/docs/docs/pyproject.md +++ b/docs/pyproject.md @@ -1,3 +1,14 @@ +--- +title: "The pyproject.toml file" +draft: false +type: docs +layout: single + +menu: + docs: + weight: 90 +--- + # The `pyproject.toml` file The `tool.poetry` section of the `pyproject.toml` file is composed of multiple sections. @@ -40,9 +51,9 @@ The recommended notation for the most common licenses is (alphabetical): Optional, but it is highly recommended to supply this. More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/). -!!!note - - If your project is proprietary and does not use a specific licence, you can set this value as `Proprietary`. +{{% note %}} +If your project is proprietary and does not use a specific licence, you can set this value as `Proprietary`. +{{% /note %}} ## authors @@ -76,7 +87,7 @@ An URL to the documentation of the project. **Optional** ## keywords -A list of keywords (max: 5) that the package is related to. **Optional** +A list of keywords that the package is related to. **Optional** ## classifiers @@ -91,11 +102,11 @@ classifiers = [ ] ``` -!!!note +{{% note %}} +Note that Python classifiers are still automatically added for you and are determined by your `python` requirement. - Note that Python classifiers are still automatically added for you and are determined by your `python` requirement. - - The `license` property will also set the License classifier automatically. +The `license` property will also set the License classifier automatically. +{{% /note %}} ## packages @@ -137,26 +148,26 @@ packages = [ From now on, only the `sdist` build archive will include the `my_other_package` package. -!!!note - - Using `packages` disables the package auto-detection feature meaning you have to - **explicitly** specify the "default" package. - - For instance, if you have a package named `my_package` and you want to also include - another package named `extra_package`, you will need to specify `my_package` explicitly: +{{% note %}} +Using `packages` disables the package auto-detection feature meaning you have to +**explicitly** specify the "default" package. - ```toml - packages = [ - { include = "my_package" }, - { include = "extra_package" }, - ] - ``` +For instance, if you have a package named `my_package` and you want to also include +another package named `extra_package`, you will need to specify `my_package` explicitly: -!!!note +```toml +packages = [ + { include = "my_package" }, + { include = "extra_package" }, +] +``` +{{% /note %}} - Poetry is clever enough to detect Python subpackages. +{{% note %}} +Poetry is clever enough to detect Python subpackages. - Thus, you only have to specify the directory where your root package resides. +Thus, you only have to specify the directory where your root package resides. +{{% /note %}} ## include and exclude @@ -190,7 +201,7 @@ If no format is specified, it will default to include both `sdist` and `wheel`. exclude = ["my_package/excluded.py"] ``` -## `dependencies` and `dev-dependencies` +## dependencies and dependency groups Poetry is configured to look for dependencies on [PyPi](https://pypi.org) by default. Only the name and a version string are required in this case. @@ -200,7 +211,8 @@ Only the name and a version string are required in this case. requests = "^2.13.0" ``` -If you want to use a private repository, you can add it to your `pyproject.toml` file, like so: +If you want to use a [private repository]({{< relref "repositories#using-a-private-repository" >}}), +you can add it to your `pyproject.toml` file, like so: ```toml [[tool.poetry.source]] @@ -208,15 +220,29 @@ name = 'private' url = 'http://example.com/simple' ``` -!!!note +{{% note %}} +Be aware that declaring the python version for which your package +is compatible is mandatory: - Be aware that declaring the python version for which your package - is compatible is mandatory: +```toml +[tool.poetry.dependencies] +python = "^3.6" +``` +{{% /note %}} + +You can organize your dependencies in [groups]({{< relref "managing-dependencies#dependency-groups" >}}) +to manage them in a more granular way. - ```toml - [tool.poetry.dependencies] - python = "^3.6" - ``` +```toml +[tool.poetry.group.test.dependencies] +pytest = "*" + +[tool.poetry.group.docs.dependencies] +mkdocs = "*" +``` + +See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for a more in-depth look +at how to manage dependency groups. ## `scripts` @@ -229,9 +255,9 @@ poetry = 'poetry.console:run' Here, we will have the `poetry` script installed which will execute `console.run` in the `poetry` package. -!!!note - - When a script is added or updated, run `poetry install` to make them available in the project's virtualenv. +{{% note %}} +When a script is added or updated, run `poetry install` to make them available in the project's virtualenv. +{{% /note %}} ## `extras` @@ -276,10 +302,11 @@ the `databases` extra can be installed as shown below. pip install awesome[databases] ``` -!!!note +{{% note %}} +The dependencies specified for each `extra` must already be defined as project dependencies. - 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. +Dependencies listed in [dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) cannot be specified as extras. +{{% /note %}} ## `plugins` @@ -322,12 +349,11 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" ``` -!!!note - - When using the `new` or `init` command this section will be automatically added. - - -!!!note +{{% note %}} +When using the `new` or `init` command this section will be automatically added. +{{% /note %}} - If your `pyproject.toml` file still references `poetry` directly as a build backend, - you should update it to reference `poetry-core` instead. +{{% note %}} +If your `pyproject.toml` file still references `poetry` directly as a build backend, +you should update it to reference `poetry-core` instead. +{{% /note %}} diff --git a/docs/docs/repositories.md b/docs/repositories.md similarity index 65% rename from docs/docs/repositories.md rename to docs/repositories.md index 952e91f15d4..889b961c4ed 100644 --- a/docs/docs/repositories.md +++ b/docs/repositories.md @@ -1,3 +1,14 @@ +--- +title: "Repositories" +draft: false +type: docs +layout: "docs" + +menu: + docs: + weight: 50 +--- + # Repositories ## Using the PyPI repository @@ -37,24 +48,25 @@ poetry config http-basic.foo username password If you do not specify the password you will be prompted to write it. -!!!note +{{% note %}} +To publish to PyPI, you can set your credentials for the repository named `pypi`. - To publish to PyPI, you can set your credentials for the repository named `pypi`. +Note that it is recommended to use [API tokens](https://pypi.org/help/#apitoken) +when uploading packages to PyPI. +Once you have created a new token, you can tell Poetry to use it: - Note that it is recommended to use [API tokens](https://pypi.org/help/#apitoken) - when uploading packages to PyPI. - Once you have created a new token, you can tell Poetry to use it: +```bash +poetry config pypi-token.pypi my-token +``` - ```bash - poetry config pypi-token.pypi my-token - ``` +If you still want to use your username and password, you can do so with the following +call to `config`. - If you still want to use your username and password, you can do so with the following - call to `config`. +```bash +poetry config http-basic.pypi username password +``` +{{% /note %}} - ```bash - poetry config http-basic.pypi username password - ``` You can also specify the username and password when using the `publish` command with the `--username` and `--password` options. @@ -63,6 +75,21 @@ If a system keyring is available and supported, the password is stored to and re Keyring support is enabled using the [keyring library](https://pypi.org/project/keyring/). For more information on supported backends refer to the [library documentation](https://keyring.readthedocs.io/en/latest/?badge=latest). +{{% note %}} + +Poetry will fallback to Pip style use of keyring so that backends like +Microsoft's [artifacts-keyring](https://pypi.org/project/artifacts-keyring/) get a change to retrieve +valid credentials. It will need to be properly installed into Poetry's virtualenv, +preferably by installing a plugin. + +If you are letting Poetry manage your virtual environments you will want a virtualenv +seeder installed in Poetry's virtualenv that installs the desired keyring backend +during `poetry install`. To again use Azure DevOps as an example: [azure-devops-artifacts-helpers](https://pypi.org/project/azure-devops-artifacts-helpers/) +provides such a seeder. This would of course best achieved by installing a Poetry plugin +if it exists for you use case instead of doing it yourself. + +{{% /note %}} + Alternatively, you can use environment variables to provide the credentials: ```bash @@ -71,7 +98,7 @@ export POETRY_HTTP_BASIC_PYPI_USERNAME=username export POETRY_HTTP_BASIC_PYPI_PASSWORD=password ``` -See [Using environment variables](/docs/configuration/#using-environment-variables) for more information +See [Using environment variables]({{< relref "configuration#using-environment-variables" >}}) for more information on how to configure Poetry with environment variables. #### Custom certificate authority and mutual TLS authentication @@ -79,9 +106,10 @@ Poetry supports repositories that are secured by a custom certificate authority certificate-based client authentication. The following will configure the "foo" repository to validate the repository's certificate using a custom certificate authority and use a client certificate (note that these config variables do not both need to be set): + ```bash - poetry config certificates.foo.cert /path/to/ca.pem - poetry config certificates.foo.client-cert /path/to/client.pem +poetry config certificates.foo.cert /path/to/ca.pem +poetry config certificates.foo.client-cert /path/to/client.pem ``` ### Install dependencies from a private repository @@ -99,19 +127,19 @@ url = "https://foo.bar/simple/" From now on, Poetry will also look for packages in your private repository. -!!!note +{{% note %}} +Any custom repository will have precedence over PyPI. - Any custom repository will have precedence over PyPI. +If you still want PyPI to be your primary source for your packages +you can declare custom repositories as secondary. - If you still want PyPI to be your primary source for your packages - you can declare custom repositories as secondary. - - ```toml - [[tool.poetry.source]] - name = "foo" - url = "https://foo.bar/simple/" - secondary = true - ``` +```toml +[[tool.poetry.source]] +name = "foo" +url = "https://foo.bar/simple/" +secondary = true +``` +{{% /note %}} If your private repository requires HTTP Basic Auth be sure to add the username and password to your `http-basic` configuration using the example above (be sure to use the diff --git a/docs/theme/main.html b/docs/theme/main.html deleted file mode 100644 index 83151151ff6..00000000000 --- a/docs/theme/main.html +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: documentation -title: {{ page.title|striptags|e }} ---- - -
-
-
-
-
-
-
    - {% set navlevel = 1 %} - - {% for nav_item in nav %} - - {% endfor %} -
-
-
- {{page.content}} -
-
-
-
-
-
diff --git a/docs/theme/nav.html b/docs/theme/nav.html deleted file mode 100644 index e9d2b383844..00000000000 --- a/docs/theme/nav.html +++ /dev/null @@ -1,18 +0,0 @@ -{{ nav_item.title }} - -{%- if nav_item == page or nav_item.children %} - -{%- endif %} diff --git a/docs/theme/toc.html b/docs/theme/toc.html deleted file mode 100644 index 4f3e8e09ab6..00000000000 --- a/docs/theme/toc.html +++ /dev/null @@ -1,10 +0,0 @@ -{% for toc_item in page.toc %} - -{% if toc_item.children %} - -{% endif %} -{% endfor %} diff --git a/get-poetry.py b/get-poetry.py index 16e4bdcddfd..dc3c235eeaa 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -449,6 +449,33 @@ def _compare_versions(x, y): break + def _is_supported(x): + mx = self.VERSION_REGEX.match(x) + vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) + return vx < (1, 2, 0) + + if not _is_supported(version): + print( + colorize( + "error", + "Version {version} does not support this installation method. Please specify a version prior to " + "1.2.0a1 explicitly using the '--version' option.\n" + "Please see " + "https://python-poetry.org/blog/announcing-poetry-1-2-0a1.html#deprecation-of-the-get-poetry-py-script " + "for more information.".format(version=version), + ) + ) + return None, None + + print( + colorize( + "warning", + "This installer is deprecated. " + "Poetry versions installed using this script will not be able to use 'self update' command to upgrade to " + "1.2.0a1 or later.", + ) + ) + current_version = None if os.path.exists(POETRY_LIB): with open( @@ -824,7 +851,7 @@ def set_windows_path_var(self, value): HWND_BROADCAST, WM_SETTINGCHANGE, 0, - u"Environment", + "Environment", SMTO_ABORTIFHUNG, 5000, ctypes.byref(result), diff --git a/install-poetry.py b/install-poetry.py index fafdf9c7754..966e574713f 100644 --- a/install-poetry.py +++ b/install-poetry.py @@ -293,6 +293,10 @@ def make(cls, target: Path) -> "VirtualEnvironment": # we do this here to ensure that outdated system default pip does not trigger older bugs env.pip("install", "--upgrade", "pip") + # We add a special file so that Poetry can detect + # its own virtual environment + target.joinpath("poetry_env").touch() + return cls(target) @staticmethod @@ -456,6 +460,29 @@ def run(self) -> int: self.display_pre_message() self.ensure_directories() + def _is_self_upgrade_supported(x): + mx = self.VERSION_REGEX.match(x) + + if mx is None: + # the version is not semver, perhaps scm or file, we assume upgrade is supported + return True + + vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) + return vx >= (1, 1, 7) + + if version and not _is_self_upgrade_supported(version): + self._write( + colorize( + "warning", + f"You are installing {version}. When using the current installer, this version does not support " + f"updating using the 'self update' command. Please use 1.1.7 or later.", + ) + ) + if not self._accept_all: + continue_install = input("Do you want to continue? ([y]/n) ") or "y" + if continue_install.lower() in {"n", "no"}: + return 0 + try: self.install(version) except subprocess.CalledProcessError as e: diff --git a/poetry.lock b/poetry.lock index 26eb85c6f31..1c441589ad0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,11 +1,3 @@ -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "atomicwrites" version = "1.4.0" @@ -28,6 +20,21 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +[[package]] +name = "backports.entry-points-selectable" +version = "1.1.0" +description = "Compatibility shim providing selectable entry points for older implementations" +category = "main" +optional = false +python-versions = ">=2.7" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] + [[package]] name = "cachecontrol" version = "0.12.6" @@ -60,7 +67,7 @@ msgpack = ["msgpack-python (>=0.5,<0.6)"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -68,7 +75,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.5" +version = "1.14.6" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -79,23 +86,26 @@ pycparser = "*" [[package]] name = "cfgv" -version = "3.3.0" +version = "3.3.1" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false python-versions = ">=3.6.1" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.6" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "cleo" -version = "1.0.0a3" +version = "1.0.0a4" description = "Cleo allows you to create beautiful and testable command-line interfaces." category = "main" optional = false @@ -115,17 +125,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "5.5" +version = "6.0.1" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.dependencies] -toml = {version = "*", optional = true, markers = "extra == \"toml\""} +python-versions = ">=3.6" [package.extras] -toml = ["toml"] +toml = ["tomli"] [[package]] name = "crashtest" @@ -137,7 +144,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "cryptography" -version = "3.4.7" +version = "35.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -150,9 +157,9 @@ cffi = ">=1.12" docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "dataclasses" @@ -178,7 +185,7 @@ cli = ["click (==7.1.2)", "pyyaml (==5.4)", "toml (==0.10.2)", "clevercsv (==0.6 [[package]] name = "distlib" -version = "0.3.1" +version = "0.3.3" description = "Distribution utilities" category = "main" optional = false @@ -194,11 +201,15 @@ python-versions = ">=2.7" [[package]] name = "filelock" -version = "3.0.12" +version = "3.3.0" description = "A platform independent file lock." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "html5lib" @@ -220,7 +231,7 @@ lxml = ["lxml"] [[package]] name = "httpretty" -version = "1.1.2" +version = "1.1.4" description = "HTTP client mock for Python" category = "dev" optional = false @@ -228,7 +239,7 @@ python-versions = ">=3" [[package]] name = "identify" -version = "2.2.4" +version = "2.3.0" description = "File identification library for Python" category = "dev" optional = false @@ -239,11 +250,11 @@ license = ["editdistance-s"] [[package]] name = "idna" -version = "2.10" +version = "3.2" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "importlib-metadata" @@ -262,14 +273,14 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" -version = "5.1.3" +version = "5.2.2" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] @@ -285,14 +296,15 @@ python-versions = "*" [[package]] name = "jeepney" -version = "0.6.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] +trio = ["trio", "async-generator"] [[package]] name = "keyring" @@ -368,7 +380,7 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.7.0" +version = "1.7.1" description = "Query metadatdata from sdists / bdists / installed packages." category = "main" optional = false @@ -377,23 +389,36 @@ python-versions = "*" [package.extras] testing = ["nose", "coverage"] +[[package]] +name = "platformdirs" +version = "2.4.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-core" -version = "1.1.0a5" +version = "1.1.0a6" description = "Poetry PEP 517 Build Backend" category = "main" optional = false @@ -405,7 +430,7 @@ importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} [[package]] name = "pre-commit" -version = "2.12.1" +version = "2.15.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -447,7 +472,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pylev" -version = "1.3.0" +version = "1.4.0" description = "A pure Python Levenshtein implementation that's not freaking GPL'd." category = "main" optional = false @@ -463,7 +488,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -476,7 +501,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -485,18 +510,19 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-cov" -version = "2.12.0" +version = "2.12.1" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = ">=5.2.1" pytest = ">=4.6" +toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-mock" @@ -543,21 +569,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "requests" -version = "2.25.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "requests-toolbelt" @@ -624,7 +650,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "tox" -version = "3.23.1" +version = "3.24.4" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false @@ -647,36 +673,37 @@ testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytes [[package]] name = "urllib3" -version = "1.26.4" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] +brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "virtualenv" -version = "20.4.4" +version = "20.8.1" description = "Virtual Python Environment builder" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -appdirs = ">=1.4.3,<2" +"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} +platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "webencodings" @@ -688,7 +715,7 @@ python-versions = "*" [[package]] name = "zipp" -version = "3.4.1" +version = "3.6.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -696,18 +723,14 @@ python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "ac67bc6eacbb6b633f9568d69533d80456f632c7bfb9a2aa61aa9dd98e862473" +content-hash = "15dc46f1a8bf5a3eb2a3c48f9a8527f89fac67487e8188e67c0a603c376fd0fd" [metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -716,6 +739,10 @@ attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] +"backports.entry-points-selectable" = [ + {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, + {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, +] cachecontrol = [ {file = "CacheControl-0.12.6-py2.py3-none-any.whl", hash = "sha256:10d056fa27f8563a271b345207402a6dcce8efab7e5b377e270329c62471b10d"}, {file = "CacheControl-0.12.6.tar.gz", hash = "sha256:be9aa45477a134aee56c8fac518627e1154df063e85f67d4f83ce0ccc23688e8"}, @@ -725,135 +752,132 @@ cachy = [ {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ - {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, - {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, - {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, - {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, - {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, - {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, - {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, - {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, - {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, - {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, - {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, - {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, - {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, - {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, - {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, - {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, - {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, - {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, - {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, + {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, + {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, + {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, + {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, + {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, + {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, + {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, + {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, + {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, + {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, + {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, + {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, + {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, + {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, + {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, + {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, + {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, + {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, + {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, ] cfgv = [ - {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, - {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, + {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, ] cleo = [ - {file = "cleo-1.0.0a3-py3-none-any.whl", hash = "sha256:46b2f970d06caa311d1e12a1013b0ce2a8149502669ac82cbedafb9e0bfdbccd"}, - {file = "cleo-1.0.0a3.tar.gz", hash = "sha256:9c1c8dd06635c936f45e4649aa2f7581517b4d52c7a9414d1b42586e63c2fe5d"}, + {file = "cleo-1.0.0a4-py3-none-any.whl", hash = "sha256:cdd0c3458c15ced3a9f0204b1e53a1b4bee3c56ebcb3ac54c872a56acc657a09"}, + {file = "cleo-1.0.0a4.tar.gz", hash = "sha256:a103a065d031b7d936ee88a6b93086a69bd9c1b40fa2ebfe8c056285a66b481d"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22"}, + {file = "coverage-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a"}, + {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16"}, + {file = "coverage-6.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec"}, + {file = "coverage-6.0.1-cp310-cp310-win32.whl", hash = "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194"}, + {file = "coverage-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f"}, + {file = "coverage-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7"}, + {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43"}, + {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5"}, + {file = "coverage-6.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b"}, + {file = "coverage-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a"}, + {file = "coverage-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb"}, + {file = "coverage-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc"}, + {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e"}, + {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7"}, + {file = "coverage-6.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666"}, + {file = "coverage-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b"}, + {file = "coverage-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724"}, + {file = "coverage-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32"}, + {file = "coverage-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397"}, + {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345"}, + {file = "coverage-6.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827"}, + {file = "coverage-6.0.1-cp38-cp38-win32.whl", hash = "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee"}, + {file = "coverage-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12"}, + {file = "coverage-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e"}, + {file = "coverage-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793"}, + {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d"}, + {file = "coverage-6.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf"}, + {file = "coverage-6.0.1-cp39-cp39-win32.whl", hash = "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad"}, + {file = "coverage-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815"}, + {file = "coverage-6.0.1-pp36-none-any.whl", hash = "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5"}, + {file = "coverage-6.0.1-pp37-none-any.whl", hash = "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26"}, + {file = "coverage-6.0.1.tar.gz", hash = "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854"}, ] crashtest = [ {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, ] cryptography = [ - {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, - {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, - {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, - {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, - {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, - {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, - {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, - {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, - {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, - {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, - {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, - {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9"}, + {file = "cryptography-35.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992"}, + {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d"}, + {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6"}, + {file = "cryptography-35.0.0-cp36-abi3-win32.whl", hash = "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8"}, + {file = "cryptography-35.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6"}, + {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2"}, + {file = "cryptography-35.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c"}, + {file = "cryptography-35.0.0.tar.gz", hash = "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, @@ -864,47 +888,47 @@ deepdiff = [ {file = "deepdiff-5.5.0.tar.gz", hash = "sha256:dd79b81c2d84bfa33aa9d94d456b037b68daff6bb87b80dfaa1eca04da68b349"}, ] distlib = [ - {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, - {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, + {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, + {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, ] entrypoints = [ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, ] filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, + {file = "filelock-3.3.0-py3-none-any.whl", hash = "sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0"}, + {file = "filelock-3.3.0.tar.gz", hash = "sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785"}, ] html5lib = [ {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, ] httpretty = [ - {file = "httpretty-1.1.2.tar.gz", hash = "sha256:73d3e342ce8b21a16b0ff6c2b4c2c2d11c2da28784c264fcf7fe8cd2d8a25090"}, + {file = "httpretty-1.1.4.tar.gz", hash = "sha256:20de0e5dd5a18292d36d928cc3d6e52f8b2ac73daec40d41eb62dee154933b68"}, ] identify = [ - {file = "identify-2.2.4-py2.py3-none-any.whl", hash = "sha256:ad9f3fa0c2316618dc4d840f627d474ab6de106392a4f00221820200f490f5a8"}, - {file = "identify-2.2.4.tar.gz", hash = "sha256:9bcc312d4e2fa96c7abebcdfb1119563b511b5e3985ac52f60d9116277865b2e"}, + {file = "identify-2.3.0-py2.py3-none-any.whl", hash = "sha256:d1e82c83d063571bb88087676f81261a4eae913c492dafde184067c584bc7c05"}, + {file = "identify-2.3.0.tar.gz", hash = "sha256:fd08c97f23ceee72784081f1ce5125c8f53a02d3f2716dde79a6ab8f1039fea5"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] importlib-metadata = [ {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, ] importlib-resources = [ - {file = "importlib_resources-5.1.3-py3-none-any.whl", hash = "sha256:3b9c774e0e7e8d9c069eb2fe6aee7e9ae71759a381dec02eb45249fba7f38713"}, - {file = "importlib_resources-5.1.3.tar.gz", hash = "sha256:0786b216556e53b34156263ab654406e543a8b0d9b1381019e25a36a09263c36"}, + {file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"}, + {file = "importlib_resources-5.2.2.tar.gz", hash = "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jeepney = [ - {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, - {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] keyring = [ {file = "keyring-22.3.0-py3-none-any.whl", hash = "sha256:2bc8363ebdd63886126a012057a85c8cb6e143877afa02619ac7dbc9f38a207b"}, @@ -960,20 +984,24 @@ pexpect = [ {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] pkginfo = [ - {file = "pkginfo-1.7.0-py2.py3-none-any.whl", hash = "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"}, - {file = "pkginfo-1.7.0.tar.gz", hash = "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4"}, + {file = "pkginfo-1.7.1-py2.py3-none-any.whl", hash = "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779"}, + {file = "pkginfo-1.7.1.tar.gz", hash = "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd"}, +] +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] poetry-core = [ - {file = "poetry-core-1.1.0a5.tar.gz", hash = "sha256:1b886de26026865325eae86a5d12eb154b80c0add8067c106eb706757594d85f"}, - {file = "poetry_core-1.1.0a5-py3-none-any.whl", hash = "sha256:b347525c1417e9b5c6aee52967eff98c0886853a9e8ab1b9dfb2659913dd37bc"}, + {file = "poetry-core-1.1.0a6.tar.gz", hash = "sha256:e22c8897216216f6344b3d57167a8cd5485a1403934817d7efaf7fb8f6bcffc9"}, + {file = "poetry_core-1.1.0a6-py3-none-any.whl", hash = "sha256:4093226d89e1b79f16c917fba766461c01b184d3184d7cad4b7be8426c0cb4be"}, ] pre-commit = [ - {file = "pre_commit-2.12.1-py2.py3-none-any.whl", hash = "sha256:70c5ec1f30406250b706eda35e868b87e3e4ba099af8787e3e8b4b01e84f4712"}, - {file = "pre_commit-2.12.1.tar.gz", hash = "sha256:900d3c7e1bf4cf0374bb2893c24c23304952181405b4d88c9c40b72bda1bb8a9"}, + {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, + {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -988,20 +1016,20 @@ pycparser = [ {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pylev = [ - {file = "pylev-1.3.0-py2.py3-none-any.whl", hash = "sha256:1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"}, - {file = "pylev-1.3.0.tar.gz", hash = "sha256:063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3"}, + {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, + {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.0.tar.gz", hash = "sha256:8535764137fecce504a49c2b742288e3d34bc09eed298ad65963616cc98fd45e"}, - {file = "pytest_cov-2.12.0-py2.py3-none-any.whl", hash = "sha256:95d4933dcbbacfa377bb60b29801daa30d90c33981ab2a79e9ab4452c165066e"}, + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] pytest-mock = [ {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, @@ -1021,25 +1049,33 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, @@ -1069,22 +1105,22 @@ tomlkit = [ {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, ] tox = [ - {file = "tox-3.23.1-py2.py3-none-any.whl", hash = "sha256:b0b5818049a1c1997599d42012a637a33f24c62ab8187223fdd318fa8522637b"}, - {file = "tox-3.23.1.tar.gz", hash = "sha256:307a81ddb82bd463971a273f33e9533a24ed22185f27db8ce3386bff27d324e3"}, + {file = "tox-3.24.4-py2.py3-none-any.whl", hash = "sha256:5e274227a53dc9ef856767c21867377ba395992549f02ce55eb549f9fb9a8d10"}, + {file = "tox-3.24.4.tar.gz", hash = "sha256:c30b57fa2477f1fb7c36aa1d83292d5c2336cd0018119e1b1c17340e2c2708ca"}, ] urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, ] virtualenv = [ - {file = "virtualenv-20.4.4-py2.py3-none-any.whl", hash = "sha256:a935126db63128861987a7d5d30e23e8ec045a73840eeccb467c148514e29535"}, - {file = "virtualenv-20.4.4.tar.gz", hash = "sha256:09c61377ef072f43568207dc8e46ddeac6bcdcaf288d49011bda0e7f4d38c4a2"}, + {file = "virtualenv-20.8.1-py2.py3-none-any.whl", hash = "sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300"}, + {file = "virtualenv-20.8.1.tar.gz", hash = "sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8"}, ] webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] zipp = [ - {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, - {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] diff --git a/poetry/__version__.py b/poetry/__version__.py index 7bfbef15537..866c89d6de1 100644 --- a/poetry/__version__.py +++ b/poetry/__version__.py @@ -1 +1 @@ -__version__ = "1.2.0a1" +__version__ = "1.2.0a2" diff --git a/poetry/console/application.py b/poetry/console/application.py index 42cc4377043..6adda8609ef 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -26,6 +26,12 @@ from .commands.command import Command +if TYPE_CHECKING: + from crashtest.solution_providers.solution_provider_repository import ( + SolutionProviderRepository, + ) + + def load_command(name: str) -> Callable: def _load() -> Type[Command]: module = import_module( @@ -159,6 +165,13 @@ def create_io( return io + def render_error(self, error: Exception, io: IO) -> None: + # We set the solution provider repository here to load providers + # only when an error occurs + self.set_solution_provider_repository(self._get_solution_provider_repository()) + + super().render_error(error, io) + def _run(self, io: IO) -> int: self._disable_plugins = io.input.parameter_option("--no-plugins") @@ -330,6 +343,20 @@ def _default_definition(self) -> "Definition": return definition + def _get_solution_provider_repository(self) -> "SolutionProviderRepository": + from crashtest.solution_providers.solution_provider_repository import ( + SolutionProviderRepository, + ) + + from poetry.mixology.solutions.providers.python_requirement_solution_provider import ( + PythonRequirementSolutionProvider, + ) + + repository = SolutionProviderRepository() + repository.register_solution_providers([PythonRequirementSolutionProvider]) + + return repository + def main() -> int: return Application().run() diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py index 777f3744787..0d8de8a82de 100644 --- a/poetry/console/commands/add.py +++ b/poetry/console/commands/add.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from typing import Dict from typing import List @@ -16,6 +15,13 @@ class AddCommand(InstallerCommand, InitCommand): arguments = [argument("name", "The packages to add.", multiple=True)] options = [ + option( + "group", + "-G", + "The group to add the dependency to.", + flag=False, + default="default", + ), option("dev", "D", "Add as a development dependency."), option("editable", "e", "Add vcs/path dependencies as editable."), option( @@ -71,31 +77,55 @@ class AddCommand(InstallerCommand, InitCommand): def handle(self) -> int: from tomlkit import inline_table + from tomlkit import parse as parse_toml + from tomlkit import table from poetry.core.semver.helpers import parse_constraint + from poetry.factory import Factory packages = self.argument("name") - is_dev = self.option("dev") + if self.option("dev"): + self.line( + "The --dev option is deprecated, " + "use the `--group dev` notation instead." + ) + self.line("") + group = "dev" + else: + group = self.option("group") if self.option("extras") and len(packages) > 1: raise ValueError( - "You can only specify one package " "when using the --extras option" + "You can only specify one package when using the --extras option" ) - section = "dependencies" - if is_dev: - section = "dev-dependencies" - - original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] - if section not in poetry_content: - poetry_content[section] = {} + if group == "default": + if "dependencies" not in poetry_content: + poetry_content["dependencies"] = table() - existing_packages = self.get_existing_packages_from_input( - packages, poetry_content, section - ) + section = poetry_content["dependencies"] + else: + if "group" not in poetry_content: + group_table = table() + group_table._is_super_table = True + poetry_content.value._insert_after("dependencies", "group", group_table) + + groups = poetry_content["group"] + if group not in groups: + group_table = parse_toml( + f"[tool.poetry.group.{group}.dependencies]\n\n" + )["tool"]["poetry"]["group"][group] + poetry_content["group"][group] = group_table + + if "dependencies" not in poetry_content["group"][group]: + poetry_content["group"][group]["dependencies"] = table() + + section = poetry_content["group"][group]["dependencies"] + + existing_packages = self.get_existing_packages_from_input(packages, section) if existing_packages: self.notify_about_existing_packages(existing_packages) @@ -165,53 +195,48 @@ def handle(self) -> int: if len(constraint) == 1 and "version" in constraint: constraint = constraint["version"] - poetry_content[section][_constraint["name"]] = constraint + section[_constraint["name"]] = constraint + self.poetry.package.add_dependency( + Factory.create_dependency( + _constraint["name"], + constraint, + groups=[group], + root_dir=self.poetry.file.parent, + ) + ) - try: - # Write new content - self.poetry.file.write(content) + # Refresh the locker + self.poetry.set_locker( + self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content) + ) + self._installer.set_locker(self.poetry.locker) - # Cosmetic new line - self.line("") + # Cosmetic new line + self.line("") - # Update packages - self.reset_poetry() - - self._installer.set_package(self.poetry.package) - self._installer.dry_run(self.option("dry-run")) - self._installer.verbose(self._io.is_verbose()) - self._installer.update(True) - if self.option("lock"): - self._installer.lock() - - self._installer.whitelist([r["name"] for r in requirements]) - - status = self._installer.run() - 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"): - # Revert changes - if not self.option("dry-run"): - self.line_error( - "\n" - "Failed to add packages, reverting the pyproject.toml file " - "to its original content." - ) + self._installer.set_package(self.poetry.package) + self._installer.dry_run(self.option("dry-run")) + self._installer.verbose(self._io.is_verbose()) + self._installer.update(True) + if self.option("lock"): + self._installer.lock() + + self._installer.whitelist([r["name"] for r in requirements]) - self.poetry.file.write(original_content) + status = self._installer.run() + + if status == 0 and not self.option("dry-run"): + self.poetry.file.write(content) return status def get_existing_packages_from_input( - self, packages: List[str], poetry_content: Dict, target_section: str + self, packages: List[str], section: Dict ) -> List[str]: existing_packages = [] for name in packages: - for key in poetry_content[target_section]: + for key in section: if key.lower() == name.lower(): existing_packages.append(name) diff --git a/poetry/console/commands/cache/clear.py b/poetry/console/commands/cache/clear.py index 7bacdb891cb..ddc45c44f8e 100644 --- a/poetry/console/commands/cache/clear.py +++ b/poetry/console/commands/cache/clear.py @@ -52,7 +52,7 @@ def handle(self) -> int: # Calculate number of entries entries_count = 0 - for path, dirs, files in os.walk(str(cache_dir)): + for _path, _dirs, files in os.walk(str(cache_dir)): entries_count += len(files) delete = self.confirm( diff --git a/poetry/console/commands/debug/resolve.py b/poetry/console/commands/debug/resolve.py index 06b0fa9d21f..9d00aec51bb 100644 --- a/poetry/console/commands/debug/resolve.py +++ b/poetry/console/commands/debug/resolve.py @@ -86,7 +86,7 @@ def handle(self) -> Optional[int]: solver = Solver(package, pool, Repository(), Repository(), self._io) - ops = solver.solve() + ops = solver.solve().calculate_operations() self.line("") self.line("Resolution results:") @@ -99,7 +99,7 @@ def handle(self) -> Optional[int]: packages = [op.package for op in ops] repo = Repository(packages) - requires = package.requires + package.dev_requires + requires = package.all_requires for pkg in repo.packages: for require in requires: if pkg.name == require.name: @@ -123,7 +123,7 @@ def handle(self) -> Optional[int]: solver = Solver(package, pool, Repository(), Repository(), NullIO()) with solver.use_environment(env): - ops = solver.solve() + ops = solver.solve().calculate_operations() for op in ops: if self.option("install") and op.skipped: diff --git a/poetry/console/commands/env/info.py b/poetry/console/commands/env/info.py index aecce3628ac..791c71a175c 100644 --- a/poetry/console/commands/env/info.py +++ b/poetry/console/commands/env/info.py @@ -44,6 +44,9 @@ def _display_complete_info(self, env: "Env") -> None: "Path: {}".format( env.path if env.is_venv() else "NA" ), + "Executable: {}".format( + env.python if env.is_venv() else "NA" + ), ] if env.is_venv(): listing.append( @@ -55,13 +58,18 @@ def _display_complete_info(self, env: "Env") -> None: self.line("") + system_env = env.parent_env self.line("System") self.line( "\n".join( [ - "Platform: {}".format(env.platform), - "OS: {}".format(env.os), - "Python: {}".format(env.base), + "Platform: {}".format(env.platform), + "OS: {}".format(env.os), + "Python: {}".format( + ".".join(str(v) for v in system_env.version_info[:3]) + ), + "Path: {}".format(system_env.path), + "Executable: {}".format(system_env.python), ] ) ) diff --git a/poetry/console/commands/export.py b/poetry/console/commands/export.py index a58d96f50fe..accdc199460 100644 --- a/poetry/console/commands/export.py +++ b/poetry/console/commands/export.py @@ -41,7 +41,7 @@ def handle(self) -> None: locker = self.poetry.locker if not locker.is_locked(): - self.line("The lock file does not exist. Locking.") + self.line_error("The lock file does not exist. Locking.") options = [] if self.io.is_debug(): options.append(("-vvv", None)) @@ -53,7 +53,7 @@ def handle(self) -> None: self.call("lock", " ".join(options)) if not locker.is_fresh(): - self.line( + self.line_error( "" "Warning: The lock file is not up to date with " "the latest changes in pyproject.toml. " diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py index b2e4fd0a315..0d525dbe3e0 100644 --- a/poetry/console/commands/init.py +++ b/poetry/console/commands/init.py @@ -86,11 +86,12 @@ def handle(self) -> int: vcs_config = GitConfig() - self.line("") - self.line( - "This command will guide you through creating your pyproject.toml config." - ) - self.line("") + if self.io.is_interactive(): + self.line("") + self.line( + "This command will guide you through creating your pyproject.toml config." + ) + self.line("") name = self.option("name") if not name: @@ -154,7 +155,8 @@ def handle(self) -> int: ) python = self.ask(question) - self.line("") + if self.io.is_interactive(): + self.line("") requirements = {} if self.option("dependency"): @@ -175,12 +177,14 @@ def handle(self) -> int: ) help_displayed = False if self.confirm(question, True): - self.line(help_message) - help_displayed = True + if self.io.is_interactive(): + self.line(help_message) + help_displayed = True requirements.update( self._format_requirements(self._determine_requirements([])) ) - self.line("") + if self.io.is_interactive(): + self.line("") dev_requirements = {} if self.option("dev-dependency"): @@ -192,13 +196,14 @@ def handle(self) -> int: "Would you like to define your development dependencies interactively?" ) if self.confirm(question, True): - if not help_displayed: + if self.io.is_interactive() and not help_displayed: self.line(help_message) dev_requirements.update( self._format_requirements(self._determine_requirements([])) ) - self.line("") + if self.io.is_interactive(): + self.line("") layout_ = layout("standard")( name, @@ -317,7 +322,8 @@ def _determine_requirements( if package is not False: requires.append(constraint) - package = self.ask("\nAdd a package:") + if self.io.is_interactive(): + package = self.ask("\nAdd a package:") return requires diff --git a/poetry/console/commands/install.py b/poetry/console/commands/install.py index 0ffb30a3756..0eca8a9be0c 100644 --- a/poetry/console/commands/install.py +++ b/poetry/console/commands/install.py @@ -9,8 +9,43 @@ class InstallCommand(InstallerCommand): description = "Installs the project dependencies." options = [ - option("no-dev", None, "Do not install the development dependencies."), - option("dev-only", None, "Only install the development dependencies."), + option( + "without", + None, + "The dependency groups to ignore for installation.", + flag=False, + multiple=True, + ), + option( + "with", + None, + "The optional dependency groups to include for installation.", + flag=False, + multiple=True, + ), + option("default", None, "Only install the default dependencies."), + option( + "only", + None, + "The only dependency groups to install.", + flag=False, + multiple=True, + ), + option( + "no-dev", + None, + "Do not install the development dependencies. (Deprecated)", + ), + option( + "dev-only", + None, + "Only install the development dependencies. (Deprecated)", + ), + option( + "sync", + None, + "Synchronize the environment with the locked packages and the specified groups.", + ), option( "no-root", None, "Do not install the root package (the current project)." ), @@ -66,10 +101,62 @@ def handle(self) -> int: extras.append(extra) self._installer.extras(extras) - self._installer.dev_mode(not self.option("no-dev")) - self._installer.dev_only(self.option("dev-only")) + + excluded_groups = [] + included_groups = [] + only_groups = [] + if self.option("no-dev"): + self.line( + "The `--no-dev` option is deprecated, " + "use the `--without dev` notation instead." + ) + excluded_groups.append("dev") + elif self.option("dev-only"): + self.line( + "The `--dev-only` option is deprecated, " + "use the `--only dev` notation instead." + ) + only_groups.append("dev") + + excluded_groups.extend( + [ + group.strip() + for groups in self.option("without") + for group in groups.split(",") + ] + ) + included_groups.extend( + [ + group.strip() + for groups in self.option("with") + for group in groups.split(",") + ] + ) + only_groups.extend( + [ + group.strip() + for groups in self.option("only") + for group in groups.split(",") + ] + ) + + if self.option("default"): + only_groups.append("default") + + with_synchronization = self.option("sync") + if self.option("remove-untracked"): + self.line( + "The `--remove-untracked` option is deprecated, " + "use the `--sync` option instead." + ) + + with_synchronization = True + + self._installer.only_groups(only_groups) + self._installer.without_groups(excluded_groups) + self._installer.with_groups(included_groups) self._installer.dry_run(self.option("dry-run")) - self._installer.remove_untracked(self.option("remove-untracked")) + self._installer.requires_synchronization(with_synchronization) self._installer.verbose(self._io.is_verbose()) return_code = self._installer.run() @@ -77,7 +164,7 @@ def handle(self) -> int: if return_code != 0: return return_code - if self.option("no-root") or self.option("dev-only"): + if self.option("no-root") or self.option("only"): return 0 try: diff --git a/poetry/console/commands/remove.py b/poetry/console/commands/remove.py index 58af353ccc6..10d06197727 100644 --- a/poetry/console/commands/remove.py +++ b/poetry/console/commands/remove.py @@ -1,7 +1,10 @@ +from typing import Any +from typing import Dict +from typing import List + from cleo.helpers import argument from cleo.helpers import option -from ...utils.helpers import canonicalize_name from .installer_command import InstallerCommand @@ -12,6 +15,7 @@ class RemoveCommand(InstallerCommand): arguments = [argument("packages", "The packages to remove.", multiple=True)] options = [ + option("group", "G", "The group to remove the dependency from.", flag=False), option("dev", "D", "Remove a package from the development dependencies."), option( "dry-run", @@ -30,39 +34,70 @@ class RemoveCommand(InstallerCommand): def handle(self) -> int: packages = self.argument("packages") - is_dev = self.option("dev") + + if self.option("dev"): + self.line( + "The --dev option is deprecated, " + "use the `--group dev` notation instead." + ) + self.line("") + group = "dev" + else: + group = self.option("group") content = self.poetry.file.read() poetry_content = content["tool"]["poetry"] - section = "dependencies" - if is_dev: - section = "dev-dependencies" - - # Deleting entries - requirements = {} - for name in packages: - found = False - for key in poetry_content[section]: - if key.lower() == name.lower(): - found = True - requirements[key] = poetry_content[section][key] - break - - if not found: - raise ValueError("Package {} not found".format(name)) - - for key in requirements: - del poetry_content[section][key] - - dependencies = ( - self.poetry.package.requires - if section == "dependencies" - else self.poetry.package.dev_requires + + if group is None: + removed = [] + group_sections = [] + for group_name, group_section in poetry_content.get("group", {}).items(): + group_sections.append( + (group_name, group_section.get("dependencies", {})) + ) + + for group_name, section in [ + ("default", poetry_content["dependencies"]) + ] + group_sections: + removed += self._remove_packages(packages, section, group_name) + if group_name != "default": + if not section: + del poetry_content["group"][group_name] + else: + poetry_content["group"][group_name]["dependencies"] = section + elif group == "dev" and "dev-dependencies" in poetry_content: + # We need to account for the old `dev-dependencies` section + removed = self._remove_packages( + packages, poetry_content["dev-dependencies"], "dev" + ) + + if not poetry_content["dev-dependencies"]: + del poetry_content["dev-dependencies"] + else: + removed = self._remove_packages( + packages, poetry_content["group"][group].get("dependencies", {}), group ) - for i, dependency in enumerate(reversed(dependencies)): - if dependency.name == canonicalize_name(key): - del dependencies[-i] + if not poetry_content["group"][group]: + del poetry_content["group"][group] + + if "group" in poetry_content and not poetry_content["group"]: + del poetry_content["group"] + + removed = set(removed) + not_found = set(packages).difference(removed) + if not_found: + raise ValueError( + "The following packages were not found: {}".format( + ", ".join(sorted(not_found)) + ) + ) + + # Refresh the locker + self.poetry.set_locker( + self.poetry.locker.__class__(self.poetry.locker.lock.path, poetry_content) + ) + self._installer.set_locker(self.poetry.locker) # Update packages self._installer.use_executor( @@ -72,7 +107,7 @@ def handle(self) -> int: self._installer.dry_run(self.option("dry-run")) self._installer.verbose(self._io.is_verbose()) self._installer.update(True) - self._installer.whitelist(requirements) + self._installer.whitelist(removed) status = self._installer.run() @@ -80,3 +115,19 @@ def handle(self) -> int: self.poetry.file.write(content) return status + + def _remove_packages( + self, packages: List[str], section: Dict[str, Any], group_name: str + ) -> List[str]: + removed = [] + group = self.poetry.package.dependency_group(group_name) + section_keys = list(section.keys()) + + for package in packages: + for existing_package in section_keys: + if existing_package.lower() == package.lower(): + del section[existing_package] + removed.append(package) + group.remove_dependency(package) + + return removed diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index 799fa4d78db..d4567bc162d 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -14,6 +14,7 @@ from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package + from poetry.packages.project_package import ProjectPackage from poetry.repositories import Repository from poetry.repositories.installed_repository import InstalledRepository @@ -25,7 +26,35 @@ class ShowCommand(EnvCommand): arguments = [argument("package", "The package to inspect", optional=True)] options = [ - option("no-dev", None, "Do not list the development dependencies."), + option( + "without", + None, + "Do not show the information of the specified groups' dependencies.", + flag=False, + multiple=True, + ), + option( + "with", + None, + "Show the information of the specified optional groups' dependencies as well.", + flag=False, + multiple=True, + ), + option( + "default", None, "Only show the information of the default dependencies." + ), + option( + "only", + None, + "Only show the information of dependencies belonging to the specified groups.", + flag=False, + multiple=True, + ), + option( + "no-dev", + None, + "Do not list the development dependencies. (Deprecated)", + ), option("tree", "t", "List the dependencies as a tree."), option("latest", "l", "Show the latest version."), option( @@ -63,19 +92,58 @@ def handle(self) -> Optional[int]: if self.option("outdated"): self._io.input.set_option("latest", True) - include_dev = not self.option("no-dev") + excluded_groups = [] + included_groups = [] + only_groups = [] + if self.option("no-dev"): + self.line( + "The `--no-dev` option is deprecated, " + "use the `--without dev` notation instead." + ) + excluded_groups.append("dev") + + excluded_groups.extend( + [ + group.strip() + for groups in self.option("without") + for group in groups.split(",") + ] + ) + included_groups.extend( + [ + group.strip() + for groups in self.option("with") + for group in groups.split(",") + ] + ) + only_groups.extend( + [ + group.strip() + for groups in self.option("only") + for group in groups.split(",") + ] + ) + + if self.option("default"): + only_groups.append("default") + locked_repo = self.poetry.locker.locked_repository(True) + if only_groups: + root = self.poetry.package.with_dependency_groups(only_groups, only=True) + else: + root = self.poetry.package.with_dependency_groups( + included_groups + ).without_dependency_groups(excluded_groups) + # Show tree view if requested if self.option("tree") and not package: - requires = self.poetry.package.requires - if include_dev: - requires += self.poetry.package.dev_requires + requires = root.all_requires packages = locked_repo.packages - for package in packages: + for pkg in packages: for require in requires: - if package.name == require.name: - self.display_package_tree(self._io, package, locked_repo) + if pkg.name == require.name: + self.display_package_tree(self._io, pkg, locked_repo) break return 0 @@ -85,7 +153,7 @@ def handle(self) -> Optional[int]: pool = Pool(ignore_repository_names=True) pool.add_repository(locked_repo) solver = Solver( - self.poetry.package, + root, pool=pool, installed=Repository(), locked=locked_repo, @@ -93,15 +161,10 @@ def handle(self) -> Optional[int]: ) solver.provider.load_deferred(False) with solver.use_environment(self.env): - ops = solver.solve() + ops = solver.solve().calculate_operations() required_locked_packages = set([op.package for op in ops if not op.skipped]) - if self.option("no-dev"): - required_locked_packages = [ - p for p in locked_packages if p.category == "main" - ] - if package: pkg = None for locked in locked_packages: @@ -117,6 +180,13 @@ def handle(self) -> Optional[int]: return 0 + required_by = {} + for locked in locked_packages: + dependencies = {d.name: d.pretty_constraint for d in locked.requires} + + if pkg.name in dependencies: + required_by[locked.pretty_name] = dependencies[pkg.name] + rows = [ ["name", " : {}".format(pkg.pretty_name)], ["version", " : {}".format(pkg.pretty_version)], @@ -136,6 +206,14 @@ def handle(self) -> Optional[int]: ) ) + if required_by: + self.line("") + self.line("required by") + for parent, requires_version in required_by.items(): + self.line( + " - {} {}".format(parent, requires_version) + ) + return 0 show_latest = self.option("latest") @@ -160,7 +238,7 @@ def handle(self) -> Optional[int]: current_length += 4 if show_latest: - latest = self.find_latest_package(locked, include_dev) + latest = self.find_latest_package(locked, root) if not latest: latest = locked @@ -377,7 +455,7 @@ def init_styles(self, io: "IO") -> None: io.error_output.formatter.set_style(color, style) def find_latest_package( - self, package: "Package", include_dev: bool + self, package: "Package", root: "ProjectPackage" ) -> Union["Package", bool]: from cleo.io.null_io import NullIO @@ -386,13 +464,11 @@ def find_latest_package( # find the latest version allowed in this pool if package.source_type in ("git", "file", "directory"): - requires = self.poetry.package.requires - if include_dev: - requires = requires + self.poetry.package.dev_requires + requires = root.all_requires for dep in requires: if dep.name == package.name: - provider = Provider(self.poetry.package, self.poetry.pool, NullIO()) + provider = Provider(root, self.poetry.pool, NullIO()) if dep.is_vcs(): return provider.search_for_vcs(dep)[0] diff --git a/poetry/console/commands/update.py b/poetry/console/commands/update.py index fd2b421bce2..d470e9e3e38 100644 --- a/poetry/console/commands/update.py +++ b/poetry/console/commands/update.py @@ -37,7 +37,9 @@ def handle(self) -> int: if packages: self._installer.whitelist({name: "*" for name in packages}) - self._installer.dev_mode(not self.option("no-dev")) + if self.option("no-dev"): + self._installer.with_groups(["dev"]) + self._installer.dry_run(self.option("dry-run")) self._installer.execute_operations(not self.option("lock")) diff --git a/poetry/factory.py b/poetry/factory.py index 144bfe35871..03b55175cd4 100644 --- a/poetry/factory.py +++ b/poetry/factory.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from pathlib import Path from typing import TYPE_CHECKING from typing import Dict @@ -10,17 +7,16 @@ from cleo.io.io import IO from cleo.io.null_io import NullIO +from poetry.config.config import Config +from poetry.config.file_config_source import FileConfigSource from poetry.core.factory import Factory as BaseFactory from poetry.core.toml.file import TOMLFile - -from .config.config import Config -from .config.file_config_source import FileConfigSource -from .locations import CONFIG_DIR -from .packages.locker import Locker -from .packages.project_package import ProjectPackage -from .plugins.plugin_manager import PluginManager -from .poetry import Poetry -from .repositories.pypi_repository import PyPiRepository +from poetry.locations import CONFIG_DIR +from poetry.packages.locker import Locker +from poetry.packages.project_package import ProjectPackage +from poetry.plugins.plugin_manager import PluginManager +from poetry.poetry import Poetry +from poetry.repositories.pypi_repository import PyPiRepository if TYPE_CHECKING: diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py index ae5f59bd63f..2632f901ded 100644 --- a/poetry/inspection/info.py +++ b/poetry/inspection/info.py @@ -157,7 +157,9 @@ def to_package( poetry_package = self._get_poetry_package(path=root_dir or self._source_url) if poetry_package: package.extras = poetry_package.extras - package.requires = poetry_package.requires + for dependency in poetry_package.requires: + package.add_dependency(dependency) + return package seen_requirements = set() @@ -191,7 +193,7 @@ def to_package( req = dependency.to_pep_508(with_extras=True) if req not in seen_requirements: - package.requires.append(dependency) + package.add_dependency(dependency) seen_requirements.add(req) return package diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index 4580656c933..00b709da2aa 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -24,8 +24,8 @@ from poetry.utils.helpers import safe_rmtree from poetry.utils.pip import pip_editable_install +from ..utils.authenticator import Authenticator from ..utils.pip import pip_install -from .authenticator import Authenticator from .chef import Chef from .chooser import Chooser from .operations.install import Install diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py index 0718eef52d0..13cd3328598 100644 --- a/poetry/installation/installer.py +++ b/poetry/installation/installer.py @@ -50,12 +50,14 @@ def __init__( self._pool = pool self._dry_run = False - self._remove_untracked = False + self._requires_synchronization = False self._update = False self._verbose = False self._write_lock = True - self._dev_mode = True - self._dev_only = False + self._without_groups = None + self._with_groups = None + self._only_groups = None + self._execute_operations = True self._lock = False @@ -120,14 +122,13 @@ def dry_run(self, dry_run: bool = True) -> "Installer": def is_dry_run(self) -> bool: return self._dry_run - def remove_untracked(self, remove_untracked: bool = True) -> "Installer": - self._remove_untracked = remove_untracked + def requires_synchronization( + self, requires_synchronization: bool = True + ) -> "Installer": + self._requires_synchronization = requires_synchronization return self - def is_remove_untracked(self) -> bool: - return self._remove_untracked - def verbose(self, verbose: bool = True) -> "Installer": self._verbose = verbose self._executor.verbose(verbose) @@ -137,21 +138,20 @@ def verbose(self, verbose: bool = True) -> "Installer": def is_verbose(self) -> bool: return self._verbose - def dev_mode(self, dev_mode: bool = True) -> "Installer": - self._dev_mode = dev_mode + def without_groups(self, groups: List[str]) -> "Installer": + self._without_groups = groups return self - def is_dev_mode(self) -> bool: - return self._dev_mode - - def dev_only(self, dev_only: bool = False) -> "Installer": - self._dev_only = dev_only + def with_groups(self, groups: List[str]) -> "Installer": + self._with_groups = groups return self - def is_dev_only(self) -> bool: - return self._dev_only + def only_groups(self, groups: List[str]) -> "Installer": + self._only_groups = groups + + return self def update(self, update: bool = True) -> "Installer": self._update = update @@ -211,7 +211,7 @@ def _do_refresh(self) -> int: self._io, ) - ops = solver.solve(use_latest=[]) + ops = solver.solve(use_latest=[]).calculate_operations() local_repo = Repository() self._populate_local_repo(local_repo, ops) @@ -246,10 +246,9 @@ def _do_install(self, local_repo: Repository) -> int: self._installed_repository, locked_repository, self._io, - remove_untracked=self._remove_untracked, ) - ops = solver.solve(use_latest=self._whitelist) + ops = solver.solve(use_latest=self._whitelist).calculate_operations() else: self._io.write_line("Installing dependencies from lock file") @@ -283,13 +282,20 @@ def _do_install(self, local_repo: Repository) -> int: # If we are only in lock mode, no need to go any further return 0 - root = self._package - if not self.is_dev_mode(): - root = root.clone() - del root.dev_requires[:] - elif self.is_dev_only(): - root = root.clone() - del root.requires[:] + if self._without_groups or self._with_groups or self._only_groups: + if self._with_groups: + # Default dependencies and opted-in optional dependencies + root = self._package.with_dependency_groups(self._with_groups) + elif self._without_groups: + # Default dependencies without selected groups + root = self._package.without_dependency_groups(self._without_groups) + else: + # Only selected groups + root = self._package.with_dependency_groups( + self._only_groups, only=True + ) + else: + root = self._package.without_optional_dependency_groups() if self._io.is_verbose(): self._io.write_line("") @@ -310,19 +316,35 @@ def _do_install(self, local_repo: Repository) -> int: pool.add_repository(repo) solver = Solver( - root, - pool, - self._installed_repository, - locked_repository, - NullIO(), - remove_untracked=self._remove_untracked, + root, pool, self._installed_repository, locked_repository, NullIO() ) # Everything is resolved at this point, so we no longer need # to load deferred dependencies (i.e. VCS, URL and path dependencies) solver.provider.load_deferred(False) with solver.use_environment(self._env): - ops = solver.solve(use_latest=self._whitelist) + ops = solver.solve(use_latest=self._whitelist).calculate_operations( + with_uninstalls=self._requires_synchronization, + synchronize=self._requires_synchronization, + ) + + if not self._requires_synchronization: + # If no packages synchronisation has been requested we need + # to calculate the uninstall operations + from poetry.puzzle.transaction import Transaction + + transaction = Transaction( + locked_repository.packages, + [(package, 0) for package in local_repo.packages], + installed_packages=self._installed_repository.packages, + root_package=root, + ) + + ops = [ + op + for op in transaction.calculate_operations(with_uninstalls=True) + if op.job_type == "uninstall" + ] + ops # We need to filter operations so that packages # not compatible with the current system, @@ -502,9 +524,7 @@ def _get_operations_from_lock( for installed in installed_repo.packages: if locked.name == installed.name: is_installed = True - if locked.category == "dev" and not self.is_dev_mode(): - ops.append(Uninstall(locked)) - elif locked.optional and locked.name not in extra_packages: + if locked.optional and locked.name not in extra_packages: # Installed but optional and not requested in extras ops.append(Uninstall(locked)) elif locked.version != installed.version: @@ -553,11 +573,6 @@ def _filter_operations(self, ops: List[Operation], repo: Repository) -> None: if package.name not in extra_packages: op.skip("Not required") - # If the package is a dev package and dev packages - # are not requested, we skip it - if package.category == "dev" and not self.is_dev_mode(): - op.skip("Dev dependencies not requested") - def _get_extra_packages(self, repo: Repository) -> List[str]: """ Returns all package names required by extras. diff --git a/poetry/json/schemas/poetry-schema.json b/poetry/json/schemas/poetry-schema.json index e94b90d28cc..41966e9e1c2 100644 --- a/poetry/json/schemas/poetry-schema.json +++ b/poetry/json/schemas/poetry-schema.json @@ -59,7 +59,7 @@ }, "classifiers": { "type": "array", - "description": "A list of trove classifers." + "description": "A list of trove classifiers." }, "packages": { "type": "array", diff --git a/poetry/layouts/layout.py b/poetry/layouts/layout.py index f4610564582..58f54db537d 100644 --- a/poetry/layouts/layout.py +++ b/poetry/layouts/layout.py @@ -30,7 +30,7 @@ [tool.poetry.dependencies] -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] """ BUILD_SYSTEM_MIN_VERSION: Optional[str] = None @@ -143,8 +143,13 @@ def generate_poetry_content( for dep_name, dep_constraint in self._dependencies.items(): poetry_content["dependencies"][dep_name] = dep_constraint - for dep_name, dep_constraint in self._dev_dependencies.items(): - poetry_content["dev-dependencies"][dep_name] = dep_constraint + if self._dev_dependencies: + for dep_name, dep_constraint in self._dev_dependencies.items(): + poetry_content["group"]["dev"]["dependencies"][ + dep_name + ] = dep_constraint + else: + del poetry_content["group"] # Add build system build_system = table() diff --git a/poetry/masonry/builders/editable.py b/poetry/masonry/builders/editable.py index 2483a5ec704..60c686844e6 100644 --- a/poetry/masonry/builders/editable.py +++ b/poetry/masonry/builders/editable.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import hashlib import os import shutil @@ -27,10 +25,11 @@ SCRIPT_TEMPLATE = """\ #!{python} +import sys from {module} import {callable_holder} if __name__ == '__main__': - {callable_}() + sys.exit({callable_}()) """ WINDOWS_CMD_TEMPLATE = """\ diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index c1cbe6609cb..6855c14c35a 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -47,7 +47,7 @@ class Locker: _VERSION = "1.1" - _relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"] + _relevant_keys = ["dependencies", "group", "source", "extras"] def __init__(self, lock: Union[str, Path], local_config: dict) -> None: self._lock = TOMLFile(lock) @@ -128,7 +128,8 @@ def locked_repository( source_resolved_reference=source.get("resolved_reference"), ) package.description = info.get("description", "") - package.category = info["category"] + package.category = info.get("category", "main") + package.groups = info.get("groups", ["default"]) package.optional = info["optional"] if "hashes" in lock_data["metadata"]: # Old lock so we create dummy files from the hashes @@ -176,20 +177,22 @@ def locked_repository( package.marker = parse_marker(split_dep[1].strip()) for dep_name, constraint in info.get("dependencies", {}).items(): + + root_dir = self._lock.path.parent + if package.source_type == "directory": + # root dir should be the source of the package relative to the lock path + root_dir = Path(package.source_url) + if isinstance(constraint, list): for c in constraint: package.add_dependency( - Factory.create_dependency( - dep_name, c, root_dir=self._lock.path.parent - ) + Factory.create_dependency(dep_name, c, root_dir=root_dir) ) continue package.add_dependency( - Factory.create_dependency( - dep_name, constraint, root_dir=self._lock.path.parent - ) + Factory.create_dependency(dep_name, constraint, root_dir=root_dir) ) if "develop" in info: @@ -513,7 +516,25 @@ def _dump_package(self, package: Package) -> dict: dependencies[dependency.pretty_name] = [] constraint = inline_table() - constraint["version"] = str(dependency.pretty_constraint) + + if dependency.is_directory() or dependency.is_file(): + constraint["path"] = dependency.path.as_posix() + + if dependency.is_directory() and dependency.develop: + constraint["develop"] = True + elif dependency.is_url(): + constraint["url"] = dependency.url + elif dependency.is_vcs(): + constraint[dependency.vcs] = dependency.source + + if dependency.branch: + constraint["branch"] = dependency.branch + elif dependency.tag: + constraint["tag"] = dependency.tag + elif dependency.rev: + constraint["rev"] = dependency.rev + else: + constraint["version"] = str(dependency.pretty_constraint) if dependency.extras: constraint["extras"] = sorted(dependency.extras) @@ -529,7 +550,10 @@ def _dump_package(self, package: Package) -> dict: # All the constraints should have the same type, # but we want to simplify them if it's possible for dependency, constraints in tuple(dependencies.items()): - if all(len(constraint) == 1 for constraint in constraints): + if all( + len(constraint) == 1 and "version" in constraint + for constraint in constraints + ): dependencies[dependency] = [ constraint["version"] for constraint in constraints ] diff --git a/poetry/packages/project_package.py b/poetry/packages/project_package.py index 22379c2026f..f2e91fd0490 100644 --- a/poetry/packages/project_package.py +++ b/poetry/packages/project_package.py @@ -21,3 +21,5 @@ def set_version( else: self._version = version self._pretty_version = pretty_version or version.text + + return self diff --git a/poetry/publishing/publisher.py b/poetry/publishing/publisher.py index cfa5c7d085d..ca1c08a73de 100644 --- a/poetry/publishing/publisher.py +++ b/poetry/publishing/publisher.py @@ -6,10 +6,9 @@ from typing import Optional from typing import Union -from poetry.utils.helpers import get_cert -from poetry.utils.helpers import get_client_cert -from poetry.utils.password_manager import PasswordManager - +from ..utils.authenticator import Authenticator +from ..utils.helpers import get_cert +from ..utils.helpers import get_client_cert from .uploader import Uploader @@ -32,7 +31,7 @@ def __init__(self, poetry: "Poetry", io: Union["BufferedIO", "ConsoleIO"]) -> No self._package = poetry.package self._io = io self._uploader = Uploader(poetry, io) - self._password_manager = PasswordManager(poetry.config) + self._authenticator = Authenticator(poetry.config, self._io) @property def files(self) -> List[Path]: @@ -58,13 +57,13 @@ def publish( if not (username and password): # Check if we have a token first - token = self._password_manager.get_pypi_token(repository_name) + token = self._authenticator.get_pypi_token(repository_name) if token: logger.debug(f"Found an API token for {repository_name}.") username = "__token__" password = token else: - auth = self._password_manager.get_http_auth(repository_name) + auth = self._authenticator.get_http_auth(repository_name) if auth: logger.debug( "Found authentication information for {}.".format( diff --git a/poetry/publishing/uploader.py b/poetry/publishing/uploader.py index 798b2533af8..a4a25956367 100644 --- a/poetry/publishing/uploader.py +++ b/poetry/publishing/uploader.py @@ -69,7 +69,7 @@ def adapter(self) -> adapters.HTTPAdapter: retry = util.Retry( connect=5, total=10, - method_whitelist=["GET"], + allowed_methods=["GET"], status_forcelist=[500, 501, 502, 503], ) diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index c7427161c2c..d77d7936aa5 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -184,6 +184,15 @@ def search_for_vcs(self, dependency: VCSDependency) -> List[Package]: dependency._constraint = package.version dependency._pretty_constraint = package.version.text + dependency._source_reference = package.source_reference + dependency._source_resolved_reference = package.source_resolved_reference + + if hasattr(package, "source_subdirectory") and hasattr( + dependency, "_source_subdirectory" + ): + # this is supported only for poetry-core >= 1.1.0a7 + dependency._source_subdirectory = package.source_subdirectory + self._deferred_cache[dependency] = package return [package] @@ -330,7 +339,8 @@ def search_for_url(self, dependency: URLDependency) -> List[Package]: for dep in package.extras[extra]: dep.activate() - package.requires += package.extras[extra] + for extra_dep in package.extras[extra]: + package.add_dependency(extra_dep) dependency._constraint = package.version dependency._pretty_constraint = package.version.text @@ -535,10 +545,10 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: # - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6" duplicates = dict() for dep in dependencies: - if dep.name not in duplicates: - duplicates[dep.name] = [] + if dep.complete_name not in duplicates: + duplicates[dep.complete_name] = [] - duplicates[dep.name].append(dep) + duplicates[dep.complete_name].append(dep) dependencies = [] for dep_name, deps in duplicates.items(): @@ -617,8 +627,8 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: # - {=2.0)>} # - {} markers = [] - for constraint, _deps in by_constraint.items(): - markers.append(_deps[0].marker) + for deps in by_constraint.values(): + markers.append(deps[0].marker) _deps = [_dep[0] for _dep in by_constraint.values()] self.debug( @@ -699,7 +709,12 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: clean_dependencies.append(dep) - package.requires = clean_dependencies + package = DependencyPackage( + package.dependency, package.with_dependency_groups([], only=True) + ) + + for dep in clean_dependencies: + package.add_dependency(dep) return package diff --git a/poetry/puzzle/solver.py b/poetry/puzzle/solver.py index 2e864bc9b20..28cb3653c39 100644 --- a/poetry/puzzle/solver.py +++ b/poetry/puzzle/solver.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING from typing import Callable from typing import Dict +from typing import FrozenSet from typing import List from typing import Optional from typing import Tuple @@ -15,9 +16,6 @@ from poetry.core.packages.package import Package from poetry.core.packages.project_package import ProjectPackage -from poetry.installation.operations import Install -from poetry.installation.operations import Uninstall -from poetry.installation.operations import Update from poetry.mixology import resolve_version from poetry.mixology.failure import SolveFailure from poetry.packages import DependencyPackage @@ -36,7 +34,8 @@ from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.vcs_dependency import VCSDependency - from poetry.installation.operations import OperationTypes + + from .transaction import Transaction class Solver: @@ -47,7 +46,6 @@ def __init__( installed: Repository, locked: Repository, io: IO, - remove_untracked: bool = False, provider: Optional[Provider] = None, ): self._package = package @@ -61,39 +59,19 @@ def __init__( self._provider = provider self._overrides = [] - self._remove_untracked = remove_untracked - - self._preserved_package_names = None @property def provider(self) -> Provider: return self._provider - @property - def preserved_package_names(self): - if self._preserved_package_names is None: - self._preserved_package_names = { - self._package.name, - *Provider.UNSAFE_PACKAGES, - } - - deps = {package.name for package in self._locked.packages} - - # preserve pip/setuptools/wheel when not managed by poetry, this is so - # to avoid externally managed virtual environments causing unnecessary - # removals. - for name in {"pip", "wheel", "setuptools"}: - if name not in deps: - self._preserved_package_names.add(name) - - return self._preserved_package_names - @contextmanager def use_environment(self, env: Env) -> None: with self.provider.use_environment(env): yield - def solve(self, use_latest: List[str] = None) -> List["OperationTypes"]: + def solve(self, use_latest: List[str] = None) -> "Transaction": + from .transaction import Transaction + with self._provider.progress(): start = time.time() packages, depths = self._solve(use_latest=use_latest) @@ -109,121 +87,11 @@ def solve(self, use_latest: List[str] = None) -> List["OperationTypes"]: f"Resolved with overrides: {', '.join(f'({b})' for b in self._overrides)}" ) - operations = [] - for i, package in enumerate(packages): - installed = False - for pkg in self._installed.packages: - if package.name == pkg.name: - installed = True - - if pkg.source_type == "git" and package.source_type == "git": - from poetry.core.vcs.git import Git - - # Trying to find the currently installed version - pkg_source_url = Git.normalize_url(pkg.source_url) - package_source_url = Git.normalize_url(package.source_url) - for locked in self._locked.packages: - if locked.name != pkg.name or locked.source_type != "git": - continue - - locked_source_url = Git.normalize_url(locked.source_url) - if ( - locked.name == pkg.name - and locked.source_type == pkg.source_type - and locked_source_url == pkg_source_url - and locked.source_reference == pkg.source_reference - and locked.source_resolved_reference - == pkg.source_resolved_reference - ): - pkg = Package( - pkg.name, - locked.version, - source_type="git", - source_url=locked.source_url, - source_reference=locked.source_reference, - source_resolved_reference=locked.source_resolved_reference, - ) - break - - if pkg_source_url != package_source_url or ( - ( - not pkg.source_resolved_reference - or not package.source_resolved_reference - ) - and pkg.source_reference != package.source_reference - and not pkg.source_reference.startswith( - package.source_reference - ) - or ( - pkg.source_resolved_reference - and package.source_resolved_reference - and pkg.source_resolved_reference - != package.source_resolved_reference - and not pkg.source_resolved_reference.startswith( - package.source_resolved_reference - ) - ) - ): - operations.append(Update(pkg, package, priority=depths[i])) - else: - operations.append( - Install(package).skip("Already installed") - ) - elif package.version != pkg.version: - # Checking version - operations.append(Update(pkg, package, priority=depths[i])) - elif pkg.source_type and package.source_type != pkg.source_type: - operations.append(Update(pkg, package, priority=depths[i])) - else: - operations.append( - Install(package, priority=depths[i]).skip( - "Already installed" - ) - ) - - break - - if not installed: - operations.append(Install(package, priority=depths[i])) - - # Checking for removals - for pkg in self._locked.packages: - remove = True - for package in packages: - if pkg.name == package.name: - remove = False - break - - if remove: - skip = True - for installed in self._installed.packages: - if installed.name == pkg.name: - skip = False - break - - op = Uninstall(pkg) - if skip: - op.skip("Not currently installed") - - operations.append(op) - - if self._remove_untracked: - locked_names = {locked.name for locked in self._locked.packages} - - for installed in self._installed.packages: - if installed.name in self.preserved_package_names: - continue - - if installed.name not in locked_names: - operations.append(Uninstall(installed)) - - return sorted( - operations, - key=lambda o: ( - -o.priority, - o.package.name, - o.package.version, - ), + return Transaction( + self._locked.packages, + list(zip(packages, depths)), + installed_packages=self._installed.packages, + root_package=self._package, ) def solve_in_compatibility_mode( @@ -254,7 +122,7 @@ def solve_in_compatibility_mode( for dep in package.requires: if dep not in pkg.requires: - pkg.requires.append(dep) + pkg.add_dependency(dep) return packages, depths @@ -277,9 +145,10 @@ def _solve(self, use_latest: List[str] = None) -> Tuple[List[Package], List[int] except SolveFailure as e: raise SolverProblemError(e) + # NOTE passing explicit empty array for seen to reset between invocations during update + install cycle results = dict( depth_first_search( - PackageNode(self._package, packages), aggregate_package_nodes + PackageNode(self._package, packages, seen=[]), aggregate_package_nodes ) ) @@ -299,7 +168,7 @@ def _solve(self, use_latest: List[str] = None) -> Tuple[List[Package], List[int] continue if dep not in _package.requires: - _package.requires.append(dep) + _package.add_dependency(dep) continue @@ -311,7 +180,9 @@ def _solve(self, use_latest: List[str] = None) -> Tuple[List[Package], List[int] class DFSNode: - def __init__(self, id: Tuple[str, str, bool], name: str, base_name: str) -> None: + def __init__( + self, id: Tuple[str, FrozenSet[str], bool], name: str, base_name: str + ) -> None: self.id = id self.name = name self.base_name = base_name @@ -390,6 +261,7 @@ def __init__( self, package: Package, packages: List[Package], + seen: List[Package], previous: Optional["PackageNode"] = None, previous_dep: Optional[ Union[ @@ -412,6 +284,7 @@ def __init__( ) -> None: self.package = package self.packages = packages + self.seen = seen self.previous = previous self.previous_dep = previous_dep @@ -420,13 +293,15 @@ def __init__( if not previous: self.category = "dev" + self.groups = frozenset() self.optional = True else: - self.category = dep.category + self.category = "main" if "default" in dep.groups else "dev" + self.groups = dep.groups self.optional = dep.is_optional() super().__init__( - (package.complete_name, self.category, self.optional), + (package.complete_name, self.groups, self.optional), package.complete_name, package.name, ) @@ -434,6 +309,12 @@ def __init__( def reachable(self) -> List["PackageNode"]: children: List[PackageNode] = [] + # skip already traversed packages + if self.package in self.seen: + return [] + else: + self.seen.append(self.package) + if ( self.previous_dep and self.previous_dep is not self.dep @@ -461,7 +342,7 @@ def reachable(self) -> List["PackageNode"]: # we merge the requirements if any( child.package.name == pkg.name - and child.category == dependency.category + and child.groups == dependency.groups for child in children ): continue @@ -470,6 +351,7 @@ def reachable(self) -> List["PackageNode"]: PackageNode( pkg, self.packages, + self.seen, self, dependency, self.dep or dependency, @@ -495,14 +377,20 @@ def aggregate_package_nodes( ) -> Tuple[Package, int]: package = nodes[0].package depth = max(node.depth for node in nodes) + groups = [] + for node in nodes: + groups.extend(node.groups) + category = ( - "main" if any(node.category == "main" for node in children + nodes) else "dev" + "main" if any("default" in node.groups for node in children + nodes) else "dev" ) optional = all(node.optional for node in children + nodes) for node in nodes: node.depth = depth node.category = category node.optional = optional + package.category = category package.optional = optional + return package, depth diff --git a/poetry/puzzle/transaction.py b/poetry/puzzle/transaction.py new file mode 100644 index 00000000000..91983379c60 --- /dev/null +++ b/poetry/puzzle/transaction.py @@ -0,0 +1,113 @@ +from typing import TYPE_CHECKING +from typing import List +from typing import Optional +from typing import Tuple + + +if TYPE_CHECKING: + from poetry.core.packages.package import Package + from poetry.installation.operations import OperationTypes + + +class Transaction: + def __init__( + self, + current_packages: List["Package"], + result_packages: List[Tuple["Package", int]], + installed_packages: Optional[List["Package"]] = None, + root_package: Optional["Package"] = None, + ) -> None: + self._current_packages = current_packages + self._result_packages = result_packages + + if installed_packages is None: + installed_packages = [] + + self._installed_packages = installed_packages + self._root_package = root_package + + def calculate_operations( + self, with_uninstalls: bool = True, synchronize: bool = False + ) -> List["OperationTypes"]: + from poetry.installation.operations.install import Install + from poetry.installation.operations.uninstall import Uninstall + from poetry.installation.operations.update import Update + + operations = [] + + for result_package, priority in self._result_packages: + installed = False + + for installed_package in self._installed_packages: + if result_package.name == installed_package.name: + installed = True + + if result_package.version != installed_package.version: + operations.append( + Update(installed_package, result_package, priority=priority) + ) + elif ( + installed_package.source_type + or result_package.source_type != "legacy" + ) and not result_package.is_same_package_as(installed_package): + operations.append( + Update(installed_package, result_package, priority=priority) + ) + else: + operations.append( + Install(result_package).skip("Already installed") + ) + + break + + if not installed: + operations.append(Install(result_package, priority=priority)) + + if with_uninstalls: + for current_package in self._current_packages: + found = False + for result_package, _ in self._result_packages: + if current_package.name == result_package.name: + found = True + + break + + if not found: + for installed_package in self._installed_packages: + if installed_package.name == current_package.name: + operations.append(Uninstall(current_package)) + + if synchronize: + current_package_names = { + current_package.name for current_package in self._current_packages + } + # We preserve pip/setuptools/wheel when not managed by poetry, this is done + # to avoid externally managed virtual environments causing unnecessary + # removals. + preserved_package_names = { + "pip", + "setuptools", + "wheel", + } - current_package_names + + for installed_package in self._installed_packages: + if ( + self._root_package + and installed_package.name == self._root_package.name + ): + continue + + if installed_package.name in preserved_package_names: + continue + + if installed_package.name not in current_package_names: + operations.append(Uninstall(installed_package)) + + return sorted( + operations, + key=lambda o: ( + -o.priority, + o.package.name, + o.package.version, + ), + ) diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py index 841c0a5e610..f5adbe0ac7b 100644 --- a/poetry/repositories/installed_repository.py +++ b/poetry/repositories/installed_repository.py @@ -63,12 +63,13 @@ def get_package_paths(cls, env: Env, name: str) -> Set[Path]: if line and not line.startswith(("#", "import ", "import\t")): path = Path(line) if not path.is_absolute(): - try: - path = lib.joinpath(path).resolve() - except FileNotFoundError: - # this is required to handle pathlib oddity on win32 python==3.5 - path = lib.joinpath(path) + path = lib.joinpath(path).resolve() paths.add(path) + + src_path = env.path / "src" / name + if not paths and src_path.exists(): + paths.add(src_path) + return paths @classmethod diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index 60fd8c8b6fa..9922184adb4 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -34,7 +34,7 @@ from ..config.config import Config from ..inspection.info import PackageInfo -from ..installation.authenticator import Authenticator +from ..utils.authenticator import Authenticator from .exceptions import PackageNotFound from .exceptions import RepositoryError from .pypi_repository import PyPiRepository diff --git a/poetry/repositories/pool.py b/poetry/repositories/pool.py index b57ba155ec2..6658530b1bb 100644 --- a/poetry/repositories/pool.py +++ b/poetry/repositories/pool.py @@ -138,7 +138,7 @@ def package( except PackageNotFound: pass else: - for idx, repo in enumerate(self._repositories): + for repo in self._repositories: try: package = repo.package(name, version, extras=extras) except PackageNotFound: diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index 1c7b7be316a..5a440d70d36 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -341,7 +341,7 @@ def _get_info_from_urls(self, urls: Dict[str, List[str]]) -> "PackageInfo": # Checking wheels first as they are more likely to hold # the necessary information if "bdist_wheel" in urls: - # Check fo a universal wheel + # Check for a universal wheel wheels = urls["bdist_wheel"] universal_wheel = None diff --git a/poetry/installation/authenticator.py b/poetry/utils/authenticator.py similarity index 70% rename from poetry/installation/authenticator.py rename to poetry/utils/authenticator.py index 8c5e1d01222..363cf157fdc 100644 --- a/poetry/installation/authenticator.py +++ b/poetry/utils/authenticator.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING from typing import Any +from typing import Dict from typing import Optional from typing import Tuple @@ -108,7 +109,7 @@ def get_credentials_for_url(self, url: str) -> Tuple[Optional[str], Optional[str if credentials == (None, None): if "@" not in netloc: - credentials = self._get_credentials_for_netloc_from_config(netloc) + credentials = self._get_credentials_for_netloc(netloc) else: # Split from the right because that's how urllib.parse.urlsplit() # behaves if more than one @ is present (which can be checked using @@ -133,28 +134,73 @@ def get_credentials_for_url(self, url: str) -> Tuple[Optional[str], Optional[str return credentials[0], credentials[1] - def _get_credentials_for_netloc_from_config( + def get_pypi_token(self, name: str) -> str: + return self._password_manager.get_pypi_token(name) + + def get_http_auth(self, name: str) -> Optional[Dict[str, str]]: + return self._get_http_auth(name, None) + + def _get_http_auth( + self, name: str, netloc: Optional[str] + ) -> Optional[Dict[str, str]]: + if name == "pypi": + url = "https://upload.pypi.org/legacy/" + else: + url = self._config.get(f"repositories.{name}.url") + if not url: + return + + parsed_url = urllib.parse.urlsplit(url) + + if netloc is None or netloc == parsed_url.netloc: + auth = self._password_manager.get_http_auth(name) + + if auth is None or auth["password"] is None: + username = auth["username"] if auth else None + auth = self._get_credentials_for_netloc_from_keyring( + url, parsed_url.netloc, username + ) + + return auth + + def _get_credentials_for_netloc( self, netloc: str ) -> Tuple[Optional[str], Optional[str]]: credentials = (None, None) for repository_name in self._config.get("repositories", []): - repository_config = self._config.get(f"repositories.{repository_name}") - if not repository_config: - continue + auth = self._get_http_auth(repository_name, netloc) - url = repository_config.get("url") - if not url: + if auth is None: continue - parsed_url = urllib.parse.urlsplit(url) - - if netloc == parsed_url.netloc: - auth = self._password_manager.get_http_auth(repository_name) - - if auth is None: - continue - - return auth["username"], auth["password"] + return auth["username"], auth["password"] return credentials + + def _get_credentials_for_netloc_from_keyring( + self, url: str, netloc: str, username: Optional[str] + ) -> Optional[Dict[str, str]]: + import keyring + + cred = keyring.get_credential(url, username) + if cred is not None: + return { + "username": cred.username, + "password": cred.password, + } + + cred = keyring.get_credential(netloc, username) + if cred is not None: + return { + "username": cred.username, + "password": cred.password, + } + + if username: + return { + "username": username, + "password": None, + } + + return None diff --git a/poetry/utils/env.py b/poetry/utils/env.py index d35fb221313..e014c781688 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -109,7 +109,7 @@ def _version_nodot(version): "platform_version": platform.version(), "python_full_version": platform.python_version(), "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], + "python_version": ".".join(platform.python_version_tuple()[:2]), "sys_platform": sys.platform, "version_info": tuple(sys.version_info), # Extra information @@ -152,6 +152,38 @@ def _version_nodot(version): print(json.dumps(sysconfig.get_paths())) """ +GET_PATHS_FOR_GENERIC_ENVS = """\ +# We can't use sysconfig.get_paths() because +# on some distributions it does not return the proper paths +# (those used by pip for instance). We go through distutils +# to get the proper ones. +import json +import site +import sysconfig + +from distutils.command.install import SCHEME_KEYS # noqa +from distutils.core import Distribution + +d = Distribution() +d.parse_config_files() +obj = d.get_command_obj("install", create=True) +obj.finalize_options() + +paths = sysconfig.get_paths().copy() +for key in SCHEME_KEYS: + if key == "headers": + # headers is not a path returned by sysconfig.get_paths() + continue + + paths[key] = getattr(obj, f"install_{key}") + +if site.check_enableusersite() and hasattr(obj, "install_usersite"): + paths["usersite"] = getattr(obj, "install_usersite") + paths["userbase"] = getattr(obj, "install_userbase") + +print(json.dumps(paths)) +""" + class SitePackages: def __init__( @@ -590,7 +622,7 @@ def get(self, reload: bool = False) -> Union["VirtualEnv", "SystemEnv"]: create_venv = self._poetry.config.get("virtualenvs.create", True) if not create_venv: - return SystemEnv(Path(sys.prefix)) + return self.get_system_env() venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: @@ -603,7 +635,7 @@ def get(self, reload: bool = False) -> Union["VirtualEnv", "SystemEnv"]: venv = venv_path / name if not venv.exists(): - return SystemEnv(Path(sys.prefix)) + return self.get_system_env() return VirtualEnv(venv) @@ -730,7 +762,7 @@ def remove(self, python: str) -> "Env": self.remove_venv(venv) - return VirtualEnv(venv) + return VirtualEnv(venv, venv) def create_venv( self, @@ -872,7 +904,7 @@ def create_venv( "" ) - return SystemEnv(Path(sys.prefix)) + return self.get_system_env() io.write_line( "Creating virtualenv {} in {}".format(name, str(venv_path)) @@ -996,7 +1028,7 @@ def remove_venv(cls, path: Union[Path, str]) -> None: shutil.rmtree(str(file_path)) @classmethod - def get_system_env(cls, naive: bool = False) -> "SystemEnv": + def get_system_env(cls, naive: bool = False) -> Union["SystemEnv", "GenericEnv"]: """ Retrieve the current Python environment. @@ -1009,18 +1041,22 @@ def get_system_env(cls, naive: bool = False) -> "SystemEnv": want to retrieve Poetry's custom virtual environment (e.g. plugin installation or self update). """ - prefix, base_prefix = Path(sys.prefix), cls.get_base_prefix() - if naive is False: - from poetry.locations import data_dir - - try: - prefix.relative_to(data_dir()) - except ValueError: - pass + prefix, base_prefix = Path(sys.prefix), Path(cls.get_base_prefix()) + env = SystemEnv(prefix) + if not naive: + if prefix.joinpath("poetry_env").exists(): + env = GenericEnv(base_prefix, child_env=env) else: - prefix = base_prefix + from poetry.locations import data_dir + + try: + prefix.relative_to(data_dir()) + except ValueError: + pass + else: + env = GenericEnv(base_prefix, child_env=env) - return SystemEnv(prefix, base_prefix) + return env @classmethod def get_base_prefix(cls) -> Path: @@ -1049,7 +1085,7 @@ class Env: def __init__(self, path: Path, base: Optional[Path] = None) -> None: self._is_windows = sys.platform == "win32" - self._is_mingw = sysconfig.get_platform() == "mingw" + self._is_mingw = sysconfig.get_platform().startswith("mingw") if not self._is_windows or self._is_mingw: bin_dir = "bin" @@ -1058,6 +1094,11 @@ def __init__(self, path: Path, base: Optional[Path] = None) -> None: self._path = path self._bin_dir = self._path / bin_dir + self._executable = "python" + self._pip_executable = "pip" + + self.find_executables() + self._base = base or path self._marker_env = None @@ -1092,7 +1133,7 @@ def python(self) -> str: """ Path to current python executable """ - return self._bin("python") + return self._bin(self._executable) @property def marker_env(self) -> Dict[str, Any]: @@ -1101,6 +1142,39 @@ def marker_env(self) -> Dict[str, Any]: return self._marker_env + @property + def parent_env(self) -> "GenericEnv": + return GenericEnv(self.base, child_env=self) + + def find_executables(self) -> None: + python_executables = sorted( + [ + p.name + for p in self._bin_dir.glob("python*") + if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name) + ] + ) + if python_executables: + executable = python_executables[0] + if executable.endswith(".exe"): + executable = executable[:-4] + + self._executable = executable + + pip_executables = sorted( + [ + p.name + for p in self._bin_dir.glob("pip*") + if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name) + ] + ) + if pip_executables: + pip_executable = pip_executables[0] + if pip_executable.endswith(".exe"): + pip_executable = pip_executable[:-4] + + self._pip_executable = pip_executable + def get_embedded_wheel(self, distribution): return get_embed_wheel( distribution, "{}.{}".format(self.version_info[0], self.version_info[1]) @@ -1118,7 +1192,7 @@ def pip(self) -> str: Path to current pip executable """ # we do not use as_posix() here due to issues with windows pathlib2 implementation - path = self._bin("pip") + path = self._bin(self._pip_executable) if not Path(path).exists(): return str(self.pip_embedded) return path @@ -1264,7 +1338,7 @@ def run_pip(self, *args: str, **kwargs: Any) -> Union[int, str]: return self._run(cmd, **kwargs) def run_python_script(self, content: str, **kwargs: Any) -> str: - return self.run("python", "-W", "ignore", "-", input_=content, **kwargs) + return self.run(self._executable, "-W", "ignore", "-", input_=content, **kwargs) def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]: """ @@ -1331,7 +1405,11 @@ def _bin(self, bin: str) -> str: """ Return path to the given executable. """ - bin_path = (self._bin_dir / bin).with_suffix(".exe" if self._is_windows else "") + if self._is_windows and not bin.endswith(".exe"): + bin_path = self._bin_dir / (bin + ".exe") + else: + bin_path = self._bin_dir / bin + if not bin_path.exists(): # On Windows, some executables can be in the base path # This is especially true when installing Python with @@ -1342,7 +1420,11 @@ def _bin(self, bin: str) -> str: # that creates a fake virtual environment pointing to # a base Python install. if self._is_windows: - bin_path = (self._path / bin).with_suffix(".exe") + if not bin.endswith(".exe"): + bin_path = self._bin_dir / (bin + ".exe") + else: + bin_path = self._path / bin + if bin_path.exists(): return str(bin_path) @@ -1405,8 +1487,8 @@ def get_paths(self) -> Dict[str, str]: paths[key] = getattr(obj, f"install_{key}") if site.check_enableusersite() and hasattr(obj, "install_usersite"): - paths["usersite"] = getattr(obj, "install_usersite") - paths["userbase"] = getattr(obj, "install_userbase") + paths["usersite"] = obj.install_usersite + paths["userbase"] = obj.install_userbase return paths @@ -1486,7 +1568,10 @@ def get_python_implementation(self) -> str: def get_pip_command(self, embedded: bool = False) -> List[str]: # We're in a virtualenv that is known to be sane, # so assume that we have a functional pip - return [self._bin("python"), self.pip_embedded if embedded else self.pip] + return [ + self._bin(self._executable), + self.pip_embedded if embedded else self.pip, + ] def get_supported_tags(self) -> List[Tag]: file_path = Path(packaging.tags.__file__) @@ -1586,6 +1671,95 @@ def _updated_path(self) -> str: return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")]) +class GenericEnv(VirtualEnv): + def __init__( + self, path: Path, base: Optional[Path] = None, child_env: Optional["Env"] = None + ) -> None: + self._child_env = child_env + + super().__init__(path, base=base) + + def find_executables(self) -> None: + patterns = [("python*", "pip*")] + + if self._child_env: + minor_version = "{}.{}".format( + self._child_env.version_info[0], self._child_env.version_info[1] + ) + major_version = "{}".format(self._child_env.version_info[0]) + patterns = [ + ("python{}".format(minor_version), "pip{}".format(minor_version)), + ("python{}".format(major_version), "pip{}".format(major_version)), + ] + + python_executable = None + pip_executable = None + + for python_pattern, pip_pattern in patterns: + if python_executable and pip_executable: + break + + if not python_executable: + python_executables = sorted( + [ + p.name + for p in self._bin_dir.glob(python_pattern) + if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name) + ] + ) + + if python_executables: + executable = python_executables[0] + if executable.endswith(".exe"): + executable = executable[:-4] + + python_executable = executable + + if not pip_executable: + pip_executables = sorted( + [ + p.name + for p in self._bin_dir.glob(pip_pattern) + if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name) + ] + ) + if pip_executables: + pip_executable = pip_executables[0] + if pip_executable.endswith(".exe"): + pip_executable = pip_executable[:-4] + + pip_executable = pip_executable + + if python_executable: + self._executable = python_executable + + if pip_executable: + self._pip_executable = pip_executable + + def get_paths(self) -> Dict[str, str]: + output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS) + + return json.loads(output) + + def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]: + command = self.get_command_from_bin(bin) + list(args) + env = kwargs.pop("env", {k: v for k, v in os.environ.items()}) + + if not self._is_windows: + return os.execvpe(command[0], command, env=env) + else: + exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs) + exe.communicate() + + return exe.returncode + + def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]: + return super(VirtualEnv, self)._run(cmd, **kwargs) + + def is_venv(self) -> bool: + return self._path != self._base + + class NullEnv(SystemEnv): def __init__( self, path: Path = None, base: Optional[Path] = None, execute: bool = False @@ -1599,7 +1773,10 @@ def __init__( self.executed = [] def get_pip_command(self, embedded: bool = False) -> List[str]: - return [self._bin("python"), self.pip_embedded if embedded else self.pip] + return [ + self._bin(self._executable), + self.pip_embedded if embedded else self.pip, + ] def _run(self, cmd: List[str], **kwargs: Any) -> int: self.executed.append(cmd) diff --git a/poetry/utils/extras.py b/poetry/utils/extras.py index 37f04681eed..95d0fa51c3f 100644 --- a/poetry/utils/extras.py +++ b/poetry/utils/extras.py @@ -44,7 +44,7 @@ def get_extra_package_names( def _extra_packages(package_names: Iterable[str]) -> Iterator[str]: """Recursively find dependencies for packages names""" - # for each extra pacakge name + # for each extra package name for package_name in package_names: # Find the actual Package object. A missing key indicates an implicit # dependency (like setuptools), which should be ignored diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index d81d0f15b3d..714606ac3ec 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -82,7 +82,7 @@ def safe_rmtree(path: str) -> None: def merge_dicts(d1: Dict, d2: Dict) -> None: - for k, v in d2.items(): + for k in d2.keys(): if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): merge_dicts(d1[k], d2[k]) else: diff --git a/pyproject.toml b/pyproject.toml index 68f78a25a50..73ede4fd24a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "poetry" -version = "1.2.0a1" +version = "1.2.0a2" description = "Python dependency management and packaging made easy." authors = [ "Sébastien Eustace " @@ -31,8 +31,8 @@ generate-setup-file = false [tool.poetry.dependencies] python = "^3.6" -poetry-core = "~1.1.0a5" -cleo = "^1.0.0a1" +poetry-core = "^1.1.0a6" +cleo = "^1.0.0a4" crashtest = "^0.3.0" requests = "^2.18" cachy = "^0.3.0" @@ -44,8 +44,8 @@ shellingham = "^1.1" tomlkit = ">=0.7.0,<1.0.0" pexpect = "^4.7.0" packaging = "^20.4" -# temporarily clamped due to https://github.com/pypa/pip/issues/9953 -virtualenv = ">=20.4.3,<20.4.5" +# exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953 +virtualenv = "(>=20.4.3,<20.4.5 || >=20.4.7)" keyring = ">=21.2.0" entrypoints = "^0.3" importlib-metadata = {version = "^1.6.0", python = "<3.8"} diff --git a/sonnet b/sonnet index 090e31ecda2..aa91f132232 100755 --- a/sonnet +++ b/sonnet @@ -159,7 +159,7 @@ class MakeReleaseCommand(Command): fileobj=gz, format=tarfile.PAX_FORMAT, ) as tar: - for root, dirs, files in os.walk(tmp_dir): + for root, _dirs, files in os.walk(tmp_dir): for f in files: if f.endswith(".pyc"): continue @@ -230,8 +230,6 @@ class MakeReleaseCommand(Command): subprocess.check_output( [python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS ) - if version == "3.4" and WINDOWS: - continue subprocess.check_output([python, "-m", "pip", "install", "pip", "-U"]) except subprocess.CalledProcessError: diff --git a/tests/conftest.py b/tests/conftest.py index d66ed9ba659..0fa30a3a7b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ import pytest from cleo.testers.command_tester import CommandTester +from keyring.backend import KeyringBackend from poetry.config.config import Config as BaseConfig from poetry.config.dict_config_source import DictConfigSource @@ -53,6 +54,61 @@ def all(self) -> Dict[str, Any]: return super(Config, self).all() +class DummyBackend(KeyringBackend): + def __init__(self): + self._passwords = {} + + @classmethod + def priority(cls): + return 42 + + def set_password(self, service, username, password): + self._passwords[service] = {username: password} + + def get_password(self, service, username): + return self._passwords.get(service, {}).get(username) + + def get_credential(self, service, username): + return self._passwords.get(service, {}).get(username) + + def delete_password(self, service, username): + if service in self._passwords and username in self._passwords[service]: + del self._passwords[service][username] + + +@pytest.fixture() +def dummy_keyring(): + return DummyBackend() + + +@pytest.fixture() +def with_simple_keyring(dummy_keyring): + import keyring + + keyring.set_keyring(dummy_keyring) + + +@pytest.fixture() +def with_fail_keyring(): + import keyring + + from keyring.backends.fail import Keyring + + keyring.set_keyring(Keyring()) + + +@pytest.fixture() +def with_chained_keyring(mocker): + from keyring.backends.fail import Keyring + + mocker.patch("keyring.backend.get_all_keyring", [Keyring()]) + import keyring + + from keyring.backends.chainer import ChainerBackend + + keyring.set_keyring(ChainerBackend()) + + @pytest.fixture def config_cache_dir(tmp_dir): path = Path(tmp_dir) / ".cache" / "pypoetry" diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index b15a68d07a2..2944dcca733 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -5,11 +5,14 @@ from poetry.core.semver.version import Version -def build_venv(path: Union[Path, str], **_: Any) -> (): +VERSION_3_7_1 = Version.parse("3.7.1") + + +def build_venv(path: Union[Path, str], **_: Any) -> None: Path(path).mkdir(parents=True, exist_ok=True) -def check_output_wrapper(version=Version.parse("3.7.1")): +def check_output_wrapper(version=VERSION_3_7_1): def check_output(cmd, *_, **__): if "sys.version_info[:3]" in cmd: return version.text diff --git a/tests/console/commands/env/test_info.py b/tests/console/commands/env/test_info.py index 123835ba7b9..f57960af039 100644 --- a/tests/console/commands/env/test_info.py +++ b/tests/console/commands/env/test_info.py @@ -1,3 +1,5 @@ +import sys + from pathlib import Path import pytest @@ -28,14 +30,21 @@ def test_env_info_displays_complete_info(tester): Python: 3.7.0 Implementation: CPython Path: {prefix} +Executable: {executable} Valid: True System -Platform: darwin -OS: posix -Python: {base_prefix} +Platform: darwin +OS: posix +Python: {base_version} +Path: {base_prefix} +Executable: {base_executable} """.format( - prefix=str(Path("/prefix")), base_prefix=str(Path("/base/prefix")) + prefix=str(Path("/prefix")), + base_prefix=str(Path("/base/prefix")), + base_version=".".join(str(v) for v in sys.version_info[:3]), + executable=sys.executable, + base_executable="python", ) assert expected == tester.io.fetch_output() diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index 26955345457..802e806de7d 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -116,7 +116,7 @@ def test_add_constraint_with_extras(app, repo, tester): cachy1 = get_package("cachy", "0.1.0") cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) - cachy1.requires = [msgpack_dep] + cachy1.add_dependency(msgpack_dep) repo.add_package(get_package("cachy", "0.2.0")) repo.add_package(cachy1) @@ -144,7 +144,7 @@ def test_add_constraint_with_extras(app, repo, tester): def test_add_constraint_dependencies(app, repo, tester): cachy2 = get_package("cachy", "0.2.0") msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6") - cachy2.requires = [msgpack_dep] + cachy2.add_dependency(msgpack_dep) repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(cachy2) @@ -443,7 +443,7 @@ def test_add_constraint_with_extras_option(app, repo, tester): cachy2 = get_package("cachy", "0.2.0") cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) - cachy2.requires = [msgpack_dep] + cachy2.add_dependency(msgpack_dep) repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(cachy2) @@ -667,13 +667,52 @@ def test_add_constraint_not_found_with_source(app, poetry, mocker, tester): assert "Could not find a matching version of package cachy" == str(e.value) -def test_add_to_section_that_does_no_exist_yet(app, repo, tester): +def test_add_to_section_that_does_not_exist_yet(app, repo, tester): + repo.add_package(get_package("cachy", "0.1.0")) + repo.add_package(get_package("cachy", "0.2.0")) + + tester.execute("cachy --group dev") + + expected = """\ +Using version ^0.2.0 for cachy + +Updating dependencies +Resolving dependencies... + +Writing lock file + +Package operations: 1 install, 0 updates, 0 removals + + • Installing cachy (0.2.0) +""" + + assert expected == tester.io.fetch_output() + assert 1 == tester.command.installer.executor.installations_count + + content = app.poetry.file.read()["tool"]["poetry"] + + assert "cachy" in content["group"]["dev"]["dependencies"] + assert content["group"]["dev"]["dependencies"]["cachy"] == "^0.2.0" + + expected = """\ + +[tool.poetry.group.dev.dependencies] +cachy = "^0.2.0" + +""" + + assert expected in content.as_string() + + +def test_add_to_dev_section_deprecated(app, repo, tester): repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) tester.execute("cachy --dev") expected = """\ +The --dev option is deprecated, use the `--group dev` notation instead. + Using version ^0.2.0 for cachy Updating dependencies @@ -691,8 +730,8 @@ def test_add_to_section_that_does_no_exist_yet(app, repo, tester): content = app.poetry.file.read()["tool"]["poetry"] - assert "cachy" in content["dev-dependencies"] - assert content["dev-dependencies"]["cachy"] == "^0.2.0" + assert "cachy" in content["group"]["dev"]["dependencies"] + assert content["group"]["dev"]["dependencies"]["cachy"] == "^0.2.0" def test_add_should_not_select_prereleases(app, repo, tester): @@ -919,7 +958,7 @@ def test_add_constraint_with_extras_old_installer(app, repo, installer, old_test cachy1 = get_package("cachy", "0.1.0") cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) - cachy1.requires = [msgpack_dep] + cachy1.add_dependency(msgpack_dep) repo.add_package(get_package("cachy", "0.2.0")) repo.add_package(cachy1) @@ -948,7 +987,7 @@ def test_add_constraint_with_extras_old_installer(app, repo, installer, old_test def test_add_constraint_dependencies_old_installer(app, repo, installer, old_tester): cachy2 = get_package("cachy", "0.2.0") msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6") - cachy2.requires = [msgpack_dep] + cachy2.add_dependency(msgpack_dep) repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(cachy2) @@ -1245,7 +1284,7 @@ def test_add_constraint_with_extras_option_old_installer( cachy2 = get_package("cachy", "0.2.0") cachy2.extras = {"msgpack": [get_dependency("msgpack-python")]} msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True) - cachy2.requires = [msgpack_dep] + cachy2.add_dependency(msgpack_dep) repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(cachy2) @@ -1487,7 +1526,7 @@ def test_add_to_section_that_does_no_exist_yet_old_installer( repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(get_package("cachy", "0.2.0")) - old_tester.execute("cachy --dev") + old_tester.execute("cachy --group dev") expected = """\ Using version ^0.2.0 for cachy @@ -1508,8 +1547,8 @@ def test_add_to_section_that_does_no_exist_yet_old_installer( content = app.poetry.file.read()["tool"]["poetry"] - assert "cachy" in content["dev-dependencies"] - assert content["dev-dependencies"]["cachy"] == "^0.2.0" + assert "cachy" in content["group"]["dev"]["dependencies"] + assert content["group"]["dev"]["dependencies"]["cachy"] == "^0.2.0" def test_add_should_not_select_prereleases_old_installer( diff --git a/tests/console/commands/test_export.py b/tests/console/commands/test_export.py index f5b9fba4a7d..80b2f14f732 100644 --- a/tests/console/commands/test_export.py +++ b/tests/console/commands/test_export.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import pytest from tests.helpers import get_package @@ -77,12 +74,12 @@ def _export_requirements(tester, poetry): def test_export_exports_requirements_txt_file_locks_if_no_lock_file(tester, poetry): assert not poetry.locker.lock.exists() _export_requirements(tester, poetry) - assert "The lock file does not exist. Locking." in tester.io.fetch_output() + assert "The lock file does not exist. Locking." in tester.io.fetch_error() def test_export_exports_requirements_txt_uses_lock_file(tester, poetry, do_lock): _export_requirements(tester, poetry) - assert "The lock file does not exist. Locking." not in tester.io.fetch_output() + assert "The lock file does not exist. Locking." not in tester.io.fetch_error() def test_export_fails_on_invalid_format(tester, do_lock): diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 4ae13465399..5e6f766050e 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -10,7 +10,7 @@ from poetry.repositories import Pool from poetry.utils._compat import decode -from tests.helpers import TestApplication +from tests.helpers import PoetryTestApplication from tests.helpers import get_package @@ -36,7 +36,7 @@ def patches(mocker, source_dir, repo): @pytest.fixture def tester(patches): # we need a test application without poetry here. - app = TestApplication(None) + app = PoetryTestApplication(None) return CommandTester(app.find("init")) @@ -71,8 +71,6 @@ def init_basic_toml(): [tool.poetry.dependencies] python = "~2.7 || ^3.6" - -[tool.poetry.dev-dependencies] """ @@ -81,6 +79,28 @@ def test_basic_interactive(tester, init_basic_inputs, init_basic_toml): assert init_basic_toml in tester.io.fetch_output() +def test_noninteractive(app, mocker, poetry, repo, tmp_path): + command = app.find("init") + command._pool = poetry.pool + + repo.add_package(get_package("pytest", "3.6.0")) + + p = mocker.patch("pathlib.Path.cwd") + p.return_value = tmp_path + + tester = CommandTester(command) + args = "--name my-package --dependency pytest" + tester.execute(args=args, interactive=False) + + expected = "Using version ^3.6.0 for pytest\n" + assert tester.io.fetch_output() == expected + assert "" == tester.io.fetch_error() + + toml_content = (tmp_path / "pyproject.toml").read_text() + assert 'name = "my-package"' in toml_content + assert 'pytest = "^3.6.0"' in toml_content + + def test_interactive_with_dependencies(tester, repo): repo.add_package(get_package("django-pendulum", "0.1.6-pre4")) repo.add_package(get_package("pendulum", "2.0.0")) @@ -121,7 +141,7 @@ def test_interactive_with_dependencies(tester, repo): python = "~2.7 || ^3.6" pendulum = "^2.0.0" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -153,8 +173,6 @@ def test_empty_license(tester): [tool.poetry.dependencies] python = "^{python}" - -[tool.poetry.dev-dependencies] """.format( python=".".join(str(c) for c in sys.version_info[:2]) ) @@ -198,7 +216,7 @@ def test_interactive_with_git_dependencies(tester, repo): python = "~2.7 || ^3.6" demo = {git = "https://github.com/demo/demo.git"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -242,7 +260,7 @@ def test_interactive_with_git_dependencies_with_reference(tester, repo): python = "~2.7 || ^3.6" demo = {git = "https://github.com/demo/demo.git", rev = "develop"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -286,7 +304,7 @@ def test_interactive_with_git_dependencies_and_other_name(tester, repo): python = "~2.7 || ^3.6" demo = {git = "https://github.com/demo/pyproject-demo.git"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -333,7 +351,7 @@ def test_interactive_with_directory_dependency(tester, repo, source_dir, fixture python = "~2.7 || ^3.6" demo = {path = "demo"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ assert expected in tester.io.fetch_output() @@ -381,7 +399,7 @@ def test_interactive_with_directory_dependency_and_other_name( python = "~2.7 || ^3.6" demo = {path = "pyproject-demo"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -428,7 +446,7 @@ def test_interactive_with_file_dependency(tester, repo, source_dir, fixture_dir) python = "~2.7 || ^3.6" demo = {path = "demo-0.1.0-py2.py3-none-any.whl"} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -460,8 +478,6 @@ def test_python_option(tester): [tool.poetry.dependencies] python = "~2.7 || ^3.6" - -[tool.poetry.dev-dependencies] """ assert expected in tester.io.fetch_output() @@ -496,8 +512,6 @@ def test_predefined_dependency(tester, repo): [tool.poetry.dependencies] python = "~2.7 || ^3.6" pendulum = "^2.0.0" - -[tool.poetry.dev-dependencies] """ assert expected in tester.io.fetch_output() @@ -574,7 +588,7 @@ def test_predefined_dev_dependency(tester, repo): [tool.poetry.dependencies] python = "~2.7 || ^3.6" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" """ @@ -616,7 +630,7 @@ def test_predefined_and_interactive_dev_dependencies(tester, repo): [tool.poetry.dependencies] python = "~2.7 || ^3.6" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^3.6.0" pytest-requests = "^0.2.0" """ @@ -683,8 +697,6 @@ def test_init_non_interactive_existing_pyproject_add_dependency( [tool.poetry.dependencies] python = "^3.6" foo = "^1.19.2" - -[tool.poetry.dev-dependencies] """ assert "{}\n{}".format(existing_section, expected) in pyproject_file.read_text() diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py new file mode 100644 index 00000000000..ac6bd21d705 --- /dev/null +++ b/tests/console/commands/test_install.py @@ -0,0 +1,30 @@ +import pytest + + +@pytest.fixture +def tester(command_tester_factory): + return command_tester_factory("install") + + +def test_group_options_are_passed_to_the_installer(tester, mocker): + """ + Group options are passed properly to the installer. + """ + mocker.patch.object(tester.command.installer, "run", return_value=1) + + tester.execute("--with foo,bar --without baz --without bim --only bam") + + assert tester.command.installer._with_groups == ["foo", "bar"] + assert tester.command.installer._without_groups == ["baz", "bim"] + assert tester.command.installer._only_groups == ["bam"] + + +def test_sync_option_is_passed_to_the_installer(tester, mocker): + """ + The --sync option is passed properly to the installer. + """ + mocker.patch.object(tester.command.installer, "run", return_value=1) + + tester.execute("--sync") + + assert tester.command.installer._requires_synchronization diff --git a/tests/console/commands/test_publish.py b/tests/console/commands/test_publish.py index 62671eda624..66f5fe0c43f 100644 --- a/tests/console/commands/test_publish.py +++ b/tests/console/commands/test_publish.py @@ -1,5 +1,6 @@ from pathlib import Path +import pytest import requests from poetry.publishing.uploader import UploadError @@ -27,6 +28,7 @@ def test_publish_returns_non_zero_code_for_upload_errors(app, app_tester, http): assert expected_error_output in app_tester.io.fetch_error() +@pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") def test_publish_returns_non_zero_code_for_connection_errors(app, app_tester, http): def request_callback(*_, **__): raise requests.ConnectionError() diff --git a/tests/console/commands/test_remove.py b/tests/console/commands/test_remove.py index 7ccddb5140b..c8e5a2333df 100644 --- a/tests/console/commands/test_remove.py +++ b/tests/console/commands/test_remove.py @@ -1,6 +1,8 @@ import pytest +import tomlkit from poetry.core.packages.package import Package +from poetry.factory import Factory @pytest.fixture() @@ -8,6 +10,152 @@ def tester(command_tester_factory): return command_tester_factory("remove") +def test_remove_without_specific_group_removes_from_all_groups( + tester, app, repo, command_tester_factory, installed +): + """ + Removing without specifying a group removes packages from all groups. + """ + installed.add_package(Package("foo", "2.0.0")) + repo.add_package(Package("foo", "2.0.0")) + repo.add_package(Package("baz", "1.0.0")) + + content = app.poetry.file.read() + + groups_content = tomlkit.parse( + """\ +[tool.poetry.group.bar.dependencies] +foo = "^2.0.0" +baz = "^1.0.0" + +""" + ) + content["tool"]["poetry"]["dependencies"]["foo"] = "^2.0.0" + content["tool"]["poetry"].value._insert_after( + "dependencies", "group", groups_content["tool"]["poetry"]["group"] + ) + app.poetry.file.write(content) + + app.poetry.package.add_dependency(Factory.create_dependency("foo", "^2.0.0")) + app.poetry.package.add_dependency( + Factory.create_dependency("foo", "^2.0.0", groups=["bar"]) + ) + app.poetry.package.add_dependency( + Factory.create_dependency("baz", "^1.0.0", groups=["bar"]) + ) + + tester.execute("foo") + + content = app.poetry.file.read()["tool"]["poetry"] + assert "foo" not in content["dependencies"] + assert "foo" not in content["group"]["bar"]["dependencies"] + assert "baz" in content["group"]["bar"]["dependencies"] + + expected = """\ + +[tool.poetry.group.bar.dependencies] +baz = "^1.0.0" + +""" + + assert expected in content.as_string() + + +def test_remove_without_specific_group_removes_from_specific_groups( + tester, app, repo, command_tester_factory, installed +): + """ + Removing with a specific group given removes packages only from this group. + """ + installed.add_package(Package("foo", "2.0.0")) + repo.add_package(Package("foo", "2.0.0")) + repo.add_package(Package("baz", "1.0.0")) + + content = app.poetry.file.read() + + groups_content = tomlkit.parse( + """\ +[tool.poetry.group.bar.dependencies] +foo = "^2.0.0" +baz = "^1.0.0" + +""" + ) + content["tool"]["poetry"]["dependencies"]["foo"] = "^2.0.0" + content["tool"]["poetry"].value._insert_after( + "dependencies", "group", groups_content["tool"]["poetry"]["group"] + ) + app.poetry.file.write(content) + + app.poetry.package.add_dependency(Factory.create_dependency("foo", "^2.0.0")) + app.poetry.package.add_dependency( + Factory.create_dependency("foo", "^2.0.0", groups=["bar"]) + ) + app.poetry.package.add_dependency( + Factory.create_dependency("baz", "^1.0.0", groups=["bar"]) + ) + + tester.execute("foo --group bar") + + content = app.poetry.file.read()["tool"]["poetry"] + assert "foo" in content["dependencies"] + assert "foo" not in content["group"]["bar"]["dependencies"] + assert "baz" in content["group"]["bar"]["dependencies"] + + expected = """\ + +[tool.poetry.group.bar.dependencies] +baz = "^1.0.0" + +""" + + assert expected in content.as_string() + + +def test_remove_does_not_live_empty_groups( + tester, app, repo, command_tester_factory, installed +): + """ + Empty groups are automatically discarded after package removal. + """ + installed.add_package(Package("foo", "2.0.0")) + repo.add_package(Package("foo", "2.0.0")) + repo.add_package(Package("baz", "1.0.0")) + + content = app.poetry.file.read() + + groups_content = tomlkit.parse( + """\ +[tool.poetry.group.bar.dependencies] +foo = "^2.0.0" +baz = "^1.0.0" + +""" + ) + content["tool"]["poetry"]["dependencies"]["foo"] = "^2.0.0" + content["tool"]["poetry"].value._insert_after( + "dependencies", "group", groups_content["tool"]["poetry"]["group"] + ) + app.poetry.file.write(content) + + app.poetry.package.add_dependency(Factory.create_dependency("foo", "^2.0.0")) + app.poetry.package.add_dependency( + Factory.create_dependency("foo", "^2.0.0", groups=["bar"]) + ) + app.poetry.package.add_dependency( + Factory.create_dependency("baz", "^1.0.0", groups=["bar"]) + ) + + tester.execute("foo baz --group bar") + + content = app.poetry.file.read()["tool"]["poetry"] + assert "foo" in content["dependencies"] + assert "foo" not in content["group"]["bar"]["dependencies"] + assert "baz" not in content["group"]["bar"]["dependencies"] + assert "[tool.poetry.group.bar]" not in content.as_string() + assert "[tool.poetry.group]" not in content.as_string() + + def test_remove_command_should_not_write_changes_upon_installer_errors( tester, app, repo, command_tester_factory, mocker ): diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index 2c69d3a3667..4cd0378bfe6 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -1,5 +1,6 @@ import pytest +from poetry.core.packages.dependency_group import DependencyGroup from poetry.factory import Factory from tests.helpers import get_package @@ -13,7 +14,7 @@ def test_show_basic_with_installed_packages(tester, poetry, installed): poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) poetry.package.add_dependency( - Factory.create_dependency("pytest", "^3.7.3", category="dev") + Factory.create_dependency("pytest", "^3.7.3", groups=["dev"]) ) cachy_010 = get_package("cachy", "0.1.0") @@ -952,7 +953,7 @@ def test_show_outdated_no_dev_git_dev_dependency(tester, poetry, installed, repo } ) - tester.execute("--outdated --no-dev") + tester.execute("--outdated --without dev") expected = """\ cachy 0.1.0 0.2.0 Cachy package @@ -1071,6 +1072,12 @@ def test_show_all_shows_incompatible_package(tester, poetry, installed, repo): def test_show_non_dev_with_basic_installed_packages(tester, poetry, installed): + poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) + poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) + poetry.package.add_dependency( + Factory.create_dependency("pytest", "*", groups=["dev"]) + ) + cachy_010 = get_package("cachy", "0.1.0") cachy_010.description = "Cachy package" @@ -1128,7 +1135,7 @@ def test_show_non_dev_with_basic_installed_packages(tester, poetry, installed): } ) - tester.execute("--no-dev") + tester.execute("--without dev") expected = """\ cachy 0.1.0 Cachy package @@ -1138,6 +1145,163 @@ def test_show_non_dev_with_basic_installed_packages(tester, poetry, installed): assert expected == tester.io.fetch_output() +def test_show_with_group_only(tester, poetry, installed): + poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) + poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) + poetry.package.add_dependency( + Factory.create_dependency("pytest", "*", groups=["dev"]) + ) + + cachy_010 = get_package("cachy", "0.1.0") + cachy_010.description = "Cachy package" + + pendulum_200 = get_package("pendulum", "2.0.0") + pendulum_200.description = "Pendulum package" + + pytest_373 = get_package("pytest", "3.7.3") + pytest_373.description = "Pytest package" + pytest_373.category = "dev" + + installed.add_package(cachy_010) + installed.add_package(pendulum_200) + installed.add_package(pytest_373) + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "category": "dev", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"cachy": [], "pendulum": [], "pytest": []}, + }, + } + ) + + tester.execute("--only dev") + + expected = """\ +pytest 3.7.3 Pytest package +""" + + assert expected == tester.io.fetch_output() + + +def test_show_with_optional_group(tester, poetry, installed): + poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) + poetry.package.add_dependency(Factory.create_dependency("pendulum", "^2.0.0")) + group = DependencyGroup("dev", optional=True) + group.add_dependency(Factory.create_dependency("pytest", "*", groups=["dev"])) + poetry.package.add_dependency_group(group) + + cachy_010 = get_package("cachy", "0.1.0") + cachy_010.description = "Cachy package" + + pendulum_200 = get_package("pendulum", "2.0.0") + pendulum_200.description = "Pendulum package" + + pytest_373 = get_package("pytest", "3.7.3") + pytest_373.description = "Pytest package" + pytest_373.category = "dev" + + installed.add_package(cachy_010) + installed.add_package(pendulum_200) + installed.add_package(pytest_373) + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "category": "dev", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"cachy": [], "pendulum": [], "pytest": []}, + }, + } + ) + + tester.execute() + + expected = """\ +cachy 0.1.0 Cachy package +pendulum 2.0.0 Pendulum package +""" + + assert expected == tester.io.fetch_output() + + tester.execute("--with dev") + + expected = """\ +cachy 0.1.0 Cachy package +pendulum 2.0.0 Pendulum package +pytest 3.7.3 Pytest package +""" + + assert expected == tester.io.fetch_output() + + def test_show_tree(tester, poetry, installed): poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) @@ -1193,7 +1357,7 @@ def test_show_tree(tester, poetry, installed): def test_show_tree_no_dev(tester, poetry, installed): poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) poetry.package.add_dependency( - Factory.create_dependency("pytest", "^6.1.0", category="dev") + Factory.create_dependency("pytest", "^6.1.0", groups=["dev"]) ) cachy2 = get_package("cachy", "0.2.0") @@ -1247,7 +1411,7 @@ def test_show_tree_no_dev(tester, poetry, installed): } ) - tester.execute("--tree --no-dev") + tester.execute("--tree --without dev") expected = """\ cachy 0.2.0 @@ -1255,3 +1419,78 @@ def test_show_tree_no_dev(tester, poetry, installed): """ assert expected == tester.io.fetch_output() + + +def test_show_required_by_deps(tester, poetry, installed): + poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0")) + poetry.package.add_dependency(Factory.create_dependency("pendulum", "2.0.0")) + + cachy2 = get_package("cachy", "0.2.0") + cachy2.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) + + pendulum = get_package("pendulum", "2.0.0") + pendulum.add_dependency(Factory.create_dependency("CachY", "^0.2.0")) + + installed.add_package(cachy2) + installed.add_package(pendulum) + + poetry.locker.mock_lock_data( + { + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": {"msgpack-python": ">=0.5 <0.6"}, + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": {"cachy": ">=0.2.0 <0.3.0"}, + }, + { + "name": "msgpack-python", + "version": "0.5.1", + "description": "", + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"cachy": [], "pendulum": [], "msgpack-python": []}, + }, + } + ) + + tester.execute("cachy") + + expected = """\ + name : cachy + version : 0.2.0 + description : + +dependencies + - msgpack-python >=0.5 <0.6 + +required by + - pendulum >=0.2.0 <0.3.0 +""".splitlines() + actual = [line.rstrip() for line in tester.io.fetch_output().splitlines()] + assert actual == expected diff --git a/tests/console/conftest.py b/tests/console/conftest.py index 82143358d4d..49dfab97c20 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -11,7 +11,7 @@ from poetry.installation.noop_installer import NoopInstaller from poetry.repositories import Pool from poetry.utils.env import MockEnv -from tests.helpers import TestApplication +from tests.helpers import PoetryTestApplication from tests.helpers import TestExecutor from tests.helpers import TestLocker from tests.helpers import mock_clone @@ -97,7 +97,7 @@ def poetry(repo, project_directory, config): @pytest.fixture def app(poetry): - app_ = TestApplication(poetry) + app_ = PoetryTestApplication(poetry) return app_ diff --git a/tests/fixtures/directory/project_with_transitive_directory_dependencies/setup.py b/tests/fixtures/directory/project_with_transitive_directory_dependencies/setup.py index 24a8f05be9f..cfce0806c30 100644 --- a/tests/fixtures/directory/project_with_transitive_directory_dependencies/setup.py +++ b/tests/fixtures/directory/project_with_transitive_directory_dependencies/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from distutils.core import setup packages = ["project_with_extras"] diff --git a/tests/fixtures/git/github.com/demo/demo/setup.py b/tests/fixtures/git/github.com/demo/demo/setup.py index faebbc83748..b6f6d0c1f8c 100644 --- a/tests/fixtures/git/github.com/demo/demo/setup.py +++ b/tests/fixtures/git/github.com/demo/demo/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from setuptools import setup diff --git a/tests/fixtures/git/github.com/demo/no-dependencies/setup.py b/tests/fixtures/git/github.com/demo/no-dependencies/setup.py index da86b53b2bb..099c69cf976 100644 --- a/tests/fixtures/git/github.com/demo/no-dependencies/setup.py +++ b/tests/fixtures/git/github.com/demo/no-dependencies/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from setuptools import setup diff --git a/tests/fixtures/git/github.com/demo/no-version/setup.py b/tests/fixtures/git/github.com/demo/no-version/setup.py index 4e1aea3034c..d14b308cb23 100644 --- a/tests/fixtures/git/github.com/demo/no-version/setup.py +++ b/tests/fixtures/git/github.com/demo/no-version/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import ast import os diff --git a/tests/fixtures/git/github.com/demo/non-canonical-name/setup.py b/tests/fixtures/git/github.com/demo/non-canonical-name/setup.py index 3e6da62e986..8004332d6d9 100644 --- a/tests/fixtures/git/github.com/demo/non-canonical-name/setup.py +++ b/tests/fixtures/git/github.com/demo/non-canonical-name/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from setuptools import setup diff --git a/tests/fixtures/project_with_setup/setup.py b/tests/fixtures/project_with_setup/setup.py index 0f9e0d095a0..c329347f9bb 100644 --- a/tests/fixtures/project_with_setup/setup.py +++ b/tests/fixtures/project_with_setup/setup.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from setuptools import setup diff --git a/tests/fixtures/sample_project/pyproject.toml b/tests/fixtures/sample_project/pyproject.toml index aff10e12c04..5a5c1356b52 100644 --- a/tests/fixtures/sample_project/pyproject.toml +++ b/tests/fixtures/sample_project/pyproject.toml @@ -46,7 +46,7 @@ functools32 = { version = "^3.2.3", markers = "python_version ~= '2.7' and sys_p [tool.poetry.extras] db = [ "orator" ] -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "~3.4" diff --git a/tests/fixtures/up_to_date_lock/poetry.lock b/tests/fixtures/up_to_date_lock/poetry.lock index a896b5d0621..918155a4d3b 100644 --- a/tests/fixtures/up_to_date_lock/poetry.lock +++ b/tests/fixtures/up_to_date_lock/poetry.lock @@ -101,7 +101,7 @@ six = "*" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "0cd068218f235c162f7b74bc8faf4ce3387b82daee1c1bb7a97af034f27ee116" +content-hash = "ae61bd854548e88c090780099edd400d58e6944ce9f3fc086d2f9aa5ac487f14" [metadata.files] certifi = [ diff --git a/tests/helpers.py b/tests/helpers.py index f1bafd42700..536b7681ba0 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -7,7 +7,6 @@ from poetry.console.application import Application from poetry.core.masonry.utils.helpers import escape_name from poetry.core.masonry.utils.helpers import escape_version -from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link from poetry.core.toml.file import TOMLFile @@ -28,15 +27,18 @@ def get_package(name, version): def get_dependency( - name, constraint=None, category="main", optional=False, allows_prereleases=False + name, constraint=None, groups=None, optional=False, allows_prereleases=False ): - return Dependency( - name, - constraint or "*", - category=category, - optional=optional, - allows_prereleases=allows_prereleases, - ) + if constraint is None: + constraint = "*" + + if isinstance(constraint, str): + constraint = {"version": constraint} + + constraint["optional"] = optional + constraint["allow_prereleases"] = allows_prereleases + + return Factory.create_dependency(name, constraint or "*", groups=groups) def fixture(path=None): @@ -130,9 +132,9 @@ def _execute_remove(self, operation): return 0 -class TestApplication(Application): +class PoetryTestApplication(Application): def __init__(self, poetry): - super(TestApplication, self).__init__() + super(PoetryTestApplication, self).__init__() self._poetry = poetry def reset_poetry(self): diff --git a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test index 7177191e2c8..fb10b1accea 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test @@ -65,8 +65,8 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -project-with-extras = "1.2.3" -project-with-transitive-file-dependencies = "1.2.3" +project-with-extras = { "path" = "../../project_with_extras" } +project-with-transitive-file-dependencies = { "path" = "../project_with_transitive_file_dependencies" } [package.source] type = "directory" @@ -82,8 +82,8 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -demo = "0.1.0" -inner-directory-project = "1.2.4" +demo = { "path" = "../../distributions/demo-0.1.0-py2.py3-none-any.whl" } +inner-directory-project = { "path" = "inner-directory-project" } [package.source] type = "directory" diff --git a/tests/installation/fixtures/with-file-dependency-transitive.test b/tests/installation/fixtures/with-file-dependency-transitive.test index 6e5d92d711a..b882f262640 100644 --- a/tests/installation/fixtures/with-file-dependency-transitive.test +++ b/tests/installation/fixtures/with-file-dependency-transitive.test @@ -48,8 +48,8 @@ python-versions = "*" version = "1.2.3" [package.dependencies] -demo = "0.1.0" -inner-directory-project = "1.2.4" +demo = { "path" = "../../distributions/demo-0.1.0-py2.py3-none-any.whl" } +inner-directory-project = { "path" = "inner-directory-project" } [package.source] type = "directory" diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 9b7b413b92b..de3945661fd 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json import re import shutil diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 00986905984..7625f07df2f 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1,8 +1,5 @@ -from __future__ import unicode_literals - import itertools import json -import sys from pathlib import Path @@ -15,6 +12,7 @@ from cleo.io.outputs.output import Verbosity from deepdiff import DeepDiff +from poetry.core.packages.dependency_group import DependencyGroup from poetry.core.packages.package import Package from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile @@ -277,7 +275,9 @@ def test_run_update_after_removing_dependencies( assert 1 == installer.executor.removals_count -def _configure_run_install_dev(locker, repo, package, installed): +def _configure_run_install_dev( + locker, repo, package, installed, with_optional_group=False +): """ Perform common test setup for `test_run_install_*dev*()` methods. """ @@ -334,24 +334,174 @@ def _configure_run_install_dev(locker, repo, package, installed): package.add_dependency(Factory.create_dependency("A", "~1.0")) package.add_dependency(Factory.create_dependency("B", "~1.1")) - package.add_dependency(Factory.create_dependency("C", "~1.2", category="dev")) + group = DependencyGroup("dev", optional=with_optional_group) + group.add_dependency(Factory.create_dependency("C", "~1.2", groups=["dev"])) + package.add_dependency_group(group) -def test_run_install_no_dev(installer, locker, repo, package, installed): + +def test_run_install_no_group(installer, locker, repo, package, installed): _configure_run_install_dev(locker, repo, package, installed) - installer.dev_mode(False) + installer.without_groups(["dev"]) installer.run() assert 0 == installer.executor.installations_count assert 0 == installer.executor.updates_count - assert 1 == installer.executor.removals_count + assert 0 == installer.executor.removals_count -def test_run_install_dev_only(installer, locker, repo, package, installed): +def test_run_install_group_only(installer, locker, repo, package, installed): _configure_run_install_dev(locker, repo, package, installed) - installer.dev_only(True) + installer.only_groups(["dev"]) + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 0 == installer.executor.removals_count + + +def test_run_install_with_optional_group_not_selected( + installer, locker, repo, package, installed +): + _configure_run_install_dev( + locker, repo, package, installed, with_optional_group=True + ) + + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 0 == installer.executor.removals_count + + +def test_run_install_does_not_remove_locked_packages_if_installed_but_not_required( + installer, locker, repo, package, installed +): + package_a = get_package("a", "1.0") + package_b = get_package("b", "1.1") + package_c = get_package("c", "1.2") + + repo.add_package(package_a) + installed.add_package(package_a) + repo.add_package(package_b) + installed.add_package(package_b) + repo.add_package(package_c) + installed.add_package(package_c) + + installed.add_package(package) # Root package never removed. + + package.add_dependency(Factory.create_dependency(package_a.name, package_a.version)) + + locker.locked(True) + locker.mock_lock_data( + { + "package": [ + { + "name": package_a.name, + "version": package_a.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": package_b.name, + "version": package_b.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": package_c.name, + "version": package_c.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {package_a.name: [], package_b.name: [], package_c.name: []}, + }, + } + ) + + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 0 == installer.executor.removals_count + + +def test_run_install_removes_locked_packages_if_installed_and_synchronization_is_required( + installer, locker, repo, package, installed +): + package_a = get_package("a", "1.0") + package_b = get_package("b", "1.1") + package_c = get_package("c", "1.2") + + repo.add_package(package_a) + installed.add_package(package_a) + repo.add_package(package_b) + installed.add_package(package_b) + repo.add_package(package_c) + installed.add_package(package_c) + + installed.add_package(package) # Root package never removed. + + package.add_dependency(Factory.create_dependency(package_a.name, package_a.version)) + + locker.locked(True) + locker.mock_lock_data( + { + "package": [ + { + "name": package_a.name, + "version": package_a.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": package_b.name, + "version": package_b.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": package_c.name, + "version": package_c.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {package_a.name: [], package_b.name: [], package_c.name: []}, + }, + } + ) + + installer.requires_synchronization(True) installer.run() assert 0 == installer.executor.installations_count @@ -359,16 +509,86 @@ def test_run_install_dev_only(installer, locker, repo, package, installed): assert 2 == installer.executor.removals_count -def test_run_install_no_dev_and_dev_only(installer, locker, repo, package, installed): - _configure_run_install_dev(locker, repo, package, installed) +def test_run_install_removes_no_longer_locked_packages_if_installed( + installer, locker, repo, package, installed +): + package_a = get_package("a", "1.0") + package_b = get_package("b", "1.1") + package_c = get_package("c", "1.2") + + repo.add_package(package_a) + installed.add_package(package_a) + repo.add_package(package_b) + installed.add_package(package_b) + repo.add_package(package_c) + installed.add_package(package_c) + + installed.add_package(package) # Root package never removed. + + package.add_dependency(Factory.create_dependency(package_a.name, package_a.version)) + + locker.locked(True) + locker.mock_lock_data( + { + "package": [ + { + "name": package_a.name, + "version": package_a.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": package_b.name, + "version": package_b.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": package_c.name, + "version": package_c.version.text, + "category": "main", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {package_a.name: [], package_b.name: [], package_c.name: []}, + }, + } + ) - installer.dev_mode(False) - installer.dev_only(True) + installer.update(True) installer.run() assert 0 == installer.executor.installations_count assert 0 == installer.executor.updates_count - assert 1 == installer.executor.removals_count + assert 2 == installer.executor.removals_count + + +def test_run_install_with_optional_group_selected( + installer, locker, repo, package, installed +): + _configure_run_install_dev( + locker, repo, package, installed, with_optional_group=True + ) + + installer.with_groups(["dev"]) + installer.run() + + assert 0 == installer.executor.installations_count + assert 0 == installer.executor.updates_count + assert 0 == installer.executor.removals_count @pytest.mark.parametrize( @@ -383,7 +603,7 @@ def test_run_install_no_dev_and_dev_only(installer, locker, repo, package, insta ) ], ) -def test_run_install_remove_untracked( +def test_run_install_with_synchronization( managed_reserved_package_names, installer, locker, repo, package, installed ): package_a = get_package("a", "1.0") @@ -439,7 +659,7 @@ def test_run_install_remove_untracked( } ) - installer.dev_mode(True).remove_untracked(True) + installer.requires_synchronization(True) installer.run() assert 0 == installer.executor.installations_count @@ -863,7 +1083,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): NullIO(), NullEnv(), package, locker, pool, config, installed=installed ) - package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev")) + package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"])) installer.run() expected = fixture("with-pypi-repository") @@ -1069,7 +1289,7 @@ def test_run_changes_category_if_needed(installer, locker, repo, package): package.add_dependency( Factory.create_dependency( - "A", {"version": "^1.0", "optional": True}, category="dev" + "A", {"version": "^1.0", "optional": True}, groups=["dev"] ) ) package.add_dependency(Factory.create_dependency("B", "^1.1")) @@ -1169,8 +1389,8 @@ def test_run_update_with_locked_extras(installer, locker, repo, package): b_dependency.in_extras.append("foo") c_dependency = get_dependency("C", "^1.0") c_dependency.python_versions = "~2.7" - package_a.requires.append(b_dependency) - package_a.requires.append(c_dependency) + package_a.add_dependency(b_dependency) + package_a.add_dependency(c_dependency) repo.add_package(package_a) repo.add_package(get_package("B", "1.0")) @@ -1528,11 +1748,7 @@ def test_installer_test_solver_finds_compatible_package_for_dependency_python_no expected = fixture("with-conditional-dependency") assert locker.written_data == expected - - if sys.version_info >= (3, 5, 0): - assert 1 == installer.executor.installations_count - else: - assert 0 == installer.executor.installations_count + assert 1 == installer.executor.installations_count def test_installer_required_extras_should_not_be_removed_when_updating_single_dependency( @@ -1869,7 +2085,7 @@ def test_installer_can_handle_old_lock_files( pool = Pool() pool.add_repository(MockRepository()) - package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev")) + package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"])) locker.locked() locker.mock_lock_data(fixture("old-lock")) diff --git a/tests/installation/test_installer_old.py b/tests/installation/test_installer_old.py index 20936fe5f09..c4a41c34ad5 100644 --- a/tests/installation/test_installer_old.py +++ b/tests/installation/test_installer_old.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - import itertools -import sys from pathlib import Path @@ -227,7 +224,7 @@ def test_run_update_after_removing_dependencies( assert len(removals) == 1 -def test_run_install_no_dev(installer, locker, repo, package, installed): +def test_run_install_no_group(installer, locker, repo, package, installed): locker.locked(True) locker.mock_lock_data( { @@ -281,9 +278,9 @@ def test_run_install_no_dev(installer, locker, repo, package, installed): package.add_dependency(Factory.create_dependency("A", "~1.0")) package.add_dependency(Factory.create_dependency("B", "~1.1")) - package.add_dependency(Factory.create_dependency("C", "~1.2", category="dev")) + package.add_dependency(Factory.create_dependency("C", "~1.2", groups=["dev"])) - installer.dev_mode(False) + installer.without_groups(["dev"]) installer.run() installs = installer.installer.installs @@ -293,7 +290,7 @@ def test_run_install_no_dev(installer, locker, repo, package, installed): assert len(updates) == 0 removals = installer.installer.removals - assert len(removals) == 1 + assert len(removals) == 0 @pytest.mark.parametrize( @@ -308,7 +305,7 @@ def test_run_install_no_dev(installer, locker, repo, package, installed): ) ], ) -def test_run_install_remove_untracked( +def test_run_install_with_synchronization( managed_reserved_package_names, installer, locker, repo, package, installed ): package_a = get_package("a", "1.0") @@ -364,7 +361,7 @@ def test_run_install_remove_untracked( } ) - installer.dev_mode(True).remove_untracked(True) + installer.requires_synchronization(True) installer.run() installs = installer.installer.installs @@ -374,6 +371,7 @@ def test_run_install_remove_untracked( assert len(updates) == 0 removals = installer.installer.removals + expected_removals = { package_b.name, package_c.name, @@ -767,7 +765,7 @@ def test_installer_with_pypi_repository(package, locker, installed, config): NullIO(), NullEnv(), package, locker, pool, config, installed=installed ) - package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev")) + package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"])) installer.run() expected = fixture("with-pypi-repository") @@ -974,7 +972,7 @@ def test_run_changes_category_if_needed(installer, locker, repo, package): package.add_dependency( Factory.create_dependency( - "A", {"version": "^1.0", "optional": True}, category="dev" + "A", {"version": "^1.0", "optional": True}, groups=["dev"] ) ) package.add_dependency(Factory.create_dependency("B", "^1.1")) @@ -1074,8 +1072,8 @@ def test_run_update_with_locked_extras(installer, locker, repo, package): b_dependency.in_extras.append("foo") c_dependency = get_dependency("C", "^1.0") c_dependency.python_versions = "~2.7" - package_a.requires.append(b_dependency) - package_a.requires.append(c_dependency) + package_a.add_dependency(b_dependency) + package_a.add_dependency(c_dependency) repo.add_package(package_a) repo.add_package(get_package("B", "1.0")) @@ -1446,11 +1444,7 @@ def test_installer_test_solver_finds_compatible_package_for_dependency_python_no assert locker.written_data == expected installs = installer.installer.installs - - if sys.version_info >= (3, 5, 0): - assert len(installs) == 1 - else: - assert len(installs) == 0 + assert len(installs) == 1 def test_installer_required_extras_should_not_be_removed_when_updating_single_dependency( @@ -1739,7 +1733,7 @@ def test_installer_can_handle_old_lock_files( pool = Pool() pool.add_repository(MockRepository()) - package.add_dependency(Factory.create_dependency("pytest", "^3.5", category="dev")) + package.add_dependency(Factory.create_dependency("pytest", "^3.5", groups=["dev"])) locker.locked() locker.mock_lock_data(fixture("old-lock")) diff --git a/tests/installation/test_pip_installer.py b/tests/installation/test_pip_installer.py index 20fc9d0a9d3..3ac26be4b38 100644 --- a/tests/installation/test_pip_installer.py +++ b/tests/installation/test_pip_installer.py @@ -65,7 +65,7 @@ def test_requirement_source_type_url(): "foo", "0.0.0", source_type="url", - source_url="https://somehwere.com/releases/foo-1.0.0.tar.gz", + source_url="https://somewhere.com/releases/foo-1.0.0.tar.gz", ) result = installer.requirement(foo, formatted=True) diff --git a/tests/masonry/builders/test_editable_builder.py b/tests/masonry/builders/test_editable_builder.py index 6fb0a07a445..4cc2c6c8c6a 100644 --- a/tests/masonry/builders/test_editable_builder.py +++ b/tests/masonry/builders/test_editable_builder.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import shutil @@ -148,10 +145,11 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ baz_script = """\ #!{python} +import sys from bar import baz if __name__ == '__main__': - baz.boom.bim() + sys.exit(baz.boom.bim()) """.format( python=tmp_venv.python ) @@ -160,10 +158,11 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ foo_script = """\ #!{python} +import sys from foo import bar if __name__ == '__main__': - bar() + sys.exit(bar()) """.format( python=tmp_venv.python ) @@ -172,10 +171,11 @@ def test_builder_installs_proper_files_for_standard_packages(simple_poetry, tmp_ fox_script = """\ #!{python} +import sys from fuz.foo import bar if __name__ == '__main__': - bar.baz() + sys.exit(bar.baz()) """.format( python=tmp_venv.python ) diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 285b91a8788..8b8a1d99d46 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -1,6 +1,8 @@ import logging import tempfile +from pathlib import Path + import pytest import tomlkit @@ -84,7 +86,7 @@ def test_lock_file_data_is_ordered(locker, root): [metadata] lock-version = "1.1" python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [ @@ -293,7 +295,7 @@ def test_lock_packages_with_null_description(locker, root): [metadata] lock-version = "1.1" python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [] @@ -333,7 +335,7 @@ def test_lock_file_should_not_have_mixed_types(locker, root): [metadata] lock-version = "1.1" python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [] @@ -346,7 +348,7 @@ def test_lock_file_should_not_have_mixed_types(locker, root): def test_reading_lock_file_should_raise_an_error_on_invalid_data(locker): - content = u"""[[package]] + content = """[[package]] name = "A" version = "1.0.0" description = "" @@ -363,7 +365,7 @@ def test_reading_lock_file_should_raise_an_error_on_invalid_data(locker): [metadata] lock-version = "1.1" python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [] @@ -408,7 +410,7 @@ def test_locking_legacy_repository_package_should_include_source_section(root, l [metadata] lock-version = "1.1" python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [] @@ -495,7 +497,7 @@ def test_extras_dependencies_are_ordered(locker, root): [metadata] lock-version = "1.1" python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [] @@ -531,3 +533,112 @@ def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatibl _ = locker.lock_data assert 0 == len(caplog.records) + + +def test_locker_dumps_dependency_information_correctly(locker, root): + root_dir = Path(__file__).parent.parent.joinpath("fixtures") + package_a = get_package("A", "1.0.0") + package_a.add_dependency( + Factory.create_dependency( + "B", {"path": "project_with_extras", "develop": True}, root_dir=root_dir + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "C", + {"path": "directory/project_with_transitive_directory_dependencies"}, + root_dir=root_dir, + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "D", {"path": "distributions/demo-0.1.0.tar.gz"}, root_dir=root_dir + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "E", {"url": "https://python-poetry.org/poetry-1.2.0.tar.gz"} + ) + ) + package_a.add_dependency( + Factory.create_dependency( + "F", {"git": "https://github.com/python-poetry/poetry.git", "branch": "foo"} + ) + ) + + packages = [package_a] + + locker.set_lock_data(root, packages) + + with locker.lock.open(encoding="utf-8") as f: + content = f.read() + + expected = """[[package]] +name = "A" +version = "1.0.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +B = {path = "project_with_extras", develop = true} +C = {path = "directory/project_with_transitive_directory_dependencies"} +D = {path = "distributions/demo-0.1.0.tar.gz"} +E = {url = "https://python-poetry.org/poetry-1.2.0.tar.gz"} +F = {git = "https://github.com/python-poetry/poetry.git", branch = "foo"} + +[metadata] +lock-version = "1.1" +python-versions = "*" +content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" + +[metadata.files] +A = [] +""" + + assert expected == content + + +def test_locked_repository_uses_root_dir_of_package(locker, mocker): + content = """\ +[[package]] +name = "lib-a" +version = "0.1.0" +description = "" +category = "main" +optional = false +python-versions = "^2.7.9" +develop = true + +[package.dependencies] +lib-b = {path = "../libB", develop = true} + +[package.source] +type = "directory" +url = "lib/libA" + +[metadata] +lock-version = "1.1" +python-versions = "*" +content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" + +[metadata.files] +lib-a = [] +lib-b = [] +""" + + locker.lock.write(tomlkit.parse(content)) + create_dependency_patch = mocker.patch( + "poetry.factory.Factory.create_dependency", autospec=True + ) + locker.locked_repository() + + create_dependency_patch.assert_called_once_with( + "lib-b", {"develop": True, "path": "../libB"}, root_dir=mocker.ANY + ) + call_kwargs = create_dependency_patch.call_args[1] + root_dir = call_kwargs["root_dir"] + assert root_dir.match("*/lib/libA") + # relative_to raises an exception if not relative - is_relative_to comes in py3.9 + assert root_dir.relative_to(locker.lock.path.parent.resolve()) is not None diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index dd193096d24..4c991225ebe 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -7,6 +7,7 @@ from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package from poetry.core.packages.project_package import ProjectPackage +from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.version.markers import parse_marker from poetry.factory import Factory from poetry.puzzle import Solver @@ -24,6 +25,12 @@ from tests.repositories.test_pypi_repository import MockRepository as MockPyPIRepository +DEFAULT_SOURCE_REF = ( + VCSDependency("poetry", "git", "git@github.com:python-poetry/poetry.git").branch + or "HEAD" +) + + class Provider(BaseProvider): def set_package_python_versions(self, python_versions): self._package.python_versions = python_versions @@ -67,12 +74,13 @@ def solver(package, pool, installed, locked, io): ) -def check_solver_result(ops, expected): +def check_solver_result(transaction, expected, synchronize=False): for e in expected: if "skipped" not in e: e["skipped"] = False result = [] + ops = transaction.calculate_operations(synchronize=synchronize) for op in ops: if "update" == op.job_type: result.append( @@ -92,6 +100,8 @@ def check_solver_result(ops, expected): assert expected == result + return ops + def test_solver_install_single(solver, repo, package): package.add_dependency(Factory.create_dependency("A", "*")) @@ -99,9 +109,9 @@ def test_solver_install_single(solver, repo, package): package_a = get_package("A", "1.0") repo.add_package(package_a) - ops = solver.solve([get_dependency("A")]) + transaction = solver.solve([get_dependency("A")]) - check_solver_result(ops, [{"job": "install", "package": package_a}]) + check_solver_result(transaction, [{"job": "install", "package": package_a}]) def test_solver_remove_if_no_longer_locked(solver, locked, installed): @@ -109,9 +119,9 @@ def test_solver_remove_if_no_longer_locked(solver, locked, installed): installed.add_package(package_a) locked.add_package(package_a) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "remove", "package": package_a}]) + check_solver_result(transaction, [{"job": "remove", "package": package_a}]) def test_remove_non_installed(solver, repo, locked): @@ -122,9 +132,9 @@ def test_remove_non_installed(solver, repo, locked): request = [] - ops = solver.solve(request) + transaction = solver.solve(request) - check_solver_result(ops, [{"job": "remove", "package": package_a, "skipped": True}]) + check_solver_result(transaction, []) def test_install_non_existing_package_fail(solver, repo, package): @@ -148,12 +158,12 @@ def test_solver_with_deps(solver, repo, package): repo.add_package(package_b) repo.add_package(new_package_b) - package_a.requires.append(get_dependency("B", "<1.1")) + package_a.add_dependency(get_dependency("B", "<1.1")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_b}, {"job": "install", "package": package_a}, @@ -176,12 +186,12 @@ def test_install_honours_not_equal(solver, repo, package): repo.add_package(new_package_b12) repo.add_package(new_package_b13) - package_a.requires.append(get_dependency("B", "<=1.3,!=1.3,!=1.2")) + package_a.add_dependency(get_dependency("B", "<=1.3,!=1.3,!=1.2")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": new_package_b11}, {"job": "install", "package": package_a}, @@ -201,15 +211,15 @@ def test_install_with_deps_in_order(solver, repo, package): repo.add_package(package_b) repo.add_package(package_c) - package_b.requires.append(get_dependency("A", ">=1.0")) - package_b.requires.append(get_dependency("C", ">=1.0")) + package_b.add_dependency(get_dependency("A", ">=1.0")) + package_b.add_dependency(get_dependency("C", ">=1.0")) - package_c.requires.append(get_dependency("A", ">=1.0")) + package_c.add_dependency(get_dependency("A", ">=1.0")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a}, {"job": "install", "package": package_c}, @@ -225,10 +235,10 @@ def test_install_installed(solver, repo, installed, package): installed.add_package(package_a) repo.add_package(package_a) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, [{"job": "install", "package": package_a, "skipped": True}] + transaction, [{"job": "install", "package": package_a, "skipped": True}] ) @@ -242,10 +252,10 @@ def test_update_installed(solver, repo, installed, package): repo.add_package(package_a) repo.add_package(new_package_a) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, [{"job": "update", "from": package_a, "to": new_package_a}] + transaction, [{"job": "update", "from": package_a, "to": new_package_a}] ) @@ -267,10 +277,10 @@ def test_update_with_use_latest(solver, repo, installed, package, locked): locked.add_package(package_a) locked.add_package(package_b) - ops = solver.solve(use_latest=[package_b.name]) + transaction = solver.solve(use_latest=[package_b.name]) check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a, "skipped": True}, {"job": "install", "package": new_package_b}, @@ -278,9 +288,9 @@ def test_update_with_use_latest(solver, repo, installed, package, locked): ) -def test_solver_sets_categories(solver, repo, package): +def test_solver_sets_groups(solver, repo, package): package.add_dependency(Factory.create_dependency("A", "*")) - package.add_dependency(Factory.create_dependency("B", "*", category="dev")) + package.add_dependency(Factory.create_dependency("B", "*", groups=["dev"])) package_a = get_package("A", "1.0") package_b = get_package("B", "1.0") @@ -291,10 +301,10 @@ def test_solver_sets_categories(solver, repo, package): repo.add_package(package_b) repo.add_package(package_c) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_a}, @@ -326,10 +336,10 @@ def test_solver_respects_root_package_python_versions(solver, repo, package): repo.add_package(package_c) repo.add_package(package_c11) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_a}, @@ -379,10 +389,10 @@ def test_solver_solves_optional_and_compatible_packages(solver, repo, package): repo.add_package(package_b) repo.add_package(package_c) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_a}, @@ -405,10 +415,10 @@ def test_solver_does_not_return_extras_if_not_requested(solver, repo, package): repo.add_package(package_b) repo.add_package(package_c) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a}, {"job": "install", "package": package_b}, @@ -429,16 +439,16 @@ def test_solver_returns_extras_if_requested(solver, repo, package): dep = get_dependency("C", "^1.0", optional=True) dep.marker = parse_marker("extra == 'foo'") package_b.extras = {"foo": [dep]} - package_b.requires.append(dep) + package_b.add_dependency(dep) repo.add_package(package_a) repo.add_package(package_b) repo.add_package(package_c) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_a}, @@ -474,15 +484,15 @@ def test_solver_returns_extras_only_requested(solver, repo, package, enabled_ext package_b.extras = {"one": [dep10], "two": [dep20]} - package_b.requires.append(dep10) - package_b.requires.append(dep20) + package_b.add_dependency(dep10) + package_b.add_dependency(dep20) repo.add_package(package_a) repo.add_package(package_b) repo.add_package(package_c10) repo.add_package(package_c20) - ops = solver.solve() + transaction = solver.solve() expected = [ {"job": "install", "package": package_a}, @@ -498,8 +508,8 @@ def test_solver_returns_extras_only_requested(solver, repo, package, enabled_ext }, ) - check_solver_result( - ops, + ops = check_solver_result( + transaction, expected, ) @@ -523,7 +533,7 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency( package_b.extras = {"one": [dep], "two": [dep]} - package_b.requires.append(dep) + package_b.add_dependency(dep) extras = [enabled_extra] if enabled_extra is not None else [] package_a.add_dependency( @@ -534,7 +544,7 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency( repo.add_package(package_b) repo.add_package(package_c) - ops = solver.solve() + transaction = solver.solve() expected = [ {"job": "install", "package": package_b}, @@ -544,8 +554,8 @@ def test_solver_returns_extras_when_multiple_extras_use_same_dependency( if enabled_extra is not None: expected.insert(0, {"job": "install", "package": package_c}) - check_solver_result( - ops, + ops = check_solver_result( + transaction, expected, ) @@ -574,8 +584,8 @@ def test_solver_returns_extras_only_requested_nested( package_b.extras = {"one": [dep10], "two": [dep20]} - package_b.requires.append(dep10) - package_b.requires.append(dep20) + package_b.add_dependency(dep10) + package_b.add_dependency(dep20) extras = [enabled_extra] if enabled_extra is not None else [] package_a.add_dependency( @@ -587,7 +597,7 @@ def test_solver_returns_extras_only_requested_nested( repo.add_package(package_c10) repo.add_package(package_c20) - ops = solver.solve() + transaction = solver.solve() expected = [ {"job": "install", "package": package_b}, @@ -603,7 +613,7 @@ def test_solver_returns_extras_only_requested_nested( }, ) - check_solver_result(ops, expected) + ops = check_solver_result(transaction, expected) assert ops[-1].package.marker.is_any() assert ops[0].package.marker.is_any() @@ -626,10 +636,10 @@ def test_solver_returns_prereleases_if_requested(solver, repo, package): repo.add_package(package_c) repo.add_package(package_c_dev) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a}, {"job": "install", "package": package_b}, @@ -653,10 +663,10 @@ def test_solver_does_not_return_prereleases_if_not_requested(solver, repo, packa repo.add_package(package_c) repo.add_package(package_c_dev) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a}, {"job": "install", "package": package_b}, @@ -685,10 +695,10 @@ def test_solver_sub_dependencies_with_requirements(solver, repo, package): repo.add_package(package_c) repo.add_package(package_d) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_d}, {"job": "install", "package": package_c}, @@ -743,10 +753,10 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package repo.add_package(package_e) repo.add_package(package_f) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_e}, {"job": "install", "package": package_f}, @@ -775,9 +785,9 @@ def test_solver_sub_dependencies_with_not_supported_python_version( repo.add_package(package_a) repo.add_package(package_b) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "install", "package": package_a}]) + check_solver_result(transaction, [{"job": "install", "package": package_a}]) def test_solver_sub_dependencies_with_not_supported_python_version_transitive( @@ -812,10 +822,10 @@ def test_solver_sub_dependencies_with_not_supported_python_version_transitive( repo.add_package(sniffio) repo.add_package(sniffio_1_1_0) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": sniffio, "skipped": False}, {"job": "install", "package": httpcore, "skipped": False}, @@ -824,14 +834,14 @@ def test_solver_sub_dependencies_with_not_supported_python_version_transitive( ) -def test_solver_with_dependency_in_both_main_and_dev_dependencies( +def test_solver_with_dependency_in_both_default_and_dev_dependencies( solver, repo, package ): solver.provider.set_package_python_versions("^3.5") package.add_dependency(Factory.create_dependency("A", "*")) package.add_dependency( Factory.create_dependency( - "A", {"version": "*", "extras": ["foo"]}, category="dev" + "A", {"version": "*", "extras": ["foo"]}, groups=["dev"] ) ) @@ -854,10 +864,10 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies( repo.add_package(package_c) repo.add_package(package_d) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_d}, {"job": "install", "package": package_b}, @@ -866,14 +876,14 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies( ], ) + d = ops[0].package b = ops[1].package c = ops[2].package - d = ops[0].package a = ops[3].package assert d.category == "dev" - assert c.category == "dev" assert b.category == "main" + assert c.category == "dev" assert a.category == "main" @@ -884,7 +894,7 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_ package.add_dependency(Factory.create_dependency("E", "*")) package.add_dependency( Factory.create_dependency( - "A", {"version": "*", "extras": ["foo"]}, category="dev" + "A", {"version": "*", "extras": ["foo"]}, groups=["dev"] ) ) @@ -911,10 +921,10 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_ repo.add_package(package_d) repo.add_package(package_e) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_b}, {"job": "install", "package": package_d}, @@ -925,15 +935,15 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_ ) b = ops[0].package - c = ops[3].package d = ops[1].package a = ops[2].package + c = ops[3].package e = ops[4].package - assert d.category == "dev" - assert c.category == "dev" assert b.category == "main" + assert d.category == "dev" assert a.category == "main" + assert c.category == "dev" assert e.category == "main" @@ -951,10 +961,10 @@ def test_solver_with_dependency_and_prerelease_sub_dependencies(solver, repo, pa package_b = get_package("B", "1.0.0.dev4") repo.add_package(package_b) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_b}, {"job": "install", "package": package_a}, @@ -978,10 +988,10 @@ def test_solver_circular_dependency(solver, repo, package): repo.add_package(package_b) repo.add_package(package_c) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_b}, @@ -1012,10 +1022,10 @@ def test_solver_circular_dependency_chain(solver, repo, package): repo.add_package(package_c) repo.add_package(package_d) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_d}, {"job": "install", "package": package_c}, @@ -1041,10 +1051,10 @@ def test_solver_dense_dependencies(solver, repo, package): for j in range(i): package_ai.add_dependency(Factory.create_dependency("a" + str(j), "^1.0")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, [{"job": "install", "package": packages[i]} for i in range(n)] + transaction, [{"job": "install", "package": packages[i]} for i in range(n)] ) @@ -1064,10 +1074,10 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package): repo.add_package(package_a) repo.add_package(package_b) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_b}, {"job": "install", "package": package_a}, @@ -1093,10 +1103,10 @@ def test_solver_duplicate_dependencies_different_constraints(solver, repo, packa repo.add_package(package_b10) repo.add_package(package_b20) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_b10}, {"job": "install", "package": package_b20}, @@ -1157,10 +1167,10 @@ def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package): repo.add_package(package_c12) repo.add_package(package_c15) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_c12}, {"job": "install", "package": package_c15}, @@ -1198,10 +1208,10 @@ def test_solver_does_not_get_stuck_in_recursion_on_circular_dependency( package.add_dependency(Factory.create_dependency("A", "^1.0")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_b}, @@ -1220,26 +1230,26 @@ def test_solver_can_resolve_git_dependencies(solver, repo, package): Factory.create_dependency("demo", {"git": "https://github.com/demo/demo.git"}) ) - ops = solver.solve() + transaction = solver.solve() demo = Package( "demo", "0.1.2", source_type="git", source_url="https://github.com/demo/demo.git", - source_reference="master", + source_reference=DEFAULT_SOURCE_REF, source_resolved_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", ) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}], ) op = ops[1] assert op.package.source_type == "git" - assert op.package.source_reference == "master" + assert op.package.source_reference == DEFAULT_SOURCE_REF assert op.package.source_resolved_reference.startswith("9cf87a2") @@ -1255,19 +1265,19 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package): ) ) - ops = solver.solve() + transaction = solver.solve() demo = Package( "demo", "0.1.2", source_type="git", source_url="https://github.com/demo/demo.git", - source_reference="master", + source_reference=DEFAULT_SOURCE_REF, source_resolved_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", ) check_solver_result( - ops, + transaction, [ {"job": "install", "package": cleo}, {"job": "install", "package": pendulum}, @@ -1300,10 +1310,10 @@ def test_solver_can_resolve_git_dependencies_with_ref(solver, repo, package, ref git_config.update(ref) package.add_dependency(Factory.create_dependency("demo", git_config)) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}], ) @@ -1327,9 +1337,9 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir repo.add_package(package_a) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "install", "package": package_a}]) + check_solver_result(transaction, [{"job": "install", "package": package_a}]) def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible_multiple( @@ -1353,10 +1363,10 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir repo.add_package(package_a) repo.add_package(package_b) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_b}, {"job": "install", "package": package_a}, @@ -1398,9 +1408,9 @@ def test_solver_finds_compatible_package_for_dependency_python_not_fully_compati repo.add_package(package_a100) repo.add_package(package_a101) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "install", "package": package_a100}]) + check_solver_result(transaction, [{"job": "install", "package": package_a100}]) def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_only_extras( @@ -1417,8 +1427,8 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl package_a = get_package("A", "1.0.0") package_a.extras = {"foo": [dep1], "bar": [dep2]} - package_a.requires.append(dep1) - package_a.requires.append(dep2) + package_a.add_dependency(dep1) + package_a.add_dependency(dep2) package_b2 = get_package("B", "2.0.0") package_b1 = get_package("B", "1.0.0") @@ -1427,10 +1437,10 @@ def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_onl repo.add_package(package_b1) repo.add_package(package_b2) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": package_b2}, {"job": "install", "package": package_a}, @@ -1462,10 +1472,10 @@ def test_solver_does_not_raise_conflict_for_locked_conditional_dependencies( repo.add_package(package_b) solver._locked = Repository([package_a]) - ops = solver.solve(use_latest=[package_b.name]) + transaction = solver.solve(use_latest=[package_b.name]) check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a}, {"job": "install", "package": package_b}, @@ -1499,10 +1509,10 @@ def test_solver_returns_extras_if_requested_in_dependencies_and_not_in_root_pack repo.add_package(package_c) repo.add_package(package_d) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_d}, {"job": "install", "package": package_c}, @@ -1539,7 +1549,7 @@ def test_solver_ignores_dependencies_with_incompatible_python_full_version_marke package.add_dependency(Factory.create_dependency("B", "^2.0")) package_a = get_package("A", "1.0.0") - package_a.requires.append( + package_a.add_dependency( Dependency.create_from_pep_508( 'B (<2.0); platform_python_implementation == "PyPy" and python_full_version < "2.7.9"' ) @@ -1552,10 +1562,10 @@ def test_solver_ignores_dependencies_with_incompatible_python_full_version_marke repo.add_package(package_b100) repo.add_package(package_b200) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a}, {"job": "install", "package": package_b200}, @@ -1574,7 +1584,7 @@ def test_solver_git_dependencies_update(solver, repo, package, installed): "0.1.2", source_type="git", source_url="https://github.com/demo/demo.git", - source_reference="master", + source_reference=DEFAULT_SOURCE_REF, source_resolved_reference="123456", ) demo = Package( @@ -1582,7 +1592,7 @@ def test_solver_git_dependencies_update(solver, repo, package, installed): "0.1.2", source_type="git", source_url="https://github.com/demo/demo.git", - source_reference="master", + source_reference=DEFAULT_SOURCE_REF, source_resolved_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", ) installed.add_package(demo_installed) @@ -1591,10 +1601,10 @@ def test_solver_git_dependencies_update(solver, repo, package, installed): Factory.create_dependency("demo", {"git": "https://github.com/demo/demo.git"}) ) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": pendulum}, {"job": "update", "from": demo_installed, "to": demo}, @@ -1605,7 +1615,7 @@ def test_solver_git_dependencies_update(solver, repo, package, installed): assert op.job_type == "update" assert op.package.source_type == "git" - assert op.package.source_reference == "master" + assert op.package.source_reference == DEFAULT_SOURCE_REF assert op.package.source_resolved_reference.startswith("9cf87a2") assert op.initial_package.source_resolved_reference == "123456" @@ -1630,10 +1640,10 @@ def test_solver_git_dependencies_update_skipped(solver, repo, package, installed Factory.create_dependency("demo", {"git": "https://github.com/demo/demo.git"}) ) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": pendulum}, {"job": "install", "package": demo, "skipped": True}, @@ -1654,7 +1664,7 @@ def test_solver_git_dependencies_short_hash_update_skipped( "0.1.2", source_type="git", source_url="https://github.com/demo/demo.git", - source_reference="master", + source_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", source_resolved_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", ) installed.add_package(demo) @@ -1665,10 +1675,10 @@ def test_solver_git_dependencies_short_hash_update_skipped( ) ) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": pendulum}, { @@ -1702,12 +1712,12 @@ def test_solver_can_resolve_directory_dependencies(solver, repo, package): package.add_dependency(Factory.create_dependency("demo", {"path": path})) - ops = solver.solve() + transaction = solver.solve() demo = Package("demo", "0.1.2", source_type="directory", source_url=path) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}], ) @@ -1730,10 +1740,10 @@ def test_solver_can_resolve_directory_dependencies_nested_editable( package, pool, installed, locked, io, provider=Provider(package, pool, io) ) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ { "job": "install", @@ -1792,12 +1802,12 @@ def test_solver_can_resolve_directory_dependencies_with_extras(solver, repo, pac Factory.create_dependency("demo", {"path": path, "extras": ["foo"]}) ) - ops = solver.solve() + transaction = solver.solve() demo = Package("demo", "0.1.2", source_type="directory", source_url=path) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": cleo}, {"job": "install", "package": pendulum}, @@ -1826,12 +1836,12 @@ def test_solver_can_resolve_sdist_dependencies(solver, repo, package): package.add_dependency(Factory.create_dependency("demo", {"path": path})) - ops = solver.solve() + transaction = solver.solve() demo = Package("demo", "0.1.0", source_type="file", source_url=path) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}], ) @@ -1860,12 +1870,12 @@ def test_solver_can_resolve_sdist_dependencies_with_extras(solver, repo, package Factory.create_dependency("demo", {"path": path, "extras": ["foo"]}) ) - ops = solver.solve() + transaction = solver.solve() demo = Package("demo", "0.1.0", source_type="file", source_url=path) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": cleo}, {"job": "install", "package": pendulum}, @@ -1894,12 +1904,12 @@ def test_solver_can_resolve_wheel_dependencies(solver, repo, package): package.add_dependency(Factory.create_dependency("demo", {"path": path})) - ops = solver.solve() + transaction = solver.solve() demo = Package("demo", "0.1.0", source_type="file", source_url=path) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}], ) @@ -1928,12 +1938,12 @@ def test_solver_can_resolve_wheel_dependencies_with_extras(solver, repo, package Factory.create_dependency("demo", {"path": path, "extras": ["foo"]}) ) - ops = solver.solve() + transaction = solver.solve() demo = Package("demo", "0.1.0", source_type="file", source_url=path) - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ {"job": "install", "package": cleo}, {"job": "install", "package": pendulum}, @@ -1959,10 +1969,10 @@ def test_solver_can_solve_with_legacy_repository_using_proper_dists( package.add_dependency(Factory.create_dependency("isort", "4.3.4")) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ { "job": "install", @@ -2003,10 +2013,10 @@ def test_solver_can_solve_with_legacy_repository_using_proper_python_compatible_ package.add_dependency(Factory.create_dependency("isort", "4.3.4")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ { "job": "install", @@ -2032,10 +2042,10 @@ def test_solver_skips_invalid_versions(package, installed, locked, io): package.add_dependency(Factory.create_dependency("trackpy", "^0.4")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, [{"job": "install", "package": get_package("trackpy", "0.4.1")}] + transaction, [{"job": "install", "package": get_package("trackpy", "0.4.1")}] ) @@ -2053,10 +2063,10 @@ def test_multiple_constraints_on_root(package, solver, repo): repo.add_package(foo15) repo.add_package(foo25) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [{"job": "install", "package": foo15}, {"job": "install", "package": foo25}], ) @@ -2072,10 +2082,10 @@ def test_solver_chooses_most_recent_version_amongst_repositories( solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, [{"job": "install", "package": get_package("tomlkit", "0.5.3")}] + ops = check_solver_result( + transaction, [{"job": "install", "package": get_package("tomlkit", "0.5.3")}] ) assert ops[0].package.source_type is None @@ -2095,10 +2105,10 @@ def test_solver_chooses_from_correct_repository_if_forced( solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ { "job": "install", @@ -2133,10 +2143,10 @@ def test_solver_chooses_from_correct_repository_if_forced_and_transitive_depende solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ { "job": "install", @@ -2170,10 +2180,10 @@ def test_solver_does_not_choose_from_secondary_repository_by_default( solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ { "job": "install", @@ -2217,10 +2227,10 @@ def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, i solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() - check_solver_result( - ops, + ops = check_solver_result( + transaction, [ { "job": "install", @@ -2269,10 +2279,10 @@ def test_solver_discards_packages_with_empty_markers( solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_c}, {"job": "install", "package": package_a}, @@ -2286,12 +2296,12 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( solver.provider.set_package_python_versions("~2.7 || ^3.5") package.add_dependency( Factory.create_dependency( - "A", {"version": "^1.0", "python": "~2.7"}, category="dev" + "A", {"version": "^1.0", "python": "~2.7"}, groups=["dev"] ) ) package.add_dependency( Factory.create_dependency( - "A", {"version": "^2.0", "python": "^3.5"}, category="dev" + "A", {"version": "^2.0", "python": "^3.5"}, groups=["dev"] ) ) @@ -2301,10 +2311,10 @@ def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies( repo.add_package(package_a100) repo.add_package(package_a200) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a100}, {"job": "install", "package": package_a200}, @@ -2335,10 +2345,10 @@ def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras( repo.add_package(requests) repo.add_package(idna) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [{"job": "install", "package": idna}, {"job": "install", "package": requests}], ) @@ -2356,8 +2366,8 @@ def test_solver_does_not_fail_with_locked_git_and_non_git_dependencies( "0.1.2", source_type="git", source_url="https://github.com/demo/demo.git", - source_reference="master", - source_resolved_reference="commit", + source_reference=DEFAULT_SOURCE_REF, + source_resolved_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", ) installed.add_package(git_package) @@ -2370,10 +2380,10 @@ def test_solver_does_not_fail_with_locked_git_and_non_git_dependencies( solver = Solver(package, pool, installed, locked, io) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": get_package("a", "1.2.3")}, {"job": "install", "package": git_package, "skipped": True}, @@ -2396,10 +2406,10 @@ def test_ignore_python_constraint_no_overlap_dependencies(solver, repo, package) repo.add_package(pytest) repo.add_package(get_package("configparser", "1.2.3")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [{"job": "install", "package": pytest}], ) @@ -2425,10 +2435,10 @@ def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies( repo.add_package(package_b10) repo.add_package(package_b20) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_b10}, {"job": "install", "package": package_b20}, @@ -2437,27 +2447,29 @@ def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies( ) -def test_solver_remove_untracked_single(package, pool, installed, locked, io): - solver = Solver(package, pool, installed, locked, io, remove_untracked=True) +def test_solver_synchronize_single(package, pool, installed, locked, io): + solver = Solver(package, pool, installed, locked, io) package_a = get_package("a", "1.0") installed.add_package(package_a) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "remove", "package": package_a}]) + check_solver_result( + transaction, [{"job": "remove", "package": package_a}], synchronize=True + ) @pytest.mark.skip(reason="Poetry no longer has critical package requirements") -def test_solver_remove_untracked_keeps_critical_package( +def test_solver_with_synchronization_keeps_critical_package( package, pool, installed, locked, io ): - solver = Solver(package, pool, installed, locked, io, remove_untracked=True) + solver = Solver(package, pool, installed, locked, io) package_pip = get_package("setuptools", "1.0") installed.add_package(package_pip) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, []) + check_solver_result(transaction, []) def test_solver_cannot_choose_another_version_for_directory_dependencies( @@ -2591,9 +2603,11 @@ def test_solver_should_not_update_same_version_packages_if_installed_has_no_sour repo.add_package(foo) installed.add_package(get_package("foo", "1.0.0")) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "install", "package": foo, "skipped": True}]) + check_solver_result( + transaction, [{"job": "install", "package": foo, "skipped": True}] + ) def test_solver_should_use_the_python_constraint_from_the_environment_if_available( @@ -2615,10 +2629,10 @@ def test_solver_should_use_the_python_constraint_from_the_environment_if_availab repo.add_package(b) with solver.use_environment(MockEnv((2, 7, 18))): - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [{"job": "install", "package": b}, {"job": "install", "package": a}], ) @@ -2658,10 +2672,10 @@ def test_solver_should_resolve_all_versions_for_multiple_duplicate_dependencies( repo.add_package(package_b30) repo.add_package(package_b40) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": package_a10}, {"job": "install", "package": package_a20}, @@ -2684,9 +2698,9 @@ def test_solver_should_not_raise_errors_for_irrelevant_python_constraints( dataclasses.python_versions = ">=3.6, <3.7" repo.add_package(dataclasses) - ops = solver.solve() + transaction = solver.solve() - check_solver_result(ops, [{"job": "install", "package": dataclasses}]) + check_solver_result(transaction, [{"job": "install", "package": dataclasses}]) def test_solver_can_resolve_transitive_extras(solver, repo, package): @@ -2712,10 +2726,10 @@ def test_solver_can_resolve_transitive_extras(solver, repo, package): repo.add_package(get_package("certifi", "2017.4.17")) repo.add_package(get_package("pyopenssl", "0.14")) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": get_package("certifi", "2017.4.17")}, {"job": "install", "package": get_package("pyopenssl", "0.14")}, @@ -2748,10 +2762,10 @@ def test_solver_can_resolve_for_packages_with_missing_extras(solver, repo, packa repo.add_package(boto3) repo.add_package(requests) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": django}, {"job": "install", "package": requests}, @@ -2782,10 +2796,10 @@ def test_solver_can_resolve_python_restricted_package_dependencies( repo.add_package(futures) repo.add_package(pre_commit) - ops = solver.solve(use_latest=["pre-commit"]) + transaction = solver.solve(use_latest=["pre-commit"]) check_solver_result( - ops, + transaction, [ {"job": "install", "package": futures}, {"job": "install", "package": pre_commit}, @@ -2831,10 +2845,10 @@ def test_solver_should_not_raise_errors_for_irrelevant_transitive_python_constra repo.add_package(pre_commit) repo.add_package(importlib_resources) repo.add_package(importlib_resources_3_2_1) - ops = solver.solve() + transaction = solver.solve() check_solver_result( - ops, + transaction, [ {"job": "install", "package": importlib_resources_3_2_1}, {"job": "install", "package": pre_commit}, diff --git a/tests/puzzle/test_transaction.py b/tests/puzzle/test_transaction.py new file mode 100644 index 00000000000..8799ea9ffab --- /dev/null +++ b/tests/puzzle/test_transaction.py @@ -0,0 +1,149 @@ +from poetry.core.packages.package import Package +from poetry.puzzle.transaction import Transaction + + +def check_operations(ops, expected): + for e in expected: + if "skipped" not in e: + e["skipped"] = False + + result = [] + for op in ops: + if "update" == op.job_type: + result.append( + { + "job": "update", + "from": op.initial_package, + "to": op.target_package, + "skipped": op.skipped, + } + ) + else: + job = "install" + if op.job_type == "uninstall": + job = "remove" + + result.append({"job": job, "package": op.package, "skipped": op.skipped}) + + assert expected == result + + +def test_it_should_calculate_operations_in_correct_order(): + transaction = Transaction( + [Package("a", "1.0.0"), Package("b", "2.0.0"), Package("c", "3.0.0")], + [ + (Package("a", "1.0.0"), 1), + (Package("b", "2.1.0"), 2), + (Package("d", "4.0.0"), 0), + ], + ) + + check_operations( + transaction.calculate_operations(), + [ + {"job": "install", "package": Package("b", "2.1.0")}, + {"job": "install", "package": Package("a", "1.0.0")}, + {"job": "install", "package": Package("d", "4.0.0")}, + ], + ) + + +def test_it_should_calculate_operations_for_installed_packages(): + transaction = Transaction( + [Package("a", "1.0.0"), Package("b", "2.0.0"), Package("c", "3.0.0")], + [ + (Package("a", "1.0.0"), 1), + (Package("b", "2.1.0"), 2), + (Package("d", "4.0.0"), 0), + ], + installed_packages=[ + Package("a", "1.0.0"), + Package("b", "2.0.0"), + Package("c", "3.0.0"), + Package("e", "5.0.0"), + ], + ) + + check_operations( + transaction.calculate_operations(), + [ + {"job": "remove", "package": Package("c", "3.0.0")}, + { + "job": "update", + "from": Package("b", "2.0.0"), + "to": Package("b", "2.1.0"), + }, + {"job": "install", "package": Package("a", "1.0.0"), "skipped": True}, + {"job": "install", "package": Package("d", "4.0.0")}, + ], + ) + + +def test_it_should_remove_installed_packages_if_required(): + transaction = Transaction( + [Package("a", "1.0.0"), Package("b", "2.0.0"), Package("c", "3.0.0")], + [ + (Package("a", "1.0.0"), 1), + (Package("b", "2.1.0"), 2), + (Package("d", "4.0.0"), 0), + ], + installed_packages=[ + Package("a", "1.0.0"), + Package("b", "2.0.0"), + Package("c", "3.0.0"), + Package("e", "5.0.0"), + ], + ) + + check_operations( + transaction.calculate_operations(synchronize=True), + [ + {"job": "remove", "package": Package("c", "3.0.0")}, + {"job": "remove", "package": Package("e", "5.0.0")}, + { + "job": "update", + "from": Package("b", "2.0.0"), + "to": Package("b", "2.1.0"), + }, + {"job": "install", "package": Package("a", "1.0.0"), "skipped": True}, + {"job": "install", "package": Package("d", "4.0.0")}, + ], + ) + + +def test_it_should_update_installed_packages_if_sources_are_different(): + transaction = Transaction( + [Package("a", "1.0.0")], + [ + ( + Package( + "a", + "1.0.0", + source_url="https://github.com/demo/demo.git", + source_type="git", + source_reference="main", + source_resolved_reference="123456", + ), + 1, + ) + ], + installed_packages=[Package("a", "1.0.0")], + ) + + check_operations( + transaction.calculate_operations(synchronize=True), + [ + { + "job": "update", + "from": Package("a", "1.0.0"), + "to": Package( + "a", + "1.0.0", + source_url="https://github.com/demo/demo.git", + source_type="git", + source_reference="main", + source_resolved_reference="123456", + ), + } + ], + ) diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index f567314a34c..e0f4bb1d393 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -191,7 +191,7 @@ def test_pypi_repository_supports_reading_bz2_files(): ] } - for name, deps in expected_extras.items(): + for name in expected_extras.keys(): assert expected_extras[name] == sorted( package.extras[name], key=lambda r: r.name ) diff --git a/tests/test_factory.py b/tests/test_factory.py index e9a9a9a5994..bb632cc9a26 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - from pathlib import Path import pytest from entrypoints import EntryPoint +from poetry.core.semver.helpers import parse_constraint from poetry.core.toml.file import TOMLFile from poetry.factory import Factory from poetry.plugins.plugin import Plugin @@ -70,7 +67,7 @@ def test_create_poetry(): pathlib2 = dependencies["pathlib2"] assert pathlib2.pretty_constraint == "^2.2" - assert pathlib2.python_versions == "~2.7" + assert parse_constraint(pathlib2.python_versions) == parse_constraint("~2.7") assert not pathlib2.is_optional() demo = dependencies["demo"] @@ -156,13 +153,13 @@ def test_create_poetry_with_multi_constraints_dependency(): assert len(package.requires) == 2 -def test_poetry_with_default_source(): +def test_poetry_with_default_source(with_simple_keyring): poetry = Factory().create_poetry(fixtures_dir / "with_default_source") assert 1 == len(poetry.pool.repositories) -def test_poetry_with_non_default_source(): +def test_poetry_with_non_default_source(with_simple_keyring): poetry = Factory().create_poetry(fixtures_dir / "with_non_default_source") assert len(poetry.pool.repositories) == 2 @@ -176,7 +173,7 @@ def test_poetry_with_non_default_source(): assert isinstance(poetry.pool.repositories[1], PyPiRepository) -def test_poetry_with_non_default_secondary_source(): +def test_poetry_with_non_default_secondary_source(with_simple_keyring): poetry = Factory().create_poetry(fixtures_dir / "with_non_default_secondary_source") assert len(poetry.pool.repositories) == 2 @@ -192,7 +189,7 @@ def test_poetry_with_non_default_secondary_source(): assert isinstance(repository, LegacyRepository) -def test_poetry_with_non_default_multiple_secondary_sources(): +def test_poetry_with_non_default_multiple_secondary_sources(with_simple_keyring): poetry = Factory().create_poetry( fixtures_dir / "with_non_default_multiple_secondary_sources" ) @@ -214,7 +211,7 @@ def test_poetry_with_non_default_multiple_secondary_sources(): assert isinstance(repository, LegacyRepository) -def test_poetry_with_non_default_multiple_sources(): +def test_poetry_with_non_default_multiple_sources(with_simple_keyring): poetry = Factory().create_poetry(fixtures_dir / "with_non_default_multiple_sources") assert len(poetry.pool.repositories) == 3 @@ -245,7 +242,7 @@ def test_poetry_with_no_default_source(): assert isinstance(poetry.pool.repositories[0], PyPiRepository) -def test_poetry_with_two_default_sources(): +def test_poetry_with_two_default_sources(with_simple_keyring): with pytest.raises(ValueError) as e: Factory().create_poetry(fixtures_dir / "with_two_default_sources") diff --git a/tests/utils/fixtures/setups/ansible/setup.py b/tests/utils/fixtures/setups/ansible/setup.py index 5d7d1f4ec60..7a3af3bf995 100644 --- a/tests/utils/fixtures/setups/ansible/setup.py +++ b/tests/utils/fixtures/setups/ansible/setup.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import json import os import os.path diff --git a/tests/utils/fixtures/setups/flask/setup.py b/tests/utils/fixtures/setups/flask/setup.py index 2117d7ccc0a..98801eb8dff 100644 --- a/tests/utils/fixtures/setups/flask/setup.py +++ b/tests/utils/fixtures/setups/flask/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import io import re from collections import OrderedDict diff --git a/tests/utils/fixtures/setups/pendulum/setup.py b/tests/utils/fixtures/setups/pendulum/setup.py index 3a6323fbbdf..4dcbe961465 100644 --- a/tests/utils/fixtures/setups/pendulum/setup.py +++ b/tests/utils/fixtures/setups/pendulum/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from distutils.core import setup from build import * diff --git a/tests/utils/fixtures/setups/pyyaml/setup.py b/tests/utils/fixtures/setups/pyyaml/setup.py index 5285386ba55..682ff9186e4 100644 --- a/tests/utils/fixtures/setups/pyyaml/setup.py +++ b/tests/utils/fixtures/setups/pyyaml/setup.py @@ -12,7 +12,7 @@ allow to represent an arbitrary Python object. PyYAML is applicable for a broad range of tasks from complex -configuration files to object serialization and persistance.""" +configuration files to object serialization and persistence.""" AUTHOR = "Kirill Simonov" AUTHOR_EMAIL = "xi@resolvent.net" LICENSE = "MIT" @@ -308,10 +308,7 @@ def run(self): build_cmd = self.get_finalized_command("build") build_cmd.run() sys.path.insert(0, build_cmd.build_lib) - if sys.version_info[0] < 3: - sys.path.insert(0, "tests/lib") - else: - sys.path.insert(0, "tests/lib3") + sys.path.insert(0, "tests/lib3") import test_all if not test_all.main([]): @@ -337,7 +334,7 @@ def run(self): url=URL, download_url=DOWNLOAD_URL, classifiers=CLASSIFIERS, - package_dir={"": {2: "lib", 3: "lib3"}[sys.version_info[0]]}, + package_dir={"": "lib3"}, packages=["yaml"], ext_modules=[ Extension( diff --git a/tests/utils/fixtures/setups/sqlalchemy/setup.py b/tests/utils/fixtures/setups/sqlalchemy/setup.py index 8842a482d27..bc367a1d5d0 100644 --- a/tests/utils/fixtures/setups/sqlalchemy/setup.py +++ b/tests/utils/fixtures/setups/sqlalchemy/setup.py @@ -12,8 +12,6 @@ from setuptools.command.test import test as TestCommand cmdclass = {} -if sys.version_info < (2, 7): - raise Exception("SQLAlchemy requires Python 2.7 or higher.") cpython = platform.python_implementation() == "CPython" diff --git a/tests/installation/test_authenticator.py b/tests/utils/test_authenticator.py similarity index 78% rename from tests/installation/test_authenticator.py rename to tests/utils/test_authenticator.py index 5f756c34f91..4e817ccb91c 100644 --- a/tests/installation/test_authenticator.py +++ b/tests/utils/test_authenticator.py @@ -7,7 +7,13 @@ from cleo.io.null_io import NullIO -from poetry.installation.authenticator import Authenticator +from poetry.utils.authenticator import Authenticator + + +class SimpleCredential: + def __init__(self, username, password): + self.username = username + self.password = password @pytest.fixture() @@ -52,7 +58,9 @@ def test_authenticator_uses_credentials_from_config_if_not_provided( assert "Basic YmFyOmJheg==" == request.headers["Authorization"] -def test_authenticator_uses_username_only_credentials(config, mock_remote, http): +def test_authenticator_uses_username_only_credentials( + config, mock_remote, http, with_simple_keyring +): config.merge( { "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, @@ -85,7 +93,7 @@ def test_authenticator_uses_password_only_credentials(config, mock_remote, http) def test_authenticator_uses_empty_strings_as_default_password( - config, mock_remote, http + config, mock_remote, http, with_simple_keyring ): config.merge( { @@ -120,6 +128,47 @@ def test_authenticator_uses_empty_strings_as_default_username( assert "Basic OmJhcg==" == request.headers["Authorization"] +def test_authenticator_falls_back_to_keyring_url( + config, mock_remote, http, with_simple_keyring, dummy_keyring +): + config.merge( + { + "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, + } + ) + + dummy_keyring.set_password( + "https://foo.bar/simple/", None, SimpleCredential(None, "bar") + ) + + authenticator = Authenticator(config, NullIO()) + authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") + + request = http.last_request() + + assert "Basic OmJhcg==" == request.headers["Authorization"] + + +def test_authenticator_falls_back_to_keyring_netloc( + config, mock_remote, http, with_simple_keyring, dummy_keyring +): + config.merge( + { + "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, + } + ) + + dummy_keyring.set_password("foo.bar", None, SimpleCredential(None, "bar")) + + authenticator = Authenticator(config, NullIO()) + authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") + + request = http.last_request() + + assert "Basic OmJhcg==" == request.headers["Authorization"] + + +@pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") def test_authenticator_request_retries_on_exception(mocker, config, http): sleep = mocker.patch("time.sleep") sdist_uri = "https://foo.bar/files/{}/foo-0.1.0.tar.gz".format(str(uuid.uuid4())) @@ -140,6 +189,7 @@ def callback(request, uri, response_headers): assert sleep.call_count == 2 +@pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") def test_authenticator_request_raises_exception_when_attempts_exhausted( mocker, config, http ): diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index d09012c821f..6366bb9a0e9 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -15,9 +15,11 @@ from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile from poetry.factory import Factory +from poetry.utils._compat import WINDOWS from poetry.utils.env import GET_BASE_PREFIX from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvManager +from poetry.utils.env import GenericEnv from poetry.utils.env import NoCompatiblePythonVersionFound from poetry.utils.env import SystemEnv from poetry.utils.env import VirtualEnv @@ -118,11 +120,14 @@ def test_env_get_venv_with_venv_folder_present( assert venv.path == in_project_venv_dir -def build_venv(path: Union[Path, str], **__: Any) -> (): +def build_venv(path: Union[Path, str], **__: Any) -> None: os.mkdir(str(path)) -def check_output_wrapper(version=Version.parse("3.7.1")): +VERSION_3_7_1 = Version.parse("3.7.1") + + +def check_output_wrapper(version=VERSION_3_7_1): def check_output(cmd, *args, **kwargs): if "sys.version_info[:3]" in cmd: return version.text @@ -958,9 +963,131 @@ def test_env_system_packages(tmp_path, config): EnvManager(config).build_venv(path=venv_path, flags={"system-site-packages": True}) - if sys.version_info >= (3, 3): - assert "include-system-site-packages = true" in pyvenv_cfg.read_text() - elif (2, 6) < sys.version_info < (3, 0): - assert not venv_path.joinpath( - "lib", "python2.7", "no-global-site-packages.txt" - ).exists() + assert "include-system-site-packages = true" in pyvenv_cfg.read_text() + + +def test_env_finds_the_correct_executables(tmp_dir, manager): + venv_path = Path(tmp_dir) / "Virtual Env" + manager.build_venv(str(venv_path), with_pip=True) + venv = VirtualEnv(venv_path) + + default_executable = expected_executable = "python" + (".exe" if WINDOWS else "") + default_pip_executable = expected_pip_executable = "pip" + ( + ".exe" if WINDOWS else "" + ) + major_executable = "python{}{}".format( + sys.version_info[0], ".exe" if WINDOWS else "" + ) + major_pip_executable = "pip{}{}".format( + sys.version_info[0], ".exe" if WINDOWS else "" + ) + + if ( + venv._bin_dir.joinpath(default_executable).exists() + and venv._bin_dir.joinpath(major_executable).exists() + ): + venv._bin_dir.joinpath(default_executable).unlink() + expected_executable = major_executable + + if ( + venv._bin_dir.joinpath(default_pip_executable).exists() + and venv._bin_dir.joinpath(major_pip_executable).exists() + ): + venv._bin_dir.joinpath(default_pip_executable).unlink() + expected_pip_executable = major_pip_executable + + venv = VirtualEnv(venv_path) + + assert Path(venv.python).name == expected_executable + assert Path(venv.pip).name.startswith(expected_pip_executable.split(".")[0]) + + +def test_env_finds_the_correct_executables_for_generic_env(tmp_dir, manager): + venv_path = Path(tmp_dir) / "Virtual Env" + child_venv_path = Path(tmp_dir) / "Child Virtual Env" + manager.build_venv(str(venv_path), with_pip=True) + parent_venv = VirtualEnv(venv_path) + manager.build_venv( + str(child_venv_path), executable=parent_venv.python, with_pip=True + ) + venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) + + expected_executable = "python{}.{}{}".format( + sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "" + ) + expected_pip_executable = "pip{}.{}{}".format( + sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "" + ) + + if WINDOWS: + expected_executable = "python.exe" + expected_pip_executable = "pip.exe" + + assert Path(venv.python).name == expected_executable + assert Path(venv.pip).name == expected_pip_executable + + +def test_env_finds_fallback_executables_for_generic_env(tmp_dir, manager): + venv_path = Path(tmp_dir) / "Virtual Env" + child_venv_path = Path(tmp_dir) / "Child Virtual Env" + manager.build_venv(str(venv_path), with_pip=True) + parent_venv = VirtualEnv(venv_path) + manager.build_venv( + str(child_venv_path), executable=parent_venv.python, with_pip=True + ) + venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) + + default_executable = "python" + (".exe" if WINDOWS else "") + major_executable = "python{}{}".format( + sys.version_info[0], ".exe" if WINDOWS else "" + ) + minor_executable = "python{}.{}{}".format( + sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "" + ) + expected_executable = minor_executable + if ( + venv._bin_dir.joinpath(expected_executable).exists() + and venv._bin_dir.joinpath(major_executable).exists() + ): + venv._bin_dir.joinpath(expected_executable).unlink() + expected_executable = major_executable + + if ( + venv._bin_dir.joinpath(expected_executable).exists() + and venv._bin_dir.joinpath(default_executable).exists() + ): + venv._bin_dir.joinpath(expected_executable).unlink() + expected_executable = default_executable + + default_pip_executable = "pip" + (".exe" if WINDOWS else "") + major_pip_executable = "pip{}{}".format( + sys.version_info[0], ".exe" if WINDOWS else "" + ) + minor_pip_executable = "pip{}.{}{}".format( + sys.version_info[0], sys.version_info[1], ".exe" if WINDOWS else "" + ) + expected_pip_executable = minor_pip_executable + if ( + venv._bin_dir.joinpath(expected_pip_executable).exists() + and venv._bin_dir.joinpath(major_pip_executable).exists() + ): + venv._bin_dir.joinpath(expected_pip_executable).unlink() + expected_pip_executable = major_pip_executable + + if ( + venv._bin_dir.joinpath(expected_pip_executable).exists() + and venv._bin_dir.joinpath(default_pip_executable).exists() + ): + venv._bin_dir.joinpath(expected_pip_executable).unlink() + expected_pip_executable = default_pip_executable + + if not venv._bin_dir.joinpath(expected_executable).exists(): + expected_executable = default_executable + + if not venv._bin_dir.joinpath(expected_pip_executable).exists(): + expected_pip_executable = default_pip_executable + + venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) + + assert Path(venv.python).name == expected_executable + assert Path(venv.pip).name == expected_pip_executable diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index eb3e000feed..03f878aaf3b 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -62,16 +62,12 @@ def poetry(fixture_dir, locker): def set_package_requires(poetry, skip=None): skip = skip or set() packages = poetry.locker.locked_repository(with_dev_reqs=True).packages - poetry.package.requires = [ - pkg.to_dependency() - for pkg in packages - if pkg.category == "main" and pkg.name not in skip - ] - poetry.package.dev_requires = [ - pkg.to_dependency() - for pkg in packages - if pkg.category == "dev" and pkg.name not in skip - ] + package = poetry.package.with_dependency_groups([], only=True) + for pkg in packages: + if pkg.name not in skip: + package.add_dependency(pkg.to_dependency()) + + poetry._package = package def test_exporter_can_export_requirements_txt_with_standard_packages( @@ -483,16 +479,18 @@ def test_exporter_can_export_requirements_txt_with_nested_packages_and_markers_a } ) - poetry.package.requires = [ + root = poetry.package.with_dependency_groups([], only=True) + root.add_dependency( Factory.create_dependency( name="a", constraint=dict(version="^1.2.3", python="<3.8") - ), - ] - poetry.package.dev_requires = [ + ) + ) + root.add_dependency( Factory.create_dependency( - name="b", constraint=dict(version="^4.5.6"), category="dev" - ), - ] + name="b", constraint=dict(version="^4.5.6"), groups=["dev"] + ) + ) + poetry._package = root exporter = Exporter(poetry) diff --git a/tests/utils/test_password_manager.py b/tests/utils/test_password_manager.py index 31d5812ce96..e14973005ca 100644 --- a/tests/utils/test_password_manager.py +++ b/tests/utils/test_password_manager.py @@ -2,80 +2,26 @@ import pytest -from keyring.backend import KeyringBackend - from poetry.utils.password_manager import KeyRing from poetry.utils.password_manager import KeyRingError from poetry.utils.password_manager import PasswordManager -class DummyBackend(KeyringBackend): - def __init__(self): - self._passwords = {} - - @classmethod - def priority(cls): - return 42 - - def set_password(self, service, username, password): - self._passwords[service] = {username: password} - - def get_password(self, service, username): - return self._passwords.get(service, {}).get(username) - - def delete_password(self, service, username): - if service in self._passwords and username in self._passwords[service]: - del self._passwords[service][username] - - -@pytest.fixture() -def backend(): - return DummyBackend() - - -@pytest.fixture() -def mock_available_backend(backend): - import keyring - - keyring.set_keyring(backend) - - -@pytest.fixture() -def mock_unavailable_backend(): - import keyring - - from keyring.backends.fail import Keyring - - keyring.set_keyring(Keyring()) - - -@pytest.fixture() -def mock_chainer_backend(mocker): - from keyring.backends.fail import Keyring - - mocker.patch("keyring.backend.get_all_keyring", [Keyring()]) - import keyring - - from keyring.backends.chainer import ChainerBackend - - keyring.set_keyring(ChainerBackend()) - - -def test_set_http_password(config, mock_available_backend, backend): +def test_set_http_password(config, with_simple_keyring, dummy_keyring): manager = PasswordManager(config) assert manager.keyring.is_available() manager.set_http_password("foo", "bar", "baz") - assert "baz" == backend.get_password("poetry-repository-foo", "bar") + assert "baz" == dummy_keyring.get_password("poetry-repository-foo", "bar") auth = config.get("http-basic.foo") assert "bar" == auth["username"] assert "password" not in auth -def test_get_http_auth(config, mock_available_backend, backend): - backend.set_password("poetry-repository-foo", "bar", "baz") +def test_get_http_auth(config, with_simple_keyring, dummy_keyring): + dummy_keyring.set_password("poetry-repository-foo", "bar", "baz") config.auth_config_source.add_property("http-basic.foo", {"username": "bar"}) manager = PasswordManager(config) @@ -86,19 +32,19 @@ def test_get_http_auth(config, mock_available_backend, backend): assert "baz" == auth["password"] -def test_delete_http_password(config, mock_available_backend, backend): - backend.set_password("poetry-repository-foo", "bar", "baz") +def test_delete_http_password(config, with_simple_keyring, dummy_keyring): + dummy_keyring.set_password("poetry-repository-foo", "bar", "baz") config.auth_config_source.add_property("http-basic.foo", {"username": "bar"}) manager = PasswordManager(config) assert manager.keyring.is_available() manager.delete_http_password("foo") - assert backend.get_password("poetry-repository-foo", "bar") is None + assert dummy_keyring.get_password("poetry-repository-foo", "bar") is None assert config.get("http-basic.foo") is None -def test_set_pypi_token(config, mock_available_backend, backend): +def test_set_pypi_token(config, with_simple_keyring, dummy_keyring): manager = PasswordManager(config) assert manager.keyring.is_available() @@ -106,28 +52,28 @@ def test_set_pypi_token(config, mock_available_backend, backend): assert config.get("pypi-token.foo") is None - assert "baz" == backend.get_password("poetry-repository-foo", "__token__") + assert "baz" == dummy_keyring.get_password("poetry-repository-foo", "__token__") -def test_get_pypi_token(config, mock_available_backend, backend): - backend.set_password("poetry-repository-foo", "__token__", "baz") +def test_get_pypi_token(config, with_simple_keyring, dummy_keyring): + dummy_keyring.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) assert manager.keyring.is_available() assert "baz" == manager.get_pypi_token("foo") -def test_delete_pypi_token(config, mock_available_backend, backend): - backend.set_password("poetry-repository-foo", "__token__", "baz") +def test_delete_pypi_token(config, with_simple_keyring, dummy_keyring): + dummy_keyring.set_password("poetry-repository-foo", "__token__", "baz") manager = PasswordManager(config) assert manager.keyring.is_available() manager.delete_pypi_token("foo") - assert backend.get_password("poetry-repository-foo", "__token__") is None + assert dummy_keyring.get_password("poetry-repository-foo", "__token__") is None -def test_set_http_password_with_unavailable_backend(config, mock_unavailable_backend): +def test_set_http_password_with_unavailable_backend(config, with_fail_keyring): manager = PasswordManager(config) assert not manager.keyring.is_available() @@ -138,7 +84,7 @@ def test_set_http_password_with_unavailable_backend(config, mock_unavailable_bac assert "baz" == auth["password"] -def test_get_http_auth_with_unavailable_backend(config, mock_unavailable_backend): +def test_get_http_auth_with_unavailable_backend(config, with_fail_keyring): config.auth_config_source.add_property( "http-basic.foo", {"username": "bar", "password": "baz"} ) @@ -151,9 +97,7 @@ def test_get_http_auth_with_unavailable_backend(config, mock_unavailable_backend assert "baz" == auth["password"] -def test_delete_http_password_with_unavailable_backend( - config, mock_unavailable_backend -): +def test_delete_http_password_with_unavailable_backend(config, with_fail_keyring): config.auth_config_source.add_property( "http-basic.foo", {"username": "bar", "password": "baz"} ) @@ -165,7 +109,7 @@ def test_delete_http_password_with_unavailable_backend( assert config.get("http-basic.foo") is None -def test_set_pypi_token_with_unavailable_backend(config, mock_unavailable_backend): +def test_set_pypi_token_with_unavailable_backend(config, with_fail_keyring): manager = PasswordManager(config) assert not manager.keyring.is_available() @@ -174,7 +118,7 @@ def test_set_pypi_token_with_unavailable_backend(config, mock_unavailable_backen assert "baz" == config.get("pypi-token.foo") -def test_get_pypi_token_with_unavailable_backend(config, mock_unavailable_backend): +def test_get_pypi_token_with_unavailable_backend(config, with_fail_keyring): config.auth_config_source.add_property("pypi-token.foo", "baz") manager = PasswordManager(config) @@ -182,7 +126,7 @@ def test_get_pypi_token_with_unavailable_backend(config, mock_unavailable_backen assert "baz" == manager.get_pypi_token("foo") -def test_delete_pypi_token_with_unavailable_backend(config, mock_unavailable_backend): +def test_delete_pypi_token_with_unavailable_backend(config, with_fail_keyring): config.auth_config_source.add_property("pypi-token.foo", "baz") manager = PasswordManager(config) @@ -192,7 +136,7 @@ def test_delete_pypi_token_with_unavailable_backend(config, mock_unavailable_bac assert config.get("pypi-token.foo") is None -def test_keyring_raises_errors_on_keyring_errors(mocker, mock_unavailable_backend): +def test_keyring_raises_errors_on_keyring_errors(mocker, with_fail_keyring): mocker.patch("poetry.utils.password_manager.KeyRing._check") key_ring = KeyRing("poetry") @@ -207,16 +151,14 @@ def test_keyring_raises_errors_on_keyring_errors(mocker, mock_unavailable_backen def test_keyring_with_chainer_backend_and_not_compatible_only_should_be_unavailable( - mock_chainer_backend, + with_chained_keyring, ): key_ring = KeyRing("poetry") assert not key_ring.is_available() -def test_get_http_auth_from_environment_variables( - environ, config, mock_available_backend -): +def test_get_http_auth_from_environment_variables(environ, config, with_simple_keyring): os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "bar" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "baz" diff --git a/tox.ini b/tox.ini index 722c0e1adf1..a1662f78169 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,10 @@ [tox] minversion = 3.3.0 isolated_build = True -envlist = py36, py37, py38, py39, doc +envlist = py36, py37, py38, py39 [testenv] whitelist_externals = poetry commands = poetry install -vv --no-root poetry run pytest {posargs} tests/ - -[testenv:doc] -whitelist_externals = -skip_install = true -deps = - markdown-include - mkdocs - pygments - pygments-github-lexers - pymdown-extensions -commands = - mkdocs build -f docs/mkdocs.yml