diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f43f8aa51c9..fce06b435d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,11 +31,11 @@ jobs: python-version: ["3.7", "3.8", "3.9", "3.10"] include: - os: Ubuntu - image: ubuntu-latest + image: ubuntu-22.04 - os: Windows image: windows-2022 - os: macOS - image: macos-11 + image: macos-12 fail-fast: false defaults: run: @@ -81,15 +81,15 @@ jobs: - name: Install dependencies run: poetry install + - name: Run mypy + run: poetry run mypy + - name: Install pytest plugin run: poetry run pip install pytest-github-actions-annotate-failures - name: Run pytest run: poetry run python -m pytest -p no:sugar -q tests/ - - name: Run mypy - run: poetry run mypy - - name: Run pytest (integration suite) env: POETRY_TEST_INTEGRATION_GIT_USERNAME: ${GITHUB_ACTOR} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e6713a837e..f462275abab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,6 +52,12 @@ repos: args: [--py37-plus] exclude: ^(install|get)-poetry.py$ + - repo: https://github.com/hadialqattan/pycln + rev: v1.3.2 + hooks: + - id: pycln + args: [--all] + - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: diff --git a/docs/_index.md b/docs/_index.md index 016c9f8d98d..3fc000fc248 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -43,9 +43,14 @@ curl -sSL https://install.python-poetry.org | python3 - **windows powershell install instructions** ```powershell -(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python - +(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py - ``` +{{% note %}} +If you have installed Python through the Microsoft Store, replace `py` with `python` in the command +above. +{{% /note %}} + {{% note %}} Note that the installer does not support Python < 3.7. {{% /note %}} diff --git a/docs/cli.md b/docs/cli.md index 11cfc05751b..81768824df0 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -357,11 +357,15 @@ If the package(s) you want to install provide extras, you can specify them when adding the package: ```bash -poetry add requests[security,socks] +poetry add "requests[security,socks]" poetry add "requests[security,socks]~=2.22.0" poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]" ``` +{{% warning %}} +Some shells may treat square braces (`[` and `]`) as special characters. It is suggested to always quote arguments containing these characters to prevent unexpected shell expansion. +{{% /warning %}} + If you want to add a package to a specific group of dependencies, you can use the `--group (-G)` option: ```bash @@ -509,6 +513,7 @@ See [Configuration]({{< relref "configuration" >}}) for all available settings. * `--unset`: Remove the configuration element named by `setting-key`. * `--list`: Show the list of current config variables. +* `--local`: Set/Get settings that are specific to a project (in the local configuration file `poetry.toml`). ## run @@ -623,6 +628,7 @@ The table below illustrates the effect of these rules with concrete examples. ### Options * `--short (-s)`: Output the version number only. +* `--dry-run`: Do not update pyproject.toml file. ## export diff --git a/docs/plugins.md b/docs/plugins.md index 96ca48e191b..761b4d68f19 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -67,11 +67,8 @@ from poetry.poetry import Poetry class MyPlugin(Plugin): def activate(self, poetry: Poetry, io: IO): - version = self.get_custom_version() - io.write_line(f"Setting package version to {version}") - poetry.package.set_version(version) - - def get_custom_version(self) -> str: + io.write_line("Setting readme") + poetry.package.readme = "README.md" ... ``` diff --git a/docs/pyproject.md b/docs/pyproject.md index a318d357e60..ec75e222e1e 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -17,12 +17,20 @@ The `tool.poetry` section of the `pyproject.toml` file is composed of multiple s The name of the package. **Required** +```toml +name = "my-package" +``` + ## version The version of the package. **Required** This should be a valid [PEP 440](https://peps.python.org/pep-0440/) string. +```toml +version = "0.1.0" +``` + {{% note %}} If you would like to use semantic versioning for your project, please see @@ -34,6 +42,10 @@ If you would like to use semantic versioning for your project, please see A short description of the package. **Required** +```toml +description = "A short description of the package." +``` + ## license The license of the package. @@ -61,40 +73,76 @@ More identifiers are listed at the [SPDX Open Source License Registry](https://s If your project is proprietary and does not use a specific licence, you can set this value as `Proprietary`. {{% /note %}} +```toml +license = "MIT" +``` + ## authors The authors of the package. **Required** This is a list of authors and should contain at least one author. Authors must be in the form `name `. +```toml +authors = [ + "Sébastien Eustace ", +] +``` + ## maintainers The maintainers of the package. **Optional** This is a list of maintainers and should be distinct from authors. Maintainers may contain an email and be in the form `name `. +```toml +maintainers = [ + "Richard Brave ", +] +``` + ## readme The readme file of the package. **Optional** The file can be either `README.rst` or `README.md`. +```toml +readme = "README.md" # or "README.rst" +``` + ## homepage An URL to the website of the project. **Optional** +```toml +homepage = "https://python-poetry.org/" +``` + ## repository An URL to the repository of the project. **Optional** +```toml +repository = "https://github.com/python-poetry/poetry" +``` + ## documentation An URL to the documentation of the project. **Optional** +```toml +documentation = "https://python-poetry.org/docs/" +``` + ## keywords A list of keywords that the package is related to. **Optional** +```toml +keywords = ["packaging", "poetry"] +``` + ## classifiers A list of PyPI [trove classifiers](https://pypi.org/classifiers/) that describe the project. **Optional** diff --git a/poetry.lock b/poetry.lock index fde7774cd30..c773098d0b9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -116,6 +116,9 @@ category = "dev" optional = false python-versions = ">=3.7" +[package.dependencies] +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -170,7 +173,7 @@ python-versions = "*" [[package]] name = "dulwich" -version = "0.20.35" +version = "0.20.38" description = "Python Git Library" category = "main" optional = false @@ -183,6 +186,7 @@ urllib3 = ">=1.24.1" [package.extras] fastimport = ["fastimport"] https = ["urllib3[secure] (>=1.24.1)"] +paramiko = ["paramiko"] pgp = ["gpg"] watch = ["pyinotify"] @@ -520,11 +524,11 @@ diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" -version = "6.2.5" +version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} @@ -535,23 +539,22 @@ iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" py = ">=1.8.2" -toml = "*" +tomli = ">=1.0.0" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" -version = "2.12.1" +version = "3.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] @@ -721,7 +724,7 @@ python-versions = ">=3.6" [[package]] name = "types-requests" -version = "2.27.25" +version = "2.27.26" description = "Typing stubs for requests" category = "dev" optional = false @@ -801,7 +804,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "2faf18664a94b0dc94c937c24fe8fdbf95aefe3e6bc85e2346fe935047bca353" +content-hash = "5421b8901d8d4589152de8dafcb79827eaefa00ae7c3af53092be08a0caed970" [metadata.files] atomicwrites = [ @@ -972,27 +975,7 @@ distlib = [ {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] dulwich = [ - {file = "dulwich-0.20.35-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:428b5fbb79f8cfba2f5ac6826cc813d1903b44b0780e9ec57e54cbd0f44feb61"}, - {file = "dulwich-0.20.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:581c6aa825c9267794747c5cc5ec3831960d96ca7fd9eb0158989e9a4099cbb1"}, - {file = "dulwich-0.20.35-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e11cc7a30b42dbbe5a0b6ebbfbfbb07138a5ffd6175bab2ddbabc9882a1c0438"}, - {file = "dulwich-0.20.35-cp310-cp310-win_amd64.whl", hash = "sha256:22c61a24edb699564b49a9701b723a08fa773f5d3322e8a0cabda897ae86816e"}, - {file = "dulwich-0.20.35-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9759cf611503681bcdd2950c9d2db04d1c057ecbb62d6fccd095b13771864f1c"}, - {file = "dulwich-0.20.35-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d683b4f30b1dae6b1668336f62f10ff57ebf2a1252c7cc76ad3eeff973879eb"}, - {file = "dulwich-0.20.35-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9d85b6b41c4be6df9ecdc4014d3cbe78a5a44a73c97bccbefac3e5de83bb74be"}, - {file = "dulwich-0.20.35-cp36-cp36m-win_amd64.whl", hash = "sha256:6dc9b082f6ace9890de572260a575a09a996d617f5930edd2858c6f8fedfd7fb"}, - {file = "dulwich-0.20.35-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:28ac2374f09487b02a8cb9b2fad083c358fc927bcfe9803d971614bc00e25076"}, - {file = "dulwich-0.20.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:195b21c7a8f85cb2de8938d54fcc6d589d1ccbceaa63bb117796b531065bb68b"}, - {file = "dulwich-0.20.35-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9bdea3a4e8e5e3b1dbd513d9ab8a692f8a9a6f4760633e25c006446bce56fc5e"}, - {file = "dulwich-0.20.35-cp37-cp37m-win_amd64.whl", hash = "sha256:3d3d07b5aa51e6b7d08707c62932da86adbbaaa62552a0129b37d413735c7786"}, - {file = "dulwich-0.20.35-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5d94cd182fb0da4ec2f182be977b27b9cc1d7dbd0ee9bbf991e101a95fdcd3d8"}, - {file = "dulwich-0.20.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f563e9f51e83c47a7df2f3cea79919f700e50d1e5556b6b753730b9cd2be1f47"}, - {file = "dulwich-0.20.35-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f221c3c2fd10260419905bb673cd00129d491e3ed38c7a8d3ac2c7662682dd9b"}, - {file = "dulwich-0.20.35-cp38-cp38-win_amd64.whl", hash = "sha256:c4f4c59445dc5c2341e9cb2fe35e51a890e8a5f42178abec0a96044811c558a9"}, - {file = "dulwich-0.20.35-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:3616a949053eb6bdf34581f57d1f6cb7192a4bb635be1a02c37f6f6dda032277"}, - {file = "dulwich-0.20.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134a2f586847c2c58569959a784d7a875b551df4226b639267302217799e4234"}, - {file = "dulwich-0.20.35-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c008b6b562af76cf011d3b5450a0d30edc96feeee7856b081d7400bc7cf42653"}, - {file = "dulwich-0.20.35-cp39-cp39-win_amd64.whl", hash = "sha256:bf228800785754d7a55d52c5f122c26c3ced51f0f3df727fde2c9fefb71d5d76"}, - {file = "dulwich-0.20.35.tar.gz", hash = "sha256:953f6301a9df8a091fa88d55eed394a88bf9988cde8be341775354910918c196"}, + {file = "dulwich-0.20.38.tar.gz", hash = "sha256:7346790d8735c86fbbc5b70b674f0ef94096c1e5099ba7273491628239817fc8"}, ] entrypoints = [ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, @@ -1166,12 +1149,12 @@ pyparsing = [ {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] pytest-mock = [ {file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"}, @@ -1285,8 +1268,8 @@ typed-ast = [ {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, ] types-requests = [ - {file = "types-requests-2.27.25.tar.gz", hash = "sha256:805ae7e38fd9d157153066dc4381cf585fd34dfa212f2fc1fece248c05aac571"}, - {file = "types_requests-2.27.25-py3-none-any.whl", hash = "sha256:2444905c89731dbcb6bbcd6d873a04252445df7623917c640e463b2b28d2a708"}, + {file = "types-requests-2.27.26.tar.gz", hash = "sha256:a6a04c0274c0949fd0525f35d8b53ac34e77afecbeb3c4932ddc6ce675ac009c"}, + {file = "types_requests-2.27.26-py3-none-any.whl", hash = "sha256:302137cb5bd482357398a155faf3ed095855fbc6994e952d0496c7fd50f44125"}, ] types-urllib3 = [ {file = "types-urllib3-1.26.14.tar.gz", hash = "sha256:2a2578e4b36341ccd240b00fccda9826988ff0589a44ba4a664bbd69ef348d27"}, diff --git a/pyproject.toml b/pyproject.toml index 88ede12c255..d7a1db7b42e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,8 +61,8 @@ dulwich = "^0.20.35" [tool.poetry.dev-dependencies] tox = "^3.18" -pytest = "^6.2" -pytest-cov = "^2.8" +pytest = "^7.1" +pytest-cov = "^3.0" pytest-mock = "^3.5" pytest-sugar = "^0.9" pre-commit = "^2.6" @@ -116,18 +116,23 @@ enable_error_code = ["ignore-without-code"] [[tool.mypy.overrides]] module = [ - 'poetry.console.commands.init', - 'poetry.inspection.info', - 'poetry.installation.chef', - 'poetry.installation.chooser', - 'poetry.installation.executor', - 'poetry.installation.installer', - 'poetry.installation.pip_installer', - 'poetry.repositories.installed_repository', 'poetry.utils.env', ] ignore_errors = true +# use of importlib-metadata backport at python3.7 makes it impossible to +# satisfy mypy without some ignores: but we get a different set of ignores at +# different python versions. +# +# , meanwhile suppress that +# warning. +[[tool.mypy.overrides]] +module = [ + 'poetry.installation.executor', + 'poetry.repositories.installed_repository', +] +warn_unused_ignores = false + [[tool.mypy.overrides]] module = [ 'cachecontrol.*', @@ -138,6 +143,7 @@ module = [ 'html5lib.*', 'jsonschema.*', 'pexpect.*', + 'pkginfo.*', 'poetry.core.*', 'requests_toolbelt.*', 'shellingham.*', diff --git a/src/poetry/__version__.py b/src/poetry/__version__.py index 90c132aaa90..13fa08fc202 100644 --- a/src/poetry/__version__.py +++ b/src/poetry/__version__.py @@ -1,10 +1,14 @@ from __future__ import annotations -from typing import Callable +from typing import TYPE_CHECKING from poetry.utils._compat import metadata +if TYPE_CHECKING: + from collections.abc import Callable + + # The metadata.version that we import for Python 3.7 is untyped, work around # that. version: Callable[[str], str] = metadata.version diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 667438243f3..f748386321e 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -9,7 +9,6 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Callable from poetry.core.toml import TOMLFile from poetry.core.utils.helpers import canonicalize_name @@ -21,6 +20,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from poetry.config.config_source import ConfigSource diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index c4a330b2143..c37cd5a677e 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -7,7 +7,6 @@ from importlib import import_module from typing import TYPE_CHECKING from typing import Any -from typing import Callable from typing import cast from cleo.application import Application as BaseApplication @@ -24,6 +23,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from cleo.events.console_command_event import ConsoleCommandEvent from cleo.io.inputs.definition import Definition from cleo.io.inputs.input import Input diff --git a/src/poetry/console/command_loader.py b/src/poetry/console/command_loader.py index 562bd94fd3b..40f6b7f31bd 100644 --- a/src/poetry/console/command_loader.py +++ b/src/poetry/console/command_loader.py @@ -1,13 +1,14 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import Callable from cleo.exceptions import LogicException from cleo.loaders.factory_command_loader import FactoryCommandLoader if TYPE_CHECKING: + from collections.abc import Callable + from poetry.console.commands.command import Command diff --git a/src/poetry/console/commands/about.py b/src/poetry/console/commands/about.py index 94c9721365c..1841c5448c7 100644 --- a/src/poetry/console/commands/about.py +++ b/src/poetry/console/commands/about.py @@ -1,10 +1,14 @@ from __future__ import annotations -from typing import Callable +from typing import TYPE_CHECKING from poetry.console.commands.command import Command +if TYPE_CHECKING: + from collections.abc import Callable + + class AboutCommand(Command): name = "about" diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index d59bfe672f2..7079a52da21 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -5,7 +5,9 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import Dict from typing import Mapping +from typing import Union from cleo.helpers import option from tomlkit import inline_table @@ -22,6 +24,8 @@ from poetry.repositories import Pool +Requirements = Dict[str, Union[str, Mapping[str, Any]]] + class InitCommand(Command): name = "init" @@ -162,7 +166,7 @@ def handle(self) -> int: if self.io.is_interactive(): self.line("") - requirements = {} + requirements: Requirements = {} if self.option("dependency"): requirements = self._format_requirements( self._determine_requirements(self.option("dependency")) @@ -192,7 +196,7 @@ def handle(self) -> int: if self.io.is_interactive(): self.line("") - dev_requirements: dict[str, str] = {} + dev_requirements: Requirements = {} if self.option("dev-dependency"): dev_requirements = self._format_requirements( self._determine_requirements(self.option("dev-dependency")) @@ -264,9 +268,9 @@ def _determine_requirements( requires: list[str], allow_prereleases: bool = False, source: str | None = None, - ) -> list[dict[str, str | list[str]]]: + ) -> list[dict[str, Any]]: if not requires: - requires = [] + result = [] package = self.ask( "Search for package to add (or leave blank to continue):" @@ -280,7 +284,7 @@ def _determine_requirements( or "version" in constraint ): self.line(f"Adding {package}") - requires.append(constraint) + result.append(constraint) package = self.ask("\nAdd a package:") continue @@ -337,16 +341,15 @@ def _determine_requirements( constraint["version"] = package_constraint if package is not False: - requires.append(constraint) + result.append(constraint) if self.io.is_interactive(): package = self.ask("\nAdd a package:") - return requires + return result - requires = self._parse_requirements(requires) result = [] - for requirement in requires: + for requirement in self._parse_requirements(requires): if "git" in requirement or "url" in requirement or "path" in requirement: result.append(requirement) continue @@ -414,10 +417,8 @@ def _parse_requirements(self, requirements: list[str]) -> list[dict[str, Any]]: for requirement in requirements ] - def _format_requirements( - self, requirements: list[dict[str, str]] - ) -> Mapping[str, str | Mapping[str, str]]: - requires = {} + def _format_requirements(self, requirements: list[dict[str, str]]) -> Requirements: + requires: Requirements = {} for requirement in requirements: name = requirement.pop("name") constraint: str | InlineTable diff --git a/src/poetry/console/commands/plugin/add.py b/src/poetry/console/commands/plugin/add.py index 70418500b97..ea41fbb1f71 100644 --- a/src/poetry/console/commands/plugin/add.py +++ b/src/poetry/console/commands/plugin/add.py @@ -62,11 +62,11 @@ def handle(self) -> int: from cleo.io.inputs.string_input import StringInput from cleo.io.io import IO + from poetry.core.packages.project_package import ProjectPackage from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.semver.helpers import parse_constraint from poetry.factory import Factory - from poetry.packages.project_package import ProjectPackage from poetry.repositories.installed_repository import InstalledRepository from poetry.utils.env import EnvManager diff --git a/src/poetry/console/commands/version.py b/src/poetry/console/commands/version.py index 7b5744d6171..28b3c6815f3 100644 --- a/src/poetry/console/commands/version.py +++ b/src/poetry/console/commands/version.py @@ -29,7 +29,14 @@ class VersionCommand(Command): optional=True, ) ] - options = [option("short", "s", "Output the version number only")] + options = [ + option("short", "s", "Output the version number only"), + option( + "dry-run", + None, + "Do not update pyproject.toml file", + ), + ] help = """\ The version command shows the current version of the project or bumps the version of @@ -66,12 +73,13 @@ def handle(self) -> None: f" to {version}" ) - content: dict[str, Any] = self.poetry.file.read() - poetry_content = content["tool"]["poetry"] - poetry_content["version"] = version.text + if not self.option("dry-run"): + content: dict[str, Any] = self.poetry.file.read() + poetry_content = content["tool"]["poetry"] + poetry_content["version"] = version.text - assert isinstance(content, TOMLDocument) - self.poetry.file.write(content) + assert isinstance(content, TOMLDocument) + self.poetry.file.write(content) else: if self.option("short"): self.line(self.poetry.package.pretty_version) diff --git a/src/poetry/factory.py b/src/poetry/factory.py index 51d560c8abb..3057729641d 100644 --- a/src/poetry/factory.py +++ b/src/poetry/factory.py @@ -11,12 +11,12 @@ from cleo.io.null_io import NullIO from poetry.core.factory import Factory as BaseFactory +from poetry.core.packages.project_package import ProjectPackage from poetry.core.toml.file import TOMLFile from tomlkit.toml_document import TOMLDocument from poetry.config.config import Config from poetry.packages.locker import Locker -from poetry.packages.project_package import ProjectPackage from poetry.plugins.plugin import Plugin from poetry.plugins.plugin_manager import PluginManager from poetry.poetry import Poetry diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index fd999a50f05..ea4c56f88c9 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -9,7 +9,10 @@ from pathlib import Path from typing import TYPE_CHECKING +from typing import Any +from typing import ContextManager from typing import Iterator +from typing import cast import pkginfo @@ -27,6 +30,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from poetry.core.packages.project_package import ProjectPackage @@ -81,9 +86,9 @@ def __init__( self.requires_python = requires_python self.files = files or [] self._cache_version = cache_version - self._source_type = None - self._source_url = None - self._source_reference = None + self._source_type: str | None = None + self._source_url: str | None = None + self._source_reference: str | None = None @property def cache_version(self) -> str | None: @@ -100,7 +105,7 @@ def update(self, other: PackageInfo) -> PackageInfo: self._cache_version = other.cache_version or self._cache_version return self - def asdict(self) -> dict[str, str | list[str] | None]: + def asdict(self) -> dict[str, Any]: """ Helper method to convert package info into a dictionary used for caching. """ @@ -116,7 +121,7 @@ def asdict(self) -> dict[str, str | list[str] | None]: } @classmethod - def load(cls, data: dict[str, str | list[str] | None]) -> PackageInfo: + def load(cls, data: dict[str, Any]) -> PackageInfo: """ Helper method to load data from a dictionary produced by `PackageInfo.asdict()`. @@ -169,7 +174,9 @@ def to_package( if root_dir or (self._source_type in {"directory"} and self._source_url): # this is a local poetry project, this means we can extract "richer" # requirement information, eg: development requirements etc. - poetry_package = self._get_poetry_package(path=root_dir or self._source_url) + poetry_package = self._get_poetry_package( + path=root_dir or Path(cast(str, self._source_url)) + ) if poetry_package: package.extras = poetry_package.extras for dependency in poetry_package.requires: @@ -274,6 +281,7 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo: # So, we unpack and introspect suffix = path.suffix + context: Callable[[str], ContextManager[zipfile.ZipFile | tarfile.TarFile]] if suffix == ".zip": context = zipfile.ZipFile else: @@ -286,8 +294,8 @@ def _from_sdist_file(cls, path: Path) -> PackageInfo: context = tarfile.open - with temporary_directory() as tmp: - tmp = Path(tmp) + with temporary_directory() as tmp_str: + tmp = Path(tmp_str) with context(path.as_posix()) as archive: archive.extractall(tmp.as_posix()) @@ -394,7 +402,7 @@ def from_metadata(cls, path: Path) -> PackageInfo | None: if path.suffix in {".dist-info", ".egg-info"}: directories = [path] else: - directories = cls._find_dist_info(path=path) + directories = list(cls._find_dist_info(path=path)) for directory in directories: try: @@ -463,6 +471,7 @@ def from_directory(cls, path: Path, disable_build: bool = False) -> PackageInfo: build is attempted in order to gather metadata. """ project_package = cls._get_poetry_package(path) + info: PackageInfo | None if project_package: info = cls.from_package(project_package) else: @@ -480,6 +489,7 @@ def from_directory(cls, path: Path, disable_build: bool = False) -> PackageInfo: # we discovered PkgInfo but no requirements were listed + assert info info._source_type = "directory" info._source_url = path.as_posix() diff --git a/src/poetry/installation/__init__.py b/src/poetry/installation/__init__.py index b7bc1c52e31..42ff15e3a35 100644 --- a/src/poetry/installation/__init__.py +++ b/src/poetry/installation/__init__.py @@ -1,3 +1,6 @@ from __future__ import annotations from poetry.installation.installer import Installer + + +__all__ = ["Installer"] diff --git a/src/poetry/installation/chef.py b/src/poetry/installation/chef.py index 0cf7b54e3b5..51cad799ab7 100644 --- a/src/poetry/installation/chef.py +++ b/src/poetry/installation/chef.py @@ -51,7 +51,7 @@ def get_cached_archive_for_link(self, link: Link) -> Link | None: if not archives: return link - candidates = [] + candidates: list[tuple[float | None, Link]] = [] for archive in archives: if not archive.is_wheel: candidates.append((float("inf"), archive)) diff --git a/src/poetry/installation/chooser.py b/src/poetry/installation/chooser.py index 82659d444eb..ebfbbdaa765 100644 --- a/src/poetry/installation/chooser.py +++ b/src/poetry/installation/chooser.py @@ -4,6 +4,7 @@ import re from typing import TYPE_CHECKING +from typing import Any from packaging.tags import Tag @@ -15,6 +16,7 @@ if TYPE_CHECKING: from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link + from poetry.core.semver.version import Version from poetry.repositories.pool import Pool from poetry.utils.env import Env @@ -144,7 +146,9 @@ def _get_links(self, package: Package) -> list[Link]: return selected_links - def _sort_key(self, package: Package, link: Link) -> tuple: + def _sort_key( + self, package: Package, link: Link + ) -> tuple[int, int, int, Version, tuple[Any, ...], int]: """ Function to pass as the `key` argument to a call to sorted() to sort InstallationCandidates by preference. @@ -168,7 +172,7 @@ def _sort_key(self, package: Package, link: Link) -> tuple: comparison operators, but then different sdist links with the same version, would have to be considered equal """ - build_tag = () + build_tag: tuple[Any, ...] = () binary_preference = 0 if link.is_wheel: wheel = Wheel(link.filename) @@ -179,9 +183,11 @@ def _sort_key(self, package: Package, link: Link) -> tuple: ) # TODO: Binary preference - pri = -(wheel.get_minimum_supported_index(self._env.supported_tags)) + pri = -(wheel.get_minimum_supported_index(self._env.supported_tags) or 0) if wheel.build_tag is not None: match = re.match(r"^(\d+)(.*)$", wheel.build_tag) + if not match: + raise ValueError(f"Unable to parse build tag: {wheel.build_tag}") build_tag_groups = match.groups() build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) else: # sdist diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 8e1f18f78fa..3d64507a191 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -12,6 +12,7 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any +from typing import cast from cleo.io.null_io import NullIO from poetry.core.packages.file_dependency import FileDependency @@ -21,6 +22,9 @@ from poetry.installation.chef import Chef from poetry.installation.chooser import Chooser +from poetry.installation.operations import Install +from poetry.installation.operations import Uninstall +from poetry.installation.operations import Update from poetry.utils._compat import decode from poetry.utils.authenticator import Authenticator from poetry.utils.env import EnvCommandError @@ -31,12 +35,10 @@ if TYPE_CHECKING: from cleo.io.io import IO + from cleo.io.outputs.section_output import SectionOutput from poetry.core.packages.package import Package from poetry.config.config import Config - from poetry.installation.operations import Install - from poetry.installation.operations import Uninstall - from poetry.installation.operations import Update from poetry.installation.operations.operation import Operation from poetry.repositories import Pool from poetry.utils.env import Env @@ -49,7 +51,7 @@ def __init__( pool: Pool, config: Config, io: IO, - parallel: bool = None, + parallel: bool | None = None, ) -> None: self._env = env self._io = io @@ -75,7 +77,7 @@ def __init__( self._executed_operations = 0 self._executed = {"install": 0, "update": 0, "uninstall": 0} self._skipped = {"install": 0, "update": 0, "uninstall": 0} - self._sections = {} + self._sections: dict[int, SectionOutput] = {} self._lock = threading.Lock() self._shutdown = False self._hashes: dict[str, str] = {} @@ -186,7 +188,7 @@ def _get_max_workers(desired_max_workers: int | None = None) -> int: # (it raises a NotImplementedError), so, in this case, we assume # that the system only has one CPU. try: - default_max_workers = os.cpu_count() + 4 + default_max_workers = (os.cpu_count() or 1) + 4 except NotImplementedError: default_max_workers = 5 @@ -312,7 +314,7 @@ def _do_execute_operation(self, operation: Operation) -> int: return 0 - result = getattr(self, f"_execute_{method}")(operation) + result: int = getattr(self, f"_execute_{method}")(operation) if result != 0: return result @@ -373,21 +375,21 @@ def get_operation_message( source_operation_color += "_dark" package_color += "_dark" - if operation.job_type == "install": + if isinstance(operation, Install): return ( f"<{base_tag}>Installing" f" <{package_color}>{operation.package.name}" f" (<{operation_color}>{operation.package.full_pretty_version})" ) - if operation.job_type == "uninstall": + if isinstance(operation, Uninstall): return ( f"<{base_tag}>Removing" f" <{package_color}>{operation.package.name}" f" (<{operation_color}>{operation.package.full_pretty_version})" ) - if operation.job_type == "update": + if isinstance(operation, Update): return ( f"<{base_tag}>Updating" f" <{package_color}>{operation.initial_package.name} " @@ -643,7 +645,7 @@ def _validate_archive_hash(archive: Path | Link, package: Package) -> str: package.name, archive_path, ) - archive_hash = "sha256:" + file_dep.hash() + archive_hash: str = "sha256:" + file_dep.hash() known_hashes = {f["hash"] for f in package.files} if archive_hash not in known_hashes: @@ -681,7 +683,7 @@ def _download_archive(self, operation: Install | Update, link: Link) -> Path: progress.start() done = 0 - archive = self._chef.get_cache_directory_for_link(link) / link.filename + archive: Path = self._chef.get_cache_directory_for_link(link) / link.filename archive.parent.mkdir(parents=True, exist_ok=True) with archive.open("wb") as f: for chunk in response.iter_content(chunk_size=4096): @@ -730,7 +732,7 @@ def _save_url_reference(self, operation: Operation) -> None: direct_url_json.unlink() return - url_reference = None + url_reference: dict[str, Any] | None = None if package.source_type == "git": url_reference = self._create_git_url_reference(package) @@ -745,26 +747,16 @@ def _save_url_reference(self, operation: Operation) -> None: for dist in self._env.site_packages.distributions( name=package.name, writable_only=True ): - dist._path.joinpath("direct_url.json").write_text( - json.dumps(url_reference), - encoding="utf-8", - ) + dist_path = cast(Path, dist._path) # type: ignore[attr-defined] + url = dist_path / "direct_url.json" + url.write_text(json.dumps(url_reference), encoding="utf-8") - record = dist._path.joinpath("RECORD") + record = dist_path / "RECORD" if record.exists(): with record.open(mode="a", encoding="utf-8") as f: writer = csv.writer(f) - writer.writerow( - [ - str( - dist._path.joinpath("direct_url.json").relative_to( - record.parent.parent - ) - ), - "", - "", - ] - ) + path = url.relative_to(record.parent.parent) + writer.writerow([str(path), "", ""]) def _create_git_url_reference( self, package: Package @@ -800,24 +792,20 @@ def _create_file_url_reference( if package.name in self._hashes: archive_info["hash"] = self._hashes[package.name] - reference = { + return { "url": Path(package.source_url).as_uri(), "archive_info": archive_info, } - return reference - def _create_directory_url_reference( self, package: Package - ) -> dict[str, str | dict[str, str]]: + ) -> dict[str, str | dict[str, bool]]: dir_info = {} if package.develop: dir_info["editable"] = True - reference = { + return { "url": Path(package.source_url).as_uri(), "dir_info": dir_info, } - - return reference diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index 25b13092673..b033e71f53c 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -58,9 +58,9 @@ def __init__( self._execute_operations = True self._lock = False - self._whitelist = [] + self._whitelist: list[str] = [] - self._extras = [] + self._extras: list[str] = [] if executor is None: executor = Executor(self._env, self._pool, config, self._io) @@ -171,7 +171,7 @@ def whitelist(self, packages: Iterable[str]) -> Installer: return self - def extras(self, extras: list) -> Installer: + def extras(self, extras: list[str]) -> Installer: self._extras = extras return self @@ -182,7 +182,7 @@ def use_executor(self, use_executor: bool = True) -> Installer: return self def _do_refresh(self) -> int: - from poetry.puzzle import Solver + from poetry.puzzle.solver import Solver # Checking extras for extra in self._extras: @@ -211,7 +211,7 @@ def _do_refresh(self) -> int: return 0 def _do_install(self, local_repo: Repository) -> int: - from poetry.puzzle import Solver + from poetry.puzzle.solver import Solver locked_repository = Repository() if self._update: @@ -475,9 +475,9 @@ def _populate_local_repo( def _get_operations_from_lock( self, locked_repository: Repository - ) -> Sequence[Operation]: + ) -> list[Operation]: installed_repo = self._installed_repository - ops = [] + ops: list[Operation] = [] extra_packages = self._get_extra_packages(locked_repository) for locked in locked_repository.packages: diff --git a/src/poetry/installation/operations/__init__.py b/src/poetry/installation/operations/__init__.py index 68127d928c3..d579ac6b8a0 100644 --- a/src/poetry/installation/operations/__init__.py +++ b/src/poetry/installation/operations/__init__.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Union - from poetry.installation.operations.install import Install from poetry.installation.operations.uninstall import Uninstall from poetry.installation.operations.update import Update diff --git a/src/poetry/installation/pip_installer.py b/src/poetry/installation/pip_installer.py index da01d507a16..ae54ff73af5 100644 --- a/src/poetry/installation/pip_installer.py +++ b/src/poetry/installation/pip_installer.py @@ -84,6 +84,7 @@ def install(self, package: Package, update: bool = False) -> None: if update: args.append("-U") + req: str | list[str] if package.files and not package.source_url: # Format as a requirements.txt # We need to create a requirements.txt file @@ -136,10 +137,10 @@ def remove(self, package: Package) -> None: if src_dir.exists(): remove_directory(src_dir, force=True) - def run(self, *args: Any, **kwargs: Any) -> str: + def run(self, *args: Any, **kwargs: Any) -> int | str: return self._env.run_pip(*args, **kwargs) - def requirement(self, package: Package, formatted: bool = False) -> str: + def requirement(self, package: Package, formatted: bool = False) -> str | list[str]: if formatted and not package.source_type: req = f"{package.name}=={package.version}" for f in package.files: @@ -161,7 +162,7 @@ def requirement(self, package: Package, formatted: bool = False) -> str: req = os.path.realpath(package.source_url) if package.develop and package.source_type == "directory": - req = ["-e", req] + return ["-e", req] return req @@ -172,7 +173,7 @@ def requirement(self, package: Package, formatted: bool = False) -> str: ) if package.develop: - req = ["-e", req] + return ["-e", req] return req @@ -183,9 +184,12 @@ def requirement(self, package: Package, formatted: bool = False) -> str: def create_temporary_requirement(self, package: Package) -> str: fd, name = tempfile.mkstemp("reqs.txt", f"{package.name}-{package.version}") + req = self.requirement(package, formatted=True) + if isinstance(req, list): + req = " ".join(req) try: - os.write(fd, encode(self.requirement(package, formatted=True))) + os.write(fd, encode(req)) finally: os.close(fd) @@ -237,7 +241,7 @@ def install_directory(self, package: Package) -> str | int: with builder.setup_py(): if package.develop: return pip_install( - directory=req, + path=req, environment=self._env, upgrade=True, editable=True, @@ -248,7 +252,7 @@ def install_directory(self, package: Package) -> str | int: if package.develop: return pip_install( - directory=req, environment=self._env, upgrade=True, editable=True + path=req, environment=self._env, upgrade=True, editable=True ) return pip_install(path=req, environment=self._env, deps=False, upgrade=True) diff --git a/src/poetry/layouts/layout.py b/src/poetry/layouts/layout.py index f5dfd28e950..c4a91506a35 100644 --- a/src/poetry/layouts/layout.py +++ b/src/poetry/layouts/layout.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import Mapping from tomlkit import dumps from tomlkit import inline_table @@ -50,8 +51,8 @@ def __init__( author: str | None = None, license: str | None = None, python: str = "*", - dependencies: dict[str, str] | None = None, - dev_dependencies: dict[str, str] | None = None, + dependencies: dict[str, str | Mapping[str, Any]] | None = None, + dev_dependencies: dict[str, str | Mapping[str, Any]] | None = None, ) -> None: self._project = canonicalize_name(project).replace(".", "-") self._package_path_relative = Path( @@ -90,7 +91,14 @@ def package_path(self) -> Path: def get_package_include(self) -> InlineTable | None: package = inline_table() - include = self._package_path_relative.parts[0] + # If a project is created in the root directory (this is reasonable inside a + # docker container, eg ) + # then parts will be empty. + parts = self._package_path_relative.parts + if not parts: + return None + + include = parts[0] package.append("include", include) # type: ignore[no-untyped-call] if self.basedir != Path(): diff --git a/src/poetry/locations.py b/src/poetry/locations.py index 9ab526fd491..33c5aa8c0ad 100644 --- a/src/poetry/locations.py +++ b/src/poetry/locations.py @@ -29,7 +29,7 @@ auth_toml = _LEGACY_CONFIG_DIR / "auth.toml" if any(file.exists() for file in (auth_toml, config_toml)): - logger.warn( + logger.warning( "Configuration file exists at %s, reusing this directory.\n\nConsider" " moving configuration to %s, as support for the legacy directory will be" " removed in an upcoming release.", diff --git a/src/poetry/masonry/builders/__init__.py b/src/poetry/masonry/builders/__init__.py index ea5ca59a998..61662422c39 100644 --- a/src/poetry/masonry/builders/__init__.py +++ b/src/poetry/masonry/builders/__init__.py @@ -1,3 +1,6 @@ from __future__ import annotations from poetry.masonry.builders.editable import EditableBuilder + + +__all__ = ["EditableBuilder"] diff --git a/src/poetry/mixology/incompatibility.py b/src/poetry/mixology/incompatibility.py index 83e2391b6ff..4c08f7cef57 100644 --- a/src/poetry/mixology/incompatibility.py +++ b/src/poetry/mixology/incompatibility.py @@ -1,7 +1,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -from typing import Callable from typing import Iterator from poetry.mixology.incompatibility_cause import ConflictCause @@ -14,6 +13,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from poetry.mixology.incompatibility_cause import IncompatibilityCause from poetry.mixology.term import Term diff --git a/src/poetry/mixology/solutions/providers/__init__.py b/src/poetry/mixology/solutions/providers/__init__.py index 9470041fd57..cfbd1873848 100644 --- a/src/poetry/mixology/solutions/providers/__init__.py +++ b/src/poetry/mixology/solutions/providers/__init__.py @@ -3,3 +3,6 @@ from poetry.mixology.solutions.providers.python_requirement_solution_provider import ( PythonRequirementSolutionProvider, ) + + +__all__ = ["PythonRequirementSolutionProvider"] diff --git a/src/poetry/mixology/solutions/solutions/__init__.py b/src/poetry/mixology/solutions/solutions/__init__.py index 51b8449071b..e78e9a53361 100644 --- a/src/poetry/mixology/solutions/solutions/__init__.py +++ b/src/poetry/mixology/solutions/solutions/__init__.py @@ -3,3 +3,6 @@ from poetry.mixology.solutions.solutions.python_requirement_solution import ( PythonRequirementSolution, ) + + +__all__ = ["PythonRequirementSolution"] diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 6fd225a4958..d39591b4b8f 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -41,18 +41,26 @@ class DependencyCache: def __init__(self, provider: Provider) -> None: self.provider = provider - self.cache: dict[str, list[DependencyPackage]] = {} + self.cache: dict[ + tuple[str, str | None, str | None, str | None], list[DependencyPackage] + ] = {} @functools.lru_cache(maxsize=128) def search_for(self, dependency: Dependency) -> list[DependencyPackage]: - complete_name = dependency.complete_name - packages = self.cache.get(complete_name) + key = ( + dependency.complete_name, + dependency.source_type, + dependency.source_url, + dependency.source_reference, + ) + + packages = self.cache.get(key) if packages is None: packages = self.provider.search_for(dependency) else: packages = [p for p in packages if dependency.constraint.allows(p.version)] - self.cache[complete_name] = packages + self.cache[key] = packages return packages diff --git a/src/poetry/packages/project_package.py b/src/poetry/packages/project_package.py deleted file mode 100644 index f2801fc5bdf..00000000000 --- a/src/poetry/packages/project_package.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -from poetry.core.packages.project_package import ProjectPackage as _ProjectPackage - - -if TYPE_CHECKING: - from poetry.core.semver.version import Version - - -class ProjectPackage(_ProjectPackage): # type: ignore[misc] - def set_version( - self, version: str | Version, pretty_version: str | None = None - ) -> None: - from poetry.core.semver.version import Version - - if not isinstance(version, Version): - self._version = Version.parse(version) - self._pretty_version = pretty_version or version - else: - self._version = version - self._pretty_version = pretty_version or version.text diff --git a/src/poetry/publishing/__init__.py b/src/poetry/publishing/__init__.py index c7aa27edb1c..2cb619a2acd 100644 --- a/src/poetry/publishing/__init__.py +++ b/src/poetry/publishing/__init__.py @@ -1,3 +1,6 @@ from __future__ import annotations from poetry.publishing.publisher import Publisher + + +__all__ = ["Publisher"] diff --git a/src/poetry/puzzle/__init__.py b/src/poetry/puzzle/__init__.py index 48280ac9bec..d5bc659574a 100644 --- a/src/poetry/puzzle/__init__.py +++ b/src/poetry/puzzle/__init__.py @@ -1,3 +1,6 @@ from __future__ import annotations from poetry.puzzle.solver import Solver + + +__all__ = ["Solver"] diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index e4cefee244e..9fcee1265f0 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -42,6 +42,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package from poetry.core.semver.version_constraint import VersionConstraint @@ -55,6 +57,24 @@ class Indicator(ProgressIndicator): # type: ignore[misc] + CONTEXT: str | None = None + + @staticmethod + @contextmanager + def context() -> Iterator[Callable[[str | None], None]]: + def _set_context(context: str | None) -> None: + Indicator.CONTEXT = context + + yield _set_context + + _set_context(None) + + def _formatter_context(self) -> str: + if Indicator.CONTEXT is None: + return " " + else: + return f" {Indicator.CONTEXT} " + def _formatter_elapsed(self) -> str: elapsed = time.time() - self._start_time @@ -530,19 +550,33 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: # An example of this is: # - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6" # - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6" - duplicates: dict[str, list[Dependency]] = {} + # + # Additional care has to be taken to ensure that hidden constraints like that + # of source type, reference etc. are taking into consideration when duplicates + # are identified. + duplicates: dict[ + tuple[str, str | None, str | None, str | None], list[Dependency] + ] = {} for dep in dependencies: - if dep.complete_name not in duplicates: - duplicates[dep.complete_name] = [] - - duplicates[dep.complete_name].append(dep) + key = ( + dep.complete_name, + dep.source_type, + dep.source_url, + dep.source_reference, + ) + if key not in duplicates: + duplicates[key] = [] + duplicates[key].append(dep) dependencies = [] - for dep_name, deps in duplicates.items(): + for key, deps in duplicates.items(): if len(deps) == 1: dependencies.append(deps[0]) continue + extra_keys = ", ".join(k for k in key[1:] if k is not None) + dep_name = f"{key[0]} ({extra_keys})" if extra_keys else key[0] + self.debug(f"Duplicate dependencies for {dep_name}") deps = self._merge_dependencies_by_marker(deps) @@ -631,7 +665,9 @@ def fmt_warning(d: Dependency) -> str: dep_other.set_constraint( dep_other.constraint.intersect(dep_any.constraint) ) - elif not inverted_marker.is_empty(): + elif not inverted_marker.is_empty() and self._python_constraint.allows_any( + get_python_constraint_from_marker(inverted_marker) + ): # if there is no any marker dependency # and the inverted marker is not empty, # a dependency with the inverted union of all markers is required @@ -794,7 +830,9 @@ def progress(self) -> Iterator[None]: self._io.write_line("Resolving dependencies...") yield else: - indicator = Indicator(self._io, "{message} ({elapsed:2s})") + indicator = Indicator( + self._io, "{message}{context}({elapsed:2s})" + ) with indicator.auto( "Resolving dependencies...", diff --git a/src/poetry/repositories/http.py b/src/poetry/repositories/http.py index bbfcb8b5c0f..f6cc6fea204 100644 --- a/src/poetry/repositories/http.py +++ b/src/poetry/repositories/http.py @@ -15,6 +15,8 @@ from poetry.core.packages.dependency import Dependency from poetry.core.packages.utils.link import Link +from poetry.core.semver.helpers import parse_constraint +from poetry.core.utils.helpers import temporary_directory from poetry.core.version.markers import parse_marker from poetry.repositories.cached import CachedRepository @@ -23,7 +25,6 @@ from poetry.repositories.link_sources.html import HTMLPage from poetry.utils.authenticator import Authenticator from poetry.utils.helpers import download_file -from poetry.utils.helpers import temporary_directory from poetry.utils.patterns import wheel_file_re @@ -148,6 +149,14 @@ def _get_info_from_urls(self, urls: dict[str, list[str]]) -> PackageInfo: info = self._get_info_from_wheel(universal_python2_wheel) py3_info = self._get_info_from_wheel(universal_python3_wheel) + + if info.requires_python or py3_info.requires_python: + info.requires_python = str( + parse_constraint(info.requires_python or "^2.7").union( + parse_constraint(py3_info.requires_python or "^3") + ) + ) + if py3_info.requires_dist: if not info.requires_dist: info.requires_dist = py3_info.requires_dist diff --git a/src/poetry/repositories/installed_repository.py b/src/poetry/repositories/installed_repository.py index d7bea9ed652..d7a6d9abb6e 100644 --- a/src/poetry/repositories/installed_repository.py +++ b/src/poetry/repositories/installed_repository.py @@ -103,7 +103,7 @@ def create_package_from_distribution( ) -> Package: # We first check for a direct_url.json file to determine # the type of package. - path = Path(str(distribution._path)) + path = Path(str(distribution._path)) # type: ignore[attr-defined] if ( path.name.endswith(".dist-info") @@ -163,13 +163,17 @@ def create_package_from_distribution( source_reference=source_reference, source_resolved_reference=source_resolved_reference, ) - package.description = distribution.metadata.get("summary", "") + + package.description = distribution.metadata.get( # type: ignore[attr-defined] + "summary", + "", + ) return package @classmethod def create_package_from_pep610(cls, distribution: metadata.Distribution) -> Package: - path = Path(str(distribution._path)) + path = Path(str(distribution._path)) # type: ignore[attr-defined] source_type = None source_url = None source_reference = None @@ -213,7 +217,10 @@ def create_package_from_pep610(cls, distribution: metadata.Distribution) -> Pack develop=develop, ) - package.description = distribution.metadata.get("summary", "") + package.description = distribution.metadata.get( # type: ignore[attr-defined] + "summary", + "", + ) return package @@ -229,15 +236,17 @@ def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository: for entry in reversed(env.sys_path): for distribution in sorted( - metadata.distributions(path=[entry]), - key=lambda d: str(d._path), + metadata.distributions( # type: ignore[no-untyped-call] + path=[entry], + ), + key=lambda d: str(d._path), # type: ignore[attr-defined] ): name = canonicalize_name(distribution.metadata["name"]) if name in seen: continue - path = Path(str(distribution._path)) + path = Path(str(distribution._path)) # type: ignore[attr-defined] try: path.relative_to(_VENDORS) diff --git a/src/poetry/repositories/repository.py b/src/poetry/repositories/repository.py index d2e624f45bd..63e169b9e3f 100644 --- a/src/poetry/repositories/repository.py +++ b/src/poetry/repositories/repository.py @@ -118,7 +118,7 @@ def _get_constraints_from_dependency( def _log(self, msg: str, level: str = "info") -> None: logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") - getattr(logger, level)(f"{self.name}: {msg}") + getattr(logger, level)(f"Source ({self.name}): {msg}") def __len__(self) -> int: return len(self._packages) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 1b6e9c96763..987a89dfcb7 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -10,6 +10,7 @@ import subprocess import sys import sysconfig +import warnings from contextlib import contextmanager from copy import deepcopy @@ -17,7 +18,6 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any -from typing import ContextManager from typing import Iterable from typing import Iterator from typing import TypeVar @@ -35,6 +35,7 @@ from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.toml.file import TOMLFile +from poetry.core.utils.helpers import temporary_directory from virtualenv.seed.wheels.embed import get_embed_wheel from poetry.locations import CACHE_DIR @@ -45,7 +46,6 @@ from poetry.utils.helpers import is_dir_writable from poetry.utils.helpers import paths_csv from poetry.utils.helpers import remove_directory -from poetry.utils.helpers import temporary_directory if TYPE_CHECKING: @@ -1550,7 +1550,9 @@ def get_paths(self) -> dict[str, str]: d = Distribution() d.parse_config_files() - obj = d.get_command_obj("install", create=True) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "setup.py install is deprecated") + obj = d.get_command_obj("install", create=True) obj.finalize_options() paths = sysconfig.get_paths().copy() @@ -1841,7 +1843,7 @@ def _bin(self, bin: str) -> str: def ephemeral_environment( executable: str | Path | None = None, flags: dict[str, bool] = None, -) -> ContextManager[VirtualEnv]: +) -> Iterator[VirtualEnv]: with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv venv_dir = Path(tmp_dir) / ".venv" diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py index c76a49285ce..a0d670c4201 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py @@ -7,15 +7,14 @@ import tempfile from collections.abc import Mapping -from contextlib import contextmanager from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Callable -from typing import Iterator if TYPE_CHECKING: + from collections.abc import Callable + from poetry.core.packages.package import Package from requests import Session @@ -34,15 +33,6 @@ def module_name(name: str) -> str: return canonicalize_name(name).replace(".", "_").replace("-", "_") -@contextmanager -def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]: - name = tempfile.mkdtemp(*args, **kwargs) - - yield name - - remove_directory(name, force=True) - - def get_cert(config: Config, repository_name: str) -> Path | None: cert = config.get(f"certificates.{repository_name}.cert") if cert: @@ -100,15 +90,41 @@ def download_file( ) -> None: import requests + from poetry.puzzle.provider import Indicator + get = requests.get if not session else session.get response = get(url, stream=True) response.raise_for_status() - with open(dest, "wb") as f: - for chunk in response.iter_content(chunk_size=chunk_size): - if chunk: - f.write(chunk) + set_indicator = False + with Indicator.context() as update_context: + update_context(f"Downloading {url}") + + if "Content-Length" in response.headers: + try: + total_size = int(response.headers["Content-Length"]) + except ValueError: + total_size = 0 + + fetched_size = 0 + last_percent = 0 + + # if less than 1MB, we simply show that we're downloading + # but skip the updating + set_indicator = total_size > 1024 * 1024 + + with open(dest, "wb") as f: + for chunk in response.iter_content(chunk_size=chunk_size): + if chunk: + f.write(chunk) + + if set_indicator: + fetched_size += len(chunk) + percent = (fetched_size * 100) // total_size + if percent > last_percent: + last_percent = percent + update_context(f"Downloading {url} {percent:3}%") def get_package_version_display_string( diff --git a/src/poetry/vcs/git/backend.py b/src/poetry/vcs/git/backend.py index 4b7d987240d..70f8dd8ae46 100644 --- a/src/poetry/vcs/git/backend.py +++ b/src/poetry/vcs/git/backend.py @@ -149,7 +149,7 @@ def __post_init__(self, repo: Repo | Path | str) -> None: class Git: @staticmethod def as_repo(repo: Path | str) -> Repo: - return Repo(str(repo)) # type: ignore[no-untyped-call] + return Repo(str(repo)) @staticmethod def get_remote_url(repo: Repo, remote: str = "origin") -> str: @@ -158,8 +158,8 @@ def get_remote_url(repo: Repo, remote: str = "origin") -> str: section = (b"remote", remote.encode("utf-8")) url = "" - if config.has_section(section): # type: ignore[no-untyped-call] - value = config.get(section, b"url") # type: ignore[no-untyped-call] + if config.has_section(section): + value = config.get(section, b"url") assert value is not None url = value.decode("utf-8") @@ -195,12 +195,10 @@ def _fetch_remote_refs(cls, url: str, local: Repo) -> FetchPackResult: kwargs["username"] = credentials.username kwargs["password"] = credentials.password - client, path = get_transport_and_path( # type: ignore[no-untyped-call] - url, **kwargs - ) + client, path = get_transport_and_path(url, **kwargs) with local: - result: FetchPackResult = client.fetch( # type: ignore[no-untyped-call] + result: FetchPackResult = client.fetch( path, local, determine_wants=local.object_store.determine_wants_all, @@ -241,7 +239,7 @@ def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo: f"Failed to checkout {url} at '{revision}'" ) - repo = Repo(str(target)) # type: ignore[no-untyped-call] + repo = Repo(str(target)) return repo @classmethod @@ -252,10 +250,10 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: """ local: Repo if not target.exists(): - local = Repo.init(str(target), mkdir=True) # type: ignore[no-untyped-call] - porcelain.remote_add(local, "origin", url) # type: ignore[no-untyped-call] + local = Repo.init(str(target), mkdir=True) + porcelain.remote_add(local, "origin", url) else: - local = Repo(str(target)) # type: ignore[no-untyped-call] + local = Repo(str(target)) remote_refs = cls._fetch_remote_refs(url=url, local=local) @@ -282,7 +280,7 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: (b"refs/remotes/origin", b"refs/heads/"), (b"refs/tags", b"refs/tags"), }: - local.refs.import_refs( # type: ignore[no-untyped-call] + local.refs.import_refs( base=base, other={ n[len(prefix) :]: v @@ -293,7 +291,7 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: try: with local: - local.reset_index() # type: ignore[no-untyped-call] + local.reset_index() except (AssertionError, KeyError) as e: # this implies the ref we need does not exist or is invalid if isinstance(e, KeyError): @@ -335,7 +333,7 @@ def _clone_submodules(cls, repo: Repo) -> None: url: bytes path: bytes - submodules = parse_submodules(config) # type: ignore[no-untyped-call] + submodules = parse_submodules(config) for path, url, _ in submodules: path_relative = Path(path.decode("utf-8")) path_absolute = repo_root.joinpath(path_relative) @@ -395,7 +393,7 @@ def clone( else: # check if the current local copy matches the requested ref spec try: - current_repo = Repo(str(target)) # type: ignore[no-untyped-call] + current_repo = Repo(str(target)) with current_repo: current_sha = current_repo.head().decode("utf-8") diff --git a/src/poetry/vcs/git/system.py b/src/poetry/vcs/git/system.py index 559ad7e6c7c..c1520539462 100644 --- a/src/poetry/vcs/git/system.py +++ b/src/poetry/vcs/git/system.py @@ -48,7 +48,7 @@ def run(*args: Any, **kwargs: Any) -> str: folder.as_posix(), ) + args - git_command = find_git_command() # type: ignore[no-untyped-call] + git_command = find_git_command() return ( subprocess.check_output(git_command + list(args), stderr=subprocess.STDOUT) .decode() diff --git a/tests/compat.py b/tests/compat.py index a778a76f87f..1e140560366 100644 --- a/tests/compat.py +++ b/tests/compat.py @@ -2,12 +2,12 @@ try: - import zipp + import zipp # nopycln: import except ImportError: import zipfile as zipp # noqa: F401, TC002 try: - from typing import Protocol + from typing import Protocol # nopycln: import except ImportError: from typing_extensions import Protocol # noqa: F401, TC002 diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 985a7666ecb..68794108dd2 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -4,7 +4,6 @@ import re from typing import TYPE_CHECKING -from typing import Callable from typing import Iterator import pytest @@ -17,6 +16,7 @@ if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path diff --git a/tests/console/commands/env/helpers.py b/tests/console/commands/env/helpers.py index 97058c796cb..c9dc0a7a41a 100644 --- a/tests/console/commands/env/helpers.py +++ b/tests/console/commands/env/helpers.py @@ -3,12 +3,13 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Callable from poetry.core.semver.version import Version if TYPE_CHECKING: + from collections.abc import Callable + from poetry.core.version.pep440.version import PEP440Version VERSION_3_7_1 = Version.parse("3.7.1") diff --git a/tests/console/commands/test_version.py b/tests/console/commands/test_version.py index bd93589412b..36487a3f38a 100644 --- a/tests/console/commands/test_version.py +++ b/tests/console/commands/test_version.py @@ -76,3 +76,12 @@ def test_version_update(tester: CommandTester): def test_short_version_update(tester: CommandTester): tester.execute("--short 2.0.0") assert tester.io.fetch_output() == "2.0.0\n" + + +def test_dry_run(tester: CommandTester): + old_pyproject = tester.command.poetry.file.path.read_text() + tester.execute("--dry-run major") + + new_pyproject = tester.command.poetry.file.path.read_text() + assert tester.io.fetch_output() == "Bumping version from 1.2.3 to 2.0.0\n" + assert old_pyproject == new_pyproject diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index 9c2d234588c..8d49a4faaf0 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1186,8 +1186,15 @@ def test_run_installs_with_local_file( package: ProjectPackage, fixture_dir: FixtureDirGetter, ): + root_dir = Path(__file__).parent.parent.parent + package.root_dir = root_dir + locker.set_lock_path(root_dir) file_path = fixture_dir("distributions/demo-0.1.0-py2.py3-none-any.whl") - package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) + package.add_dependency( + Factory.create_dependency( + "demo", {"file": str(file_path.relative_to(root_dir))}, root_dir=root_dir + ) + ) repo.add_package(get_package("pendulum", "1.4.4")) @@ -1206,10 +1213,17 @@ def test_run_installs_wheel_with_no_requires_dist( package: ProjectPackage, fixture_dir: FixtureDirGetter, ): + root_dir = Path(__file__).parent.parent.parent + package.root_dir = root_dir + locker.set_lock_path(root_dir) file_path = fixture_dir( "wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.whl" ) - package.add_dependency(Factory.create_dependency("demo", {"file": str(file_path)})) + package.add_dependency( + Factory.create_dependency( + "demo", {"file": str(file_path.relative_to(root_dir))}, root_dir=root_dir + ) + ) installer.run() @@ -1228,10 +1242,15 @@ def test_run_installs_with_local_poetry_directory_and_extras( tmpdir: Path, fixture_dir: FixtureDirGetter, ): + root_dir = Path(__file__).parent.parent.parent + package.root_dir = root_dir + locker.set_lock_path(root_dir) file_path = fixture_dir("project_with_extras") package.add_dependency( Factory.create_dependency( - "project-with-extras", {"path": str(file_path), "extras": ["extras_a"]} + "project-with-extras", + {"path": str(file_path.relative_to(root_dir)), "extras": ["extras_a"]}, + root_dir=root_dir, ) ) @@ -1319,9 +1338,16 @@ def test_run_installs_with_local_setuptools_directory( tmpdir: Path, fixture_dir: FixtureDirGetter, ): + root_dir = Path(__file__).parent.parent.parent + package.root_dir = root_dir + locker.set_lock_path(root_dir) file_path = fixture_dir("project_with_setup/") package.add_dependency( - Factory.create_dependency("project-with-setup", {"path": str(file_path)}) + Factory.create_dependency( + "project-with-setup", + {"path": str(file_path.relative_to(root_dir))}, + root_dir=root_dir, + ) ) repo.add_package(get_package("pendulum", "1.4.4")) diff --git a/tests/mixology/helpers.py b/tests/mixology/helpers.py index 544def12bd0..0120bdb72ff 100644 --- a/tests/mixology/helpers.py +++ b/tests/mixology/helpers.py @@ -11,7 +11,8 @@ if TYPE_CHECKING: - from poetry.packages.project_package import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage + from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider diff --git a/tests/mixology/version_solver/test_backtracking.py b/tests/mixology/version_solver/test_backtracking.py index bdbbfba11cc..6354d4c6a48 100644 --- a/tests/mixology/version_solver/test_backtracking.py +++ b/tests/mixology/version_solver/test_backtracking.py @@ -8,7 +8,8 @@ if TYPE_CHECKING: - from poetry.packages.project_package import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage + from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider diff --git a/tests/mixology/version_solver/test_basic_graph.py b/tests/mixology/version_solver/test_basic_graph.py index 9f392b2f26f..f8758f00c3e 100644 --- a/tests/mixology/version_solver/test_basic_graph.py +++ b/tests/mixology/version_solver/test_basic_graph.py @@ -8,7 +8,8 @@ if TYPE_CHECKING: - from poetry.packages.project_package import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage + from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider diff --git a/tests/mixology/version_solver/test_dependency_cache.py b/tests/mixology/version_solver/test_dependency_cache.py new file mode 100644 index 00000000000..469a1e569db --- /dev/null +++ b/tests/mixology/version_solver/test_dependency_cache.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from poetry.factory import Factory +from poetry.mixology.version_solver import DependencyCache +from tests.mixology.helpers import add_to_repo + + +if TYPE_CHECKING: + from poetry.core.packages.project_package import ProjectPackage + + from poetry.repositories import Repository + from tests.mixology.version_solver.conftest import Provider + + +def test_solver_dependency_cache_respects_source_type( + root: ProjectPackage, provider: Provider, repo: Repository +): + dependency_pypi = Factory.create_dependency("demo", ">=0.1.0") + dependency_git = Factory.create_dependency( + "demo", {"git": "https://github.com/demo/demo.git"}, groups=["dev"] + ) + root.add_dependency(dependency_pypi) + root.add_dependency(dependency_git) + + add_to_repo(repo, "demo", "1.0.0") + + cache = DependencyCache(provider) + cache.search_for.cache_clear() + + # ensure cache was never hit for both calls + cache.search_for(dependency_pypi) + cache.search_for(dependency_git) + assert not cache.search_for.cache_info().hits + + packages_pypi = cache.search_for(dependency_pypi) + packages_git = cache.search_for(dependency_git) + + assert cache.search_for.cache_info().hits == 2 + assert cache.search_for.cache_info().currsize == 2 + + assert len(packages_pypi) == len(packages_git) == 1 + assert packages_pypi != packages_git + + package_pypi = packages_pypi[0] + package_git = packages_git[0] + + assert package_pypi.package.name == dependency_pypi.name + assert package_pypi.package.version.text == "1.0.0" + + assert package_git.package.name == dependency_git.name + assert package_git.package.version.text == "0.1.2" + assert package_git.package.source_type == dependency_git.source_type + assert package_git.package.source_url == dependency_git.source_url + assert ( + package_git.package.source_resolved_reference + == "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" + ) diff --git a/tests/mixology/version_solver/test_python_constraint.py b/tests/mixology/version_solver/test_python_constraint.py index f60e3a49293..52bbdd7bdab 100644 --- a/tests/mixology/version_solver/test_python_constraint.py +++ b/tests/mixology/version_solver/test_python_constraint.py @@ -8,7 +8,8 @@ if TYPE_CHECKING: - from poetry.packages.project_package import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage + from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider diff --git a/tests/mixology/version_solver/test_unsolvable.py b/tests/mixology/version_solver/test_unsolvable.py index 072925e5507..578fcfe688f 100644 --- a/tests/mixology/version_solver/test_unsolvable.py +++ b/tests/mixology/version_solver/test_unsolvable.py @@ -8,7 +8,8 @@ if TYPE_CHECKING: - from poetry.packages.project_package import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage + from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider diff --git a/tests/mixology/version_solver/test_with_lock.py b/tests/mixology/version_solver/test_with_lock.py index cc9e28f12d6..e9d07d21d7b 100644 --- a/tests/mixology/version_solver/test_with_lock.py +++ b/tests/mixology/version_solver/test_with_lock.py @@ -9,7 +9,8 @@ if TYPE_CHECKING: - from poetry.packages.project_package import ProjectPackage + from poetry.core.packages.project_package import ProjectPackage + from poetry.repositories import Repository from tests.mixology.version_solver.conftest import Provider diff --git a/tests/plugins/test_plugin_manager.py b/tests/plugins/test_plugin_manager.py index 965d04499b5..7332566f14b 100644 --- a/tests/plugins/test_plugin_manager.py +++ b/tests/plugins/test_plugin_manager.py @@ -7,9 +7,9 @@ from cleo.io.buffered_io import BufferedIO from entrypoints import EntryPoint +from poetry.core.packages.project_package import ProjectPackage from poetry.packages.locker import Locker -from poetry.packages.project_package import ProjectPackage from poetry.plugins import ApplicationPlugin from poetry.plugins import Plugin from poetry.plugins.plugin_manager import PluginManager @@ -32,8 +32,8 @@ def __call__(self, group: str = Plugin.group) -> PluginManager: class MyPlugin(Plugin): def activate(self, poetry: Poetry, io: BufferedIO) -> None: - io.write_line("Updating version") - poetry.package.set_version("9.9.9") + io.write_line("Setting readmes") + poetry.package.readmes = ("README.md",) class MyCommandPlugin(ApplicationPlugin): @@ -95,8 +95,8 @@ def test_load_plugins_and_activate( manager.load_plugins() manager.activate(poetry, io) - assert poetry.package.version.text == "9.9.9" - assert io.fetch_output() == "Updating version\n" + assert poetry.package.readmes == ("README.md",) + assert io.fetch_output() == "Setting readmes\n" def test_load_plugins_with_invalid_plugin( diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index c86f48d0df2..fe47e8bd220 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -14,6 +14,7 @@ from poetry.core.version.markers import parse_marker from poetry.factory import Factory +from poetry.packages import DependencyPackage from poetry.puzzle import Solver from poetry.puzzle.exceptions import SolverProblemError from poetry.puzzle.provider import Provider as BaseProvider @@ -32,7 +33,7 @@ if TYPE_CHECKING: import httpretty - from poetry.installation.operation.operation import Operation + from poetry.installation.operations.operation import Operation from poetry.puzzle.transaction import Transaction DEFAULT_SOURCE_REF = ( @@ -1337,6 +1338,67 @@ def test_solver_duplicate_dependencies_different_constraints_merge_by_marker( ) +def test_solver_duplicate_dependencies_different_sources_types_are_preserved( + solver: Solver, repo: Repository, package: Package +): + pendulum = get_package("pendulum", "2.0.3") + repo.add_package(pendulum) + repo.add_package(get_package("cleo", "1.0.0")) + repo.add_package(get_package("demo", "0.1.0")) + + dependency_pypi = Factory.create_dependency("demo", ">=0.1.0") + dependency_git = Factory.create_dependency( + "demo", {"git": "https://github.com/demo/demo.git"}, groups=["dev"] + ) + package.add_dependency(dependency_git) + package.add_dependency(dependency_pypi) + + demo = Package( + "demo", + "0.1.2", + source_type="git", + source_url="https://github.com/demo/demo.git", + source_reference=DEFAULT_SOURCE_REF, + source_resolved_reference="9cf87a285a2d3fbb0b9fa621997b3acc3631ed24", + ) + + transaction = solver.solve() + + ops = check_solver_result( + transaction, + [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}], + ) + + op = ops[1] + + assert op.package.source_type == demo.source_type + assert op.package.source_reference == DEFAULT_SOURCE_REF + assert op.package.source_resolved_reference.startswith( + demo.source_resolved_reference + ) + + complete_package = solver.provider.complete_package( + DependencyPackage(package.to_dependency(), package) + ) + + assert len(complete_package.all_requires) == 2 + + pypi, git = complete_package.all_requires + + assert isinstance(pypi, Dependency) + assert pypi == dependency_pypi + + assert isinstance(git, VCSDependency) + assert git.constraint + assert git.constraint != dependency_git.constraint + assert (git.name, git.source_type, git.source_url, git.source_reference) == ( + dependency_git.name, + dependency_git.source_type, + dependency_git.source_url, + DEFAULT_SOURCE_REF, + ) + + def test_solver_duplicate_dependencies_different_constraints_merge_no_markers( solver: Solver, repo: Repository, package: Package ): diff --git a/tests/repositories/fixtures/legacy/poetry-test-py2-py3-metadata-merge.html b/tests/repositories/fixtures/legacy/poetry-test-py2-py3-metadata-merge.html new file mode 100644 index 00000000000..7b43db0f21e --- /dev/null +++ b/tests/repositories/fixtures/legacy/poetry-test-py2-py3-metadata-merge.html @@ -0,0 +1,11 @@ + + + + Links for poetry-test-py2-py3-metadata-merge + + +

