diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index fc88f3abb04..9c03a21d386 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -22,7 +22,7 @@ assignees: '' - **pyproject.toml**: -- [ ] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) stable Poetry version, installed using a recommended method. +- [ ] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) stable Poetry version, installed using a [recommended method](https://python-poetry.org/docs/#installation). - [ ] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate. - [ ] I have consulted the [FAQ](https://python-poetry.org/docs/faq/) and [blog](https://python-poetry.org/blog/) for any relevant entries or release notes. - [ ] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option) and have included the output below. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e8cc1e11119..4f68e8d9851 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,6 +16,10 @@ on: - "**" - "!docs/**" +permissions: + contents: read + pull-requests: write + jobs: deploy: name: Build & Deploy @@ -36,7 +40,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" @@ -51,7 +55,7 @@ jobs: - name: Fetch Documentation run: | python -m pip install poetry - poetry install --only main + poetry install --no-root --only main poetry run python bin/website build --local ./poetry - name: Install Hugo diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb382bdbb5a..116122b6f99 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f5d910fbb87..107a3910521 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 29e15b4b1dd..fe7c98b7259 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,17 +21,17 @@ repos: - id: check-docstring-first - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.10.1 + rev: 23.12.1 hooks: - id: black exclude: tests/([^/]*/)*fixtures/ - repo: https://github.com/pre-commit/pre-commit - rev: v3.5.0 + rev: v3.6.0 hooks: - id: validate_manifest - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.9 hooks: - id: ruff diff --git a/CHANGELOG.md b/CHANGELOG.md index f21ba73efe5..78ac5cdfb9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log + +## [1.7.1] - 2023-11-16 + +### Fixed + +- Fix an issue where sdists that call CLI tools of their build requirements could not be installed ([#8630](https://github.com/python-poetry/poetry/pull/8630)). +- Fix an issue where sdists with symlinks could not be installed due to a broken tarfile datafilter ([#8649](https://github.com/python-poetry/poetry/pull/8649)). +- Fix an issue where `poetry init` failed when trying to add dependencies ([#8655](https://github.com/python-poetry/poetry/pull/8655)). +- Fix an issue where `poetry install` failed if `virtualenvs.create` was set to `false` ([#8672](https://github.com/python-poetry/poetry/pull/8672)). + + ## [1.7.0] - 2023-11-03 ### Added @@ -2024,7 +2035,8 @@ Initial release -[Unreleased]: https://github.com/python-poetry/poetry/compare/1.7.0...master +[Unreleased]: https://github.com/python-poetry/poetry/compare/1.7.1...master +[1.7.1]: https://github.com/python-poetry/poetry/releases/tag/1.7.1 [1.7.0]: https://github.com/python-poetry/poetry/releases/tag/1.7.0 [1.6.1]: https://github.com/python-poetry/poetry/releases/tag/1.6.1 [1.6.0]: https://github.com/python-poetry/poetry/releases/tag/1.6.0 diff --git a/docs/_index.md b/docs/_index.md index 06e800ea818..094e91f7a60 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -25,9 +25,10 @@ on Linux, macOS and Windows. {{% warning %}} Poetry should always be installed in a dedicated virtual environment to isolate it from the rest of your system. -In no case, it should be installed in the environment of the project that is to be managed by Poetry. +It should in no case be installed in the environment of the project that is to be managed by Poetry. This ensures that Poetry's own dependencies will not be accidentally upgraded or uninstalled. (Each of the following installation methods ensures that Poetry is installed into an isolated environment.) +In addition, the isolated virtual environment in which poetry is installed should not be activated for running poetry commands. {{% /warning %}} {{% note %}} @@ -175,6 +176,8 @@ You can also install Poetry from a `git` repository by using the `--git` option: ```bash curl -sSL https://install.python-poetry.org | python3 - --git https://github.com/python-poetry/poetry.git@master ```` +If you want to install different versions of Poetry in parallel, a good approach is the installation with pipx and suffix. + {{< /step >}} {{< step >}} **Add Poetry to your PATH** diff --git a/docs/basic-usage.md b/docs/basic-usage.md index dd872f4a1c5..b9c0a0d7c41 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -128,6 +128,19 @@ any Poetry commands that expect to manipulate an environment. To run your script simply use `poetry run python your_script.py`. Likewise if you have command line tools such as `pytest` or `black` you can run them using `poetry run pytest`. +{{% note %}} +If managing your own virtual environment externally, you do not need to use `poetry run` or `poetry shell` since +you will, presumably, already have activated that virtual environment and made available the correct python instance. +For example, these commands should output the same python path: +```shell +conda activate your_env_name +which python +poetry run which python +poetry shell +which python +``` +{{% /note %}} + ### Activating the virtual environment The easiest way to activate the virtual environment is to create a nested shell with `poetry shell`. diff --git a/docs/cli.md b/docs/cli.md index 787a1afd01f..74ec6dc5334 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -189,7 +189,7 @@ The `--sync` can be combined with group-related options: ```bash poetry install --without dev --sync poetry install --with docs --sync -poetry install --only dev +poetry install --only dev --sync ``` You can also specify the extras you want installed @@ -287,10 +287,13 @@ If you just want to update a few packages and not all, you can list them as such poetry update requests toml ``` -Note that this will not update versions for dependencies outside their version constraints specified -in the `pyproject.toml` file. In other terms, `poetry update foo` will be a no-op if the version constraint -specified for `foo` is `~2.3` or `2.3` and `2.4` is available. In order for `foo` to be updated, you must -update the constraint, for example `^2.3`. You can do this using the `add` command. +Note that this will not update versions for dependencies outside their +[version constraints]({{< relref "dependency-specification#version-constraints" >}}) +specified in the `pyproject.toml` file. +In other terms, `poetry update foo` will be a no-op if the version constraint +specified for `foo` is `~2.3` or `2.3` and `2.4` is available. +In order for `foo` to be updated, you must update the constraint, for example `^2.3`. +You can do this using the `add` command. ### Options @@ -503,7 +506,7 @@ required by ### Options * `--without`: The dependency groups to ignore. -* `--why`: When showing the full list, or a `--tree` for a single package, display why a package is included. +* `--why`: When showing the full list, or a `--tree` for a single package, display whether they are a direct dependency or required by other packages. * `--with`: The optional dependency groups to include. * `--only`: The only dependency groups to include. * `--no-dev`: Do not list the dev dependencies. (**Deprecated**, use `--without dev` or `--only main` instead) @@ -577,6 +580,14 @@ 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]({{< relref "configuration" >}}) for all available settings. +{{% warning %}} +Use `--` to terminate option parsing if your values may start with a hyphen (`-`), e.g. +```bash +poetry config http-basic.custom-repo gitlab-ci-token -- ${GITLAB_JOB_TOKEN} +``` +Without `--` this command will fail if `${GITLAB_JOB_TOKEN}` starts with a hyphen. +{{% /warning%}} + ### Options * `--unset`: Remove the configuration element named by `setting-key`. diff --git a/docs/faq.md b/docs/faq.md index daca15d90aa..050622fc616 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -204,7 +204,7 @@ For example, you might have a Dockerfile that looks something like this: FROM python COPY pyproject.toml poetry.lock . COPY src/ ./src -RUN pip install poetry && poetry install --no-dev +RUN pip install poetry && poetry install --without dev ``` As soon as *any* source file changes, the cache for the `RUN` layer will be invalidated, which forces all 3rd party dependencies (likely the slowest step out of these) to be installed again if you changed any files in `src/`. @@ -221,7 +221,7 @@ FROM python COPY pyproject.toml poetry.lock . RUN pip install poetry && poetry install --no-root --no-directory COPY src/ ./src -RUN poetry install --no-dev +RUN poetry install --without dev ``` The two key options we are using here are `--no-root` (skips installing the project source) and `--no-directory` (skips installing any local directory path dependencies, you can omit this if you don't have any). diff --git a/docs/managing-environments.md b/docs/managing-environments.md index 7a7a16f0779..a663c806397 100644 --- a/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -88,13 +88,13 @@ poetry env info will output something similar to this: ```text -Virtual environment +Virtualenv Python: 3.7.1 Implementation: CPython Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7 Valid: True -System +Base Platform: darwin OS: posix Python: /path/to/main/python diff --git a/docs/pre-commit-hooks.md b/docs/pre-commit-hooks.md index 556f4893adc..1b6938b41be 100644 --- a/docs/pre-commit-hooks.md +++ b/docs/pre-commit-hooks.md @@ -58,7 +58,7 @@ This hook is provided by the [Export Poetry Plugin](https://github.com/python-po {{% /warning %}} {{% note %}} -It is recommended to run the [`poetry-lock`](#poetry-lock) hook prior to this one. +It is recommended to run the [`poetry-lock`](#poetry-lock) hook or [`poetry-check`](#poetry-check) with argument `--lock` prior to this one. {{% /note %}} ### Arguments diff --git a/docs/repositories.md b/docs/repositories.md index ba71b8369b4..a06d45f9f46 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -232,6 +232,14 @@ poetry source add --priority=supplemental https://foo.bar/simple/ There can be more than one supplemental package source. +{{% warning %}} + +Take into account that someone could publish a new package to a primary source +which matches a package in your supplemental source. They could coincidentally +or intentionally replace your dependency with something you did not expect. + +{{% /warning %}} + #### Explicit Package Sources @@ -430,7 +438,7 @@ poetry config repositories.testpypi https://test.pypi.org/legacy/ [Legacy Upload API](https://warehouse.pypa.io/api-reference/legacy.html#upload-api) URLs are typically different to the same one provided by the repository for the simple API. You'll note that in the example of [Test PyPI](https://test.pypi.org/), both the host (`test.pypi.org`) as -well as the path (`/legacy`) are different to it's simple API (`https://test.pypi.org/simple`). +well as the path (`/legacy`) are different to its simple API (`https://test.pypi.org/simple`). {{% /note %}} @@ -490,11 +498,12 @@ if it exists for you use case instead of doing it yourself. Alternatively, you can use environment variables to provide the credentials: ```bash -export POETRY_PYPI_TOKEN_PYPI=my-token -export POETRY_HTTP_BASIC_PYPI_USERNAME= -export POETRY_HTTP_BASIC_PYPI_PASSWORD= +export POETRY_PYPI_TOKEN_FOO=my-token +export POETRY_HTTP_BASIC_FOO_USERNAME= +export POETRY_HTTP_BASIC_FOO_PASSWORD= ``` +where `FOO` is the name of the repository in uppercase (e.g. `PYPI`). See [Using environment variables]({{< relref "configuration#using-environment-variables" >}}) for more information on how to configure Poetry with environment variables. diff --git a/poetry.lock b/poetry.lock index 2d1672364df..f1378501177 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "build" @@ -1500,87 +1500,76 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "xattr" -version = "0.10.1" +version = "1.0.0" description = "Python wrapper for extended filesystem attributes" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "xattr-0.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1"}, - {file = "xattr-0.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503"}, - {file = "xattr-0.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68"}, - {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75"}, - {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79"}, - {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2"}, - {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c"}, - {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81"}, - {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18"}, - {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840"}, - {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1"}, - {file = "xattr-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca"}, - {file = "xattr-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d"}, - {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593"}, - {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06"}, - {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee"}, - {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f"}, - {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b"}, - {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b"}, - {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b"}, - {file = "xattr-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6"}, - {file = "xattr-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5"}, - {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c"}, - {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b"}, - {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2"}, - {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db"}, - {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab"}, - {file = "xattr-0.10.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea"}, - {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0"}, - {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a"}, - {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b"}, - {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6"}, - {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4"}, - {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d"}, - {file = "xattr-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b"}, - {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a"}, - {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5"}, - {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5"}, - {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f"}, - {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e"}, - {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8"}, - {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2"}, - {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894"}, - {file = "xattr-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85"}, - {file = "xattr-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302"}, - {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe"}, - {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d"}, - {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010"}, - {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7"}, - {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e"}, - {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac"}, - {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c"}, - {file = "xattr-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203"}, - {file = "xattr-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22"}, - {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f"}, - {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f"}, - {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d"}, - {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4"}, - {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250"}, - {file = "xattr-0.10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8"}, - {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12"}, - {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4"}, - {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f"}, - {file = "xattr-0.10.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea"}, - {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1"}, - {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d"}, - {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496"}, - {file = "xattr-0.10.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee"}, - {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e"}, - {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6"}, - {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649"}, - {file = "xattr-0.10.1.tar.gz", hash = "sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5"}, + {file = "xattr-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:322f5bb858e59556d67de41b4e42d40b5794bc3944eaf3a3a3e97d1b7352e6ea"}, + {file = "xattr-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ad65a3402e00bbacde61011c8a1ee9ae3bf5c0511b7be195e6c5eabc758ce0e9"}, + {file = "xattr-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:693a91a671ba7aee0dc34d0598334449ce66bdfae476a3674de04d2176bce9a0"}, + {file = "xattr-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f216c756a927836c3417946ecd0083230e44e243b30053e8dfa770bda5c3ffaf"}, + {file = "xattr-1.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fe97714d5069108c16a097c91bb9ac6f20759732de69f1e3983c99d7ff8909c"}, + {file = "xattr-1.0.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6ccca250cbaa0bb8909ed988960252e219798315f6ff3b232c725900afe258c"}, + {file = "xattr-1.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba1f628ac5ca3f82bc527cebb7db5969eb0d0555183f27d6f1c6ddcfa11abcb1"}, + {file = "xattr-1.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4ea16d69910eeec3691fdcc5b0795c28af5fdddb2a77fd5d87caab4f5f0dad83"}, + {file = "xattr-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7b7d2658824ae6fc29a85e6260010d6e4766216173cddd0d421bd07249d93"}, + {file = "xattr-1.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a308f0d0ae672ad6c8a1d30a7a97f2297fcc13392b6e5418aa38c64bbb96036"}, + {file = "xattr-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d8116b4c2d139b39337089749ca08403f90b09a20c41dc5b0f24074ff5e9230"}, + {file = "xattr-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2d99576566d47683a8c6cca2e1803e754d25a7f552b9ee0dc5a1c99408cd9a"}, + {file = "xattr-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69aa6bfcf413e6f3b7bf6565fd6d05c9a0f4a800c78a459aa84dba697a273aa6"}, + {file = "xattr-1.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81f97e6816dd6132c4952c25be236b87e0cb9dcf83b36dac117dea25c29359c"}, + {file = "xattr-1.0.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4a93053beffd077a955f29ff6c5f01e159a9406ace2264ad0c3fb40e94df5b3"}, + {file = "xattr-1.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:919ab1ceecb3b90d3278d278d405806bdbc58325771348148c7129a72defd594"}, + {file = "xattr-1.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6d870ec5114a4f182782169092e102d38b1f96171f9399ef9f18662f09919d04"}, + {file = "xattr-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3434a97a8225e71558ff55d9699a55be80dd606b773873484a90089d2faccf2"}, + {file = "xattr-1.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d5d0550f1cf4bec2ea91d1d1cec222c2171785bf7f9ca29348c1277c4471b24e"}, + {file = "xattr-1.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fef7a10ee0fa9965cfcf10a03ec40d0eda4123ffef7f5448276c15d05e90556b"}, + {file = "xattr-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ae05cb39800b58d502a32a57ae6a69f9f9009898c0c4cb2c470c4e2aba6e4ba0"}, + {file = "xattr-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02eb0ce4669d841d3d95ddcbc4c00042fad7b865420c06158d8ae153cbfd14f0"}, + {file = "xattr-1.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a00b7a737826ecf7248a2defc923c0bd7ead7f3e1ff3646a6139fcaf55dd08"}, + {file = "xattr-1.0.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7baee7be877c8ef7a9ec8c84cd983850a0ffb3640e26bdf2efa321eaa72b2693"}, + {file = "xattr-1.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3adfb72183551b3828766794171d6b7d1799e5806490a9fbaa3bf92bf5f2ef"}, + {file = "xattr-1.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3c42f2857ff30c4f7888e87b2de08c205fdc0fa6f5c75587ec2d35e7d570a487"}, + {file = "xattr-1.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:933b416104d5fad1618b0a36cb86df52955cac3f9ee1939ba8baeb8a6bb0f10f"}, + {file = "xattr-1.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3536eb90cfe0707597db09fb0fba34e1eab0ac821718940087087db1438334a5"}, + {file = "xattr-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e20a57094063c1df93bd26971ec1dc60fcac302c41fc4cf6f1ab7f7304e734ad"}, + {file = "xattr-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae3ee8e4308d7d1219f04db85919fe5c15be94de531f7ce84a44634da56f6dee"}, + {file = "xattr-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:320635b6a939646e314ea0de461b8d9b290ff514c7f663397af02568cc3423c0"}, + {file = "xattr-1.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ccdf9e90fc404fd429e71eed29045b548e4841dfc249b4f781334a79270d78c"}, + {file = "xattr-1.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b5e036ef198a9ff012ec0a5091ca9665692fa641e566128d53f08d363c9b407"}, + {file = "xattr-1.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dc720284ae9e6ec8d6630003104e5c61b5104442f6269e32fe2adab87f929085"}, + {file = "xattr-1.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:62fa9b76b4297303248d858af3d5b2097fd4a24d4ee03dc8c5921d168944828d"}, + {file = "xattr-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:357d79f07d629c84e293931fb2f877abb9f4e0bf54614434b39d3caf7abb3b78"}, + {file = "xattr-1.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:004e5c6af5fe3cf38a519c830c6c0c0a74bb0d5f0fc72d3f82b76418647860bc"}, + {file = "xattr-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f584c9ab5e6ba536898701ca38a168572bebabfe0fca038188ca884005068f69"}, + {file = "xattr-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0cef02f9b33682f74eccc15496d4a6deedb69f87f5833398d897cfd2f92d1770"}, + {file = "xattr-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d5ac274b6b68551b590ef2e42e09b85be61779052893117655f7fc9d76f24fe"}, + {file = "xattr-1.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a68d3edad97b3843fd5c4cc9e0b3f9716719f8688229cbb45c8075eed4d807e"}, + {file = "xattr-1.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6057f1e53174df8691d4fef2d170125e60aad0c8cfc527500f1898972b1bec8"}, + {file = "xattr-1.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:658e5405d389a5461b17de4c9b8888425884f57e8ebd794d23ff3a60e8342cfd"}, + {file = "xattr-1.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:852ca3204cd5d1d1ee91fa454e820ff5195584f81f0784b3db12d553ad3ac97c"}, + {file = "xattr-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0ecda996369532b3dd6118329d6040463254bb655ed6d400fc56dd513d22ab5b"}, + {file = "xattr-1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:17ece9a745ac2fc3f0278475be095170bc102af416227026d84399794bb47664"}, + {file = "xattr-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7a802631d9ce950f1ac7bb5b571a49cb3ee1f4e78c0039a7e4affb933d8287c"}, + {file = "xattr-1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b10d39425d9c6a2d54d1f6c1ed10363cf5b94deecca73d1530e58f90df39f025"}, + {file = "xattr-1.0.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e20243875302722812e091bcd5649c52a2fe03f78c2f7b969ac1333ab48c4a3c"}, + {file = "xattr-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06f487e8e73b26f96ccc5f3c2819c39d8ea75d5fadc2e7445d960ee72860ddd6"}, + {file = "xattr-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:924d05b70ba8947a1df063b4f30f5068f468645c89d3bc98588e6065c766eb7f"}, + {file = "xattr-1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7df1cfa8ac4044cb206c3fbd061303fe34a22617ed3288b0486018ecb1edf111"}, + {file = "xattr-1.0.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9de013198d014c11f8eaddb3e79a3257403781aa54bbc1743149227df908b3e"}, + {file = "xattr-1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0f1b3584733a27eb1671000fd917e21b1eba2d563c593cbbc7436873c4822224"}, + {file = "xattr-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c5481ed9fa04ebac81710d8a02d91cd67f35da16fa0250d1643f55e4d2c0d1"}, + {file = "xattr-1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0d7de4a1db1d4a8a3382c12a914443114c31f8ebe07ab1fcc89b4d2ffe67ee"}, + {file = "xattr-1.0.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8f3e5ef665f093987ec19304cb36b7e8aaa2e0011705f18a75956ec45ee774"}, + {file = "xattr-1.0.0.tar.gz", hash = "sha256:a2c7cb2ef441bf679e240b65e205f08a3ee315e190fea5673e60fd6812e634a5"}, ] [package.dependencies] -cffi = ">=1.0" +cffi = ">=1.16.0" + +[package.extras] +test = ["pytest"] [[package]] name = "zipp" @@ -1600,4 +1589,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "49ef0835550621bbc7a5ade677c17ac2077d6f2892e17441f2984ed12641cda6" +content-hash = "074b4ff68b661151ba011c6f5b2cfe4a2f9a4b2e522abc850ad0ac98b88071f2" diff --git a/pyproject.toml b/pyproject.toml index fe7a2c28d8d..447b32e1f1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,22 +47,23 @@ keyring = "^24.0.0" packaging = ">=20.5" pexpect = "^4.7.0" pkginfo = "^1.9.4" -platformdirs = "^3.0.0" +platformdirs = ">=3.0.0,<5" pyproject-hooks = "^1.0.0" requests = "^2.26" -requests-toolbelt = ">=0.9.1,<2" +requests-toolbelt = "^1.0.0" shellingham = "^1.5" tomli = { version = "^2.0.1", python = "<3.11" } tomlkit = ">=0.11.4,<1.0.0" # trove-classifiers uses calver, so version is unclamped trove-classifiers = ">=2022.5.19" virtualenv = "^20.23.0" -xattr = { version = "^0.10.0", markers = "sys_platform == 'darwin'" } +xattr = { version = "^1.0.0", markers = "sys_platform == 'darwin'" } [tool.poetry.group.dev.dependencies] pre-commit = ">=2.10" [tool.poetry.group.test.dependencies] +coverage = ">=7.2.0" deepdiff = "^6.3" httpretty = "^1.1" pytest = "^7.1" @@ -200,7 +201,6 @@ markers = [ [tool.coverage.report] -exclude_lines = [ - "pragma: no cover", +exclude_also = [ "if TYPE_CHECKING:" ] diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index cbd89ebf614..9f0ecaec792 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -72,13 +72,11 @@ def normalize(cls, policy: str) -> list[str]: else: return [":none:"] - return list( - { - name.strip() if cls.is_reserved(name) else canonicalize_name(name) - for name in policy.strip().split(",") - if name - } - ) + return list({ + name.strip() if cls.is_reserved(name) else canonicalize_name(name) + for name in policy.strip().split(",") + if name + }) @classmethod def validator(cls, policy: str) -> bool: diff --git a/src/poetry/console/commands/add.py b/src/poetry/console/commands/add.py index 37277c94514..fc13f6fc1c4 100644 --- a/src/poetry/console/commands/add.py +++ b/src/poetry/console/commands/add.py @@ -298,5 +298,5 @@ def notify_about_existing_packages(self, existing_packages: list[str]) -> None: " be skipped:\n" ) for name in existing_packages: - self.line(f" • {name}") + self.line(f" - {name}") self.line(self._hint_update_packages) diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py index 570a7f4e768..ac75e377d57 100644 --- a/src/poetry/console/commands/check.py +++ b/src/poetry/console/commands/check.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing import Any from cleo.helpers import option @@ -88,6 +89,37 @@ def _validate_readme(self, readme: str | list[str], poetry_file: Path) -> list[s errors.append(f"Declared README file does not exist: {name}") return errors + def _validate_dependencies_source(self, config: dict[str, Any]) -> list[str]: + """Check dependencies's source are valid""" + sources = {k["name"] for k in config.get("source", [])} + + dependency_declarations: list[ + dict[str, str | dict[str, str] | list[dict[str, str]]] + ] = [] + # scan dependencies and group dependencies settings in pyproject.toml + if "dependencies" in config: + dependency_declarations.append(config["dependencies"]) + + for group in config.get("group", {}).values(): + if "dependencies" in group: + dependency_declarations.append(group["dependencies"]) + + all_referenced_sources: set[str] = set() + + for dependency_declaration in dependency_declarations: + for declaration in dependency_declaration.values(): + if isinstance(declaration, list): + for item in declaration: + if "source" in item: + all_referenced_sources.add(item["source"]) + elif isinstance(declaration, dict) and "source" in declaration: + all_referenced_sources.add(declaration["source"]) + + return [ + f'Invalid source "{source}" referenced in dependencies.' + for source in sorted(all_referenced_sources - sources) + ] + def handle(self) -> int: from poetry.factory import Factory from poetry.pyproject.toml import PyProjectTOML @@ -108,6 +140,8 @@ def handle(self) -> int: errors = self._validate_readme(config["readme"], poetry_file) check_result["errors"].extend(errors) + check_result["errors"] += self._validate_dependencies_source(config) + # Verify that lock file is consistent if self.option("lock") and not self.poetry.locker.is_locked(): check_result["errors"] += ["poetry.lock was not found."] diff --git a/src/poetry/console/commands/debug/info.py b/src/poetry/console/commands/debug/info.py index d76c808cee9..4ce9845ced1 100644 --- a/src/poetry/console/commands/debug/info.py +++ b/src/poetry/console/commands/debug/info.py @@ -15,12 +15,10 @@ def handle(self) -> int: self.line("") self.line("Poetry") self.line( - "\n".join( - [ - f"Version: {self.poetry.VERSION}", - f"Python: {poetry_python_version}", - ] - ) + "\n".join([ + f"Version: {self.poetry.VERSION}", + f"Python: {poetry_python_version}", + ]) ) command = self.get_application().get("env info") diff --git a/src/poetry/console/commands/env/info.py b/src/poetry/console/commands/env/info.py index 2de77f73a6d..34c53ad270e 100644 --- a/src/poetry/console/commands/env/info.py +++ b/src/poetry/console/commands/env/info.py @@ -71,17 +71,15 @@ def _display_complete_info(self, env: Env) -> None: self.line("") - system_env = env.parent_env - python = ".".join(str(v) for v in system_env.version_info[:3]) - self.line("System") + base_env = env.parent_env + python = ".".join(str(v) for v in base_env.version_info[:3]) + self.line("Base") self.line( - "\n".join( - [ - f"Platform: {env.platform}", - f"OS: {env.os}", - f"Python: {python}", - f"Path: {system_env.path}", - f"Executable: {system_env.python}", - ] - ) + "\n".join([ + f"Platform: {env.platform}", + f"OS: {env.os}", + f"Python: {python}", + f"Path: {base_env.path}", + f"Executable: {base_env.python}", + ]) ) diff --git a/src/poetry/console/commands/env/remove.py b/src/poetry/console/commands/env/remove.py index d23fafe5526..4325f045b39 100644 --- a/src/poetry/console/commands/env/remove.py +++ b/src/poetry/console/commands/env/remove.py @@ -45,5 +45,10 @@ def handle(self) -> int: for venv in manager.list(): manager.remove_venv(venv.path) self.line(f"Deleted virtualenv: {venv.path}") + # Since we remove all the virtualenvs, we can also remove the entry + # in the envs file. (Strictly speaking, we should do this explicitly, + # in case it points to a virtualenv that had been removed manually before.) + if manager.envs_file.exists(): + manager.envs_file.remove_section(manager.base_env_name) return 0 diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index cf59517b3a6..fe708c98ab4 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -456,10 +456,12 @@ def _format_requirements(self, requirements: list[dict[str, str]]) -> Requiremen return requires - def _validate_author(self, author: str, default: str) -> str | None: + @staticmethod + def _validate_author(author: str, default: str) -> str | None: from poetry.core.packages.package import AUTHOR_REGEX + from poetry.core.utils.helpers import combine_unicode - author = author or default + author = combine_unicode(author or default) if author in ["n", "no"]: return None @@ -481,6 +483,7 @@ def _validate_package(package: str | None) -> str | None: return package def _get_pool(self) -> RepositoryPool: + from poetry.config.config import Config from poetry.repositories import RepositoryPool from poetry.repositories.pypi_repository import PyPiRepository @@ -489,7 +492,7 @@ def _get_pool(self) -> RepositoryPool: if self._pool is None: self._pool = RepositoryPool() - pool_size = self.poetry.config.installer_max_workers + pool_size = Config.create().installer_max_workers self._pool.add_repository(PyPiRepository(pool_size=pool_size)) return self._pool diff --git a/src/poetry/console/commands/self/show/plugins.py b/src/poetry/console/commands/self/show/plugins.py index 15c98548a9e..9f8299a35fa 100644 --- a/src/poetry/console/commands/self/show/plugins.py +++ b/src/poetry/console/commands/self/show/plugins.py @@ -88,7 +88,7 @@ def _system_project_handle(self) -> int: package = info.package description = " " + package.description if package.description else "" self.line("") - self.line(f" • {name} ({package.version}){description}") + self.line(f" - {name} ({package.version}){description}") provide_line = " " if info.plugins: diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index 8b22e089b8b..1208ca4c3c2 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -48,8 +48,9 @@ class ShowCommand(GroupCommand, EnvCommand): option( "why", None, - "When showing the full list, or a --tree for a single" - " package, also display why it's included.", + "When showing the full list, or a --tree for a single package," + " display whether they are a direct dependency or required by other" + " packages", ), option("latest", "l", "Show the latest version."), option( diff --git a/src/poetry/console/commands/version.py b/src/poetry/console/commands/version.py index 0af2a004719..7e8d118759c 100644 --- a/src/poetry/console/commands/version.py +++ b/src/poetry/console/commands/version.py @@ -86,7 +86,7 @@ def handle(self) -> int: self.line(self.poetry.package.pretty_version) else: self.line( - f"{self.poetry.package.name}" + f"{self.poetry.package.pretty_name}" f" {self.poetry.package.pretty_version}" ) diff --git a/src/poetry/factory.py b/src/poetry/factory.py index 15bd004e747..f4013cc2baf 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -370,9 +370,10 @@ def validate( dependencies = {canonicalize_name(d) for d in dependencies} - if canonicalize_name(config["name"]) in dependencies: + project_name = config.get("name") + if project_name is not None and canonicalize_name(project_name) in dependencies: results["errors"].append( - f"Project name ({config['name']}) is same as one of its dependencies" + f"Project name ({project_name}) is same as one of its dependencies" ) return results diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 545eed25adf..8ca3e2fe975 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -44,10 +44,8 @@ dest = '{dest}' with build.env.DefaultIsolatedEnv() as env: - builder = build.ProjectBuilder( - source_dir=source, - python_executable=env.python_executable, - runner=pyproject_hooks.quiet_subprocess_runner, + builder = build.ProjectBuilder.from_isolated_env( + env, source, runner=pyproject_hooks.quiet_subprocess_runner ) env.install(builder.build_system_requires) env.install(builder.get_requires_for_build('wheel')) @@ -247,8 +245,8 @@ def _from_distribution( else: requires = Path(dist.filename) / "requires.txt" if requires.exists(): - with requires.open(encoding="utf-8") as f: - requirements = parse_requires(f.read()) + text = requires.read_text(encoding="utf-8") + requirements = parse_requires(text) info = cls( name=dist.name, @@ -274,16 +272,13 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo: """ info = None - try: - info = cls._from_distribution(pkginfo.SDist(str(path))) - except ValueError: - # Unable to determine dependencies - # We pass and go deeper - pass - else: - if info.requires_dist is not None: - # we successfully retrieved dependencies from sdist metadata - return info + with contextlib.suppress(ValueError): + sdist = pkginfo.SDist(str(path)) + info = cls._from_distribution(sdist) + + if info is not None and info.requires_dist is not None: + # we successfully retrieved dependencies from sdist metadata + return info # Still not dependencies found # So, we unpack and introspect @@ -313,6 +308,8 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo: # now this is an unpacked directory we know how to deal with new_info = cls.from_directory(path=sdist_dir) + new_info._source_type = "file" + new_info._source_url = path.resolve().as_posix() if not info: return new_info @@ -518,7 +515,8 @@ def from_wheel(cls, path: Path) -> PackageInfo: :param path: Path to wheel. """ try: - return cls._from_distribution(pkginfo.Wheel(str(path))) + wheel = pkginfo.Wheel(str(path)) + return cls._from_distribution(wheel) except ValueError: return PackageInfo() @@ -529,14 +527,12 @@ def from_bdist(cls, path: Path) -> PackageInfo: :param path: Path to bdist. """ - if isinstance(path, (pkginfo.BDist, pkginfo.Wheel)): - cls._from_distribution(dist=path) - if path.suffix == ".whl": return cls.from_wheel(path=path) try: - return cls._from_distribution(pkginfo.BDist(str(path))) + bdist = pkginfo.BDist(str(path)) + return cls._from_distribution(bdist) except ValueError as e: raise PackageInfoError(path, e) diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 4d5546969dd..c97ae727e41 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -35,13 +35,11 @@ class ChefBuildError(ChefError): ... class ChefInstallError(ChefError): def __init__(self, requirements: Collection[str], output: str, error: str) -> None: - message = "\n\n".join( - ( - f"Failed to install {', '.join(requirements)}.", - f"Output:\n{output}", - f"Error:\n{error}", - ) - ) + message = "\n\n".join(( + f"Failed to install {', '.join(requirements)}.", + f"Output:\n{output}", + f"Error:\n{error}", + )) super().__init__(message) self._requirements = requirements diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 0d4ca0575c4..263a3fc1095 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -248,18 +248,18 @@ def _execute_operation(self, operation: Operation) -> None: with self._lock: self._sections[id(operation)] = self._io.section() self._sections[id(operation)].write_line( - f" • {op_message}:" + f" - {op_message}:" " Pending..." ) else: if self._should_write_operation(operation): if not operation.skipped: self._io.write_line( - f" • {op_message}" + f" - {op_message}" ) else: self._io.write_line( - f" • {op_message}: " + f" - {op_message}: " "Skipped " "for the following reason: " f"{operation.skip_reason}" @@ -287,7 +287,7 @@ def _execute_operation(self, operation: Operation) -> None: io = self._io else: message = ( - " " + " -" f" {self.get_operation_message(operation, error=True)}:" " Failed" ) @@ -343,7 +343,7 @@ def _execute_operation(self, operation: Operation) -> None: except KeyboardInterrupt: try: message = ( - " " + " -" f" {self.get_operation_message(operation, warning=True)}:" " Cancelled" ) @@ -363,7 +363,7 @@ def _do_execute_operation(self, operation: Operation) -> int: if self.supports_fancy_output(): self._write( operation, - f" • {operation_message}: " + f" - {operation_message}: " "Skipped " "for the following reason: " f"{operation.skip_reason}", @@ -382,7 +382,7 @@ def _do_execute_operation(self, operation: Operation) -> int: return result operation_message = self.get_operation_message(operation, done=True) - message = f" • {operation_message}" + message = f" - {operation_message}" self._write(operation, message) self._increment_operations_count(operation, True) @@ -516,7 +516,7 @@ def _execute_update(self, operation: Install | Update) -> int: def _execute_uninstall(self, operation: Uninstall) -> int: op_msg = self.get_operation_message(operation) - message = f" • {op_msg}: Removing..." + message = f" - {op_msg}: Removing..." self._write(operation, message) return self._remove(operation.package) @@ -543,7 +543,7 @@ def _install(self, operation: Install | Update) -> int: operation_message = self.get_operation_message(operation) message = ( - f" • {operation_message}:" + f" - {operation_message}:" " Installing..." ) self._write(operation, message) @@ -591,7 +591,7 @@ def _prepare_archive( operation_message = self.get_operation_message(operation) message = ( - f" • {operation_message}:" + f" - {operation_message}:" " Preparing..." ) self._write(operation, message) @@ -630,7 +630,7 @@ def _prepare_git_archive(self, operation: Install | Update) -> Path: operation_message = self.get_operation_message(operation) message = ( - f" • {operation_message}: Cloning..." + f" - {operation_message}: Cloning..." ) self._write(operation, message) @@ -673,7 +673,7 @@ def _install_directory_without_wheel_installer( operation_message = self.get_operation_message(operation) message = ( - f" • {operation_message}:" + f" - {operation_message}:" " Building..." ) self._write(operation, message) @@ -773,7 +773,7 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path: if archive.suffix != ".whl": message = ( - f" • {self.get_operation_message(operation)}:" + f" - {self.get_operation_message(operation)}:" " Preparing..." ) self._write(operation, message) @@ -814,7 +814,7 @@ def _download_archive( operation_message = self.get_operation_message(operation) message = ( - f" • {operation_message}: Downloading..." + f" - {operation_message}: Downloading..." ) progress = None if self.supports_fancy_output(): diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index c30dd1b45ea..12d54f3c567 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -267,7 +267,7 @@ def _do_install(self) -> int: ops = self._get_operations_from_lock(locked_repository) lockfile_repo = LockfileRepository() - self._populate_lockfile_repo(lockfile_repo, ops) + uninstalls = self._populate_lockfile_repo(lockfile_repo, ops) if not self.executor.enabled: # If we are only in lock mode, no need to go any further @@ -324,6 +324,8 @@ def _do_install(self) -> int: for op in transaction.calculate_operations(with_uninstalls=True) if op.job_type == "uninstall" ] + ops + else: + ops = uninstalls + ops # We need to filter operations so that packages # not compatible with the current system, @@ -335,7 +337,7 @@ def _do_install(self) -> int: dep = op.package.to_dependency() if dep.is_file() or dep.is_directory(): dep = cast("PathDependency", dep) - dep.validate(raise_error=True) + dep.validate(raise_error=not op.skipped) # Execute operations status = self._execute(ops) @@ -359,18 +361,19 @@ def _execute(self, operations: list[Operation]) -> int: def _populate_lockfile_repo( self, repo: LockfileRepository, ops: Iterable[Operation] - ) -> None: + ) -> list[Uninstall]: + uninstalls = [] for op in ops: if isinstance(op, Uninstall): + uninstalls.append(op) continue - elif isinstance(op, Update): - package = op.target_package - else: - package = op.package + package = op.target_package if isinstance(op, Update) else op.package if not repo.has_package(package): repo.add_package(package) + return uninstalls + def _get_operations_from_lock( self, locked_repository: Repository ) -> list[Operation]: diff --git a/src/poetry/masonry/builders/editable.py b/src/poetry/masonry/builders/editable.py index e0f5dc5ad7a..076843ab3db 100644 --- a/src/poetry/masonry/builders/editable.py +++ b/src/poetry/masonry/builders/editable.py @@ -231,12 +231,10 @@ def _add_dist_info(self, added_files: list[Path]) -> None: # write PEP 610 metadata direct_url_json = dist_info.joinpath("direct_url.json") direct_url_json.write_text( - json.dumps( - { - "dir_info": {"editable": True}, - "url": self._poetry.file.path.parent.absolute().as_uri(), - } - ) + json.dumps({ + "dir_info": {"editable": True}, + "url": self._poetry.file.path.parent.absolute().as_uri(), + }) ) added_files.append(direct_url_json) diff --git a/src/poetry/publishing/uploader.py b/src/poetry/publishing/uploader.py index ab415dbfdd2..e9b22561b92 100644 --- a/src/poetry/publishing/uploader.py +++ b/src/poetry/publishing/uploader.py @@ -205,13 +205,11 @@ def _upload_file( raise UploadError(f"Archive ({file}) does not exist") data = self.post_data(file) - data.update( - { - # action - ":action": "file_upload", - "protocol_version": "1", - } - ) + data.update({ + # action + ":action": "file_upload", + "protocol_version": "1", + }) data_to_send: list[tuple[str, Any]] = self._prepare_data(data) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 09fbe8677b4..dc39ee11469 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -347,12 +347,10 @@ def _search_for_file(self, dependency: FileDependency) -> Package: if dependency.base is not None: package.root_dir = dependency.base - package.files = [ - { - "file": dependency.path.name, - "hash": "sha256:" + get_file_hash(dependency.full_path), - } - ] + package.files = [{ + "file": dependency.path.name, + "hash": "sha256:" + get_file_hash(dependency.full_path), + }] return package @@ -780,12 +778,10 @@ def debug(self, message: str, depth: int = 0) -> None: if self.is_debugging(): debug_info = str(message) debug_info = ( - "\n".join( - [ - f"{str(depth).rjust(4)}: {s}" - for s in debug_info.split("\n") - ] - ) + "\n".join([ + f"{str(depth).rjust(4)}: {s}" + for s in debug_info.split("\n") + ]) + "\n" ) diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 66f6fb6d86c..26a7d497c70 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -155,12 +155,10 @@ def _get_release_info( for file_info in version_info: if file_info["packagetype"] in SUPPORTED_PACKAGE_TYPES: - data.files.append( - { - "file": file_info["filename"], - "hash": "sha256:" + file_info["digests"]["sha256"], - } - ) + data.files.append({ + "file": file_info["filename"], + "hash": "sha256:" + file_info["digests"]["sha256"], + }) if self._fallback and data.requires_dist is None: self._log("No dependencies found, downloading archives", level="debug") diff --git a/src/poetry/utils/env/env_manager.py b/src/poetry/utils/env/env_manager.py index e7b0cc751d9..ba91f145d22 100644 --- a/src/poetry/utils/env/env_manager.py +++ b/src/poetry/utils/env/env_manager.py @@ -9,6 +9,7 @@ import subprocess import sys +from functools import cached_property from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING @@ -45,6 +46,44 @@ from poetry.utils.env.base_env import Env +class EnvsFile(TOMLFile): + """ + This file contains one section per project with the project's base env name + as section name. Each section contains the minor and patch version of the + python executable used to create the currently active virtualenv. + + Example: + + [poetry-QRErDmmj] + minor = "3.9" + patch = "3.9.13" + + [poetry-core-m5r7DkRA] + minor = "3.11" + patch = "3.11.6" + """ + + def remove_section(self, name: str, minor: str | None = None) -> str | None: + """ + Remove a section from the envs file. + + If "minor" is given, the section is only removed if its minor value + matches "minor". + + Returns the "minor" value of the removed section. + """ + envs = self.read() + current_env = envs.get(name) + if current_env is not None and (not minor or current_env["minor"] == minor): + del envs[name] + self.write(envs) + minor = current_env["minor"] + assert isinstance(minor, str) + return minor + + return None + + class EnvManager: """ Environments manager @@ -121,11 +160,19 @@ def in_project_venv(self) -> Path: venv: Path = self._poetry.file.path.parent / ".venv" return venv + @cached_property + def envs_file(self) -> EnvsFile: + return EnvsFile(self._poetry.config.virtualenvs_path / self.ENVS_FILE) + + @cached_property + def base_env_name(self) -> str: + return self.generate_env_name( + self._poetry.package.name, + str(self._poetry.file.path.parent), + ) + def activate(self, python: str) -> Env: venv_path = self._poetry.config.virtualenvs_path - cwd = self._poetry.file.path.parent - - envs_file = TOMLFile(venv_path / self.ENVS_FILE) try: python_version = Version.parse(python) @@ -170,10 +217,9 @@ def activate(self, python: str) -> Env: return self.get(reload=True) envs = tomlkit.document() - base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) - if envs_file.exists(): - envs = envs_file.read() - current_env = envs.get(base_env_name) + if self.envs_file.exists(): + envs = self.envs_file.read() + current_env = envs.get(self.base_env_name) if current_env is not None: current_minor = current_env["minor"] current_patch = current_env["patch"] @@ -182,7 +228,7 @@ def activate(self, python: str) -> Env: # We need to recreate create = True - name = f"{base_env_name}-py{minor}" + name = f"{self.base_env_name}-py{minor}" venv = venv_path / name # Create if needed @@ -202,29 +248,21 @@ def activate(self, python: str) -> Env: self.create_venv(executable=python_path, force=create) # Activate - envs[base_env_name] = {"minor": minor, "patch": patch} - envs_file.write(envs) + envs[self.base_env_name] = {"minor": minor, "patch": patch} + self.envs_file.write(envs) return self.get(reload=True) def deactivate(self) -> None: venv_path = self._poetry.config.virtualenvs_path - name = self.generate_env_name( - self._poetry.package.name, str(self._poetry.file.path.parent) - ) - envs_file = TOMLFile(venv_path / self.ENVS_FILE) - if envs_file.exists(): - envs = envs_file.read() - env = envs.get(name) - if env is not None: - venv = venv_path / f"{name}-py{env['minor']}" - self._io.write_error_line( - f"Deactivating virtualenv: {venv}" - ) - del envs[name] - - envs_file.write(envs) + if self.envs_file.exists() and ( + minor := self.envs_file.remove_section(self.base_env_name) + ): + venv = venv_path / f"{self.base_env_name}-py{minor}" + self._io.write_error_line( + f"Deactivating virtualenv: {venv}" + ) def get(self, reload: bool = False) -> Env: if self._env is not None and not reload: @@ -237,15 +275,10 @@ def get(self, reload: bool = False) -> Env: precision=2, prefer_active_python=prefer_active_python, io=self._io ).to_string() - venv_path = self._poetry.config.virtualenvs_path - - cwd = self._poetry.file.path.parent - envs_file = TOMLFile(venv_path / self.ENVS_FILE) env = None - base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) - if envs_file.exists(): - envs = envs_file.read() - env = envs.get(base_env_name) + if self.envs_file.exists(): + envs = self.envs_file.read() + env = envs.get(self.base_env_name) if env: python_minor = env["minor"] @@ -272,7 +305,7 @@ def get(self, reload: bool = False) -> Env: venv_path = self._poetry.config.virtualenvs_path - name = f"{base_env_name}-py{python_minor.strip()}" + name = f"{self.base_env_name}-py{python_minor.strip()}" venv = venv_path / name @@ -313,12 +346,6 @@ def check_env_is_for_current_project(env: str, base_env_name: str) -> bool: return env.startswith(base_env_name) def remove(self, python: str) -> Env: - venv_path = self._poetry.config.virtualenvs_path - - cwd = self._poetry.file.path.parent - envs_file = TOMLFile(venv_path / self.ENVS_FILE) - base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) - python_path = Path(python) if python_path.is_file(): # Validate env name if provided env is a full path to python @@ -327,34 +354,21 @@ def remove(self, python: str) -> Env: [python, "-c", GET_ENV_PATH_ONELINER], text=True ).strip("\n") env_name = Path(env_dir).name - if not self.check_env_is_for_current_project(env_name, base_env_name): + if not self.check_env_is_for_current_project( + env_name, self.base_env_name + ): raise IncorrectEnvError(env_name) except CalledProcessError as e: raise EnvCommandError(e) - if self.check_env_is_for_current_project(python, base_env_name): + if self.check_env_is_for_current_project(python, self.base_env_name): venvs = self.list() for venv in venvs: if venv.path.name == python: # Exact virtualenv name - if not envs_file.exists(): - self.remove_venv(venv.path) - - return venv - - venv_minor = ".".join(str(v) for v in venv.version_info[:2]) - base_env_name = self.generate_env_name(cwd.name, str(cwd)) - envs = envs_file.read() - - current_env = envs.get(base_env_name) - if not current_env: - self.remove_venv(venv.path) - - return venv - - if current_env["minor"] == venv_minor: - del envs[base_env_name] - envs_file.write(envs) + if self.envs_file.exists(): + venv_minor = ".".join(str(v) for v in venv.version_info[:2]) + self.envs_file.remove_section(self.base_env_name, venv_minor) self.remove_venv(venv.path) @@ -389,21 +403,14 @@ def remove(self, python: str) -> Env: python_version = Version.parse(python_version_string.strip()) minor = f"{python_version.major}.{python_version.minor}" - name = f"{base_env_name}-py{minor}" + name = f"{self.base_env_name}-py{minor}" venv_path = venv_path / name if not venv_path.exists(): raise ValueError(f'Environment "{name}" does not exist.') - if envs_file.exists(): - envs = envs_file.read() - current_env = envs.get(base_env_name) - if current_env is not None: - current_minor = current_env["minor"] - - if current_minor == minor: - del envs[base_env_name] - envs_file.write(envs) + if self.envs_file.exists(): + self.envs_file.remove_section(self.base_env_name, minor) self.remove_venv(venv_path) diff --git a/src/poetry/utils/env/virtual_env.py b/src/poetry/utils/env/virtual_env.py index c8a81153f66..8a573426fec 100644 --- a/src/poetry/utils/env/virtual_env.py +++ b/src/poetry/utils/env/virtual_env.py @@ -140,7 +140,7 @@ def _updated_path(self) -> str: @cached_property def includes_system_site_packages(self) -> bool: pyvenv_cfg = self._path / "pyvenv.cfg" - return ( + return pyvenv_cfg.exists() and ( re.search( r"^\s*include-system-site-packages\s*=\s*true\s*$", pyvenv_cfg.read_text(), diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py index 5c312a6b6a9..e2ee03b467b 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py @@ -139,8 +139,11 @@ def __init__( self._dest = dest get = requests.get if not session else session.get + headers = {"Accept-Encoding": "Identity"} - self._response = get(url, stream=True, timeout=REQUESTS_TIMEOUT) + self._response = get( + url, stream=True, headers=headers, timeout=REQUESTS_TIMEOUT + ) self._response.raise_for_status() @cached_property diff --git a/src/poetry/utils/shell.py b/src/poetry/utils/shell.py index f8c413afd81..caf08c82348 100644 --- a/src/poetry/utils/shell.py +++ b/src/poetry/utils/shell.py @@ -70,7 +70,13 @@ def get(cls) -> Shell: def activate(self, env: VirtualEnv) -> int | None: activate_script = self._get_activate_script() - bin_dir = "Scripts" if WINDOWS else "bin" + if WINDOWS: + bin_path = env.path / "Scripts" + # Python innstalled via msys2 on Windows might produce a POSIX-like venv + # See https://github.com/python-poetry/poetry/issues/8638 + bin_dir = "Scripts" if bin_path.exists() else "bin" + else: + bin_dir = "bin" activate_path = env.path / bin_dir / activate_script # mypy requires using sys.platform instead of WINDOWS constant @@ -100,7 +106,12 @@ def activate(self, env: VirtualEnv) -> int | None: cmd = f"{self._get_source_command()} {shlex.quote(str(activate_path))}" with env.temp_environ(): - args = ["-e", cmd] if self._name == "nu" else ["-i"] + if self._name == "nu": + args = ["-e", cmd] + elif self._name == "fish": + args = ["-i", "--init-command", cmd] + else: + args = ["-i"] c = pexpect.spawn( self._path, args, dimensions=(terminal.lines, terminal.columns) @@ -114,14 +125,11 @@ def activate(self, env: VirtualEnv) -> int | None: c.sendline(f"emulate bash -c '. {shlex.quote(str(activate_path))}'") elif self._name == "xonsh": c.sendline(f"vox activate {shlex.quote(str(env.path))}") - elif self._name == "nu": - # If this is nu, we don't want to send the activation command to the + elif self._name in ["nu", "fish"]: + # If this is nu or fish, we don't want to send the activation command to the # command line since we already ran it via the shell's invocation. pass else: - if self._name in ["fish"]: - # Under fish, "\r" should be sent explicitly - cmd += "\r" c.sendline(cmd) def resize(sig: Any, data: Any) -> None: diff --git a/tests/console/commands/env/test_info.py b/tests/console/commands/env/test_info.py index c971999d23d..bc9faf24973 100644 --- a/tests/console/commands/env/test_info.py +++ b/tests/console/commands/env/test_info.py @@ -43,7 +43,7 @@ def test_env_info_displays_complete_info(tester: CommandTester) -> None: Executable: {sys.executable} Valid: True -System +Base Platform: darwin OS: posix Python: {'.'.join(str(v) for v in sys.version_info[:3])} diff --git a/tests/console/commands/env/test_remove.py b/tests/console/commands/env/test_remove.py index 9586be90ea1..38998f92634 100644 --- a/tests/console/commands/env/test_remove.py +++ b/tests/console/commands/env/test_remove.py @@ -62,12 +62,32 @@ def test_remove_by_name( assert tester.io.fetch_output() == expected +@pytest.mark.parametrize( + "envs_file", [None, "empty", "self", "other", "self_and_other"] +) def test_remove_all( tester: CommandTester, venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, + envs_file: str | None, ) -> None: + envs_file_path = venv_cache / "envs.toml" + if envs_file == "empty": + envs_file_path.touch() + elif envs_file == "self": + envs_file_path.write_text(f'[{venv_name}]\nminor = "3.9"\npatch = "3.9.1"\n') + elif envs_file == "other": + envs_file_path.write_text('[other-abcdefgh]\nminor = "3.9"\npatch = "3.9.1"\n') + elif envs_file == "self_and_other": + envs_file_path.write_text( + f'[{venv_name}]\nminor = "3.9"\npatch = "3.9.1"\n' + '[other-abcdefgh]\nminor = "3.9"\npatch = "3.9.1"\n' + ) + else: + # no envs file -> nothing to prepare + assert envs_file is None + expected = {""} tester.execute("--all") for name in venvs_in_cache_dirs: @@ -75,6 +95,17 @@ def test_remove_all( expected.add(f"Deleted virtualenv: {venv_cache / name}") assert set(tester.io.fetch_output().split("\n")) == expected + if envs_file is not None: + assert envs_file_path.exists() + envs_file_content = envs_file_path.read_text() + assert venv_name not in envs_file_content + if "other" in envs_file: + assert "other-abcdefgh" in envs_file_content + else: + assert envs_file_content == "" + else: + assert not envs_file_path.exists() + def test_remove_all_and_version( tester: CommandTester, diff --git a/tests/console/commands/self/test_add_plugins.py b/tests/console/commands/self/test_add_plugins.py index 07b0c905b1f..78627afa066 100644 --- a/tests/console/commands/self/test_add_plugins.py +++ b/tests/console/commands/self/test_add_plugins.py @@ -57,7 +57,7 @@ def test_add_no_constraint( Package operations: 1 install, 0 updates, 0 removals - • Installing poetry-plugin (0.1.0) + - Installing poetry-plugin (0.1.0) Writing lock file """ @@ -79,7 +79,7 @@ def test_add_with_constraint( Package operations: 1 install, 0 updates, 0 removals - • Installing poetry-plugin (0.2.0) + - Installing poetry-plugin (0.2.0) Writing lock file """ @@ -101,8 +101,8 @@ def test_add_with_git_constraint( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (2.0.5) - • Installing poetry-plugin (0.1.2 9cf87a2) + - Installing pendulum (2.0.5) + - Installing poetry-plugin (0.1.2 9cf87a2) Writing lock file """ @@ -127,9 +127,9 @@ def test_add_with_git_constraint_with_extras( Package operations: 3 installs, 0 updates, 0 removals - • Installing pendulum (2.0.5) - • Installing tomlkit (0.7.0) - • Installing poetry-plugin (0.1.2 9cf87a2) + - Installing pendulum (2.0.5) + - Installing tomlkit (0.7.0) + - Installing poetry-plugin (0.1.2 9cf87a2) Writing lock file """ @@ -167,8 +167,8 @@ def test_add_with_git_constraint_with_subdirectory( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (2.0.5) - • Installing poetry-plugin (0.1.2 9cf87a2) + - Installing pendulum (2.0.5) + - Installing poetry-plugin (0.1.2 9cf87a2) Writing lock file """ @@ -220,7 +220,7 @@ def test_add_existing_plugin_warns_about_no_operation( The following packages are already present in the pyproject.toml and will be\ skipped: - • poetry-plugin + - poetry-plugin {tester.command._hint_update_packages} Nothing to add. """ @@ -264,7 +264,7 @@ def test_add_existing_plugin_updates_if_requested( Package operations: 0 installs, 1 update, 0 removals - • Updating poetry-plugin (1.2.3 -> 2.3.4) + - Updating poetry-plugin (1.2.3 -> 2.3.4) Writing lock file """ @@ -300,8 +300,8 @@ def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( Package operations: 1 install, 1 update, 0 removals - • Updating tomlkit (0.7.1 -> 0.7.2) - • Installing poetry-plugin (1.2.3) + - Updating tomlkit (0.7.1 -> 0.7.2) + - Installing poetry-plugin (1.2.3) Writing lock file """ diff --git a/tests/console/commands/self/test_remove_plugins.py b/tests/console/commands/self/test_remove_plugins.py index 8000f2cead1..be5e44f6130 100644 --- a/tests/console/commands/self/test_remove_plugins.py +++ b/tests/console/commands/self/test_remove_plugins.py @@ -74,7 +74,7 @@ def test_remove_installed_package(tester: CommandTester) -> None: Package operations: 0 installs, 0 updates, 1 removal - • Removing poetry-plugin (1.2.3) + - Removing poetry-plugin (1.2.3) Writing lock file """ @@ -95,8 +95,8 @@ def test_remove_installed_package_dry_run(tester: CommandTester) -> None: Package operations: 0 installs, 0 updates, 1 removal, 1 skipped - • Removing poetry-plugin (1.2.3) - • Installing poetry ({__version__}): Skipped for the following reason: Already \ + - Removing poetry-plugin (1.2.3) + - Installing poetry ({__version__}): Skipped for the following reason: Already \ installed """ diff --git a/tests/console/commands/self/test_show_plugins.py b/tests/console/commands/self/test_show_plugins.py index 20ae7f5d930..880f821e790 100644 --- a/tests/console/commands/self/test_show_plugins.py +++ b/tests/console/commands/self/test_show_plugins.py @@ -68,16 +68,14 @@ def plugin_distro(plugin_package: Package, tmp_path: Path) -> metadata.Distribut class MockDistribution(metadata.Distribution): def read_text(self, filename: str) -> str | None: if filename == "METADATA": - return "\n".join( - [ - f"Name: {plugin_package.name}", - f"Version: {plugin_package.version}", - *[ - f"Requires-Dist: {dep.to_pep_508()}" - for dep in plugin_package.requires - ], - ] - ) + return "\n".join([ + f"Name: {plugin_package.name}", + f"Version: {plugin_package.version}", + *[ + f"Requires-Dist: {dep.to_pep_508()}" + for dep in plugin_package.requires + ], + ]) return None def locate_file(self, path: str | PathLike[str]) -> Path: @@ -149,12 +147,10 @@ def mock_metadata_entry_points( @pytest.mark.parametrize("entry_point_name", ["poetry-plugin", "not-package-name"]) @pytest.mark.parametrize( "entry_point_values_by_group", - [ - { - ApplicationPlugin.group: ["FirstApplicationPlugin"], - Plugin.group: ["FirstPlugin"], - } - ], + [{ + ApplicationPlugin.group: ["FirstApplicationPlugin"], + Plugin.group: ["FirstPlugin"], + }], ) def test_show_displays_installed_plugins( app: PoetryTestApplication, @@ -163,7 +159,7 @@ def test_show_displays_installed_plugins( tester.execute("") expected = """ - • poetry-plugin (1.2.3) + - poetry-plugin (1.2.3) 1 plugin and 1 application plugin """ @@ -172,15 +168,13 @@ def test_show_displays_installed_plugins( @pytest.mark.parametrize( "entry_point_values_by_group", - [ - { - ApplicationPlugin.group: [ - "FirstApplicationPlugin", - "SecondApplicationPlugin", - ], - Plugin.group: ["FirstPlugin", "SecondPlugin"], - } - ], + [{ + ApplicationPlugin.group: [ + "FirstApplicationPlugin", + "SecondApplicationPlugin", + ], + Plugin.group: ["FirstPlugin", "SecondPlugin"], + }], ) def test_show_displays_installed_plugins_with_multiple_plugins( app: PoetryTestApplication, @@ -189,7 +183,7 @@ def test_show_displays_installed_plugins_with_multiple_plugins( tester.execute("") expected = """ - • poetry-plugin (1.2.3) + - poetry-plugin (1.2.3) 2 plugins and 2 application plugins """ @@ -201,12 +195,10 @@ def test_show_displays_installed_plugins_with_multiple_plugins( ) @pytest.mark.parametrize( "entry_point_values_by_group", - [ - { - ApplicationPlugin.group: ["FirstApplicationPlugin"], - Plugin.group: ["FirstPlugin"], - } - ], + [{ + ApplicationPlugin.group: ["FirstApplicationPlugin"], + Plugin.group: ["FirstPlugin"], + }], ) def test_show_displays_installed_plugins_with_dependencies( app: PoetryTestApplication, @@ -215,7 +207,7 @@ def test_show_displays_installed_plugins_with_dependencies( tester.execute("") expected = """ - • poetry-plugin (1.2.3) + - poetry-plugin (1.2.3) 1 plugin and 1 application plugin Dependencies diff --git a/tests/console/commands/self/test_update.py b/tests/console/commands/self/test_update.py index d2c6ef0c933..808a7663a06 100644 --- a/tests/console/commands/self/test_update.py +++ b/tests/console/commands/self/test_update.py @@ -71,8 +71,8 @@ def test_self_update_can_update_from_recommended_installation( Package operations: 0 installs, 2 updates, 0 removals - • Updating cleo (0.8.2 -> 1.0.0) - • Updating poetry ({__version__} -> {new_version}) + - Updating cleo (0.8.2 -> 1.0.0) + - Updating poetry ({__version__} -> {new_version}) Writing lock file """ diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py index c8fd87a5766..f334f586582 100644 --- a/tests/console/commands/test_add.py +++ b/tests/console/commands/test_add.py @@ -85,7 +85,7 @@ def test_add_no_constraint( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -117,7 +117,7 @@ def test_add_replace_by_constraint( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -138,7 +138,7 @@ def test_add_replace_by_constraint( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.1.0) + - Installing cachy (0.1.0) Writing lock file """ @@ -189,7 +189,7 @@ def test_add_equal_constraint(repo: TestRepository, tester: CommandTester) -> No Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.1.0) + - Installing cachy (0.1.0) Writing lock file """ @@ -212,7 +212,7 @@ def test_add_greater_constraint(repo: TestRepository, tester: CommandTester) -> Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -246,8 +246,8 @@ def test_add_constraint_with_extras( Package operations: 2 installs, 0 updates, 0 removals - • Installing msgpack-python (0.5.3) - • Installing cachy (0.1.0) + - Installing msgpack-python (0.5.3) + - Installing cachy (0.1.0) Writing lock file """ @@ -277,8 +277,8 @@ def test_add_constraint_dependencies( Package operations: 2 installs, 0 updates, 0 removals - • Installing msgpack-python (0.5.3) - • Installing cachy (0.2.0) + - Installing msgpack-python (0.5.3) + - Installing cachy (0.2.0) Writing lock file """ @@ -309,8 +309,8 @@ def test_add_git_constraint( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.2 9cf87a2) + - Installing pendulum (1.4.4) + - Installing demo (0.1.2 9cf87a2) Writing lock file """ @@ -346,8 +346,8 @@ def test_add_git_constraint_with_poetry( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.2 9cf87a2) + - Installing pendulum (1.4.4) + - Installing demo (0.1.2 9cf87a2) Writing lock file """ @@ -380,10 +380,10 @@ def test_add_git_constraint_with_extras( Package operations: 4 installs, 0 updates, 0 removals - • Installing cleo (0.6.5) - • Installing pendulum (1.4.4) - • Installing tomlkit (0.5.5) - • Installing demo (0.1.2 9cf87a2) + - Installing cleo (0.6.5) + - Installing pendulum (1.4.4) + - Installing tomlkit (0.5.5) + - Installing demo (0.1.2 9cf87a2) Writing lock file """ @@ -425,7 +425,7 @@ def test_add_git_constraint_with_subdirectory( Package operations: 1 install, 0 updates, 0 removals - • Installing two (2.0.0 9cf87a2) + - Installing two (2.0.0 9cf87a2) Writing lock file """ @@ -472,8 +472,8 @@ def test_add_git_ssh_constraint( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.2 9cf87a2) + - Installing pendulum (1.4.4) + - Installing demo (0.1.2 9cf87a2) Writing lock file """ @@ -521,8 +521,8 @@ def test_add_directory_constraint( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.2 {demo_path}) + - Installing pendulum (1.4.4) + - Installing demo (0.1.2 {demo_path}) Writing lock file """ @@ -565,8 +565,8 @@ def test_add_directory_with_poetry( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.2 {demo_path}) + - Installing pendulum (1.4.4) + - Installing demo (0.1.2 {demo_path}) Writing lock file """ @@ -598,8 +598,8 @@ def test_add_file_constraint_wheel( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.0 {demo_path}) + - Installing pendulum (1.4.4) + - Installing demo (0.1.0 {demo_path}) Writing lock file """ @@ -637,8 +637,8 @@ def test_add_file_constraint_sdist( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo (0.1.0 {demo_path}) + - Installing pendulum (1.4.4) + - Installing demo (0.1.0 {demo_path}) Writing lock file """ @@ -679,8 +679,8 @@ def test_add_constraint_with_extras_option( Package operations: 2 installs, 0 updates, 0 removals - • Installing msgpack-python (0.5.3) - • Installing cachy (0.2.0) + - Installing msgpack-python (0.5.3) + - Installing cachy (0.2.0) Writing lock file """ @@ -721,8 +721,8 @@ def test_add_url_constraint_wheel( Package operations: 2 installs, 0 updates, 0 removals - • Installing pendulum (1.4.4) - • Installing demo\ + - Installing pendulum (1.4.4) + - Installing demo\ (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl) Writing lock file @@ -764,10 +764,10 @@ def test_add_url_constraint_wheel_with_extras( Package operations: 4 installs, 0 updates, 0 removals - • Installing cleo (0.6.5) - • Installing pendulum (1.4.4) - • Installing tomlkit (0.5.5) - • Installing demo\ + - Installing cleo (0.6.5) + - Installing pendulum (1.4.4) + - Installing tomlkit (0.5.5) + - Installing demo\ (0.1.0 https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl) Writing lock file @@ -837,7 +837,7 @@ def test_add_constraint_with_python( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -876,7 +876,7 @@ def test_add_constraint_with_platform( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -924,7 +924,7 @@ def test_add_constraint_with_source( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -985,7 +985,7 @@ def test_add_to_section_that_does_not_exist_yet( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -1034,7 +1034,7 @@ def test_add_to_dev_section_deprecated( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -1067,7 +1067,7 @@ def test_add_should_not_select_prereleases( Package operations: 1 install, 0 updates, 0 removals - • Installing pyyaml (3.13) + - Installing pyyaml (3.13) Writing lock file """ @@ -1097,7 +1097,7 @@ def test_add_should_skip_when_adding_existing_package_with_no_constraint( expected = """\ The following packages are already present in the pyproject.toml and will be skipped: - • foo + - foo If you want to update it to the latest compatible version,\ you can use `poetry update package`. @@ -1122,7 +1122,7 @@ def test_add_should_skip_when_adding_canonicalized_existing_package_with_no_cons expected = """\ The following packages are already present in the pyproject.toml and will be skipped: - • Foo_Bar + - Foo_Bar If you want to update it to the latest compatible version,\ you can use `poetry update package`. @@ -1203,7 +1203,7 @@ def test_add_should_work_when_adding_existing_package_with_latest_constraint( Package operations: 1 install, 0 updates, 0 removals - • Installing foo (1.1.2) + - Installing foo (1.1.2) Writing lock file """ @@ -1233,7 +1233,7 @@ def test_add_chooses_prerelease_if_only_prereleases_are_available( Package operations: 1 install, 0 updates, 0 removals - • Installing foo (1.2.3b1) + - Installing foo (1.2.3b1) Writing lock file """ @@ -1256,7 +1256,7 @@ def test_add_prefers_stable_releases( Package operations: 1 install, 0 updates, 0 removals - • Installing foo (1.2.3) + - Installing foo (1.2.3) Writing lock file """ @@ -1303,7 +1303,7 @@ def test_add_to_section_that_does_no_exist_yet( Package operations: 1 install, 0 updates, 0 removals - • Installing cachy (0.2.0) + - Installing cachy (0.2.0) Writing lock file """ @@ -1462,9 +1462,9 @@ def test_add_extras_are_parsed_and_included( Package operations: 3 installs, 0 updates, 0 removals - • Installing msgpack-python (0.5.1) - • Installing redis (3.4.0) - • Installing cachy (0.2.0) + - Installing msgpack-python (0.5.1) + - Installing redis (3.4.0) + - Installing cachy (0.2.0) Writing lock file """ diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index d5e8a44cea8..eb94772f3ca 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -5,6 +5,7 @@ import pytest from poetry.packages import Locker +from poetry.toml import TOMLFile if TYPE_CHECKING: @@ -67,8 +68,6 @@ def test_check_valid(tester: CommandTester) -> None: def test_check_invalid( mocker: MockerFixture, tester: CommandTester, fixture_dir: FixtureDirGetter ) -> None: - from poetry.toml import TOMLFile - mocker.patch( "poetry.poetry.Poetry.file", return_value=TOMLFile(fixture_dir("invalid_pyproject") / "pyproject.toml"), @@ -77,13 +76,15 @@ def test_check_invalid( tester.execute("--lock") - jsonschema_error = "'description' is a required property" fastjsonschema_error = "data must contain ['description'] properties" + custom_error = "The fields ['description'] are required in package mode." expected_template = """\ Error: {schema_error} Error: Project name (invalid) is same as one of its dependencies Error: Unrecognized classifiers: ['Intended Audience :: Clowns']. Error: Declared README file does not exist: never/exists.md +Error: Invalid source "not-exists" referenced in dependencies. +Error: Invalid source "not-exists2" referenced in dependencies. Error: poetry.lock was not found. Warning: A wildcard Python dependency is ambiguous.\ Consider specifying a more explicit one. @@ -97,7 +98,7 @@ def test_check_invalid( """ expected = { expected_template.format(schema_error=schema_error) - for schema_error in (jsonschema_error, fastjsonschema_error) + for schema_error in (fastjsonschema_error, custom_error) } assert tester.io.fetch_error() in expected @@ -107,8 +108,9 @@ def test_check_private( mocker: MockerFixture, tester: CommandTester, fixture_dir: FixtureDirGetter ) -> None: mocker.patch( - "poetry.factory.Factory.locate", - return_value=fixture_dir("private_pyproject") / "pyproject.toml", + "poetry.poetry.Poetry.file", + return_value=TOMLFile(fixture_dir("private_pyproject") / "pyproject.toml"), + new_callable=mocker.PropertyMock, ) tester.execute() @@ -135,8 +137,6 @@ def test_check_lock_missing( expected: str, expected_status: int, ) -> None: - from poetry.toml import TOMLFile - mocker.patch( "poetry.poetry.Poetry.file", return_value=TOMLFile(fixture_dir("private_pyproject") / "pyproject.toml"), diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 2a0a9bcaa25..f18570a4eed 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -62,19 +62,17 @@ def tester(patches: None) -> CommandTester: @pytest.fixture def init_basic_inputs() -> str: - return "\n".join( - [ - "my-package", # Package name - "1.2.3", # Version - "This is a description", # Description - "n", # Author - "MIT", # License - "~2.7 || ^3.6", # Python - "n", # Interactive packages - "n", # Interactive dev packages - "\n", # Generate - ] - ) + return "\n".join([ + "my-package", # Package name + "1.2.3", # Version + "This is a description", # Description + "n", # Author + "MIT", # License + "~2.7 || ^3.6", # Python + "n", # Interactive packages + "n", # Interactive dev packages + "\n", # Generate + ]) @pytest.fixture() @@ -995,6 +993,22 @@ def test_validate_package_invalid(name: str) -> None: assert InitCommand._validate_package(name) +@pytest.mark.parametrize( + "author", + [ + str(b"Jos\x65\xcc\x81 Duarte", "utf-8"), + str(b"Jos\xc3\xa9 Duarte", "utf-8"), + ], +) +def test_validate_author(author: str) -> None: + """ + This test was added following issue #8779, hence, we're looking to see if the test + no longer throws an exception, hence the seemingly "useless" test of just running + the method. + """ + InitCommand._validate_author(author, "") + + @pytest.mark.parametrize( "package_name, include", ( @@ -1087,3 +1101,17 @@ def mock_check_output(cmd: str, *_: Any, **__: Any) -> str: """ assert expected in pyproject_file.read_text() + + +def test_get_pool(mocker: MockerFixture, source_dir: Path) -> None: + """ + Since we are mocking _get_pool() in the other tests, we at least should make + sure it works in general. See https://github.com/python-poetry/poetry/issues/8634. + """ + mocker.patch("pathlib.Path.cwd", return_value=source_dir) + + app = Application() + command = app.find("init") + assert isinstance(command, InitCommand) + pool = command._get_pool() + assert pool.repositories diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index bf0789c8616..ad42d093465 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -416,7 +416,7 @@ def test_install_logs_output_decorated( assert tester.io.fetch_output() == expected -@pytest.mark.parametrize("with_root", [True]) +@pytest.mark.parametrize("with_root", [True, False]) @pytest.mark.parametrize("error", ["module", "readme", ""]) def test_install_warning_corrupt_root( command_tester_factory: CommandTesterFactory, @@ -470,6 +470,25 @@ def test_install_path_dependency_does_not_exist( tester.execute(options) +@pytest.mark.parametrize("options", ["", "--extras notinstallable"]) +def test_install_extra_path_dependency_does_not_exist( + command_tester_factory: CommandTesterFactory, + project_factory: ProjectFactory, + fixture_dir: FixtureDirGetter, + options: str, +) -> None: + project = "missing_extra_directory_dependency" + poetry = _project_factory(project, project_factory, fixture_dir) + assert isinstance(poetry.locker, TestLocker) + poetry.locker.locked(True) + tester = command_tester_factory("install", poetry=poetry) + if not options: + tester.execute(options) + else: + with pytest.raises(ValueError, match="does not exist"): + tester.execute(options) + + @pytest.mark.parametrize("options", ["", "--no-directory"]) def test_install_missing_directory_dependency_with_no_directory( command_tester_factory: CommandTesterFactory, diff --git a/tests/console/commands/test_remove.py b/tests/console/commands/test_remove.py index 8cb0650e776..5fbff275172 100644 --- a/tests/console/commands/test_remove.py +++ b/tests/console/commands/test_remove.py @@ -327,7 +327,7 @@ def test_remove_performs_uninstall_op( Package operations: 0 installs, 0 updates, 1 removal - • Removing docker (4.3.1) + - Removing docker (4.3.1) Writing lock file """ diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py index 162fa73a13d..f45f12ad9f7 100644 --- a/tests/console/commands/test_show.py +++ b/tests/console/commands/test_show.py @@ -51,45 +51,43 @@ def test_show_basic_with_installed_packages( installed.add_package(pytest_373) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pytest", - "version": "3.7.3", - "description": "Pytest package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "pytest": []}, + }, + }) tester.execute() @@ -128,45 +126,43 @@ def _configure_project_with_groups(poetry: Poetry, installed: Repository) -> Non installed.add_package(pytest_373) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pytest", - "version": "3.7.3", - "description": "Pytest package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "pytest": []}, + }, + }) @pytest.mark.parametrize( @@ -271,27 +267,25 @@ def test_show_basic_with_installed_packages_single( installed.add_package(cachy_010) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { - "python-versions": "*", + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": []}, + }, + }) tester.execute("cachy") @@ -313,27 +307,25 @@ def test_show_basic_with_installed_packages_single_canonicalized( installed.add_package(foo_bar) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "foo-bar", - "version": "0.1.0", - "description": "Foobar package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { - "python-versions": "*", + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "foo-bar", + "version": "0.1.0", + "description": "Foobar package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"foo-bar": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"foo-bar": []}, + }, + }) tester.execute("Foo_Bar") @@ -359,36 +351,34 @@ def test_show_basic_with_not_installed_packages_non_decorated( installed.add_package(cachy_010) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute() @@ -415,36 +405,34 @@ def test_show_basic_with_not_installed_packages_decorated( installed.add_package(cachy_010) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute(decorated=True) @@ -484,36 +472,34 @@ def test_show_latest_non_decorated( repo.add_package(pendulum_201) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--latest") @@ -553,36 +539,34 @@ def test_show_latest_decorated( repo.add_package(pendulum_201) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--latest", decorated=True) @@ -621,36 +605,34 @@ def test_show_outdated( repo.add_package(pendulum_200) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--outdated") @@ -674,27 +656,25 @@ def test_show_outdated_with_only_up_to_date_packages( repo.add_package(cachy_020) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.2.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { - "python-versions": "*", + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "Cachy package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": []}, + }, + }) tester.execute("--outdated") @@ -733,36 +713,34 @@ def test_show_outdated_has_prerelease_but_not_allowed( repo.add_package(pendulum_200) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--outdated") @@ -807,36 +785,34 @@ def test_show_outdated_has_prerelease_and_allowed( repo.add_package(pendulum_200) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0.dev1", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0.dev1", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--outdated") @@ -875,36 +851,34 @@ def test_show_outdated_formatting( repo.add_package(pendulum_201) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--outdated") @@ -957,73 +931,71 @@ def test_show_outdated_local_dependencies( repo.add_package(pendulum_200) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.2.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "demo", - "version": "0.1.0", - "description": "Demo package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "source": { - "type": "file", - "reference": "", - "url": "../distributions/demo-0.1.0-py2.py3-none-any.whl", - }, - }, - { - "name": "project-with-setup", - "version": "0.1.1", - "description": "Demo project.", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": { - "pendulum": ">=1.4.4", - "cachy": {"version": ">=0.2.0", "extras": ["msgpack"]}, - }, - "source": { - "type": "directory", - "reference": "", - "url": "../project_with_setup", - }, - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": { - "cachy": [], - "pendulum": [], - "demo": [], - "project-with-setup": [], + "python-versions": "*", + "checksum": [], + }, + { + "name": "demo", + "version": "0.1.0", + "description": "Demo package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "source": { + "type": "file", + "reference": "", + "url": "../distributions/demo-0.1.0-py2.py3-none-any.whl", }, }, - } - ) + { + "name": "project-with-setup", + "version": "0.1.1", + "description": "Demo project.", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": { + "pendulum": ">=1.4.4", + "cachy": {"version": ">=0.2.0", "extras": ["msgpack"]}, + }, + "source": { + "type": "directory", + "reference": "", + "url": "../project_with_setup", + }, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": { + "cachy": [], + "pendulum": [], + "demo": [], + "project-with-setup": [], + }, + }, + }) tester.execute("--outdated") @@ -1069,60 +1041,58 @@ def test_show_outdated_git_dev_dependency( repo.add_package(pytest) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "demo", - "version": "0.1.1", - "description": "Demo package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "source": { - "type": "git", - "reference": MOCK_DEFAULT_GIT_REVISION, - "resolved_reference": MOCK_DEFAULT_GIT_REVISION, - "url": "https://github.com/demo/demo.git", - }, - }, - { - "name": "pytest", - "version": "3.4.3", - "description": "Pytest", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "demo": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "demo", + "version": "0.1.1", + "description": "Demo package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "source": { + "type": "git", + "reference": MOCK_DEFAULT_GIT_REVISION, + "resolved_reference": MOCK_DEFAULT_GIT_REVISION, + "url": "https://github.com/demo/demo.git", + }, + }, + { + "name": "pytest", + "version": "3.4.3", + "description": "Pytest", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "demo": [], "pytest": []}, + }, + }) tester.execute("--outdated") @@ -1166,59 +1136,57 @@ def test_show_outdated_no_dev_git_dev_dependency( repo.add_package(pytest) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "demo", - "version": "0.1.1", - "description": "Demo package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "source": { - "type": "git", - "reference": MOCK_DEFAULT_GIT_REVISION, - "url": "https://github.com/demo/pyproject-demo.git", - }, - }, - { - "name": "pytest", - "version": "3.4.3", - "description": "Pytest", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "demo": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "demo", + "version": "0.1.1", + "description": "Demo package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "source": { + "type": "git", + "reference": MOCK_DEFAULT_GIT_REVISION, + "url": "https://github.com/demo/pyproject-demo.git", + }, + }, + { + "name": "pytest", + "version": "3.4.3", + "description": "Pytest", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "demo": [], "pytest": []}, + }, + }) tester.execute("--outdated --without dev") @@ -1249,36 +1217,34 @@ def test_show_hides_incompatible_package( installed.add_package(pendulum_200) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute() @@ -1304,37 +1270,35 @@ def test_show_all_shows_incompatible_package( installed.add_package(pendulum_200) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "requirements": {"python": "1.0"}, - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "requirements": {"python": "1.0"}, + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": []}, + }, + }) tester.execute("--all") @@ -1360,31 +1324,29 @@ def test_show_hides_incompatible_package_with_duplicate( ) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "files": [], - }, - { - "name": "cachy", - "version": "0.1.1", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "files": [], - }, - ], - "metadata": {"content-hash": "123456789"}, - } - ) + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", + "python-versions": "*", + "files": [], + }, + { + "name": "cachy", + "version": "0.1.1", + "description": "Cachy package", + "optional": False, + "platform": "*", + "python-versions": "*", + "files": [], + }, + ], + "metadata": {"content-hash": "123456789"}, + }) tester.execute() @@ -1409,31 +1371,29 @@ def test_show_all_shows_all_duplicates( ) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "files": [], - }, - { - "name": "cachy", - "version": "0.1.1", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "files": [], - }, - ], - "metadata": {"content-hash": "123456789"}, - } - ) + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", + "python-versions": "*", + "files": [], + }, + { + "name": "cachy", + "version": "0.1.1", + "description": "Cachy package", + "optional": False, + "platform": "*", + "python-versions": "*", + "files": [], + }, + ], + "metadata": {"content-hash": "123456789"}, + }) tester.execute("--all") @@ -1468,45 +1428,43 @@ def test_show_non_dev_with_basic_installed_packages( installed.add_package(pytest_373) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pytest", - "version": "3.7.3", - "description": "Pytest package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "pytest": []}, + }, + }) tester.execute("--without dev") @@ -1541,45 +1499,43 @@ def test_show_with_group_only( installed.add_package(pytest_373) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pytest", - "version": "3.7.3", - "description": "Pytest package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "pytest": []}, + }, + }) tester.execute("--only dev") @@ -1613,45 +1569,43 @@ def test_show_with_optional_group( installed.add_package(pytest_373) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.1.0", - "description": "Cachy package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pytest", - "version": "3.7.3", - "description": "Pytest package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.1.0", + "description": "Cachy package", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "pytest", + "version": "3.7.3", + "description": "Pytest package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "pytest": []}, + }, + }) tester.execute() @@ -1684,37 +1638,35 @@ def test_show_tree( installed.add_package(cachy2) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.2.0", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"msgpack-python": ">=0.5 <0.6"}, - }, - { - "name": "msgpack-python", - "version": "0.5.1", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"msgpack-python": ">=0.5 <0.6"}, + }, + { + "name": "msgpack-python", + "version": "0.5.1", + "description": "", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "msgpack-python": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "msgpack-python": []}, + }, + }) tester.execute("--tree", supports_utf8=False) @@ -1742,46 +1694,44 @@ def test_show_tree_no_dev( installed.add_package(pytest) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.2.0", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"msgpack-python": ">=0.5 <0.6"}, - }, - { - "name": "msgpack-python", - "version": "0.5.1", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "pytest", - "version": "6.1.1", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"msgpack-python": ">=0.5 <0.6"}, + }, + { + "name": "msgpack-python", + "version": "0.5.1", + "description": "", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "msgpack-python": [], "pytest": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "pytest", + "version": "6.1.1", + "description": "", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "msgpack-python": [], "pytest": []}, + }, + }) tester.execute("--tree --without dev") @@ -1810,38 +1760,36 @@ def test_show_tree_why_package( installed.add_package(c) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "a", - "version": "0.0.1", - "dependencies": {"b": "=0.0.1"}, - "python-versions": "*", - "optional": False, - }, - { - "name": "b", - "version": "0.0.1", - "dependencies": {"c": "=0.0.1"}, - "python-versions": "*", - "optional": False, - }, - { - "name": "c", - "version": "0.0.1", - "python-versions": "*", - "optional": False, - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "a", + "version": "0.0.1", + "dependencies": {"b": "=0.0.1"}, "python-versions": "*", - "platform": "*", - "content-hash": "123456789", - "files": {"a": [], "b": [], "c": []}, + "optional": False, }, - } - ) + { + "name": "b", + "version": "0.0.1", + "dependencies": {"c": "=0.0.1"}, + "python-versions": "*", + "optional": False, + }, + { + "name": "c", + "version": "0.0.1", + "python-versions": "*", + "optional": False, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"a": [], "b": [], "c": []}, + }, + }) tester.execute("--tree --why b") @@ -1870,38 +1818,36 @@ def test_show_tree_why( installed.add_package(c) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "a", - "version": "0.0.1", - "dependencies": {"b": "=0.0.1"}, - "python-versions": "*", - "optional": False, - }, - { - "name": "b", - "version": "0.0.1", - "dependencies": {"c": "=0.0.1"}, - "python-versions": "*", - "optional": False, - }, - { - "name": "c", - "version": "0.0.1", - "python-versions": "*", - "optional": False, - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "a", + "version": "0.0.1", + "dependencies": {"b": "=0.0.1"}, "python-versions": "*", - "platform": "*", - "content-hash": "123456789", - "files": {"a": [], "b": [], "c": []}, + "optional": False, }, - } - ) + { + "name": "b", + "version": "0.0.1", + "dependencies": {"c": "=0.0.1"}, + "python-versions": "*", + "optional": False, + }, + { + "name": "c", + "version": "0.0.1", + "python-versions": "*", + "optional": False, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"a": [], "b": [], "c": []}, + }, + }) tester.execute("--why") @@ -1928,47 +1874,45 @@ def test_show_required_by_deps( installed.add_package(pendulum) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "cachy", - "version": "0.2.0", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"msgpack-python": ">=0.5 <0.6"}, - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"cachy": ">=0.2.0 <0.3.0"}, - }, - { - "name": "msgpack-python", - "version": "0.5.1", - "description": "", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "cachy", + "version": "0.2.0", + "description": "", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"msgpack-python": ">=0.5 <0.6"}, + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "pendulum": [], "msgpack-python": []}, + "python-versions": "*", + "checksum": [], + "dependencies": {"cachy": ">=0.2.0 <0.3.0"}, }, - } - ) + { + "name": "msgpack-python", + "version": "0.5.1", + "description": "", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "pendulum": [], "msgpack-python": []}, + }, + }) tester.execute("cachy") @@ -2022,41 +1966,39 @@ def test_show_dependency_installed_from_git_in_dev( # The git package is the one that gets into the lockfile. assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "demo", - "version": "0.1.2", - "description": "Demo package", - "optional": False, - "python-versions": "*", - "develop": False, - "source": { - "type": "git", - "reference": MOCK_DEFAULT_GIT_REVISION, - "resolved_reference": MOCK_DEFAULT_GIT_REVISION, - "url": "https://github.com/demo/demo.git", - }, - }, - { - "name": "pendulum", - "version": "2.0.0", - "description": "Pendulum package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "demo", + "version": "0.1.2", + "description": "Demo package", + "optional": False, "python-versions": "*", + "develop": False, + "source": { + "type": "git", + "reference": MOCK_DEFAULT_GIT_REVISION, + "resolved_reference": MOCK_DEFAULT_GIT_REVISION, + "url": "https://github.com/demo/demo.git", + }, + }, + { + "name": "pendulum", + "version": "2.0.0", + "description": "Pendulum package", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"demo": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"demo": [], "pendulum": []}, + }, + }) # Nothing needs updating, there is no confusion between the git and not-git # packages. @@ -2083,31 +2025,27 @@ def test_url_dependency_is_not_outdated_by_repository_package( repo.add_package(demo_100) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "demo", - "version": "0.1.0", - "description": "Demo package", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "source": { - "type": "url", - "url": demo_url, - }, - } - ], - "metadata": { - "python-versions": "*", - "platform": "*", - "content-hash": "123456789", - "hashes": {"demo": []}, - }, - } - ) + poetry.locker.mock_lock_data({ + "package": [{ + "name": "demo", + "version": "0.1.0", + "description": "Demo package", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "source": { + "type": "url", + "url": demo_url, + }, + }], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "hashes": {"demo": []}, + }, + }) # The url dependency on demo is not made outdated by the existence of a newer # version in the repository. @@ -2126,39 +2064,37 @@ def test_show_top_level( installed.add_package(cachy2) assert isinstance(poetry.locker, TestLocker) - 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": "msgpack-python", - "version": "0.5.1", - "description": "", - "category": "main", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + 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": "msgpack-python", + "version": "0.5.1", + "description": "", + "category": "main", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"cachy": [], "msgpack-python": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"cachy": [], "msgpack-python": []}, + }, + }) tester.execute("--top-level") @@ -2181,39 +2117,37 @@ def test_show_top_level_with_explicitly_defined_depenancy( installed.add_package(b) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "a", - "version": "0.1.0", - "description": "", - "category": "main", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"b": "0.2.0"}, - }, - { - "name": "b", - "version": "0.2.0", - "description": "", - "category": "main", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "a", + "version": "0.1.0", + "description": "", + "category": "main", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"b": "0.2.0"}, + }, + { + "name": "b", + "version": "0.2.0", + "description": "", + "category": "main", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"a": [], "b": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"a": [], "b": []}, + }, + }) tester.execute("--top-level") @@ -2244,45 +2178,43 @@ def test_show_top_level_with_extras( installed.add_package(black_package) assert isinstance(poetry.locker, TestLocker) - poetry.locker.mock_lock_data( - { - "package": [ - { - "name": "black", - "version": "23.3.0", - "description": "", - "category": "main", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": { - "aiohttp": { - "version": ">=3.7.4", - "optional": True, - "markers": 'extra == "d"', - } - }, - }, - { - "name": "aiohttp", - "version": "3.8.4", - "description": "", - "category": "main", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + poetry.locker.mock_lock_data({ + "package": [ + { + "name": "black", + "version": "23.3.0", + "description": "", + "category": "main", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": { + "aiohttp": { + "version": ">=3.7.4", + "optional": True, + "markers": 'extra == "d"', + } + }, + }, + { + "name": "aiohttp", + "version": "3.8.4", + "description": "", + "category": "main", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"black": [], "aiohttp": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"black": [], "aiohttp": []}, + }, + }) tester.execute("--top-level") diff --git a/tests/console/commands/test_version.py b/tests/console/commands/test_version.py index 8d671273bbb..4772a400be1 100644 --- a/tests/console/commands/test_version.py +++ b/tests/console/commands/test_version.py @@ -10,7 +10,10 @@ if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester + from poetry.poetry import Poetry from tests.types import CommandTesterFactory + from tests.types import FixtureDirGetter + from tests.types import ProjectFactory @pytest.fixture() @@ -23,6 +26,18 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester: return command_tester_factory("version") +@pytest.fixture +def poetry_with_underscore( + project_factory: ProjectFactory, fixture_dir: FixtureDirGetter +) -> Poetry: + source = fixture_dir("simple_project") + pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") + pyproject_content = pyproject_content.replace("simple-project", "simple_project") + return project_factory( + "project_with_underscore", pyproject_content=pyproject_content + ) + + @pytest.mark.parametrize( "version, rule, expected", [ @@ -79,6 +94,14 @@ def test_version_show(tester: CommandTester) -> None: assert tester.io.fetch_output() == "simple-project 1.2.3\n" +def test_version_show_with_underscore( + command_tester_factory: CommandTesterFactory, poetry_with_underscore: Poetry +) -> None: + tester = command_tester_factory("version", poetry=poetry_with_underscore) + tester.execute() + assert tester.io.fetch_output() == "simple_project 1.2.3\n" + + def test_short_version_show(tester: CommandTester) -> None: tester.execute("--short") assert tester.io.fetch_output() == "1.2.3\n" diff --git a/tests/fixtures/distributions/demo_no_pkg_info-0.1.0.tar.gz b/tests/fixtures/distributions/demo_no_pkg_info-0.1.0.tar.gz new file mode 100644 index 00000000000..9b9c20ace8c Binary files /dev/null and b/tests/fixtures/distributions/demo_no_pkg_info-0.1.0.tar.gz differ diff --git a/tests/fixtures/invalid_pyproject/pyproject.toml b/tests/fixtures/invalid_pyproject/pyproject.toml index bafa0936489..94c7d9fb4d5 100644 --- a/tests/fixtures/invalid_pyproject/pyproject.toml +++ b/tests/fixtures/invalid_pyproject/pyproject.toml @@ -17,3 +17,12 @@ classifiers = [ python = "*" pendulum = {"version" = "^2.0.5", allows-prereleases = true} invalid = "1.0" +invalid_source = { "version" = "*", source = "not-exists" } +invalid_source_multi = [ + { "version" = "*", platform = "linux", source = "exists" }, + { "version" = "*", platform = "win32", source = "not-exists2" }, +] + +[[tool.poetry.source]] +name = "exists" +priority = "explicit" diff --git a/tests/fixtures/missing_extra_directory_dependency/poetry.lock b/tests/fixtures/missing_extra_directory_dependency/poetry.lock new file mode 100644 index 00000000000..1b9c1817ca2 --- /dev/null +++ b/tests/fixtures/missing_extra_directory_dependency/poetry.lock @@ -0,0 +1,22 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "missing" +version = "1.2.3" +description = "Some description." +optional = true +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "directory" +url = "missing" + +[extras] +notinstallable = ["missing"] + +[metadata] +lock-version = "2.0" +python-versions = "*" +content-hash = "4f4f0f292967d2df3ec32007be09f917a08730a19378d535cc9463a11dd3df01" diff --git a/tests/fixtures/missing_extra_directory_dependency/pyproject.toml b/tests/fixtures/missing_extra_directory_dependency/pyproject.toml new file mode 100644 index 00000000000..1c8fc05ec3e --- /dev/null +++ b/tests/fixtures/missing_extra_directory_dependency/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "project-with-missing-extra-directory-dependency" +version = "1.2.3" +description = "This is a description" +authors = ["Your Name "] +license = "MIT" +packages = [] + +[tool.poetry.dependencies] +python = "*" +missing = { path = "./missing", optional = true } + +[tool.poetry.extras] +notinstallable = ["missing"] diff --git a/tests/fixtures/nameless_pyproject/pyproject.toml b/tests/fixtures/nameless_pyproject/pyproject.toml new file mode 100644 index 00000000000..213d2646749 --- /dev/null +++ b/tests/fixtures/nameless_pyproject/pyproject.toml @@ -0,0 +1,8 @@ +[tool.poetry] +version = "0.1.0" +description = "" +authors = ["Foo "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" diff --git a/tests/inspection/test_info.py b/tests/inspection/test_info.py index 297af700286..dfa0f1dafc8 100644 --- a/tests/inspection/test_info.py +++ b/tests/inspection/test_info.py @@ -1,5 +1,7 @@ from __future__ import annotations +import shutil + from subprocess import CalledProcessError from typing import TYPE_CHECKING @@ -57,15 +59,13 @@ def demo_setup(source_dir: Path) -> Path: def demo_setup_cfg(source_dir: Path) -> Path: setup_cfg = source_dir / "setup.cfg" setup_cfg.write_text( - "\n".join( - [ - "[metadata]", - "name = demo", - "version = 0.1.0", - "[options]", - "install_requires = package", - ] - ) + "\n".join([ + "[metadata]", + "name = demo", + "version = 0.1.0", + "[options]", + "install_requires = package", + ]) ) return source_dir @@ -89,6 +89,33 @@ def demo_setup_complex_pep517_legacy(demo_setup_complex: Path) -> Path: return demo_setup_complex +@pytest.fixture +def demo_setup_complex_calls_script( + fixture_dir: FixtureDirGetter, source_dir: Path, tmp_path: Path +) -> Path: + # make sure the scripts project is on the same drive (for Windows tests in CI) + scripts_dir = tmp_path / "scripts" + shutil.copytree(fixture_dir("scripts"), scripts_dir) + + pyproject = source_dir / "pyproject.toml" + pyproject.write_text(f"""\ + [build-system] + requires = ["setuptools", "scripts @ {scripts_dir.as_uri()}"] + build-backend = "setuptools.build_meta:__legacy__" +""") + + setup_py = source_dir / "setup.py" + setup_py.write_text("""\ +import subprocess +from setuptools import setup +if subprocess.call(["exit-code"]) != 42: + raise RuntimeError("Wrong exit code.") +setup(name="demo", version="0.1.0", install_requires=[i for i in ["package"]]) +""") + + return source_dir + + def demo_check_info(info: PackageInfo, requires_dist: set[str] | None = None) -> None: assert info.name == "demo" assert info.version == "0.1.0" @@ -116,16 +143,30 @@ def demo_check_info(info: PackageInfo, requires_dist: set[str] | None = None) -> def test_info_from_sdist(demo_sdist: Path) -> None: info = PackageInfo.from_sdist(demo_sdist) demo_check_info(info) + assert info._source_type == "file" + assert info._source_url == demo_sdist.resolve().as_posix() + + +def test_info_from_sdist_no_pkg_info(fixture_dir: FixtureDirGetter) -> None: + path = fixture_dir("distributions") / "demo_no_pkg_info-0.1.0.tar.gz" + info = PackageInfo.from_sdist(path) + demo_check_info(info) + assert info._source_type == "file" + assert info._source_url == path.resolve().as_posix() def test_info_from_wheel(demo_wheel: Path) -> None: info = PackageInfo.from_wheel(demo_wheel) demo_check_info(info) + assert info._source_type == "file" + assert info._source_url == demo_wheel.resolve().as_posix() def test_info_from_bdist(demo_wheel: Path) -> None: info = PackageInfo.from_bdist(demo_wheel) demo_check_info(info) + assert info._source_type == "file" + assert info._source_url == demo_wheel.resolve().as_posix() def test_info_from_poetry_directory(fixture_dir: FixtureDirGetter) -> None: @@ -233,6 +274,13 @@ def test_info_setup_complex_disable_build( assert info.requires_dist is None +@pytest.mark.network +def test_info_setup_complex_calls_script(demo_setup_complex_calls_script: Path) -> None: + """Building the project requires calling a script from its build_requires.""" + info = PackageInfo.from_directory(demo_setup_complex_calls_script) + demo_check_info(info, requires_dist={"package"}) + + @pytest.mark.network @pytest.mark.parametrize("missing", ["version", "name", "install_requires"]) def test_info_setup_missing_mandatory_should_trigger_pep517( diff --git a/tests/installation/conftest.py b/tests/installation/conftest.py new file mode 100644 index 00000000000..c19a17f6f88 --- /dev/null +++ b/tests/installation/conftest.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import re + +from pathlib import Path +from typing import TYPE_CHECKING +from typing import Any +from urllib.parse import urlparse + +import pytest + + +if TYPE_CHECKING: + from httpretty import httpretty + from httpretty.core import HTTPrettyRequest + + from tests.types import FixtureDirGetter + + +@pytest.fixture +def mock_file_downloads(http: type[httpretty], fixture_dir: FixtureDirGetter) -> None: + def callback( + request: HTTPrettyRequest, uri: str, headers: dict[str, Any] + ) -> list[int | dict[str, Any] | bytes]: + name = Path(urlparse(uri).path).name + + fixture = Path(__file__).parent.parent.joinpath( + "repositories/fixtures/pypi.org/dists/" + name + ) + + if not fixture.exists(): + fixture = fixture_dir("distributions") / name + + if not fixture.exists(): + fixture = ( + fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" + ) + + return [200, headers, fixture.read_bytes()] + + http.register_uri( + http.GET, + re.compile("^https://files.pythonhosted.org/.*$"), + body=callback, + ) diff --git a/tests/installation/test_chef.py b/tests/installation/test_chef.py index 9a7e63c75c9..ba2b9eff9cf 100644 --- a/tests/installation/test_chef.py +++ b/tests/installation/test_chef.py @@ -50,7 +50,9 @@ def setup(mocker: MockerFixture, pool: RepositoryPool) -> None: mocker.patch.object(Factory, "create_pool", return_value=pool) -def test_isolated_env_install_success(pool: RepositoryPool) -> None: +def test_isolated_env_install_success( + pool: RepositoryPool, mock_file_downloads: None +) -> None: with ephemeral_environment(Path(sys.executable)) as venv: env = IsolatedEnv(venv, pool) assert "poetry-core" not in venv.run("pip", "freeze") @@ -85,12 +87,12 @@ def test_isolated_env_install_failure( assert e.value.requirements == {"a", "b>1"} -@pytest.mark.network def test_prepare_sdist( config: Config, config_cache_dir: Path, artifact_cache: ArtifactCache, fixture_dir: FixtureDirGetter, + mock_file_downloads: None, ) -> None: chef = Chef( artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) @@ -104,12 +106,12 @@ def test_prepare_sdist( assert wheel.name == "demo-0.1.0-py3-none-any.whl" -@pytest.mark.network def test_prepare_directory( config: Config, config_cache_dir: Path, artifact_cache: ArtifactCache, fixture_dir: FixtureDirGetter, + mock_file_downloads: None, ) -> None: chef = Chef( artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) @@ -145,12 +147,12 @@ def test_prepare_directory_with_extensions( os.unlink(wheel) -@pytest.mark.network def test_prepare_directory_editable( config: Config, config_cache_dir: Path, artifact_cache: ArtifactCache, fixture_dir: FixtureDirGetter, + mock_file_downloads: None, ) -> None: chef = Chef( artifact_cache, EnvManager.get_system_env(), Factory.create_pool(config) diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py index c2027dcbd29..0a27b5c7afe 100644 --- a/tests/installation/test_chooser.py +++ b/tests/installation/test_chooser.py @@ -275,12 +275,12 @@ def test_chooser_chooses_distributions_that_match_the_package_hashes( chooser = Chooser(pool, env) package = Package("isort", "4.3.4") - files = [ - { - "hash": "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", - "filename": "isort-4.3.4.tar.gz", - } - ] + files = [{ + "hash": ( + "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8" + ), + "filename": "isort-4.3.4.tar.gz", + }] if source_type == "legacy": package = Package( package.name, @@ -308,12 +308,12 @@ def test_chooser_chooses_yanked_if_no_others( chooser = Chooser(pool, env) package = Package("black", "21.11b0") - files = [ - { - "filename": "black-21.11b0-py3-none-any.whl", - "hash": "sha256:0b1f66cbfadcd332ceeaeecf6373d9991d451868d2e2219ad0ac1213fb701117", - } - ] + files = [{ + "filename": "black-21.11b0-py3-none-any.whl", + "hash": ( + "sha256:0b1f66cbfadcd332ceeaeecf6373d9991d451868d2e2219ad0ac1213fb701117" + ), + }] if source_type == "legacy": package = Package( package.name, @@ -385,12 +385,12 @@ def test_chooser_throws_an_error_if_package_hashes_do_not_match( chooser = Chooser(pool, env) package = Package("isort", "4.3.4") - files = [ - { - "hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "filename": "isort-4.3.4.tar.gz", - } - ] + files = [{ + "hash": ( + "sha256:0000000000000000000000000000000000000000000000000000000000000000" + ), + "filename": "isort-4.3.4.tar.gz", + }] if source_type == "legacy": package = Package( package.name, @@ -419,11 +419,11 @@ def test_chooser_md5_remote_fallback_to_sha256_inline_calculation( source_reference="foo", source_url="https://foo.bar/simple/", ) - package.files = [ - { - "hash": "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", - "filename": "demo-0.1.0.tar.gz", - } - ] + package.files = [{ + "hash": ( + "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" + ), + "filename": "demo-0.1.0.tar.gz", + }] res = chooser.choose_for(package) assert res.filename == "demo-0.1.0.tar.gz" diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index 3b2aec4e315..17aeba7b8fd 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING from typing import Any from typing import Callable -from urllib.parse import urlparse import pytest @@ -40,9 +39,6 @@ if TYPE_CHECKING: from collections.abc import Iterator - import httpretty - - from httpretty.core import HTTPrettyRequest from pytest_mock import MockerFixture from poetry.config.config import Config @@ -134,36 +130,6 @@ def pool() -> RepositoryPool: return pool -@pytest.fixture -def mock_file_downloads( - http: type[httpretty.httpretty], fixture_dir: FixtureDirGetter -) -> None: - def callback( - request: HTTPrettyRequest, uri: str, headers: dict[str, Any] - ) -> list[int | dict[str, Any] | bytes]: - name = Path(urlparse(uri).path).name - - fixture = Path(__file__).parent.parent.joinpath( - "repositories/fixtures/pypi.org/dists/" + name - ) - - if not fixture.exists(): - fixture = fixture_dir("distributions") / name - - if not fixture.exists(): - fixture = ( - fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" - ) - - return [200, headers, fixture.read_bytes()] - - http.register_uri( - http.GET, - re.compile("^https://files.pythonhosted.org/.*$"), - body=callback, - ) - - @pytest.fixture def copy_wheel(tmp_path: Path, fixture_dir: FixtureDirGetter) -> Callable[[], Path]: def _copy_wheel() -> Path: @@ -240,29 +206,27 @@ def test_execute_executes_a_batch_of_operations( develop=True, ) - return_code = executor.execute( - [ - Install(Package("pytest", "3.5.1")), - Uninstall(Package("attrs", "17.4.0")), - Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")), - Update(Package("pytest", "3.5.1"), Package("pytest", "3.5.0")), - Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"), - Install(file_package), - Install(directory_package), - Install(git_package), - ] - ) + return_code = executor.execute([ + Install(Package("pytest", "3.5.1")), + Uninstall(Package("attrs", "17.4.0")), + Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")), + Update(Package("pytest", "3.5.1"), Package("pytest", "3.5.0")), + Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"), + Install(file_package), + Install(directory_package), + Install(git_package), + ]) expected = f""" Package operations: 4 installs, 2 updates, 1 removal - • Installing pytest (3.5.1) - • Removing attrs (17.4.0) - • Updating requests (2.18.3 -> 2.18.4) - • Downgrading pytest (3.5.1 -> 3.5.0) - • Installing demo (0.1.0 {file_package.source_url}) - • Installing simple-project (1.2.3 {directory_package.source_url}) - • Installing demo (0.1.0 master) + - Installing pytest (3.5.1) + - Removing attrs (17.4.0) + - Updating requests (2.18.3 -> 2.18.4) + - Downgrading pytest (3.5.1 -> 3.5.0) + - Installing demo (0.1.0 {file_package.source_url}) + - Installing simple-project (1.2.3 {directory_package.source_url}) + - Installing demo (0.1.0 master) """ expected_lines = set(expected.splitlines()) @@ -352,26 +316,24 @@ def test_execute_prints_warning_for_invalid_wheels( base_url = "https://files.pythonhosted.org/" wheel1 = "demo_invalid_record-0.1.0-py2.py3-none-any.whl" wheel2 = "demo_invalid_record2-0.1.0-py2.py3-none-any.whl" - return_code = executor.execute( - [ - Install( - Package( - "demo-invalid-record", - "0.1.0", - source_type="url", - source_url=f"{base_url}/{wheel1}", - ) - ), - Install( - Package( - "demo-invalid-record2", - "0.1.0", - source_type="url", - source_url=f"{base_url}/{wheel2}", - ) - ), - ] - ) + return_code = executor.execute([ + Install( + Package( + "demo-invalid-record", + "0.1.0", + source_type="url", + source_url=f"{base_url}/{wheel1}", + ) + ), + Install( + Package( + "demo-invalid-record2", + "0.1.0", + source_type="url", + source_url=f"{base_url}/{wheel2}", + ) + ), + ]) warning1 = f"""\ Warning: Validation of the RECORD file of {wheel1} failed.\ @@ -419,7 +381,7 @@ def test_execute_shows_skipped_operations_if_verbose( expected = """ Package operations: 0 installs, 0 updates, 0 removals, 1 skipped - • Removing clikit (0.2.3): Skipped for the following reason: Not currently installed + - Removing clikit (0.2.3): Skipped for the following reason: Not currently installed """ assert io.fetch_output() == expected assert len(env.executed) == 0 @@ -442,7 +404,7 @@ def test_execute_should_show_errors( expected = """ Package operations: 1 install, 0 updates, 0 removals - • Installing clikit (0.2.3) + - Installing clikit (0.2.3) Exception @@ -464,19 +426,17 @@ def test_execute_works_with_ansi_output( executor = Executor(env, pool, config, io_decorated) - return_code = executor.execute( - [ - Install(Package("cleo", "1.0.0a5")), - ] - ) + return_code = executor.execute([ + Install(Package("cleo", "1.0.0a5")), + ]) # fmt: off expected = [ "\x1b[39;1mPackage operations\x1b[39;22m: \x1b[34m1\x1b[39m install, \x1b[34m0\x1b[39m updates, \x1b[34m0\x1b[39m removals", - "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m", - "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m", - "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m", - "\x1b[32;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[32m1.0.0a5\x1b[39m\x1b[39m)\x1b[39m", # finished + "\x1b[34;1m-\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m", + "\x1b[34;1m-\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m", + "\x1b[34;1m-\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m1.0.0a5\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m", + "\x1b[32;1m-\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mcleo\x1b[39m\x1b[39m (\x1b[39m\x1b[32m1.0.0a5\x1b[39m\x1b[39m)\x1b[39m", # finished ] # fmt: on @@ -501,16 +461,14 @@ def test_execute_works_with_no_ansi_output( executor = Executor(env, pool, config, io_not_decorated) - return_code = executor.execute( - [ - Install(Package("cleo", "1.0.0a5")), - ] - ) + return_code = executor.execute([ + Install(Package("cleo", "1.0.0a5")), + ]) expected = """ Package operations: 1 install, 0 updates, 0 removals - • Installing cleo (1.0.0a5) + - Installing cleo (1.0.0a5) """ expected_lines = set(expected.splitlines()) output_lines = set(io_not_decorated.fetch_output().splitlines()) @@ -536,8 +494,8 @@ def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_inter expected = """ Package operations: 1 install, 0 updates, 0 removals - • Installing clikit (0.2.3) - • Installing clikit (0.2.3): Cancelled + - Installing clikit (0.2.3) + - Installing clikit (0.2.3): Cancelled """ assert io.fetch_output() == expected @@ -557,6 +515,7 @@ def test_execute_should_gracefully_handle_io_error( def write_line(string: str, **kwargs: Any) -> None: # Simulate UnicodeEncodeError + string = string.replace("-", "•") string.encode("ascii") original_write_line(string, **kwargs) @@ -667,12 +626,12 @@ def test_executor_should_not_write_pep610_url_references_for_cached_package( io: BufferedIO, ) -> None: link_cached = fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl" - package.files = [ - { - "file": "demo-0.1.0-py2.py3-none-any.whl", - "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", - } - ] + package.files = [{ + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": ( + "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" + ), + }] mocker.patch( "poetry.installation.executor.Executor._download", return_value=link_cached @@ -694,12 +653,12 @@ def test_executor_should_write_pep610_url_references_for_wheel_files( url = (fixture_dir("distributions") / "demo-0.1.0-py2.py3-none-any.whl").resolve() package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) # Set package.files so the executor will attempt to hash the package - package.files = [ - { - "file": "demo-0.1.0-py2.py3-none-any.whl", - "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", - } - ] + package.files = [{ + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": ( + "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" + ), + }] executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) @@ -723,16 +682,17 @@ def test_executor_should_write_pep610_url_references_for_non_wheel_files( config: Config, io: BufferedIO, fixture_dir: FixtureDirGetter, + mock_file_downloads: None, ) -> None: url = (fixture_dir("distributions") / "demo-0.1.0.tar.gz").resolve() package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) # Set package.files so the executor will attempt to hash the package - package.files = [ - { - "file": "demo-0.1.0.tar.gz", - "hash": "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", - } - ] + package.files = [{ + "file": "demo-0.1.0.tar.gz", + "hash": ( + "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" + ), + }] executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) @@ -836,12 +796,12 @@ def test_executor_should_write_pep610_url_references_for_wheel_urls( source_url="https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", ) # Set package.files so the executor will attempt to hash the package - package.files = [ - { - "file": "demo-0.1.0-py2.py3-none-any.whl", - "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", - } - ] + package.files = [{ + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": ( + "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" + ), + }] executor = Executor(tmp_venv, pool, config, io) operation = Install(package) @@ -930,12 +890,12 @@ def mock_get_cached_archive_func( source_url="https://files.pythonhosted.org/demo-0.1.0.tar.gz", ) # Set package.files so the executor will attempt to hash the package - package.files = [ - { - "file": "demo-0.1.0.tar.gz", - "hash": "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", - } - ] + package.files = [{ + "file": "demo-0.1.0.tar.gz", + "hash": ( + "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" + ), + }] executor = Executor(tmp_venv, pool, config, io) operation = Install(package) @@ -1203,12 +1163,10 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( "poetry.factory.Factory.create_poetry", side_effect=RuntimeError ) - config.merge( - { - "cache-dir": str(tmp_path), - "installer": {"modern-installation": False}, - } - ) + config.merge({ + "cache-dir": str(tmp_path), + "installer": {"modern-installation": False}, + }) executor = Executor(env, pool, config, io) @@ -1219,16 +1177,14 @@ def test_executor_fallback_on_poetry_create_error_without_wheel_installer( source_url=fixture_dir("simple_project").resolve().as_posix(), ) - return_code = executor.execute( - [ - Install(directory_package), - ] - ) + return_code = executor.execute([ + Install(directory_package), + ]) expected = f""" Package operations: 1 install, 0 updates, 0 removals - • Installing simple-project (1.2.3 {directory_package.source_url}) + - Installing simple-project (1.2.3 {directory_package.source_url}) """ expected_lines = set(expected.splitlines()) @@ -1290,7 +1246,7 @@ def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess( expected_start = f""" Package operations: 1 install, 0 updates, 0 removals - • Installing {package_name} ({package_version} {package_url}) + - Installing {package_name} ({package_version} {package_url}) ChefBuildError @@ -1392,7 +1348,7 @@ def test_build_system_requires_not_available( expected_start = f"""\ Package operations: 1 install, 0 updates, 0 removals - • Installing {package_name} ({package_version} {package_url}) + - Installing {package_name} ({package_version} {package_url}) SolveFailure @@ -1439,7 +1395,7 @@ def test_build_system_requires_install_failure( expected_start = f"""\ Package operations: 1 install, 0 updates, 0 removals - • Installing {package_name} ({package_version} {package_url}) + - Installing {package_name} ({package_version} {package_url}) ChefInstallError @@ -1491,7 +1447,7 @@ def test_other_error( expected_start = f"""\ Package operations: 1 install, 0 updates, 0 removals - • Installing {package_name} ({package_version} {package_url}) + - Installing {package_name} ({package_version} {package_url}) FileNotFoundError """ diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index fca6307bef5..b903348c4d9 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -234,42 +234,40 @@ def test_run_update_after_removing_dependencies( installed: CustomInstalledRepository, ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "B", - "version": "1.1", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "C", - "version": "1.2", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "B", + "version": "1.1", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": [], "C": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "C", + "version": "1.2", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": [], "C": []}, + }, + }) package_a = get_package("A", "1.0") package_b = get_package("B", "1.1") package_c = get_package("C", "1.2") @@ -308,42 +306,40 @@ def _configure_run_install_dev( Perform common test setup for `test_run_install_*dev*()` methods. """ locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "B", - "version": "1.1", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "C", - "version": "1.2", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "B", + "version": "1.1", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "C", + "version": "1.2", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": [], "C": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": [], "C": []}, + }, + }) package_a = get_package("A", "1.0") package_b = get_package("B", "1.1") package_c = get_package("C", "1.2") @@ -437,42 +433,40 @@ def test_run_install_does_not_remove_locked_packages_if_installed_but_not_requir ) locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": package_a.name, - "version": package_a.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": package_b.name, - "version": package_b.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": package_c.name, - "version": package_c.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": package_a.name, + "version": package_a.version.text, + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": package_b.name, + "version": package_b.version.text, + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {package_a.name: [], package_b.name: [], package_c.name: []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": package_c.name, + "version": package_c.version.text, + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {package_a.name: [], package_b.name: [], package_c.name: []}, + }, + }) result = installer.run() assert result == 0 @@ -507,42 +501,40 @@ def test_run_install_removes_locked_packages_if_installed_and_synchronization_is ) locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": package_a.name, - "version": package_a.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": package_b.name, - "version": package_b.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": package_c.name, - "version": package_c.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": package_a.name, + "version": package_a.version.text, + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": package_b.name, + "version": package_b.version.text, + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {package_a.name: [], package_b.name: [], package_c.name: []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": package_c.name, + "version": package_c.version.text, + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {package_a.name: [], package_b.name: [], package_c.name: []}, + }, + }) installer.requires_synchronization(True) installer.run() @@ -577,42 +569,40 @@ def test_run_install_removes_no_longer_locked_packages_if_installed( ) locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": package_a.name, - "version": package_a.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": package_b.name, - "version": package_b.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": package_c.name, - "version": package_c.version.text, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": package_a.name, + "version": package_a.version.text, + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": package_b.name, + "version": package_b.version.text, + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {package_a.name: [], package_b.name: [], package_c.name: []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": package_c.name, + "version": package_c.version.text, + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {package_a.name: [], package_b.name: [], package_c.name: []}, + }, + }) installer.update(True) result = installer.run() @@ -663,27 +653,25 @@ def test_run_install_with_synchronization( ) locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": pkg.name, - "version": pkg.version, - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - } - for pkg in locked_packages - ], - "metadata": { - "python-versions": "*", + locker.mock_lock_data({ + "package": [ + { + "name": pkg.name, + "version": pkg.version, + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {pkg.name: [] for pkg in locked_packages}, - }, - } - ) + "python-versions": "*", + "checksum": [], + } + for pkg in locked_packages + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {pkg.name: [] for pkg in locked_packages}, + }, + }) installer.requires_synchronization(True) result = installer.run() @@ -707,26 +695,22 @@ def test_run_whitelist_add( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - } - ], - "metadata": { - "python-versions": "*", - "platform": "*", - "content-hash": "123456789", - "files": {"A": []}, - }, - } - ) + locker.mock_lock_data({ + "package": [{ + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": []}, + }, + }) package_a = get_package("A", "1.0") package_a_new = get_package("A", "1.1") package_b = get_package("B", "1.1") @@ -755,34 +739,32 @@ def test_run_whitelist_remove( installed: CustomInstalledRepository, ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "B", - "version": "1.1", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + }, + { + "name": "B", + "version": "1.1", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": []}, + }, + }) package_a = get_package("A", "1.0") package_b = get_package("B", "1.1") repo.add_package(package_a) @@ -1009,73 +991,20 @@ def test_run_with_dependencies_nested_extras( assert locker.written_data == expected -def test_run_does_not_install_extras_if_not_requested( - installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -) -> None: - package.extras[canonicalize_name("foo")] = [get_dependency("D")] - package_a = get_package("A", "1.0") - package_b = get_package("B", "1.0") - package_c = get_package("C", "1.0") - package_d = get_package("D", "1.1") - - repo.add_package(package_a) - repo.add_package(package_b) - repo.add_package(package_c) - repo.add_package(package_d) - - package.add_dependency(Factory.create_dependency("A", "^1.0")) - package.add_dependency(Factory.create_dependency("B", "^1.0")) - package.add_dependency(Factory.create_dependency("C", "^1.0")) - package.add_dependency( - Factory.create_dependency("D", {"version": "^1.0", "optional": True}) - ) - - result = installer.run() - assert result == 0 - - expected = fixture("extras") - # Extras are pinned in lock - assert locker.written_data == expected - - # But should not be installed - assert installer.executor.installations_count == 3 # A, B, C - - -def test_run_installs_extras_if_requested( - installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -) -> None: - package.extras[canonicalize_name("foo")] = [get_dependency("D")] - package_a = get_package("A", "1.0") - package_b = get_package("B", "1.0") - package_c = get_package("C", "1.0") - package_d = get_package("D", "1.1") - - repo.add_package(package_a) - repo.add_package(package_b) - repo.add_package(package_c) - repo.add_package(package_d) - - package.add_dependency(Factory.create_dependency("A", "^1.0")) - package.add_dependency(Factory.create_dependency("B", "^1.0")) - package.add_dependency(Factory.create_dependency("C", "^1.0")) - package.add_dependency( - Factory.create_dependency("D", {"version": "^1.0", "optional": True}) - ) - - installer.extras(["foo"]) - result = installer.run() - assert result == 0 - - # Extras are pinned in lock - expected = fixture("extras") - assert locker.written_data == expected - - # But should not be installed - assert installer.executor.installations_count == 4 # A, B, C, D - - +@pytest.mark.parametrize("is_locked", [False, True]) +@pytest.mark.parametrize("is_installed", [False, True]) +@pytest.mark.parametrize("with_extras", [False, True]) +@pytest.mark.parametrize("do_sync", [False, True]) def test_run_installs_extras_with_deps_if_requested( - installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage + installer: Installer, + locker: Locker, + repo: Repository, + installed: CustomInstalledRepository, + package: ProjectPackage, + is_locked: bool, + is_installed: bool, + with_extras: bool, + do_sync: bool, ) -> None: package.extras[canonicalize_name("foo")] = [get_dependency("C")] package_a = get_package("A", "1.0") @@ -1096,48 +1025,38 @@ def test_run_installs_extras_with_deps_if_requested( package_c.add_dependency(Factory.create_dependency("D", "^1.0")) - installer.extras(["foo"]) - result = installer.run() - assert result == 0 - - expected = fixture("extras-with-dependencies") - - # Extras are pinned in lock - assert locker.written_data == expected - - # But should not be installed - assert installer.executor.installations_count == 4 # A, B, C, D - - -def test_run_installs_extras_with_deps_if_requested_locked( - installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage -) -> None: - locker.locked(True) - locker.mock_lock_data(fixture("extras-with-dependencies")) - package.extras[canonicalize_name("foo")] = [get_dependency("C")] - package_a = get_package("A", "1.0") - package_b = get_package("B", "1.0") - package_c = get_package("C", "1.0") - package_d = get_package("D", "1.1") - - repo.add_package(package_a) - repo.add_package(package_b) - repo.add_package(package_c) - repo.add_package(package_d) - - package.add_dependency(Factory.create_dependency("A", "^1.0")) - package.add_dependency(Factory.create_dependency("B", "^1.0")) - package.add_dependency( - Factory.create_dependency("C", {"version": "^1.0", "optional": True}) - ) + if is_locked: + locker.locked(True) + locker.mock_lock_data(fixture("extras-with-dependencies")) - package_c.add_dependency(Factory.create_dependency("D", "^1.0")) + if is_installed: + installed.add_package(package_a) + installed.add_package(package_b) + installed.add_package(package_c) + installed.add_package(package_d) - installer.extras(["foo"]) + if with_extras: + installer.extras(["foo"]) + installer.requires_synchronization(do_sync) result = installer.run() assert result == 0 - assert installer.executor.installations_count == 4 # A, B, C, D + if not is_locked: + assert locker.written_data == fixture("extras-with-dependencies") + + if with_extras: + # A, B, C, D + expected_installations_count = 0 if is_installed else 4 + expected_removals_count = 0 + else: + # A, B + expected_installations_count = 0 if is_installed else 2 + # We only want to uninstall extras if we do a "poetry install" without extras, + # not if we do a "poetry update" or "poetry add". + expected_removals_count = 2 if is_installed and is_locked else 0 + + assert installer.executor.installations_count == expected_installations_count + assert installer.executor.removals_count == expected_removals_count @pytest.mark.network @@ -1373,26 +1292,22 @@ def test_run_with_prereleases( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0a2", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - } - ], - "metadata": { - "python-versions": "*", - "platform": "*", - "content-hash": "123456789", - "files": {"A": []}, - }, - } - ) + locker.mock_lock_data({ + "package": [{ + "name": "A", + "version": "1.0a2", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": []}, + }, + }) package_a = get_package("A", "1.0a2") package_b = get_package("B", "1.1") repo.add_package(package_a) @@ -1417,26 +1332,22 @@ def test_run_update_all_with_lock( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": True, - "platform": "*", - "python-versions": "*", - "checksum": [], - } - ], - "metadata": { - "python-versions": "*", - "platform": "*", - "content-hash": "123456789", - "files": {"A": []}, - }, - } - ) + locker.mock_lock_data({ + "package": [{ + "name": "A", + "version": "1.0", + "optional": True, + "platform": "*", + "python-versions": "*", + "checksum": [], + }], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": []}, + }, + }) package_a = get_package("A", "1.1") repo.add_package(get_package("A", "1.0")) repo.add_package(package_a) @@ -1456,44 +1367,42 @@ def test_run_update_with_locked_extras( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"B": "^1.0", "C": "^1.0"}, - }, - { - "name": "B", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "C", - "version": "1.1", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "requirements": {"python": "~2.7"}, - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"B": "^1.0", "C": "^1.0"}, + }, + { + "name": "B", + "version": "1.0", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": [], "C": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + { + "name": "C", + "version": "1.1", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "requirements": {"python": "~2.7"}, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": [], "C": []}, + }, + }) package_a = get_package("A", "1.0") package_a.extras[canonicalize_name("foo")] = [get_dependency("B")] b_dependency = get_dependency("B", "^1.0", optional=True) @@ -1572,68 +1481,66 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": { - "B": [ - {"version": "^1.0", "python": "<4.0"}, - {"version": "^2.0", "python": ">=4.0"}, - ] - }, - }, - { - "name": "B", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"C": "1.2"}, - "requirements": {"python": "<4.0"}, - }, - { - "name": "B", - "version": "2.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"C": "1.5"}, - "requirements": {"python": ">=4.0"}, - }, - { - "name": "C", - "version": "1.2", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "C", - "version": "1.5", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": { + "B": [ + {"version": "^1.0", "python": "<4.0"}, + {"version": "^2.0", "python": ">=4.0"}, + ] }, - ], - "metadata": { + }, + { + "name": "B", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"C": "1.2"}, + "requirements": {"python": "<4.0"}, + }, + { + "name": "B", + "version": "2.0", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": [], "C": []}, + "python-versions": "*", + "checksum": [], + "dependencies": {"C": "1.5"}, + "requirements": {"python": ">=4.0"}, }, - } - ) + { + "name": "C", + "version": "1.2", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "C", + "version": "1.5", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": [], "C": []}, + }, + }) package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1679,35 +1586,33 @@ def test_run_update_uninstalls_after_removal_transient_dependency( installed: CustomInstalledRepository, ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"B": {"version": "^1.0", "python": "<2.0"}}, - }, - { - "name": "B", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"B": {"version": "^1.0", "python": "<2.0"}}, + }, + { + "name": "B", + "version": "1.0", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": []}, + "python-versions": "*", + "checksum": [], }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": []}, + }, + }) package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.0") @@ -1740,68 +1645,66 @@ def test_run_install_duplicate_dependencies_different_constraints_with_lock_upda installed: CustomInstalledRepository, ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": { - "B": [ - {"version": "^1.0", "python": "<2.7"}, - {"version": "^2.0", "python": ">=2.7"}, - ] - }, - }, - { - "name": "B", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"C": "1.2"}, - "requirements": {"python": "<2.7"}, - }, - { - "name": "B", - "version": "2.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"C": "1.5"}, - "requirements": {"python": ">=2.7"}, - }, - { - "name": "C", - "version": "1.2", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - }, - { - "name": "C", - "version": "1.5", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + "dependencies": { + "B": [ + {"version": "^1.0", "python": "<2.7"}, + {"version": "^2.0", "python": ">=2.7"}, + ] }, - ], - "metadata": { + }, + { + "name": "B", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"C": "1.2"}, + "requirements": {"python": "<2.7"}, + }, + { + "name": "B", + "version": "2.0", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": [], "C": []}, + "python-versions": "*", + "checksum": [], + "dependencies": {"C": "1.5"}, + "requirements": {"python": ">=2.7"}, }, - } - ) + { + "name": "C", + "version": "1.2", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + { + "name": "C", + "version": "1.5", + "optional": False, + "platform": "*", + "python-versions": "*", + "checksum": [], + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": [], "C": []}, + }, + }) package.add_dependency(Factory.create_dependency("A", "*")) package_a = get_package("A", "1.1") @@ -2061,36 +1964,34 @@ def test_update_multiple_times_with_split_dependencies_is_idempotent( installer: Installer, locker: Locker, repo: Repository, package: ProjectPackage ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "A", - "version": "1.0", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"B": ">=1.0"}, - }, - { - "name": "B", - "version": "1.0.1", - "optional": False, - "platform": "*", - "python-versions": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", - "checksum": [], - "dependencies": {}, - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "A", + "version": "1.0", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"B": ">=1.0"}, + }, + { + "name": "B", + "version": "1.0.1", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"A": [], "B": []}, + "python-versions": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", + "checksum": [], + "dependencies": {}, }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"A": [], "B": []}, + }, + }) package.python_versions = "~2.7 || ^3.4" package.add_dependency(Factory.create_dependency("A", "^1.0")) @@ -2404,42 +2305,40 @@ def test_installer_should_use_the_locked_version_of_git_dependencies( installer: Installer, locker: Locker, package: ProjectPackage, repo: Repository ) -> None: locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "demo", - "version": "0.1.1", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {"pendulum": ">=1.4.4"}, - "source": { - "type": "git", - "url": "https://github.com/demo/demo.git", - "reference": "master", - "resolved_reference": "123456", - }, - }, - { - "name": "pendulum", - "version": "1.4.4", - "optional": False, - "platform": "*", - "python-versions": "*", - "checksum": [], - "dependencies": {}, - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "demo", + "version": "0.1.1", + "optional": False, + "platform": "*", "python-versions": "*", + "checksum": [], + "dependencies": {"pendulum": ">=1.4.4"}, + "source": { + "type": "git", + "url": "https://github.com/demo/demo.git", + "reference": "master", + "resolved_reference": "123456", + }, + }, + { + "name": "pendulum", + "version": "1.4.4", + "optional": False, "platform": "*", - "content-hash": "123456789", - "files": {"demo": [], "pendulum": []}, + "python-versions": "*", + "checksum": [], + "dependencies": {}, }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + "files": {"demo": [], "pendulum": []}, + }, + }) package.add_dependency( Factory.create_dependency( @@ -2583,36 +2482,34 @@ def test_installer_distinguishes_locked_packages_with_local_version_by_source( # Locking finds both the pypi and the pytorch packages. locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "torch", - "version": "1.11.0", - "optional": False, - "files": [], - "python-versions": "*", - }, - { - "name": "torch", - "version": "1.11.0+cpu", - "optional": False, - "files": [], - "python-versions": "*", - "source": { - "type": "legacy", - "url": "https://download.pytorch.org/whl", - "reference": "pytorch", - }, - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "torch", + "version": "1.11.0", + "optional": False, + "files": [], "python-versions": "*", - "platform": "*", - "content-hash": "123456789", }, - } - ) + { + "name": "torch", + "version": "1.11.0+cpu", + "optional": False, + "files": [], + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://download.pytorch.org/whl", + "reference": "pytorch", + }, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + }, + }) installer = Installer( NullIO(), MockEnv(platform=env_platform), @@ -2684,36 +2581,34 @@ def test_installer_distinguishes_locked_packages_with_same_version_by_source( # Locking finds both the pypi and the pyhweels packages. locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "kivy", - "version": "2.2.1", - "optional": False, - "files": [], - "python-versions": "*", - }, - { - "name": "kivy", - "version": "2.2.1", - "optional": False, - "files": [], - "python-versions": "*", - "source": { - "type": "legacy", - "url": "https://www.piwheels.org/simple", - "reference": "pywheels", - }, - }, - ], - "metadata": { + locker.mock_lock_data({ + "package": [ + { + "name": "kivy", + "version": "2.2.1", + "optional": False, + "files": [], "python-versions": "*", - "platform": "*", - "content-hash": "123456789", }, - } - ) + { + "name": "kivy", + "version": "2.2.1", + "optional": False, + "files": [], + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://www.piwheels.org/simple", + "reference": "pywheels", + }, + }, + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + }, + }) installer = Installer( NullIO(), MockEnv(platform_machine=env_platform_machine), @@ -2794,48 +2689,46 @@ def test_explicit_source_dependency_with_direct_origin_dependency( # Locking finds both the direct origin and the explicit source packages. locker.locked(True) - locker.mock_lock_data( - { - "package": [ - { - "name": "demo", - "version": "0.1.0", - "optional": False, - "files": [], - "python-versions": "*", - "dependencies": {"pendulum": ">=1.4.4"}, - "source": { - "type": "url", - "url": demo_url, - }, - }, - { - "name": "demo", - "version": "0.1.0", - "optional": False, - "files": [], - "python-versions": "*", - "source": { - "type": "legacy", - "url": "https://www.demo.org/simple", - "reference": "repo", - }, + locker.mock_lock_data({ + "package": [ + { + "name": "demo", + "version": "0.1.0", + "optional": False, + "files": [], + "python-versions": "*", + "dependencies": {"pendulum": ">=1.4.4"}, + "source": { + "type": "url", + "url": demo_url, }, - { - "name": "pendulum", - "version": "1.4.4", - "optional": False, - "files": [], - "python-versions": "*", + }, + { + "name": "demo", + "version": "0.1.0", + "optional": False, + "files": [], + "python-versions": "*", + "source": { + "type": "legacy", + "url": "https://www.demo.org/simple", + "reference": "repo", }, - ], - "metadata": { + }, + { + "name": "pendulum", + "version": "1.4.4", + "optional": False, + "files": [], "python-versions": "*", - "platform": "*", - "content-hash": "123456789", }, - } - ) + ], + "metadata": { + "python-versions": "*", + "platform": "*", + "content-hash": "123456789", + }, + }) installer = Installer( NullIO(), MockEnv(platform=env_platform), diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index b08ba240839..0ee6431d15c 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -310,17 +310,15 @@ def test_configured_repository_http_auth( spy_clone_legacy = mocker.spy(Git, "_clone_legacy") spy_get_transport_and_path = mocker.spy(backend, "get_transport_and_path") - config.merge( - { - "repositories": {"git-repo": {"url": source_url}}, - "http-basic": { - "git-repo": { - "username": GIT_USERNAME, - "password": GIT_PASSWORD, - } - }, - } - ) + config.merge({ + "repositories": {"git-repo": {"url": source_url}}, + "http-basic": { + "git-repo": { + "username": GIT_USERNAME, + "password": GIT_PASSWORD, + } + }, + }) dummy_git_config = ConfigFile() mocker.patch( diff --git a/tests/publishing/test_publisher.py b/tests/publishing/test_publisher.py index bf30141541e..ace0893edd6 100644 --- a/tests/publishing/test_publisher.py +++ b/tests/publishing/test_publisher.py @@ -53,12 +53,10 @@ def test_publish_can_publish_to_given_repository( uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") - config.merge( - { - "repositories": {"foo": {"url": "http://foo.bar"}}, - "http-basic": {"foo": {"username": "foo", "password": "bar"}}, - } - ) + config.merge({ + "repositories": {"foo": {"url": "http://foo.bar"}}, + "http-basic": {"foo": {"username": "foo", "password": "bar"}}, + }) mocker.patch("poetry.config.config.Config.create", return_value=config) poetry = Factory().create_poetry(fixture_dir(fixture_name)) @@ -118,13 +116,11 @@ def test_publish_uses_cert( uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config - poetry.config.merge( - { - "repositories": {"foo": {"url": "https://foo.bar"}}, - "http-basic": {"foo": {"username": "foo", "password": "bar"}}, - "certificates": {"foo": {"cert": cert}}, - } - ) + poetry.config.merge({ + "repositories": {"foo": {"url": "https://foo.bar"}}, + "http-basic": {"foo": {"username": "foo", "password": "bar"}}, + "certificates": {"foo": {"cert": cert}}, + }) publisher = Publisher(poetry, NullIO()) publisher.publish("foo", None, None) @@ -148,12 +144,10 @@ def test_publish_uses_client_cert( uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config - poetry.config.merge( - { - "repositories": {"foo": {"url": "https://foo.bar"}}, - "certificates": {"foo": {"client-cert": client_cert}}, - } - ) + poetry.config.merge({ + "repositories": {"foo": {"url": "https://foo.bar"}}, + "certificates": {"foo": {"client-cert": client_cert}}, + }) publisher = Publisher(poetry, NullIO()) publisher.publish("foo", None, None) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 0b85d824326..8de5f34a76d 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -95,14 +95,12 @@ def check_solver_result( for op in ops: if op.job_type == "update": assert isinstance(op, Update) - result.append( - { - "job": "update", - "from": op.initial_package, - "to": op.target_package, - "skipped": op.skipped, - } - ) + result.append({ + "job": "update", + "from": op.initial_package, + "to": op.target_package, + "skipped": op.skipped, + }) else: job = "install" if op.job_type == "uninstall": @@ -2961,18 +2959,16 @@ def test_solver_can_solve_with_legacy_repository_using_proper_python_compatible_ check_solver_result( transaction, - [ - { - "job": "install", - "package": Package( - "isort", - "4.3.4", - source_type="legacy", - source_url=repo.url, - source_reference=repo.name, - ), - } - ], + [{ + "job": "install", + "package": Package( + "isort", + "4.3.4", + source_type="legacy", + source_url=repo.url, + source_reference=repo.name, + ), + }], ) @@ -3055,18 +3051,16 @@ def test_solver_chooses_from_correct_repository_if_forced( ops = check_solver_result( transaction, - [ - { - "job": "install", - "package": Package( - "tomlkit", - "0.5.2", - source_type="legacy", - source_url=repo.url, - source_reference=repo.name, - ), - } - ], + [{ + "job": "install", + "package": Package( + "tomlkit", + "0.5.2", + source_type="legacy", + source_url=repo.url, + source_reference=repo.name, + ), + }], ) assert ops[0].package.source_url == "http://legacy.foo.bar" @@ -4362,12 +4356,10 @@ def test_solver_resolves_duplicate_dependency_in_extra( check_solver_result( transaction, - ( - [ - {"job": "install", "package": package_b1 if with_extra else package_b2}, - {"job": "install", "package": package_a}, - ] - ), + ([ + {"job": "install", "package": package_b1 if with_extra else package_b2}, + {"job": "install", "package": package_a}, + ]), ) @@ -4402,11 +4394,9 @@ def test_solver_resolves_duplicate_dependencies_with_restricted_extras( check_solver_result( transaction, - ( - [ - {"job": "install", "package": package_b1}, - {"job": "install", "package": package_b2}, - {"job": "install", "package": package_a}, - ] - ), + ([ + {"job": "install", "package": package_b1}, + {"job": "install", "package": package_b2}, + {"job": "install", "package": package_a}, + ]), ) diff --git a/tests/puzzle/test_transaction.py b/tests/puzzle/test_transaction.py index 05b19649c60..f17660b9425 100644 --- a/tests/puzzle/test_transaction.py +++ b/tests/puzzle/test_transaction.py @@ -22,14 +22,12 @@ def check_operations(ops: list[Operation], expected: list[dict[str, Any]]) -> No for op in ops: if op.job_type == "update": assert isinstance(op, Update) - result.append( - { - "job": "update", - "from": op.initial_package, - "to": op.target_package, - "skipped": op.skipped, - } - ) + result.append({ + "job": "update", + "from": op.initial_package, + "to": op.target_package, + "skipped": op.skipped, + }) else: job = "install" if op.job_type == "uninstall": @@ -151,36 +149,32 @@ def test_it_should_not_remove_installed_packages_that_are_in_result() -> None: def test_it_should_update_installed_packages_if_sources_are_different() -> None: 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, - ) - ], + [( + 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", - ), - } - ], + [{ + "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_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 180b7b3870b..3577836d420 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -422,12 +422,12 @@ def test_get_package_retrieves_packages_with_no_hashes() -> None: package = repo.package("jupyter", Version.parse("1.0.0")) - assert [ - { - "file": "jupyter-1.0.0.tar.gz", - "hash": "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f", - } - ] == package.files + assert [{ + "file": "jupyter-1.0.0.tar.gz", + "hash": ( + "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f" + ), + }] == package.files @pytest.mark.parametrize( @@ -569,15 +569,13 @@ def test_authenticator_with_implicit_repository_configuration( re.compile("^https?://foo.bar/(.+?)$"), ) - config.merge( - { - "repositories": repositories, - "http-basic": { - "source": {"username": "foo", "password": "bar"}, - "publish": {"username": "baz", "password": "qux"}, - }, - } - ) + config.merge({ + "repositories": repositories, + "http-basic": { + "source": {"username": "foo", "password": "bar"}, + "publish": {"username": "baz", "password": "qux"}, + }, + }) repo = LegacyRepository(name="source", url="https://foo.bar/simple", config=config) repo.get_page("/foo") diff --git a/tests/test_factory.py b/tests/test_factory.py index b1895d25fea..ec9fda4476c 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -524,8 +524,8 @@ def test_create_poetry_fails_on_invalid_configuration( with pytest.raises(RuntimeError) as e: Factory().create_poetry(fixture_dir("invalid_pyproject") / "pyproject.toml") - jsonschema_error = "'description' is a required property" fastjsonschema_error = "data must contain ['description'] properties" + custom_error = "The fields ['description'] are required in package mode." expected_template = """\ The Poetry configuration is invalid: @@ -534,7 +534,28 @@ def test_create_poetry_fails_on_invalid_configuration( """ expected = { expected_template.format(schema_error=schema_error) - for schema_error in (jsonschema_error, fastjsonschema_error) + for schema_error in (fastjsonschema_error, custom_error) + } + + assert str(e.value) in expected + + +def test_create_poetry_fails_on_nameless_project( + fixture_dir: FixtureDirGetter, +) -> None: + with pytest.raises(RuntimeError) as e: + Factory().create_poetry(fixture_dir("nameless_pyproject") / "pyproject.toml") + + fastjsonschema_error = "data must contain ['name'] properties" + custom_error = "The fields ['name'] are required in package mode." + + expected_template = """\ +The Poetry configuration is invalid: + - {schema_error} +""" + expected = { + expected_template.format(schema_error=schema_error) + for schema_error in (fastjsonschema_error, custom_error) } assert str(e.value) in expected diff --git a/tests/utils/env/__init__.py b/tests/utils/env/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/utils/env/conftest.py b/tests/utils/env/conftest.py new file mode 100644 index 00000000000..72d88a0de17 --- /dev/null +++ b/tests/utils/env/conftest.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from poetry.utils.env import EnvManager + + +if TYPE_CHECKING: + + from poetry.poetry import Poetry + from tests.types import FixtureDirGetter + from tests.types import ProjectFactory + + +@pytest.fixture +def poetry(project_factory: ProjectFactory, fixture_dir: FixtureDirGetter) -> Poetry: + return project_factory("simple", source=fixture_dir("simple_project")) + + +@pytest.fixture +def manager(poetry: Poetry) -> EnvManager: + return EnvManager(poetry) diff --git a/tests/utils/env/test_env.py b/tests/utils/env/test_env.py new file mode 100644 index 00000000000..da481455b55 --- /dev/null +++ b/tests/utils/env/test_env.py @@ -0,0 +1,541 @@ +from __future__ import annotations + +import contextlib +import os +import site +import subprocess +import sys + +from pathlib import Path +from threading import Thread +from typing import TYPE_CHECKING + +import pytest + +from poetry.factory import Factory +from poetry.repositories.installed_repository import InstalledRepository +from poetry.utils._compat import WINDOWS +from poetry.utils._compat import metadata +from poetry.utils.env import EnvCommandError +from poetry.utils.env import EnvManager +from poetry.utils.env import GenericEnv +from poetry.utils.env import MockEnv +from poetry.utils.env import SystemEnv +from poetry.utils.env import VirtualEnv +from poetry.utils.env import build_environment + + +if TYPE_CHECKING: + + from pytest_mock import MockerFixture + + from poetry.poetry import Poetry + from tests.types import FixtureDirGetter + +MINIMAL_SCRIPT = """\ + +print("Minimal Output"), +""" + +# Script expected to fail. +ERRORING_SCRIPT = """\ +import nullpackage + +print("nullpackage loaded"), +""" + + +class MockVirtualEnv(VirtualEnv): + def __init__( + self, + path: Path, + base: Path | None = None, + sys_path: list[str] | None = None, + ) -> None: + super().__init__(path, base=base) + + self._sys_path = sys_path + + @property + def sys_path(self) -> list[str]: + if self._sys_path is not None: + return self._sys_path + + return super().sys_path + + +def test_virtualenvs_with_spaces_in_their_path_work_as_expected( + tmp_path: Path, manager: EnvManager +) -> None: + venv_path = tmp_path / "Virtual Env" + + manager.build_venv(venv_path) + + venv = VirtualEnv(venv_path) + + assert venv.run("python", "-V").startswith("Python") + + +def test_env_commands_with_spaces_in_their_arg_work_as_expected( + tmp_path: Path, manager: EnvManager +) -> None: + venv_path = tmp_path / "Virtual Env" + manager.build_venv(venv_path) + venv = VirtualEnv(venv_path) + assert venv.run("python", str(venv.pip), "--version").startswith( + f"pip {venv.pip_version} from " + ) + + +def test_env_get_supported_tags_matches_inside_virtualenv( + tmp_path: Path, manager: EnvManager +) -> None: + venv_path = tmp_path / "Virtual Env" + manager.build_venv(venv_path) + venv = VirtualEnv(venv_path) + + import packaging.tags + + assert venv.get_supported_tags() == list(packaging.tags.sys_tags()) + + +@pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows") +def test_env_has_symlinks_on_nix(tmp_path: Path, tmp_venv: VirtualEnv) -> None: + assert os.path.islink(tmp_venv.python) + + +def test_run_with_keyboard_interrupt( + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture +) -> None: + mocker.patch("subprocess.check_output", side_effect=KeyboardInterrupt()) + with pytest.raises(KeyboardInterrupt): + tmp_venv.run("python", "-c", MINIMAL_SCRIPT) + subprocess.check_output.assert_called_once() # type: ignore[attr-defined] + + +def test_call_with_keyboard_interrupt( + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture +) -> None: + mocker.patch("subprocess.check_call", side_effect=KeyboardInterrupt()) + kwargs = {"call": True} + with pytest.raises(KeyboardInterrupt): + tmp_venv.run("python", "-", **kwargs) + subprocess.check_call.assert_called_once() # type: ignore[attr-defined] + + +def test_run_with_called_process_error( + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture +) -> None: + mocker.patch( + "subprocess.check_output", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run("python", "-c", MINIMAL_SCRIPT) + subprocess.check_output.assert_called_once() # type: ignore[attr-defined] + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_call_no_input_with_called_process_error( + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture +) -> None: + mocker.patch( + "subprocess.check_call", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + kwargs = {"call": True} + with pytest.raises(EnvCommandError) as error: + tmp_venv.run("python", "-", **kwargs) + subprocess.check_call.assert_called_once() # type: ignore[attr-defined] + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_check_output_with_called_process_error( + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture +) -> None: + mocker.patch( + "subprocess.check_output", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run("python", "-") + subprocess.check_output.assert_called_once() # type: ignore[attr-defined] + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +@pytest.mark.parametrize("out", ["sys.stdout", "sys.stderr"]) +def test_call_does_not_block_on_full_pipe( + tmp_path: Path, tmp_venv: VirtualEnv, out: str +) -> None: + """see https://github.com/python-poetry/poetry/issues/7698""" + script = tmp_path / "script.py" + script.write_text(f"""\ +import sys +for i in range(10000): + print('just print a lot of text to fill the buffer', file={out}) +""") + + def target(result: list[int]) -> None: + tmp_venv.run("python", str(script), call=True) + result.append(0) + + results: list[int] = [] + # use a separate thread, so that the test does not block in case of error + thread = Thread(target=target, args=(results,)) + thread.start() + thread.join(1) # must not block + assert results and results[0] == 0 + + +def test_run_python_script_called_process_error( + tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture +) -> None: + mocker.patch( + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run_python_script(MINIMAL_SCRIPT) + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_run_python_script_only_stdout(tmp_path: Path, tmp_venv: VirtualEnv) -> None: + output = tmp_venv.run_python_script( + "import sys; print('some warning', file=sys.stderr); print('some output')" + ) + assert "some output" in output + assert "some warning" not in output + + +def test_system_env_has_correct_paths() -> None: + env = SystemEnv(Path(sys.prefix)) + + paths = env.paths + + assert paths.get("purelib") is not None + assert paths.get("platlib") is not None + assert paths.get("scripts") is not None + assert env.site_packages.path == Path(paths["purelib"]) + assert paths["include"] is not None + + +@pytest.mark.parametrize( + "enabled", + [True, False], +) +def test_system_env_usersite(mocker: MockerFixture, enabled: bool) -> None: + mocker.patch("site.check_enableusersite", return_value=enabled) + env = SystemEnv(Path(sys.prefix)) + assert (enabled and env.usersite is not None) or ( + not enabled and env.usersite is None + ) + + +def test_venv_has_correct_paths(tmp_venv: VirtualEnv) -> None: + paths = tmp_venv.paths + + assert paths.get("purelib") is not None + assert paths.get("platlib") is not None + assert paths.get("scripts") is not None + assert tmp_venv.site_packages.path == Path(paths["purelib"]) + assert paths["include"] == str( + tmp_venv.path.joinpath( + f"include/site/python{tmp_venv.version_info[0]}.{tmp_venv.version_info[1]}" + ) + ) + + +@pytest.mark.parametrize("with_system_site_packages", [True, False]) +def test_env_system_packages( + tmp_path: Path, poetry: Poetry, with_system_site_packages: bool +) -> None: + venv_path = tmp_path / "venv" + pyvenv_cfg = venv_path / "pyvenv.cfg" + + EnvManager(poetry).build_venv( + path=venv_path, flags={"system-site-packages": with_system_site_packages} + ) + env = VirtualEnv(venv_path) + + assert ( + f"include-system-site-packages = {str(with_system_site_packages).lower()}" + in pyvenv_cfg.read_text() + ) + assert env.includes_system_site_packages is with_system_site_packages + + +def test_generic_env_system_packages(poetry: Poetry) -> None: + """https://github.com/python-poetry/poetry/issues/8646""" + env = GenericEnv(Path(sys.base_prefix)) + assert not env.includes_system_site_packages + + +@pytest.mark.parametrize("with_system_site_packages", [True, False]) +def test_env_system_packages_are_relative_to_lib( + tmp_path: Path, poetry: Poetry, with_system_site_packages: bool +) -> None: + venv_path = tmp_path / "venv" + EnvManager(poetry).build_venv( + path=venv_path, flags={"system-site-packages": with_system_site_packages} + ) + env = VirtualEnv(venv_path) + site_dir = Path(site.getsitepackages()[-1]) + for dist in metadata.distributions(): + # Emulate is_relative_to, only available in 3.9+ + with contextlib.suppress(ValueError): + dist._path.relative_to(site_dir) # type: ignore[attr-defined] + break + assert ( + env.is_path_relative_to_lib(dist._path) # type: ignore[attr-defined] + is with_system_site_packages + ) + + +@pytest.mark.parametrize( + ("flags", "packages"), + [ + ({"no-pip": False}, {"pip"}), + ({"no-pip": False, "no-wheel": True}, {"pip"}), + ({"no-pip": False, "no-wheel": False}, {"pip", "wheel"}), + ({"no-pip": True}, set()), + ({"no-setuptools": False}, {"setuptools"}), + ({"no-setuptools": True}, set()), + ({"setuptools": "bundle"}, {"setuptools"}), + ({"no-pip": True, "no-setuptools": False}, {"setuptools"}), + ({"no-wheel": False}, {"wheel"}), + ({"wheel": "bundle"}, {"wheel"}), + ({}, set()), + ], +) +def test_env_no_pip( + tmp_path: Path, poetry: Poetry, flags: dict[str, str | bool], packages: set[str] +) -> None: + venv_path = tmp_path / "venv" + EnvManager(poetry).build_venv(path=venv_path, flags=flags) + env = VirtualEnv(venv_path) + installed_repository = InstalledRepository.load(env=env, with_dependencies=True) + installed_packages = { + package.name + for package in installed_repository.packages + # workaround for BSD test environments + if package.name != "sqlite3" + } + + # For python >= 3.12, virtualenv defaults to "--no-setuptools" and "--no-wheel" + # behaviour, so setting these values to False becomes meaningless. + if sys.version_info >= (3, 12): + if not flags.get("no-setuptools", True): + packages.discard("setuptools") + if not flags.get("no-wheel", True): + packages.discard("wheel") + + assert installed_packages == packages + + +def test_env_finds_the_correct_executables(tmp_path: Path, manager: EnvManager) -> None: + venv_path = tmp_path / "Virtual Env" + manager.build_venv(venv_path, with_pip=True) + venv = VirtualEnv(venv_path) + + default_executable = expected_executable = f"python{'.exe' if WINDOWS else ''}" + default_pip_executable = expected_pip_executable = f"pip{'.exe' if WINDOWS else ''}" + major_executable = f"python{sys.version_info[0]}{'.exe' if WINDOWS else ''}" + major_pip_executable = f"pip{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_path: Path, manager: EnvManager +) -> None: + venv_path = tmp_path / "Virtual Env" + child_venv_path = tmp_path / "Child Virtual Env" + manager.build_venv(venv_path, with_pip=True) + parent_venv = VirtualEnv(venv_path) + manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) + venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) + + expected_executable = ( + f"python{sys.version_info[0]}.{sys.version_info[1]}{'.exe' if WINDOWS else ''}" + ) + expected_pip_executable = ( + f"pip{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_path: Path, manager: EnvManager +) -> None: + venv_path = tmp_path / "Virtual Env" + child_venv_path = tmp_path / "Child Virtual Env" + manager.build_venv(venv_path, with_pip=True) + parent_venv = VirtualEnv(venv_path) + manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) + venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) + + default_executable = f"python{'.exe' if WINDOWS else ''}" + major_executable = f"python{sys.version_info[0]}{'.exe' if WINDOWS else ''}" + minor_executable = ( + f"python{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 = f"pip{'.exe' if WINDOWS else ''}" + major_pip_executable = f"pip{sys.version_info[0]}{'.exe' if WINDOWS else ''}" + minor_pip_executable = ( + f"pip{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 + + +@pytest.fixture +def extended_without_setup_poetry(fixture_dir: FixtureDirGetter) -> Poetry: + poetry = Factory().create_poetry(fixture_dir("extended_project_without_setup")) + + return poetry + + +def test_build_environment_called_build_script_specified( + mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_path: Path +) -> None: + project_env = MockEnv(path=tmp_path / "project") + ephemeral_env = MockEnv(path=tmp_path / "ephemeral") + + mocker.patch( + "poetry.utils.env.ephemeral_environment" + ).return_value.__enter__.return_value = ephemeral_env + + with build_environment(extended_without_setup_poetry, project_env) as env: + assert env == ephemeral_env + assert env.executed == [[ # type: ignore[attr-defined] + str(sys.executable), + str(env.pip_embedded), + "install", + "--disable-pip-version-check", + "--ignore-installed", + "--no-input", + *extended_without_setup_poetry.pyproject.build_system.requires, + ]] + + +def test_build_environment_not_called_without_build_script_specified( + mocker: MockerFixture, poetry: Poetry, tmp_path: Path +) -> None: + project_env = MockEnv(path=tmp_path / "project") + ephemeral_env = MockEnv(path=tmp_path / "ephemeral") + + mocker.patch( + "poetry.utils.env.ephemeral_environment" + ).return_value.__enter__.return_value = ephemeral_env + + with build_environment(poetry, project_env) as env: + assert env == project_env + assert not env.executed # type: ignore[attr-defined] + + +def test_fallback_on_detect_active_python( + poetry: Poetry, mocker: MockerFixture +) -> None: + m = mocker.patch( + "subprocess.check_output", + side_effect=subprocess.CalledProcessError(1, "some command"), + ) + env_manager = EnvManager(poetry) + active_python = env_manager._detect_active_python() + + assert active_python is None + assert m.call_count == 1 + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows only") +def test_detect_active_python_with_bat(poetry: Poetry, tmp_path: Path) -> None: + """On Windows pyenv uses batch files for python management.""" + python_wrapper = tmp_path / "python.bat" + wrapped_python = Path(r"C:\SpecialPython\python.exe") + with python_wrapper.open("w") as f: + f.write(f"@echo {wrapped_python}") + os.environ["PATH"] = str(python_wrapper.parent) + os.pathsep + os.environ["PATH"] + + active_python = EnvManager(poetry)._detect_active_python() + + assert active_python == wrapped_python + + +def test_command_from_bin_preserves_relative_path(manager: EnvManager) -> None: + # https://github.com/python-poetry/poetry/issues/7959 + env = manager.get() + command = env.get_command_from_bin("./foo.py") + assert command == ["./foo.py"] diff --git a/tests/utils/test_env.py b/tests/utils/env/test_env_manager.py similarity index 66% rename from tests/utils/test_env.py rename to tests/utils/env/test_env_manager.py index de2f90d3205..995d99ab4e3 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/env/test_env_manager.py @@ -1,13 +1,9 @@ from __future__ import annotations -import contextlib import os -import site -import subprocess import sys from pathlib import Path -from threading import Thread from typing import TYPE_CHECKING from typing import Any @@ -16,23 +12,14 @@ from poetry.core.constraints.version import Version -from poetry.factory import Factory -from poetry.repositories.installed_repository import InstalledRepository from poetry.toml.file import TOMLFile -from poetry.utils._compat import WINDOWS -from poetry.utils._compat import metadata from poetry.utils.env import GET_PYTHON_VERSION_ONELINER -from poetry.utils.env import EnvCommandError from poetry.utils.env import EnvManager -from poetry.utils.env import GenericEnv from poetry.utils.env import IncorrectEnvError from poetry.utils.env import InvalidCurrentPythonVersionError -from poetry.utils.env import MockEnv from poetry.utils.env import NoCompatiblePythonVersionFound from poetry.utils.env import PythonVersionNotFound -from poetry.utils.env import SystemEnv -from poetry.utils.env import VirtualEnv -from poetry.utils.env import build_environment +from poetry.utils.env.env_manager import EnvsFile from poetry.utils.helpers import remove_directory @@ -47,137 +34,14 @@ from tests.types import FixtureDirGetter from tests.types import ProjectFactory -MINIMAL_SCRIPT = """\ -print("Minimal Output"), -""" - -# Script expected to fail. -ERRORING_SCRIPT = """\ -import nullpackage - -print("nullpackage loaded"), -""" - - -class MockVirtualEnv(VirtualEnv): - def __init__( - self, - path: Path, - base: Path | None = None, - sys_path: list[str] | None = None, - ) -> None: - super().__init__(path, base=base) - - self._sys_path = sys_path - - @property - def sys_path(self) -> list[str]: - if self._sys_path is not None: - return self._sys_path - - return super().sys_path - - -@pytest.fixture() -def poetry(project_factory: ProjectFactory, fixture_dir: FixtureDirGetter) -> Poetry: - return project_factory("simple", source=fixture_dir("simple_project")) - - -@pytest.fixture() -def manager(poetry: Poetry) -> EnvManager: - return EnvManager(poetry) - - -def test_virtualenvs_with_spaces_in_their_path_work_as_expected( - tmp_path: Path, manager: EnvManager -) -> None: - venv_path = tmp_path / "Virtual Env" - - manager.build_venv(venv_path) - - venv = VirtualEnv(venv_path) - - assert venv.run("python", "-V").startswith("Python") - - -@pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") -def test_venv_backup_exclusion(tmp_path: Path, manager: EnvManager) -> None: - import xattr - - venv_path = tmp_path / "Virtual Env" - - manager.build_venv(venv_path) - - value = ( - b"bplist00_\x10\x11com.apple.backupd" - b"\x08\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00" - b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c" - ) - assert ( - xattr.getxattr( - str(venv_path), "com.apple.metadata:com_apple_backup_excludeItem" - ) - == value - ) - - -def test_env_commands_with_spaces_in_their_arg_work_as_expected( - tmp_path: Path, manager: EnvManager -) -> None: - venv_path = tmp_path / "Virtual Env" - manager.build_venv(venv_path) - venv = VirtualEnv(venv_path) - assert venv.run("python", str(venv.pip), "--version").startswith( - f"pip {venv.pip_version} from " - ) - - -def test_env_get_supported_tags_matches_inside_virtualenv( - tmp_path: Path, manager: EnvManager -) -> None: - venv_path = tmp_path / "Virtual Env" - manager.build_venv(venv_path) - venv = VirtualEnv(venv_path) - - import packaging.tags - - assert venv.get_supported_tags() == list(packaging.tags.sys_tags()) - - -@pytest.fixture -def in_project_venv_dir(poetry: Poetry) -> Iterator[Path]: - os.environ.pop("VIRTUAL_ENV", None) - venv_dir = poetry.file.path.parent.joinpath(".venv") - venv_dir.mkdir() - try: - yield venv_dir - finally: - venv_dir.rmdir() - - -@pytest.mark.parametrize("in_project", [True, False, None]) -def test_env_get_venv_with_venv_folder_present( - manager: EnvManager, - poetry: Poetry, - in_project_venv_dir: Path, - in_project: bool | None, -) -> None: - poetry.config.config["virtualenvs"]["in-project"] = in_project - venv = manager.get() - if in_project is False: - assert venv.path != in_project_venv_dir - else: - assert venv.path == in_project_venv_dir +VERSION_3_7_1 = Version.parse("3.7.1") def build_venv(path: Path | str, **__: Any) -> None: os.mkdir(str(path)) -VERSION_3_7_1 = Version.parse("3.7.1") - - def check_output_wrapper( version: Version = VERSION_3_7_1, ) -> Callable[[list[str], Any, Any], str]: @@ -210,6 +74,52 @@ def check_output(cmd: list[str], *args: Any, **kwargs: Any) -> str: return check_output +@pytest.fixture +def in_project_venv_dir(poetry: Poetry) -> Iterator[Path]: + os.environ.pop("VIRTUAL_ENV", None) + venv_dir = poetry.file.path.parent.joinpath(".venv") + venv_dir.mkdir() + try: + yield venv_dir + finally: + venv_dir.rmdir() + + +@pytest.mark.parametrize( + ("section", "version", "expected"), + [ + ("foo", None, "3.10"), + ("bar", None, "3.11"), + ("baz", None, "3.12"), + ("bar", "3.11", "3.11"), + ("bar", "3.10", None), + ], +) +def test_envs_file_remove_section( + tmp_path: Path, section: str, version: str | None, expected: str | None +) -> None: + envs_file_path = tmp_path / "envs.toml" + + envs_file = TOMLFile(envs_file_path) + doc = tomlkit.document() + doc["foo"] = {"minor": "3.10", "patch": "3.10.13"} + doc["bar"] = {"minor": "3.11", "patch": "3.11.7"} + doc["baz"] = {"minor": "3.12", "patch": "3.12.1"} + envs_file.write(doc) + + minor = EnvsFile(envs_file_path).remove_section(section, version) + + assert minor == expected + + envs = TOMLFile(envs_file_path).read() + if expected is None: + assert section in envs + else: + assert section not in envs + for other_section in {"foo", "bar", "baz"} - {section}: + assert other_section in envs + + def test_activate_in_project_venv_no_explicit_config( tmp_path: Path, manager: EnvManager, @@ -534,6 +444,46 @@ def test_activate_does_not_recreate_when_switching_minor( assert (tmp_path / f"{venv_name}-py3.6").exists() +def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( + manager: EnvManager, + poetry: Poetry, + config: Config, + tmp_path: Path, + mocker: MockerFixture, + venv_flags_default: dict[str, bool], +) -> None: + if "VIRTUAL_ENV" in os.environ: + del os.environ["VIRTUAL_ENV"] + + config.merge( + { + "virtualenvs": { + "path": str(tmp_path / "virtualenvs"), + "in-project": True, + } + } + ) + + mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") + mocker.patch( + "subprocess.check_output", + side_effect=check_output_wrapper(), + ) + m = mocker.patch("poetry.utils.env.EnvManager.build_venv") + + manager.activate("python3.7") + + m.assert_called_with( + poetry.file.path.parent / ".venv", + executable=Path("/usr/bin/python3.7"), + flags=venv_flags_default, + prompt="simple-project-py3.7", + ) + + envs_file = TOMLFile(tmp_path / "virtualenvs" / "envs.toml") + assert not envs_file.exists() + + def test_deactivate_non_activated_but_existing( tmp_path: Path, manager: EnvManager, @@ -601,6 +551,21 @@ def test_deactivate_activated( assert len(envs) == 0 +@pytest.mark.parametrize("in_project", [True, False, None]) +def test_get_venv_with_venv_folder_present( + manager: EnvManager, + poetry: Poetry, + in_project_venv_dir: Path, + in_project: bool | None, +) -> None: + poetry.config.config["virtualenvs"]["in-project"] = in_project + venv = manager.get() + if in_project is False: + assert venv.path != in_project_venv_dir + else: + assert venv.path == in_project_venv_dir + + def test_get_prefers_explicitly_activated_virtualenvs_over_env_var( tmp_path: Path, manager: EnvManager, @@ -751,7 +716,7 @@ def test_remove_by_full_path_to_python( assert not expected_venv_path.exists() -def test_raises_if_acting_on_different_project_by_full_path( +def test_remove_raises_if_acting_on_different_project_by_full_path( tmp_path: Path, manager: EnvManager, poetry: Poetry, @@ -778,7 +743,7 @@ def test_raises_if_acting_on_different_project_by_full_path( manager.remove(str(python_path)) -def test_raises_if_acting_on_different_project_by_name( +def test_remove_raises_if_acting_on_different_project_by_name( tmp_path: Path, manager: EnvManager, poetry: Poetry, @@ -918,126 +883,6 @@ def err_on_rm_venv_only(path: Path, *args: Any, **kwargs: Any) -> None: m.side_effect = remove_directory # Avoid teardown using `err_on_rm_venv_only` -@pytest.mark.skipif(os.name == "nt", reason="Symlinks are not support for Windows") -def test_env_has_symlinks_on_nix(tmp_path: Path, tmp_venv: VirtualEnv) -> None: - assert os.path.islink(tmp_venv.python) - - -def test_run_with_keyboard_interrupt( - tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture -) -> None: - mocker.patch("subprocess.check_output", side_effect=KeyboardInterrupt()) - with pytest.raises(KeyboardInterrupt): - tmp_venv.run("python", "-c", MINIMAL_SCRIPT) - subprocess.check_output.assert_called_once() # type: ignore[attr-defined] - - -def test_call_with_keyboard_interrupt( - tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture -) -> None: - mocker.patch("subprocess.check_call", side_effect=KeyboardInterrupt()) - kwargs = {"call": True} - with pytest.raises(KeyboardInterrupt): - tmp_venv.run("python", "-", **kwargs) - subprocess.check_call.assert_called_once() # type: ignore[attr-defined] - - -def test_run_with_called_process_error( - tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture -) -> None: - mocker.patch( - "subprocess.check_output", - side_effect=subprocess.CalledProcessError( - 42, "some_command", "some output", "some error" - ), - ) - with pytest.raises(EnvCommandError) as error: - tmp_venv.run("python", "-c", MINIMAL_SCRIPT) - subprocess.check_output.assert_called_once() # type: ignore[attr-defined] - assert "some output" in str(error.value) - assert "some error" in str(error.value) - - -def test_call_no_input_with_called_process_error( - tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture -) -> None: - mocker.patch( - "subprocess.check_call", - side_effect=subprocess.CalledProcessError( - 42, "some_command", "some output", "some error" - ), - ) - kwargs = {"call": True} - with pytest.raises(EnvCommandError) as error: - tmp_venv.run("python", "-", **kwargs) - subprocess.check_call.assert_called_once() # type: ignore[attr-defined] - assert "some output" in str(error.value) - assert "some error" in str(error.value) - - -def test_check_output_with_called_process_error( - tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture -) -> None: - mocker.patch( - "subprocess.check_output", - side_effect=subprocess.CalledProcessError( - 42, "some_command", "some output", "some error" - ), - ) - with pytest.raises(EnvCommandError) as error: - tmp_venv.run("python", "-") - subprocess.check_output.assert_called_once() # type: ignore[attr-defined] - assert "some output" in str(error.value) - assert "some error" in str(error.value) - - -@pytest.mark.parametrize("out", ["sys.stdout", "sys.stderr"]) -def test_call_does_not_block_on_full_pipe( - tmp_path: Path, tmp_venv: VirtualEnv, out: str -) -> None: - """see https://github.com/python-poetry/poetry/issues/7698""" - script = tmp_path / "script.py" - script.write_text(f"""\ -import sys -for i in range(10000): - print('just print a lot of text to fill the buffer', file={out}) -""") - - def target(result: list[int]) -> None: - tmp_venv.run("python", str(script), call=True) - result.append(0) - - results: list[int] = [] - # use a separate thread, so that the test does not block in case of error - thread = Thread(target=target, args=(results,)) - thread.start() - thread.join(1) # must not block - assert results and results[0] == 0 - - -def test_run_python_script_called_process_error( - tmp_path: Path, tmp_venv: VirtualEnv, mocker: MockerFixture -) -> None: - mocker.patch( - "subprocess.run", - side_effect=subprocess.CalledProcessError( - 42, "some_command", "some output", "some error" - ), - ) - with pytest.raises(EnvCommandError) as error: - tmp_venv.run_python_script(MINIMAL_SCRIPT) - assert "some output" in str(error.value) - assert "some error" in str(error.value) - - -def test_run_python_script_only_stdout(tmp_path: Path, tmp_venv: VirtualEnv) -> None: - output = tmp_venv.run_python_script( - "import sys; print('some warning', file=sys.stderr); print('some output')" - ) - assert "some output" in output - assert "some warning" not in output - - def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first( manager: EnvManager, poetry: Poetry, @@ -1297,266 +1142,45 @@ def test_create_venv_fails_if_current_python_version_is_not_supported( assert expected_message == str(e.value) -def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( - manager: EnvManager, - poetry: Poetry, +def test_create_venv_project_name_empty_sets_correct_prompt( + fixture_dir: FixtureDirGetter, + project_factory: ProjectFactory, config: Config, - tmp_path: Path, mocker: MockerFixture, - venv_flags_default: dict[str, bool], + config_virtualenvs_path: Path, ) -> None: if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] - config.merge( - { - "virtualenvs": { - "path": str(tmp_path / "virtualenvs"), - "in-project": True, - } - } - ) + poetry = project_factory("no", source=fixture_dir("no_name_project")) + manager = EnvManager(poetry) + + poetry.package.python_versions = "^3.7" + venv_name = manager.generate_env_name("", str(poetry.file.path.parent)) + mocker.patch("sys.version_info", (2, 7, 16)) mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") mocker.patch( "subprocess.check_output", - side_effect=check_output_wrapper(), - ) - m = mocker.patch("poetry.utils.env.EnvManager.build_venv") - - manager.activate("python3.7") - - m.assert_called_with( - poetry.file.path.parent / ".venv", - executable=Path("/usr/bin/python3.7"), - flags=venv_flags_default, - prompt="simple-project-py3.7", - ) - - envs_file = TOMLFile(tmp_path / "virtualenvs" / "envs.toml") - assert not envs_file.exists() - - -def test_system_env_has_correct_paths() -> None: - env = SystemEnv(Path(sys.prefix)) - - paths = env.paths - - assert paths.get("purelib") is not None - assert paths.get("platlib") is not None - assert paths.get("scripts") is not None - assert env.site_packages.path == Path(paths["purelib"]) - assert paths["include"] is not None - - -@pytest.mark.parametrize( - "enabled", - [True, False], -) -def test_system_env_usersite(mocker: MockerFixture, enabled: bool) -> None: - mocker.patch("site.check_enableusersite", return_value=enabled) - env = SystemEnv(Path(sys.prefix)) - assert (enabled and env.usersite is not None) or ( - not enabled and env.usersite is None - ) - - -def test_venv_has_correct_paths(tmp_venv: VirtualEnv) -> None: - paths = tmp_venv.paths - - assert paths.get("purelib") is not None - assert paths.get("platlib") is not None - assert paths.get("scripts") is not None - assert tmp_venv.site_packages.path == Path(paths["purelib"]) - assert paths["include"] == str( - tmp_venv.path.joinpath( - f"include/site/python{tmp_venv.version_info[0]}.{tmp_venv.version_info[1]}" - ) - ) - - -def test_env_system_packages(tmp_path: Path, poetry: Poetry) -> None: - venv_path = tmp_path / "venv" - pyvenv_cfg = venv_path / "pyvenv.cfg" - - EnvManager(poetry).build_venv(path=venv_path, flags={"system-site-packages": True}) - env = VirtualEnv(venv_path) - - assert "include-system-site-packages = true" in pyvenv_cfg.read_text() - assert env.includes_system_site_packages - - -def test_env_system_packages_are_relative_to_lib( - tmp_path: Path, poetry: Poetry -) -> None: - venv_path = tmp_path / "venv" - EnvManager(poetry).build_venv(path=venv_path, flags={"system-site-packages": True}) - env = VirtualEnv(venv_path) - site_dir = Path(site.getsitepackages()[-1]) - for dist in metadata.distributions(): - # Emulate is_relative_to, only available in 3.9+ - with contextlib.suppress(ValueError): - dist._path.relative_to(site_dir) # type: ignore[attr-defined] - break - assert env.is_path_relative_to_lib(dist._path) # type: ignore[attr-defined] - - -@pytest.mark.parametrize( - ("flags", "packages"), - [ - ({"no-pip": False}, {"pip"}), - ({"no-pip": False, "no-wheel": True}, {"pip"}), - ({"no-pip": False, "no-wheel": False}, {"pip", "wheel"}), - ({"no-pip": True}, set()), - ({"no-setuptools": False}, {"setuptools"}), - ({"no-setuptools": True}, set()), - ({"setuptools": "bundle"}, {"setuptools"}), - ({"no-pip": True, "no-setuptools": False}, {"setuptools"}), - ({"no-wheel": False}, {"wheel"}), - ({"wheel": "bundle"}, {"wheel"}), - ({}, set()), - ], -) -def test_env_no_pip( - tmp_path: Path, poetry: Poetry, flags: dict[str, str | bool], packages: set[str] -) -> None: - venv_path = tmp_path / "venv" - EnvManager(poetry).build_venv(path=venv_path, flags=flags) - env = VirtualEnv(venv_path) - installed_repository = InstalledRepository.load(env=env, with_dependencies=True) - installed_packages = { - package.name - for package in installed_repository.packages - # workaround for BSD test environments - if package.name != "sqlite3" - } - - # For python >= 3.12, virtualenv defaults to "--no-setuptools" and "--no-wheel" - # behaviour, so setting these values to False becomes meaningless. - if sys.version_info >= (3, 12): - if not flags.get("no-setuptools", True): - packages.discard("setuptools") - if not flags.get("no-wheel", True): - packages.discard("wheel") - - assert installed_packages == packages - - -def test_env_finds_the_correct_executables(tmp_path: Path, manager: EnvManager) -> None: - venv_path = tmp_path / "Virtual Env" - manager.build_venv(venv_path, with_pip=True) - venv = VirtualEnv(venv_path) - - default_executable = expected_executable = f"python{'.exe' if WINDOWS else ''}" - default_pip_executable = expected_pip_executable = f"pip{'.exe' if WINDOWS else ''}" - major_executable = f"python{sys.version_info[0]}{'.exe' if WINDOWS else ''}" - major_pip_executable = f"pip{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_path: Path, manager: EnvManager -) -> None: - venv_path = tmp_path / "Virtual Env" - child_venv_path = tmp_path / "Child Virtual Env" - manager.build_venv(venv_path, with_pip=True) - parent_venv = VirtualEnv(venv_path) - manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) - venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) - - expected_executable = ( - f"python{sys.version_info[0]}.{sys.version_info[1]}{'.exe' if WINDOWS else ''}" + side_effect=check_output_wrapper(Version.parse("3.7.5")), ) - expected_pip_executable = ( - f"pip{sys.version_info[0]}.{sys.version_info[1]}{'.exe' if WINDOWS else ''}" + m = mocker.patch( + "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" ) - 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 - + manager.create_venv() -def test_env_finds_fallback_executables_for_generic_env( - tmp_path: Path, manager: EnvManager -) -> None: - venv_path = tmp_path / "Virtual Env" - child_venv_path = tmp_path / "Child Virtual Env" - manager.build_venv(venv_path, with_pip=True) - parent_venv = VirtualEnv(venv_path) - manager.build_venv(child_venv_path, executable=parent_venv.python, with_pip=True) - venv = GenericEnv(parent_venv.path, child_env=VirtualEnv(child_venv_path)) - - default_executable = f"python{'.exe' if WINDOWS else ''}" - major_executable = f"python{sys.version_info[0]}{'.exe' if WINDOWS else ''}" - minor_executable = ( - f"python{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 = f"pip{'.exe' if WINDOWS else ''}" - major_pip_executable = f"pip{sys.version_info[0]}{'.exe' if WINDOWS else ''}" - minor_pip_executable = ( - f"pip{sys.version_info[0]}.{sys.version_info[1]}{'.exe' if WINDOWS else ''}" + m.assert_called_with( + config_virtualenvs_path / f"{venv_name}-py3.7", + executable=Path("/usr/bin/python3"), + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, + prompt="virtualenv-py3.7", ) - 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 def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( @@ -1607,6 +1231,27 @@ def mock_check_output(cmd: str, *args: Any, **kwargs: Any) -> str: ) +@pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") +def test_venv_backup_exclusion(tmp_path: Path, manager: EnvManager) -> None: + import xattr + + venv_path = tmp_path / "Virtual Env" + + manager.build_venv(venv_path) + + value = ( + b"bplist00_\x10\x11com.apple.backupd" + b"\x08\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c" + ) + assert ( + xattr.getxattr( + str(venv_path), "com.apple.metadata:com_apple_backup_excludeItem" + ) + == value + ) + + def test_generate_env_name_ignores_case_for_case_insensitive_fs( poetry: Poetry, tmp_path: Path, @@ -1626,126 +1271,3 @@ def test_generate_env_name_uses_real_path( venv_name1 = EnvManager.generate_env_name("simple-project", "the_real_dir") venv_name2 = EnvManager.generate_env_name("simple-project", "linked_dir") assert venv_name1 == venv_name2 - - -@pytest.fixture() -def extended_without_setup_poetry(fixture_dir: FixtureDirGetter) -> Poetry: - poetry = Factory().create_poetry(fixture_dir("extended_project_without_setup")) - - return poetry - - -def test_build_environment_called_build_script_specified( - mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_path: Path -) -> None: - project_env = MockEnv(path=tmp_path / "project") - ephemeral_env = MockEnv(path=tmp_path / "ephemeral") - - mocker.patch( - "poetry.utils.env.ephemeral_environment" - ).return_value.__enter__.return_value = ephemeral_env - - with build_environment(extended_without_setup_poetry, project_env) as env: - assert env == ephemeral_env - assert env.executed == [ # type: ignore[attr-defined] - [ - str(sys.executable), - str(env.pip_embedded), - "install", - "--disable-pip-version-check", - "--ignore-installed", - "--no-input", - *extended_without_setup_poetry.pyproject.build_system.requires, - ] - ] - - -def test_build_environment_not_called_without_build_script_specified( - mocker: MockerFixture, poetry: Poetry, tmp_path: Path -) -> None: - project_env = MockEnv(path=tmp_path / "project") - ephemeral_env = MockEnv(path=tmp_path / "ephemeral") - - mocker.patch( - "poetry.utils.env.ephemeral_environment" - ).return_value.__enter__.return_value = ephemeral_env - - with build_environment(poetry, project_env) as env: - assert env == project_env - assert not env.executed # type: ignore[attr-defined] - - -def test_create_venv_project_name_empty_sets_correct_prompt( - fixture_dir: FixtureDirGetter, - project_factory: ProjectFactory, - config: Config, - mocker: MockerFixture, - config_virtualenvs_path: Path, -) -> None: - if "VIRTUAL_ENV" in os.environ: - del os.environ["VIRTUAL_ENV"] - - poetry = project_factory("no", source=fixture_dir("no_name_project")) - manager = EnvManager(poetry) - - poetry.package.python_versions = "^3.7" - venv_name = manager.generate_env_name("", str(poetry.file.path.parent)) - - mocker.patch("sys.version_info", (2, 7, 16)) - mocker.patch("shutil.which", side_effect=lambda py: f"/usr/bin/{py}") - mocker.patch( - "subprocess.check_output", - side_effect=check_output_wrapper(Version.parse("3.7.5")), - ) - m = mocker.patch( - "poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "" - ) - - manager.create_venv() - - m.assert_called_with( - config_virtualenvs_path / f"{venv_name}-py3.7", - executable=Path("/usr/bin/python3"), - flags={ - "always-copy": False, - "system-site-packages": False, - "no-pip": False, - "no-setuptools": False, - }, - prompt="virtualenv-py3.7", - ) - - -def test_fallback_on_detect_active_python( - poetry: Poetry, mocker: MockerFixture -) -> None: - m = mocker.patch( - "subprocess.check_output", - side_effect=subprocess.CalledProcessError(1, "some command"), - ) - env_manager = EnvManager(poetry) - active_python = env_manager._detect_active_python() - - assert active_python is None - assert m.call_count == 1 - - -@pytest.mark.skipif(sys.platform != "win32", reason="Windows only") -def test_detect_active_python_with_bat(poetry: Poetry, tmp_path: Path) -> None: - """On Windows pyenv uses batch files for python management.""" - python_wrapper = tmp_path / "python.bat" - wrapped_python = Path(r"C:\SpecialPython\python.exe") - with python_wrapper.open("w") as f: - f.write(f"@echo {wrapped_python}") - os.environ["PATH"] = str(python_wrapper.parent) + os.pathsep + os.environ["PATH"] - - active_python = EnvManager(poetry)._detect_active_python() - - assert active_python == wrapped_python - - -def test_command_from_bin_preserves_relative_path(manager: EnvManager) -> None: - # https://github.com/python-poetry/poetry/issues/7959 - env = manager.get() - command = env.get_command_from_bin("./foo.py") - assert command == ["./foo.py"] diff --git a/tests/utils/test_env_site.py b/tests/utils/env/test_env_site_packages.py similarity index 100% rename from tests/utils/test_env_site.py rename to tests/utils/env/test_env_site_packages.py diff --git a/tests/utils/test_authenticator.py b/tests/utils/test_authenticator.py index 844f761ae56..04db95a132a 100644 --- a/tests/utils/test_authenticator.py +++ b/tests/utils/test_authenticator.py @@ -50,12 +50,10 @@ def repo() -> dict[str, dict[str, str]]: @pytest.fixture def mock_config(config: Config, repo: dict[str, dict[str, str]]) -> Config: - config.merge( - { - "repositories": repo, - "http-basic": {"foo": {"username": "bar", "password": "baz"}}, - } - ) + config.merge({ + "repositories": repo, + "http-basic": {"foo": {"username": "bar", "password": "baz"}}, + }) return config @@ -144,12 +142,10 @@ def test_authenticator_uses_empty_strings_as_default_password( http: type[httpretty.httpretty], with_simple_keyring: None, ) -> None: - config.merge( - { - "repositories": repo, - "http-basic": {"foo": {"username": "bar"}}, - } - ) + config.merge({ + "repositories": repo, + "http-basic": {"foo": {"username": "bar"}}, + }) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") @@ -165,12 +161,10 @@ def test_authenticator_uses_empty_strings_as_default_username( repo: dict[str, dict[str, str]], http: type[httpretty.httpretty], ) -> None: - config.merge( - { - "repositories": repo, - "http-basic": {"foo": {"username": None, "password": "bar"}}, - } - ) + config.merge({ + "repositories": repo, + "http-basic": {"foo": {"username": None, "password": "bar"}}, + }) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") @@ -188,11 +182,9 @@ def test_authenticator_falls_back_to_keyring_url( with_simple_keyring: None, dummy_keyring: DummyBackend, ) -> None: - config.merge( - { - "repositories": repo, - } - ) + config.merge({ + "repositories": repo, + }) dummy_keyring.set_password( "https://foo.bar/simple/", None, SimpleCredential("foo", "bar") @@ -214,11 +206,9 @@ def test_authenticator_falls_back_to_keyring_netloc( with_simple_keyring: None, dummy_keyring: DummyBackend, ) -> None: - config.merge( - { - "repositories": repo, - } - ) + config.merge({ + "repositories": repo, + }) dummy_keyring.set_password("foo.bar", None, SimpleCredential("foo", "bar")) @@ -388,13 +378,11 @@ def test_authenticator_uses_certs_from_config_if_not_provided( configured_cert = "/path/to/cert" configured_client_cert = "/path/to/client-cert" - mock_config.merge( - { - "certificates": { - "foo": {"cert": configured_cert, "client-cert": configured_client_cert} - }, - } - ) + mock_config.merge({ + "certificates": { + "foo": {"cert": configured_cert, "client-cert": configured_client_cert} + }, + }) authenticator = Authenticator(mock_config, NullIO()) url = "https://foo.bar/files/foo-0.1.0.tar.gz" @@ -415,18 +403,16 @@ def test_authenticator_uses_certs_from_config_if_not_provided( def test_authenticator_uses_credentials_from_config_matched_by_url_path( config: Config, mock_remote: None, http: type[httpretty.httpretty] ) -> None: - config.merge( - { - "repositories": { - "foo-alpha": {"url": "https://foo.bar/alpha/files/simple/"}, - "foo-beta": {"url": "https://foo.bar/beta/files/simple/"}, - }, - "http-basic": { - "foo-alpha": {"username": "bar", "password": "alpha"}, - "foo-beta": {"username": "baz", "password": "beta"}, - }, - } - ) + config.merge({ + "repositories": { + "foo-alpha": {"url": "https://foo.bar/alpha/files/simple/"}, + "foo-beta": {"url": "https://foo.bar/beta/files/simple/"}, + }, + "http-basic": { + "foo-alpha": {"username": "bar", "password": "alpha"}, + "foo-beta": {"username": "baz", "password": "beta"}, + }, + }) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/alpha/files/simple/foo-0.1.0.tar.gz") @@ -448,16 +434,14 @@ def test_authenticator_uses_credentials_from_config_matched_by_url_path( def test_authenticator_uses_credentials_from_config_with_at_sign_in_path( config: Config, mock_remote: None, http: type[httpretty.httpretty] ) -> None: - config.merge( - { - "repositories": { - "foo": {"url": "https://foo.bar/beta/files/simple/"}, - }, - "http-basic": { - "foo": {"username": "bar", "password": "baz"}, - }, - } - ) + config.merge({ + "repositories": { + "foo": {"url": "https://foo.bar/beta/files/simple/"}, + }, + "http-basic": { + "foo": {"username": "bar", "password": "baz"}, + }, + }) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/beta/files/simple/f@@-0.1.0.tar.gz") @@ -474,14 +458,12 @@ def test_authenticator_falls_back_to_keyring_url_matched_by_path( with_simple_keyring: None, dummy_keyring: DummyBackend, ) -> None: - config.merge( - { - "repositories": { - "foo-alpha": {"url": "https://foo.bar/alpha/files/simple/"}, - "foo-beta": {"url": "https://foo.bar/beta/files/simple/"}, - } + config.merge({ + "repositories": { + "foo-alpha": {"url": "https://foo.bar/alpha/files/simple/"}, + "foo-beta": {"url": "https://foo.bar/beta/files/simple/"}, } - ) + }) dummy_keyring.set_password( "https://foo.bar/alpha/files/simple/", None, SimpleCredential("foo", "bar") @@ -517,14 +499,12 @@ def test_authenticator_uses_env_provided_credentials_matched_by_url_path( monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_BETA_USERNAME", "baz") monkeypatch.setenv("POETRY_HTTP_BASIC_FOO_BETA_PASSWORD", "beta") - config.merge( - { - "repositories": { - "foo-alpha": {"url": "https://foo.bar/alpha/files/simple/"}, - "foo-beta": {"url": "https://foo.bar/beta/files/simple/"}, - } + config.merge({ + "repositories": { + "foo-alpha": {"url": "https://foo.bar/alpha/files/simple/"}, + "foo-beta": {"url": "https://foo.bar/beta/files/simple/"}, } - ) + }) authenticator = Authenticator(config, NullIO()) @@ -548,22 +528,16 @@ def test_authenticator_azure_feed_guid_credentials( with_simple_keyring: None, dummy_keyring: DummyBackend, ) -> None: - config.merge( - { - "repositories": { - "alpha": { - "url": "https://foo.bar/org-alpha/_packaging/feed/pypi/simple/" - }, - "beta": { - "url": "https://foo.bar/org-beta/_packaging/feed/pypi/simple/" - }, - }, - "http-basic": { - "alpha": {"username": "foo", "password": "bar"}, - "beta": {"username": "baz", "password": "qux"}, - }, - } - ) + config.merge({ + "repositories": { + "alpha": {"url": "https://foo.bar/org-alpha/_packaging/feed/pypi/simple/"}, + "beta": {"url": "https://foo.bar/org-beta/_packaging/feed/pypi/simple/"}, + }, + "http-basic": { + "alpha": {"username": "foo", "password": "bar"}, + "beta": {"username": "baz", "password": "qux"}, + }, + }) authenticator = Authenticator(config, NullIO()) @@ -593,13 +567,11 @@ def test_authenticator_add_repository( with_simple_keyring: None, dummy_keyring: DummyBackend, ) -> None: - config.merge( - { - "http-basic": { - "source": {"username": "foo", "password": "bar"}, - }, - } - ) + config.merge({ + "http-basic": { + "source": {"username": "foo", "password": "bar"}, + }, + }) authenticator = Authenticator(config, NullIO()) @@ -629,18 +601,16 @@ def test_authenticator_git_repositories( with_simple_keyring: None, dummy_keyring: DummyBackend, ) -> None: - config.merge( - { - "repositories": { - "one": {"url": "https://foo.bar/org/one.git"}, - "two": {"url": "https://foo.bar/org/two.git"}, - }, - "http-basic": { - "one": {"username": "foo", "password": "bar"}, - "two": {"username": "baz", "password": "qux"}, - }, - } - ) + config.merge({ + "repositories": { + "one": {"url": "https://foo.bar/org/one.git"}, + "two": {"url": "https://foo.bar/org/two.git"}, + }, + "http-basic": { + "one": {"username": "foo", "password": "bar"}, + "two": {"username": "baz", "password": "qux"}, + }, + }) authenticator = Authenticator(config, NullIO()) diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index bf69bea7f44..84e1520a39b 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -6,10 +6,15 @@ from poetry.core.utils.helpers import parse_requires +from poetry.utils.helpers import download_file from poetry.utils.helpers import get_file_hash if TYPE_CHECKING: + from pathlib import Path + + from httpretty import httpretty + from tests.types import FixtureDirGetter @@ -119,3 +124,18 @@ def test_guaranteed_hash( ) -> None: file_path = fixture_dir("distributions") / "demo-0.1.0.tar.gz" assert get_file_hash(file_path, hash_name) == expected + + +def test_download_file( + http: type[httpretty], fixture_dir: FixtureDirGetter, tmp_path: Path +) -> None: + file_path = fixture_dir("distributions") / "demo-0.1.0.tar.gz" + url = "https://foo.com/demo-0.1.0.tar.gz" + http.register_uri(http.GET, url, body=file_path.read_bytes()) + dest = tmp_path / "demo-0.1.0.tar.gz" + + download_file(url, dest) + + expect_sha_256 = "9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad" + assert get_file_hash(dest) == expect_sha_256 + assert http.last_request().headers["Accept-Encoding"] == "Identity" diff --git a/tests/utils/test_password_manager.py b/tests/utils/test_password_manager.py index 5efcf7f721b..181ebcc98f0 100644 --- a/tests/utils/test_password_manager.py +++ b/tests/utils/test_password_manager.py @@ -291,11 +291,9 @@ def test_get_http_auth_does_not_call_keyring_when_credentials_in_environment_var def test_get_http_auth_does_not_call_keyring_when_password_in_environment_variables( environ: None, config: Config ) -> None: - config.merge( - { - "http-basic": {"foo": {"username": "bar"}}, - } - ) + config.merge({ + "http-basic": {"foo": {"username": "bar"}}, + }) os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "baz" manager = PasswordManager(config) diff --git a/tests/vcs/git/test_backend.py b/tests/vcs/git/test_backend.py new file mode 100644 index 00000000000..cd9a7c5e781 --- /dev/null +++ b/tests/vcs/git/test_backend.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from poetry.vcs.git.backend import is_revision_sha + + +VALID_SHA = "c5c7624ef64f34d9f50c3b7e8118f7f652fddbbd" + + +def test_invalid_revision_sha() -> None: + result = is_revision_sha("invalid_input") + assert result is False + + +def test_valid_revision_sha() -> None: + result = is_revision_sha(VALID_SHA) + assert result is True + + +def test_invalid_revision_sha_min_len() -> None: + result = is_revision_sha("c5c7") + assert result is False + + +def test_invalid_revision_sha_max_len() -> None: + result = is_revision_sha(VALID_SHA + "42") + assert result is False