From a80f9850fc5e8c6aaefb19693d286aa6204a9f3a Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Fri, 20 May 2022 14:34:51 -0600 Subject: [PATCH 01/12] doc: refer to PowerShell on Windows --- docs/basic-usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/basic-usage.md b/docs/basic-usage.md index 59569735631..6dcc966ef68 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -131,15 +131,15 @@ in order for the subsequent commands to run from within the virtual environment. Alternatively, to avoid creating a new shell, you can manually activate the -virtual environment by running `source {path_to_venv}/bin/activate` (`{path_to_venv}\Scripts\activate.bat` on Windows). +virtual environment by running `source {path_to_venv}/bin/activate` (`{path_to_venv}\Scripts\activate.ps1` on Windows PowerShell). To get the path to your virtual environment run `poetry env info --path`. You can also combine these into a nice one-liner, `source $(poetry env info --path)/bin/activate` To deactivate this virtual environment simply use `deactivate`. -| | POSIX Shell | Windows | Exit/Deactivate | +| | POSIX Shell | Windows (PowerShell) | Exit/Deactivate | | ----------------- | ----------------------------------------------- | ------------------------------------- | --------------- | | New Shell | `poetry shell` | `poetry shell` | `exit` | -| Manual Activation | `source {path_to_venv}/bin/activate` | `{path_to_venv}\Scripts\activate.bat` | `deactivate` | +| Manual Activation | `source {path_to_venv}/bin/activate` | `{path_to_venv}\Scripts\activate.ps1` | `deactivate` | | One-liner | `source $(poetry env info --path)/bin/activate` | | `deactivate` | From efbb5ceb4865504f7d43253f7cdd191e078d0dd1 Mon Sep 17 00:00:00 2001 From: Tom Solberg Date: Sat, 21 May 2022 13:47:48 +0200 Subject: [PATCH 02/12] doc: clarify`--why` command --- docs/cli.md | 3 ++- src/poetry/console/commands/show.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 81768824df0..d1765fc85ec 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -444,7 +444,7 @@ required by ### Options * `--without`: The dependency groups to ignore. -* `--why`: Include reverse dependencies where applicable. +* `--why`: When showing the full list, or a `--tree` for a single package, display why a package is included. * `--with`: The optional dependency groups to include. * `--only`: The only dependency groups to include. * `--default`: Only include the main dependencies. (**Deprecated**) @@ -457,6 +457,7 @@ required by When `--only` is specified, `--with` and `--without` options are ignored. {{% /note %}} + ## build The `build` command builds the source and wheels archives. diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py index 8764595c3e3..e5e91cb363d 100644 --- a/src/poetry/console/commands/show.py +++ b/src/poetry/console/commands/show.py @@ -50,7 +50,8 @@ class ShowCommand(GroupCommand): option( "why", None, - "When listing the tree for a single package, start from parents.", + "When showing the full list, or a --tree for a single package," + " also display why it's included.", ), option("latest", "l", "Show the latest version."), option( From dacb720ba756cb544737b2353506dc4d9c69fc71 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 19 May 2022 20:59:11 +0200 Subject: [PATCH 03/12] tests: isolate config always --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9c25776819b..b2c232e1fb5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -190,7 +190,7 @@ def auth_config_source() -> DictConfigSource: return source -@pytest.fixture +@pytest.fixture(autouse=True) def config( config_source: DictConfigSource, auth_config_source: DictConfigSource, From 021253db4031be317056ba81b6c0712219e52c0d Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 19 May 2022 21:00:44 +0200 Subject: [PATCH 04/12] support subdirectories for vcs dependencies --- src/poetry/puzzle/provider.py | 25 +++++++++++++++++--- src/poetry/utils/dependency_specification.py | 10 +++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index d3e2faa0b6b..f45158cba07 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -87,6 +87,7 @@ def _get_package_from_git( branch: str | None = None, tag: str | None = None, rev: str | None = None, + subdirectory: str | None = None, source_root: Path | None = None, ) -> Package: source = Git.clone( @@ -99,11 +100,16 @@ def _get_package_from_git( ) revision = Git.get_revision(source) - package = Provider.get_package_from_directory(Path(source.path)) + path = Path(source.path) + if subdirectory: + path = path.joinpath(subdirectory) + + package = Provider.get_package_from_directory(path) package._source_type = "git" package._source_url = url package._source_reference = rev or tag or branch or "HEAD" package._source_resolved_reference = revision + package._source_subdirectory = subdirectory return package @@ -230,7 +236,13 @@ def search_for_vcs(self, dependency: VCSDependency) -> list[Package]: Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ - if dependency in self._deferred_cache: + # we ensure subdirectory match here as workaround until poetry-core is updated + # to >1.1.0a7 + if ( + dependency in self._deferred_cache + and self._deferred_cache[dependency].source_subdirectory + == dependency.source_subdirectory + ): return [self._deferred_cache[dependency]] package = self.get_package_from_vcs( @@ -239,6 +251,7 @@ def search_for_vcs(self, dependency: VCSDependency) -> list[Package]: branch=dependency.branch, tag=dependency.tag, rev=dependency.rev, + subdirectory=dependency.source_subdirectory, source_root=self._source_root or (self._env.path.joinpath("src") if self._env else None), ) @@ -265,13 +278,19 @@ def get_package_from_vcs( branch: str | None = None, tag: str | None = None, rev: str | None = None, + subdirectory: str | None = None, source_root: Path | None = None, ) -> Package: if vcs != "git": raise ValueError(f"Unsupported VCS dependency {vcs}") return _get_package_from_git( - url=url, branch=branch, tag=tag, rev=rev, source_root=source_root + url=url, + branch=branch, + tag=tag, + rev=rev, + subdirectory=subdirectory, + source_root=source_root, ) def search_for_file(self, dependency: FileDependency) -> list[Package]: diff --git a/src/poetry/utils/dependency_specification.py b/src/poetry/utils/dependency_specification.py index 57d029599d1..96c666e9459 100644 --- a/src/poetry/utils/dependency_specification.py +++ b/src/poetry/utils/dependency_specification.py @@ -37,12 +37,20 @@ def _parse_dependency_specification_git_url( url = Git.normalize_url(requirement) pair = {"name": parsed.name, "git": url.url} + if parsed.rev: pair["rev"] = url.revision + if parsed.subdirectory: + pair["subdirectory"] = parsed.subdirectory + source_root = env.path.joinpath("src") if env else None package = Provider.get_package_from_vcs( - "git", url=url.url, rev=pair.get("rev"), source_root=source_root + "git", + url=url.url, + rev=pair.get("rev"), + subdirectory=parsed.subdirectory, + source_root=source_root, ) pair["name"] = package.name return pair From e1af707a17b6889e014e5d2aa5a78ce242b6e397 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 19 May 2022 21:01:59 +0200 Subject: [PATCH 05/12] fix lru cache usage for dependency cache --- src/poetry/mixology/version_solver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index d39591b4b8f..fafe0ab941c 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -44,9 +44,9 @@ def __init__(self, provider: Provider) -> None: self.cache: dict[ tuple[str, str | None, str | None, str | None], list[DependencyPackage] ] = {} + self.search_for = functools.lru_cache(maxsize=128)(self._search_for) - @functools.lru_cache(maxsize=128) - def search_for(self, dependency: Dependency) -> list[DependencyPackage]: + def _search_for(self, dependency: Dependency) -> list[DependencyPackage]: key = ( dependency.complete_name, dependency.source_type, From 5cacba890a2a3a067984e34733998d0a98a90315 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 19 May 2022 21:05:54 +0200 Subject: [PATCH 06/12] make dependency cache compatible with core 1.1.0a7 This change is required to allow for subdirectories. --- pyproject.toml | 1 + src/poetry/mixology/version_solver.py | 10 +++++++++- src/poetry/puzzle/provider.py | 1 + tests/mixology/version_solver/test_dependency_cache.py | 6 ++++-- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d7a1db7b42e..57fae9785dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,6 +130,7 @@ ignore_errors = true module = [ 'poetry.installation.executor', 'poetry.repositories.installed_repository', + 'poetry.mixology.version_solver', ] warn_unused_ignores = false diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index fafe0ab941c..7b9c9cccc7a 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -19,6 +19,7 @@ from poetry.mixology.set_relation import SetRelation from poetry.mixology.term import Term from poetry.packages import DependencyPackage +from poetry.utils._compat import metadata if TYPE_CHECKING: @@ -44,7 +45,14 @@ def __init__(self, provider: Provider) -> None: self.cache: dict[ tuple[str, str | None, str | None, str | None], list[DependencyPackage] ] = {} - self.search_for = functools.lru_cache(maxsize=128)(self._search_for) + + # TODO: re-enable cache when poetry-core upgrade is completed + self.search_for = functools.lru_cache( + maxsize=128 + if metadata.version("poetry-core") # type: ignore[no-untyped-call] + != "1.1.0a7" + else 0 + )(self._search_for) def _search_for(self, dependency: Dependency) -> list[DependencyPackage]: key = ( diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index f45158cba07..36a829c30bd 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -236,6 +236,7 @@ def search_for_vcs(self, dependency: VCSDependency) -> list[Package]: Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ + # TODO: remove explicit subdirectory check once poetry-core is updated # we ensure subdirectory match here as workaround until poetry-core is updated # to >1.1.0a7 if ( diff --git a/tests/mixology/version_solver/test_dependency_cache.py b/tests/mixology/version_solver/test_dependency_cache.py index 469a1e569db..bb03a1c4de2 100644 --- a/tests/mixology/version_solver/test_dependency_cache.py +++ b/tests/mixology/version_solver/test_dependency_cache.py @@ -4,6 +4,7 @@ from poetry.factory import Factory from poetry.mixology.version_solver import DependencyCache +from tests.compat import is_poetry_core_1_1_0a7_compat from tests.mixology.helpers import add_to_repo @@ -37,8 +38,9 @@ def test_solver_dependency_cache_respects_source_type( 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 + if not is_poetry_core_1_1_0a7_compat: + 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 From 77d897586c5f1f036bc27e2cfb75b1517062b0c0 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 19 May 2022 21:07:54 +0200 Subject: [PATCH 07/12] ensure provider respects subdirectory when merging --- src/poetry/mixology/version_solver.py | 4 +- src/poetry/puzzle/provider.py | 3 +- .../subdirectories/one-copy/pyproject.toml | 9 +++ .../demo/subdirectories/one/pyproject.toml | 9 +++ .../demo/subdirectories/two/pyproject.toml | 9 +++ .../demo_one/pyproject.toml | 9 +++ .../demo_two/pyproject.toml | 9 +++ .../with_conditional_path_deps/pyproject.toml | 13 ++++ .../version_solver/test_dependency_cache.py | 62 +++++++++++++++ tests/puzzle/test_provider.py | 75 +++++++++++++++++++ 10 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/git/github.com/demo/subdirectories/one-copy/pyproject.toml create mode 100644 tests/fixtures/git/github.com/demo/subdirectories/one/pyproject.toml create mode 100644 tests/fixtures/git/github.com/demo/subdirectories/two/pyproject.toml create mode 100644 tests/fixtures/with_conditional_path_deps/demo_one/pyproject.toml create mode 100644 tests/fixtures/with_conditional_path_deps/demo_two/pyproject.toml create mode 100644 tests/fixtures/with_conditional_path_deps/pyproject.toml diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 7b9c9cccc7a..b7c3410b0ad 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -43,7 +43,8 @@ class DependencyCache: def __init__(self, provider: Provider) -> None: self.provider = provider self.cache: dict[ - tuple[str, str | None, str | None, str | None], list[DependencyPackage] + tuple[str, str | None, str | None, str | None, str | None], + list[DependencyPackage], ] = {} # TODO: re-enable cache when poetry-core upgrade is completed @@ -60,6 +61,7 @@ def _search_for(self, dependency: Dependency) -> list[DependencyPackage]: dependency.source_type, dependency.source_url, dependency.source_reference, + dependency.source_subdirectory, ) packages = self.cache.get(key) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 36a829c30bd..3a1589abc14 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -575,7 +575,7 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: # 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] + tuple[str, str | None, str | None, str | None, str | None], list[Dependency] ] = {} for dep in dependencies: key = ( @@ -583,6 +583,7 @@ def complete_package(self, package: DependencyPackage) -> DependencyPackage: dep.source_type, dep.source_url, dep.source_reference, + dep.source_subdirectory, ) if key not in duplicates: duplicates[key] = [] diff --git a/tests/fixtures/git/github.com/demo/subdirectories/one-copy/pyproject.toml b/tests/fixtures/git/github.com/demo/subdirectories/one-copy/pyproject.toml new file mode 100644 index 00000000000..39265efe4a3 --- /dev/null +++ b/tests/fixtures/git/github.com/demo/subdirectories/one-copy/pyproject.toml @@ -0,0 +1,9 @@ +[tool.poetry] +name = "one" +version = "1.0.0" +description = "Some description." +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" diff --git a/tests/fixtures/git/github.com/demo/subdirectories/one/pyproject.toml b/tests/fixtures/git/github.com/demo/subdirectories/one/pyproject.toml new file mode 100644 index 00000000000..39265efe4a3 --- /dev/null +++ b/tests/fixtures/git/github.com/demo/subdirectories/one/pyproject.toml @@ -0,0 +1,9 @@ +[tool.poetry] +name = "one" +version = "1.0.0" +description = "Some description." +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" diff --git a/tests/fixtures/git/github.com/demo/subdirectories/two/pyproject.toml b/tests/fixtures/git/github.com/demo/subdirectories/two/pyproject.toml new file mode 100644 index 00000000000..58fde435649 --- /dev/null +++ b/tests/fixtures/git/github.com/demo/subdirectories/two/pyproject.toml @@ -0,0 +1,9 @@ +[tool.poetry] +name = "two" +version = "2.0.0" +description = "Some description." +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" diff --git a/tests/fixtures/with_conditional_path_deps/demo_one/pyproject.toml b/tests/fixtures/with_conditional_path_deps/demo_one/pyproject.toml new file mode 100644 index 00000000000..85dab468dd6 --- /dev/null +++ b/tests/fixtures/with_conditional_path_deps/demo_one/pyproject.toml @@ -0,0 +1,9 @@ +[tool.poetry] +name = "demo" +version = "1.2.3" +description = "Some description." +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" diff --git a/tests/fixtures/with_conditional_path_deps/demo_two/pyproject.toml b/tests/fixtures/with_conditional_path_deps/demo_two/pyproject.toml new file mode 100644 index 00000000000..85dab468dd6 --- /dev/null +++ b/tests/fixtures/with_conditional_path_deps/demo_two/pyproject.toml @@ -0,0 +1,9 @@ +[tool.poetry] +name = "demo" +version = "1.2.3" +description = "Some description." +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" diff --git a/tests/fixtures/with_conditional_path_deps/pyproject.toml b/tests/fixtures/with_conditional_path_deps/pyproject.toml new file mode 100644 index 00000000000..1e2ea754059 --- /dev/null +++ b/tests/fixtures/with_conditional_path_deps/pyproject.toml @@ -0,0 +1,13 @@ +[tool.poetry] +name = "sample" +version = "1.0.0" +description = "Sample Project" +authors = [] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.7" +demo = [ + { path = "demo_one", platform = "linux" }, + { path = "demo_two", platform = "win32" }, +] diff --git a/tests/mixology/version_solver/test_dependency_cache.py b/tests/mixology/version_solver/test_dependency_cache.py index bb03a1c4de2..c1645604e28 100644 --- a/tests/mixology/version_solver/test_dependency_cache.py +++ b/tests/mixology/version_solver/test_dependency_cache.py @@ -59,3 +59,65 @@ def test_solver_dependency_cache_respects_source_type( package_git.package.source_resolved_reference == "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" ) + + +def test_solver_dependency_cache_respects_subdirectories( + root: ProjectPackage, provider: Provider, repo: Repository +): + dependency_one = Factory.create_dependency( + "one", + { + "git": "https://github.com/demo/subdirectories.git", + "subdirectory": "one", + "platform": "linux", + }, + ) + dependency_one_copy = Factory.create_dependency( + "one", + { + "git": "https://github.com/demo/subdirectories.git", + "subdirectory": "one-copy", + "platform": "win32", + }, + ) + + root.add_dependency(dependency_one) + root.add_dependency(dependency_one_copy) + + cache = DependencyCache(provider) + cache.search_for.cache_clear() + + # ensure cache was never hit for both calls + cache.search_for(dependency_one) + cache.search_for(dependency_one_copy) + assert not cache.search_for.cache_info().hits + + packages_one = cache.search_for(dependency_one) + packages_one_copy = cache.search_for(dependency_one_copy) + + if not is_poetry_core_1_1_0a7_compat: + assert cache.search_for.cache_info().hits == 2 + assert cache.search_for.cache_info().currsize == 2 + + assert len(packages_one) == len(packages_one_copy) == 1 + + package_one = packages_one[0] + package_one_copy = packages_one_copy[0] + + assert package_one.package.name == package_one_copy.name + assert package_one.package.version.text == package_one_copy.package.version.text + assert package_one.package.source_type == package_one_copy.source_type == "git" + assert ( + package_one.package.source_resolved_reference + == package_one_copy.source_resolved_reference + == "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" + ) + assert ( + package_one.package.source_subdirectory != package_one_copy.source_subdirectory + ) + assert package_one.package.source_subdirectory == "one" + assert package_one_copy.package.source_subdirectory == "one-copy" + + assert package_one.dependency.marker.intersect( + package_one_copy.dependency.marker + ).is_empty() diff --git a/tests/puzzle/test_provider.py b/tests/puzzle/test_provider.py index ea1b552c907..a1f7076ae74 100644 --- a/tests/puzzle/test_provider.py +++ b/tests/puzzle/test_provider.py @@ -12,7 +12,9 @@ from poetry.core.packages.project_package import ProjectPackage from poetry.core.packages.vcs_dependency import VCSDependency +from poetry.factory import Factory from poetry.inspection.info import PackageInfo +from poetry.packages import DependencyPackage from poetry.puzzle.provider import Provider from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository @@ -510,3 +512,76 @@ def test_search_for_file_wheel_with_extras(provider: Provider): "foo": [get_dependency("cleo")], "bar": [get_dependency("tomlkit")], } + + +def test_complete_package_preserves_source_type(provider: Provider) -> None: + fixtures = Path(__file__).parent.parent / "fixtures" + project_dir = fixtures.joinpath("with_conditional_path_deps") + poetry = Factory().create_poetry(cwd=project_dir) + + complete_package = provider.complete_package( + DependencyPackage(poetry.package.to_dependency(), poetry.package) + ) + + requires = complete_package.package.all_requires + + assert len(requires) == 2 + + assert {requires[0].source_url, requires[1].source_url} == { + project_dir.joinpath("demo_one").as_posix(), + project_dir.joinpath("demo_two").as_posix(), + } + assert {str(requires[0].marker), str(requires[1].marker)} == { + 'sys_platform == "linux"', + 'sys_platform == "win32"', + } + + +def test_complete_package_preserves_source_type_with_subdirectories( + provider: Provider, root: ProjectPackage +) -> None: + dependency_one = Factory.create_dependency( + "one", + { + "git": "https://github.com/demo/subdirectories.git", + "subdirectory": "one", + "platform": "linux", + }, + ) + dependency_one_copy = Factory.create_dependency( + "one", + { + "git": "https://github.com/demo/subdirectories.git", + "subdirectory": "one-copy", + "platform": "win32", + }, + ) + dependency_two = Factory.create_dependency( + "two", + {"git": "https://github.com/demo/subdirectories.git", "subdirectory": "two"}, + ) + + root.add_dependency( + Factory.create_dependency( + "one", + { + "git": "https://github.com/demo/subdirectories.git", + "subdirectory": "one", + "platform": "linux", + }, + ) + ) + root.add_dependency(dependency_one_copy) + root.add_dependency(dependency_two) + + complete_package = provider.complete_package( + DependencyPackage(root.to_dependency(), root) + ) + + requires = complete_package.package.all_requires + assert len(requires) == 3 + assert {r.to_pep_508() for r in requires} == { + dependency_one.to_pep_508(), + dependency_one_copy.to_pep_508(), + dependency_two.to_pep_508(), + } From caffd4f016863c50cfb473c511800dadc935a578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 21 May 2022 15:49:15 +0200 Subject: [PATCH 08/12] solver: print more information about packages with source_type when solving fails to avoid weird messages like "Because myapp depends on both demo (1.2.3) and demo (1.2.3), version solving failed." --- src/poetry/mixology/incompatibility.py | 2 ++ .../version_solver/test_unsolvable.py | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/poetry/mixology/incompatibility.py b/src/poetry/mixology/incompatibility.py index b4d3bba955f..b629ea7a699 100644 --- a/src/poetry/mixology/incompatibility.py +++ b/src/poetry/mixology/incompatibility.py @@ -438,6 +438,8 @@ def _terse(self, term: Term, allow_every: bool = False) -> str: pretty_name: str = term.dependency.pretty_name return pretty_name + if term.dependency.source_type: + return str(term.dependency) return f"{term.dependency.pretty_name} ({term.dependency.pretty_constraint})" def _single_term_where(self, callable: Callable[[Term], bool]) -> Term | None: diff --git a/tests/mixology/version_solver/test_unsolvable.py b/tests/mixology/version_solver/test_unsolvable.py index 578fcfe688f..7e134d649fb 100644 --- a/tests/mixology/version_solver/test_unsolvable.py +++ b/tests/mixology/version_solver/test_unsolvable.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from typing import TYPE_CHECKING from poetry.factory import Factory @@ -92,6 +93,25 @@ def test_disjoint_root_constraints( check_solver_result(root, provider, error=error) +def test_disjoint_root_constraints_path_dependencies( + root: ProjectPackage, provider: Provider, repo: Repository +): + provider.set_package_python_versions("^3.7") + fixtures = Path(__file__).parent.parent.parent / "fixtures" + project_dir = fixtures.joinpath("with_conditional_path_deps") + path1 = project_dir / "demo_one" + root.add_dependency(Factory.create_dependency("demo", {"path": path1})) + path2 = project_dir / "demo_two" + root.add_dependency(Factory.create_dependency("demo", {"path": path2})) + + error = ( + f"Because myapp depends on both demo (1.2.3 {path1.as_posix()}) " + f"and demo (1.2.3 {path2.as_posix()}), version solving failed." + ) + + check_solver_result(root, provider, error=error) + + def test_no_valid_solution(root: ProjectPackage, provider: Provider, repo: Repository): root.add_dependency(Factory.create_dependency("a", "*")) root.add_dependency(Factory.create_dependency("b", "*")) From d959f1fc1adaf985754069f666401f0d7149e976 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 21 May 2022 15:51:23 +0100 Subject: [PATCH 09/12] use types-entrypoints --- poetry.lock | 14 +++++++++++++- pyproject.toml | 2 +- src/poetry/console/commands/plugin/show.py | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index c773098d0b9..e2bef1ae089 100644 --- a/poetry.lock +++ b/poetry.lock @@ -722,6 +722,14 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "types-entrypoints" +version = "0.3.7" +description = "Typing stubs for entrypoints" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-requests" version = "2.27.26" @@ -804,7 +812,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "5421b8901d8d4589152de8dafcb79827eaefa00ae7c3af53092be08a0caed970" +content-hash = "de4d886bc0dfaf7a2cbc42d82147bbcb9490b1f13f1e7abada8c824f1e592a2a" [metadata.files] atomicwrites = [ @@ -1267,6 +1275,10 @@ typed-ast = [ {file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"}, {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, ] +types-entrypoints = [ + {file = "types-entrypoints-0.3.7.tar.gz", hash = "sha256:3d002ba32fc2c7fb44f65228fb912969a495cb4aa4ae63e05152f4304bc9bba2"}, + {file = "types_entrypoints-0.3.7-py3-none-any.whl", hash = "sha256:06aea9245f25b7b335ef0c3e8e4cc422b1f7c2cc47ae11f9a000a0a15d8ea6aa"}, +] types-requests = [ {file = "types-requests-2.27.26.tar.gz", hash = "sha256:a6a04c0274c0949fd0525f35d8b53ac34e77afecbeb3c4932ddc6ce675ac009c"}, {file = "types_requests-2.27.26-py3-none-any.whl", hash = "sha256:302137cb5bd482357398a155faf3ed095855fbc6994e952d0496c7fd50f44125"}, diff --git a/pyproject.toml b/pyproject.toml index 57fae9785dc..c54e27771ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ typing-extensions = { version = "^4.0.0", python = "<3.8" } zipp = { version = "^3.4", python = "<3.8" } flatdict = "^4.0.1" mypy = ">=0.950" +types-entrypoints = ">=0.3.7" types-requests = ">=2.27.11" [tool.poetry.scripts] @@ -140,7 +141,6 @@ module = [ 'cachy.*', 'cleo.*', 'crashtest.*', - 'entrypoints.*', 'html5lib.*', 'jsonschema.*', 'pexpect.*', diff --git a/src/poetry/console/commands/plugin/show.py b/src/poetry/console/commands/plugin/show.py index f5d1bbaf4ea..2ff223914cc 100644 --- a/src/poetry/console/commands/plugin/show.py +++ b/src/poetry/console/commands/plugin/show.py @@ -47,6 +47,7 @@ def handle(self) -> int: if issubclass(plugin, ApplicationPlugin): category = "application_plugins" + assert entry_point.distro is not None package = packages_by_name[canonicalize_name(entry_point.distro.name)] plugins[package.pretty_name]["package"] = package plugins[package.pretty_name][category].append(entry_point) From 504eb96b15dea1592326fa0eff31f61ea3e78f86 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 21 May 2022 15:59:07 +0100 Subject: [PATCH 10/12] use types-html5lib --- poetry.lock | 14 ++++++++- pyproject.toml | 2 +- src/poetry/repositories/pypi_repository.py | 33 +++++++++++++++------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index e2bef1ae089..7acf19f1af8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -730,6 +730,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "types-html5lib" +version = "1.1.7" +description = "Typing stubs for html5lib" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-requests" version = "2.27.26" @@ -812,7 +820,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "de4d886bc0dfaf7a2cbc42d82147bbcb9490b1f13f1e7abada8c824f1e592a2a" +content-hash = "58010109be37f846df33e17737ed95a64df555288aabf96c3089fd0d58cc6872" [metadata.files] atomicwrites = [ @@ -1279,6 +1287,10 @@ types-entrypoints = [ {file = "types-entrypoints-0.3.7.tar.gz", hash = "sha256:3d002ba32fc2c7fb44f65228fb912969a495cb4aa4ae63e05152f4304bc9bba2"}, {file = "types_entrypoints-0.3.7-py3-none-any.whl", hash = "sha256:06aea9245f25b7b335ef0c3e8e4cc422b1f7c2cc47ae11f9a000a0a15d8ea6aa"}, ] +types-html5lib = [ + {file = "types-html5lib-1.1.7.tar.gz", hash = "sha256:fdb307102ceea52adf52aea0e10255d142ee29d7f169014144a730707a92066a"}, + {file = "types_html5lib-1.1.7-py3-none-any.whl", hash = "sha256:4bc5a1de03b9a697b7cc04b0d03eeed5d36c0073165dd9dc54f2f43d0f23b31f"}, +] types-requests = [ {file = "types-requests-2.27.26.tar.gz", hash = "sha256:a6a04c0274c0949fd0525f35d8b53ac34e77afecbeb3c4932ddc6ce675ac009c"}, {file = "types_requests-2.27.26-py3-none-any.whl", hash = "sha256:302137cb5bd482357398a155faf3ed095855fbc6994e952d0496c7fd50f44125"}, diff --git a/pyproject.toml b/pyproject.toml index c54e27771ae..e6633b03ea6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ zipp = { version = "^3.4", python = "<3.8" } flatdict = "^4.0.1" mypy = ">=0.950" types-entrypoints = ">=0.3.7" +types-html5lib = ">=1.1.7" types-requests = ">=2.27.11" [tool.poetry.scripts] @@ -141,7 +142,6 @@ module = [ 'cachy.*', 'cleo.*', 'crashtest.*', - 'html5lib.*', 'jsonschema.*', 'pexpect.*', 'pkginfo.*', diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index 97089ca2750..6da9e649098 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -106,20 +106,33 @@ def search(self, query: str) -> list[Package]: response = requests.session().get(self._base_url + "search", params=search) content = parse(response.content, namespaceHTMLElements=False) for result in content.findall(".//*[@class='package-snippet']"): - name = result.find("h3/*[@class='package-snippet__name']").text - version = result.find("h3/*[@class='package-snippet__version']").text - - if not name or not version: + name_element = result.find("h3/*[@class='package-snippet__name']") + version_element = result.find("h3/*[@class='package-snippet__version']") + + if ( + name_element is None + or version_element is None + or not name_element.text + or not version_element.text + ): continue - description = result.find("p[@class='package-snippet__description']").text - if not description: - description = "" + name = name_element.text + version = version_element.text + + description_element = result.find( + "p[@class='package-snippet__description']" + ) + description = ( + description_element.text + if description_element is not None and description_element.text + else "" + ) try: - result = Package(name, version, description) - result.description = to_str(description.strip()) - results.append(result) + package = Package(name, version) + package.description = to_str(description.strip()) + results.append(package) except InvalidVersion: self._log( f'Unable to parse version "{version}" for the {name} package,' From 80b03ece039ec72e86d87295ea557a4282891bd9 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 21 May 2022 16:31:02 +0100 Subject: [PATCH 11/12] use types-jsonschema --- poetry.lock | 14 +++++++++++++- pyproject.toml | 2 +- src/poetry/json/__init__.py | 6 +++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7acf19f1af8..d98783c894c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -738,6 +738,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "types-jsonschema" +version = "4.4.4" +description = "Typing stubs for jsonschema" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-requests" version = "2.27.26" @@ -820,7 +828,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "58010109be37f846df33e17737ed95a64df555288aabf96c3089fd0d58cc6872" +content-hash = "35d68e6eba695382502b1b6faab833606823a2e0cadc1b96137059f1e343e1ad" [metadata.files] atomicwrites = [ @@ -1291,6 +1299,10 @@ types-html5lib = [ {file = "types-html5lib-1.1.7.tar.gz", hash = "sha256:fdb307102ceea52adf52aea0e10255d142ee29d7f169014144a730707a92066a"}, {file = "types_html5lib-1.1.7-py3-none-any.whl", hash = "sha256:4bc5a1de03b9a697b7cc04b0d03eeed5d36c0073165dd9dc54f2f43d0f23b31f"}, ] +types-jsonschema = [ + {file = "types-jsonschema-4.4.4.tar.gz", hash = "sha256:d03f0c1a97ff06dda9535dfa51916a98f38bf40d6828ef4d93bc40708effe507"}, + {file = "types_jsonschema-4.4.4-py3-none-any.whl", hash = "sha256:294d2de9ea3564fbec6c56153e84d1f3f7d9b2ada36e183d88a63c126da7bc3d"}, +] types-requests = [ {file = "types-requests-2.27.26.tar.gz", hash = "sha256:a6a04c0274c0949fd0525f35d8b53ac34e77afecbeb3c4932ddc6ce675ac009c"}, {file = "types_requests-2.27.26-py3-none-any.whl", hash = "sha256:302137cb5bd482357398a155faf3ed095855fbc6994e952d0496c7fd50f44125"}, diff --git a/pyproject.toml b/pyproject.toml index e6633b03ea6..589fdd0d49e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ flatdict = "^4.0.1" mypy = ">=0.950" types-entrypoints = ">=0.3.7" types-html5lib = ">=1.1.7" +types-jsonschema = ">=4.4.4" types-requests = ">=2.27.11" [tool.poetry.scripts] @@ -142,7 +143,6 @@ module = [ 'cachy.*', 'cleo.*', 'crashtest.*', - 'jsonschema.*', 'pexpect.*', 'pkginfo.*', 'poetry.core.*', diff --git a/src/poetry/json/__init__.py b/src/poetry/json/__init__.py index d5914f04737..0e20d6f7720 100644 --- a/src/poetry/json/__init__.py +++ b/src/poetry/json/__init__.py @@ -17,12 +17,12 @@ class ValidationError(ValueError): def validate_object(obj: dict[str, Any], schema_name: str) -> list[str]: - schema = os.path.join(SCHEMA_DIR, f"{schema_name}.json") + schema_file = os.path.join(SCHEMA_DIR, f"{schema_name}.json") - if not os.path.exists(schema): + if not os.path.exists(schema_file): raise ValueError(f"Schema {schema_name} does not exist.") - with open(schema, encoding="utf-8") as f: + with open(schema_file, encoding="utf-8") as f: schema = json.loads(f.read()) validator = jsonschema.Draft7Validator(schema) From aa958ba0a94ac3217148ccfd1440e933fb6ef297 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Fri, 20 May 2022 21:48:06 +0200 Subject: [PATCH 12/12] mixology: choose direct ref deps when merging --- src/poetry/mixology/term.py | 35 ++++++++++++++++++++++++++++------- tests/puzzle/test_solver.py | 6 +++++- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/poetry/mixology/term.py b/src/poetry/mixology/term.py index 260567b1b7f..b208c2635ca 100644 --- a/src/poetry/mixology/term.py +++ b/src/poetry/mixology/term.py @@ -127,17 +127,17 @@ def intersect(self, other: Term) -> Term | None: negative = other if self.is_positive() else self return self._non_empty_term( - positive.constraint.difference(negative.constraint), True + positive.constraint.difference(negative.constraint), True, other ) elif self.is_positive(): # foo ^1.0.0 ∩ foo >=1.5.0 <3.0.0 → foo ^1.5.0 return self._non_empty_term( - self.constraint.intersect(other.constraint), True + self.constraint.intersect(other.constraint), True, other ) else: # not foo ^1.0.0 ∩ not foo >=1.5.0 <3.0.0 → not foo >=1.0.0 <3.0.0 return self._non_empty_term( - self.constraint.union(other.constraint), False + self.constraint.union(other.constraint), False, other ) elif self.is_positive() != other.is_positive(): return self if self.is_positive() else other @@ -151,21 +151,42 @@ def difference(self, other: Term) -> Term | None: """ return self.intersect(other.inverse) + @staticmethod + def _is_direct_origin(dependency: Dependency) -> bool: + return dependency.source_type in ["directory", "file", "url", "git"] + def _compatible_dependency(self, other: Dependency) -> bool: - compatible: bool = ( + return ( self.dependency.is_root or other.is_root or other.is_same_package_as(self.dependency) + or ( + # we do this here to indicate direct origin dependencies are + # compatible with NVR dependencies + self.dependency.complete_name == other.complete_name + and self._is_direct_origin(self.dependency) + != self._is_direct_origin(other) + and ( + self.dependency.constraint.allows_all(other.constraint) + or other.constraint.allows_all(self.dependency.constraint) + ) + ) ) - return compatible def _non_empty_term( - self, constraint: VersionConstraint, is_positive: bool + self, constraint: VersionConstraint, is_positive: bool, other: Term ) -> Term | None: if constraint.is_empty(): return None - return Term(self.dependency.with_constraint(constraint), is_positive) + # when creating a new term prefer direct-reference dependencies + dependency = ( + other.dependency + if not self._is_direct_origin(self.dependency) + and self._is_direct_origin(other.dependency) + else self.dependency + ) + return Term(dependency.with_constraint(constraint), is_positive) def __str__(self) -> str: prefix = "not " if not self.is_positive() else "" diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index fe47e8bd220..2ca281be0e6 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -22,6 +22,7 @@ from poetry.repositories.pool import Pool from poetry.repositories.repository import Repository from poetry.utils.env import MockEnv +from tests.compat import is_poetry_core_1_1_0a7_compat from tests.helpers import get_dependency from tests.helpers import get_package from tests.repositories.test_legacy_repository import ( @@ -1383,7 +1384,10 @@ def test_solver_duplicate_dependencies_different_sources_types_are_preserved( assert len(complete_package.all_requires) == 2 - pypi, git = complete_package.all_requires + if is_poetry_core_1_1_0a7_compat: + pypi, git = complete_package.all_requires + else: + git, pypi = complete_package.all_requires assert isinstance(pypi, Dependency) assert pypi == dependency_pypi