Skip to content

Commit

Permalink
fix: explicit source dependency is not satisfied by direct origin
Browse files Browse the repository at this point in the history
  • Loading branch information
ralbertazzi committed May 24, 2023
1 parent 9217975 commit 4b0a796
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 7 deletions.
23 changes: 23 additions & 0 deletions docs/dependency-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,29 @@ The constraints **must** have different requirements (like `python`)
otherwise it will cause an error when resolving dependencies.
{{% /note %}}

### Combining git / url / path dependencies with source repositories

Direct origin (`git`/ `url`/ `path`) dependencies can satisfy the requirement of a dependency that
doesn't explicitly specify a source, even when mutually exclusive markers are used. For instance
in the following example the url package will also be a valid solution for the second requirement:
```toml
foo = [
{ platform = "darwin", url = "https://example.com/example-1.0-py3-none-any.whl" },
{ platform = "linux", version = "^1.0" },
]
```

Sometimes you may instead want to use a direct origin dependency for specific conditions
(i.e. a compiled package that is not available on PyPI for a certain platform/architecture) while
falling back on source repositories in other cases. In this case you should explicitly ask for your
dependency to be satisfied by another `source`. For example:
```toml
foo = [
{ platform = "darwin", url = "https://example.com/foo-1.0.0-py3-none-macosx_11_0_arm64.whl" },
{ platform = "linux", version = "^1.0", source = "pypi" },
]
```

## Expanded dependency specification syntax

In the case of more complex dependency specifications, you may find that you
Expand Down
8 changes: 2 additions & 6 deletions src/poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,8 @@ def search_for(self, dependency: Dependency) -> list[DependencyPackage]:
#
# We rely on the VersionSolver resolving direct-origin dependencies first.
direct_origin_package = self._direct_origin_packages.get(dependency.name)
if direct_origin_package is not None:
packages = (
[direct_origin_package]
if dependency.constraint.allows(direct_origin_package.version)
else []
)
if direct_origin_package and direct_origin_package.satisfies(dependency):
packages = [direct_origin_package]
return PackageCollection(dependency, packages)

packages = self._pool.find_packages(dependency)
Expand Down
71 changes: 71 additions & 0 deletions tests/installation/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2659,3 +2659,74 @@ def test_installer_distinguishes_locked_packages_by_source(
source_url=source_url,
source_reference=source_reference,
)


@pytest.mark.parametrize("env_platform", ["darwin", "linux"])
def test_explicit_source_dependency_with_direct_origin_dependency(
pool: RepositoryPool,
locker: Locker,
installed: CustomInstalledRepository,
config: Config,
repo: Repository,
package: ProjectPackage,
env_platform: str,
) -> None:
"""
A dependency with explicit source should not be satisfied by
a direct origin dependency even if there is a version match.
"""
package.add_dependency(
Factory.create_dependency(
"demo",
{
"markers": "sys_platform != 'darwin'",
"url": "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
},
)
)
package.add_dependency(
Factory.create_dependency(
"demo",
{
"version": "0.1.0",
"markers": "sys_platform == 'darwin'",
"source": "repo",
},
)
)
# The url demo dependency depends on pendulum.
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("demo", "0.1.0"))

installer = Installer(
NullIO(),
MockEnv(platform=env_platform),
package,
locker,
pool,
config,
installed=installed,
executor=Executor(
MockEnv(platform=env_platform),
pool,
config,
NullIO(),
),
)

result = installer.run()

assert result == 0
assert isinstance(installer.executor, Executor)
if env_platform == "linux":
assert installer.executor.installations == [
Package("pendulum", "1.4.4"),
Package(
"demo",
"0.1.0",
source_type="url",
source_url="https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl",
),
]
else:
assert installer.executor.installations == [Package("demo", "0.1.0")]
37 changes: 36 additions & 1 deletion tests/puzzle/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def test_search_for(
Dependency("foo", ">=2"),
URLDependency("foo", SOME_URL),
[Package("foo", "3")],
[],
[Package("foo", "3")],
),
(
Dependency("foo", ">=1", extras=["bar"]),
Expand Down Expand Up @@ -722,3 +722,38 @@ def test_complete_package_fetches_optional_vcs_dependency_only_if_requested(
spy.assert_called()
else:
spy.assert_not_called()


def test_source_dependency_is_satisfied_by_direct_origin(
provider: Provider, repository: Repository
) -> None:
direct_origin_package = Package("foo", "1.1", source_type="url")
repository.add_package(Package("foo", "1.0"))
provider._direct_origin_packages = {"foo": direct_origin_package}
dep = Dependency("foo", ">=1")

assert provider.search_for(dep) == [direct_origin_package]


def test_explicit_source_dependency_is_not_satisfied_by_direct_origin(
provider: Provider, repository: Repository
) -> None:
repo_package = Package("foo", "1.0")
repository.add_package(repo_package)
provider._direct_origin_packages = {"foo": Package("foo", "1.1", source_type="url")}
dep = Dependency("foo", ">=1")
dep.source_name = repository.name

assert provider.search_for(dep) == [repo_package]


def test_source_dependency_is_not_satisfied_by_incompatible_direct_origin(
provider: Provider, repository: Repository
) -> None:
repo_package = Package("foo", "2.0")
repository.add_package(repo_package)
provider._direct_origin_packages = {"foo": Package("foo", "1.0", source_type="url")}
dep = Dependency("foo", ">=2")
dep.source_name = repository.name

assert provider.search_for(dep) == [repo_package]

0 comments on commit 4b0a796

Please sign in to comment.