Links for ipython

+ poetry_test_py2_py3_metadata_merge-0.1.0-py2-none-any.whl
+ poetry_test_py2_py3_metadata_merge-0.1.0-py3-none-any.whl
+ + diff --git a/tests/repositories/fixtures/pypi.org/dists/poetry_test_py2_py3_metadata_merge-0.1.0-py2-none-any.whl b/tests/repositories/fixtures/pypi.org/dists/poetry_test_py2_py3_metadata_merge-0.1.0-py2-none-any.whl new file mode 100644 index 00000000000..255fcf7dab7 Binary files /dev/null and b/tests/repositories/fixtures/pypi.org/dists/poetry_test_py2_py3_metadata_merge-0.1.0-py2-none-any.whl differ diff --git a/tests/repositories/fixtures/pypi.org/dists/poetry_test_py2_py3_metadata_merge-0.1.0-py3-none-any.whl b/tests/repositories/fixtures/pypi.org/dists/poetry_test_py2_py3_metadata_merge-0.1.0-py3-none-any.whl new file mode 100644 index 00000000000..c4f890baafb Binary files /dev/null and b/tests/repositories/fixtures/pypi.org/dists/poetry_test_py2_py3_metadata_merge-0.1.0-py3-none-any.whl differ diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index f1f44841fda..bb0c33f6a76 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -295,6 +295,16 @@ def test_get_package_from_both_py2_and_py3_specific_wheels(): assert str(required[5].marker) == 'sys_platform != "win32"' +def test_get_package_from_both_py2_and_py3_specific_wheels_python_constraint(): + repo = MockRepository() + + package = repo.package("poetry-test-py2-py3-metadata-merge", "0.1.0") + + assert package.name == "poetry-test-py2-py3-metadata-merge" + assert package.version.text == "0.1.0" + assert package.python_versions == ">=2.7,<2.8 || >=3.7,<4.0" + + def test_get_package_with_dist_and_universal_py3_wheel(): repo = MockRepository() diff --git a/tests/test_factory.py b/tests/test_factory.py index ab360c14576..a281ef78b4d 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -28,8 +28,8 @@ class MyPlugin(Plugin): def activate(self, poetry: Poetry, io: IO) -> None: - io.write_line("Updating version") - poetry.package.set_version("9.9.9") + io.write_line("Setting readmes") + poetry.package.readmes = ("README.md",) def test_create_poetry(): @@ -343,4 +343,4 @@ def test_create_poetry_with_plugins(mocker: MockerFixture): poetry = Factory().create_poetry(fixtures_dir / "sample_project") - assert poetry.package.version.text == "9.9.9" + assert poetry.package.readmes == ("README.md",) diff --git a/tests/utils/fixtures/setups/pendulum/setup.py b/tests/utils/fixtures/setups/pendulum/setup.py index d0af694c887..705bd404289 100644 --- a/tests/utils/fixtures/setups/pendulum/setup.py +++ b/tests/utils/fixtures/setups/pendulum/setup.py @@ -2,7 +2,7 @@ from distutils.core import setup -from build import * +from build import * # nopycln: import packages = [ diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index bf8fe8dfe9b..7d83c57a91f 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -7,7 +7,6 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Callable from typing import Iterator import pytest @@ -34,6 +33,8 @@ if TYPE_CHECKING: + from collections.abc import Callable + from pytest_mock import MockerFixture from poetry.poetry import Poetry diff --git a/tests/utils/test_setup_reader.py b/tests/utils/test_setup_reader.py index 7dbf2ba91ae..d72e5386275 100644 --- a/tests/utils/test_setup_reader.py +++ b/tests/utils/test_setup_reader.py @@ -2,7 +2,7 @@ import os -from typing import Callable +from typing import TYPE_CHECKING import pytest @@ -11,6 +11,10 @@ from poetry.utils.setup_reader import SetupReader +if TYPE_CHECKING: + from collections.abc import Callable + + @pytest.fixture() def setup() -> Callable[[str], str]: def _setup(name: str) -> str: