Skip to content

Commit 4a4a8a8

Browse files
committed
installer: support for duplicate direct origin dependencies with same version
1 parent 1a15cb4 commit 4a4a8a8

File tree

3 files changed

+122
-18
lines changed

3 files changed

+122
-18
lines changed

src/poetry/installation/installer.py

+18-18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from poetry.repositories import Pool
1313
from poetry.repositories import Repository
1414
from poetry.repositories.installed_repository import InstalledRepository
15+
from poetry.repositories.lockfile_repository import LockfileRepository
1516
from poetry.utils.extras import get_extra_package_names
1617
from poetry.utils.helpers import canonicalize_name
1718
from poetry.utils.helpers import pluralize
@@ -107,9 +108,7 @@ def run(self) -> int:
107108
self._write_lock = False
108109
self._execute_operations = False
109110

110-
local_repo = Repository()
111-
112-
return self._do_install(local_repo)
111+
return self._do_install()
113112

114113
def dry_run(self, dry_run: bool = True) -> Installer:
115114
self._dry_run = dry_run
@@ -204,14 +203,14 @@ def _do_refresh(self) -> int:
204203
):
205204
ops = solver.solve(use_latest=[]).calculate_operations()
206205

207-
local_repo = Repository()
208-
self._populate_local_repo(local_repo, ops)
206+
lockfile_repo = LockfileRepository()
207+
self._populate_lockfile_repo(lockfile_repo, ops)
209208

210-
self._write_lock_file(local_repo, force=True)
209+
self._write_lock_file(lockfile_repo, force=True)
211210

212211
return 0
213212

214-
def _do_install(self, local_repo: Repository) -> int:
213+
def _do_install(self) -> int:
215214
from poetry.puzzle.solver import Solver
216215

217216
locked_repository = Repository()
@@ -266,10 +265,11 @@ def _do_install(self, local_repo: Repository) -> int:
266265
# currently installed
267266
ops = self._get_operations_from_lock(locked_repository)
268267

269-
self._populate_local_repo(local_repo, ops)
268+
lockfile_repo = LockfileRepository()
269+
self._populate_lockfile_repo(lockfile_repo, ops)
270270

271271
if self._update:
272-
self._write_lock_file(local_repo)
272+
self._write_lock_file(lockfile_repo)
273273

274274
if self._lock:
275275
# If we are only in lock mode, no need to go any further
@@ -292,8 +292,8 @@ def _do_install(self, local_repo: Repository) -> int:
292292
# Making a new repo containing the packages
293293
# newly resolved and the ones from the current lock file
294294
repo = Repository()
295-
for package in local_repo.packages + locked_repository.packages:
296-
if not repo.has_package(package):
295+
for package in lockfile_repo.packages + locked_repository.packages:
296+
if not package.is_direct_origin() and not repo.has_package(package):
297297
repo.add_package(package)
298298

299299
pool.add_repository(repo)
@@ -318,7 +318,7 @@ def _do_install(self, local_repo: Repository) -> int:
318318

319319
transaction = Transaction(
320320
locked_repository.packages,
321-
[(package, 0) for package in local_repo.packages],
321+
[(package, 0) for package in lockfile_repo.packages],
322322
installed_packages=self._installed_repository.packages,
323323
root_package=root,
324324
)
@@ -332,12 +332,12 @@ def _do_install(self, local_repo: Repository) -> int:
332332
# We need to filter operations so that packages
333333
# not compatible with the current system,
334334
# or optional and not requested, are dropped
335-
self._filter_operations(ops, local_repo)
335+
self._filter_operations(ops, lockfile_repo)
336336

337337
# Execute operations
338338
return self._execute(ops)
339339

340-
def _write_lock_file(self, repo: Repository, force: bool = False) -> None:
340+
def _write_lock_file(self, repo: LockfileRepository, force: bool = False) -> None:
341341
if self._write_lock and (force or self._update):
342342
updated_lock = self._locker.set_lock_data(self._package, repo.packages)
343343

@@ -460,8 +460,8 @@ def _execute_uninstall(self, operation: Uninstall) -> None:
460460

461461
self._installer.remove(operation.package)
462462

463-
def _populate_local_repo(
464-
self, local_repo: Repository, ops: Sequence[Operation]
463+
def _populate_lockfile_repo(
464+
self, repo: LockfileRepository, ops: Sequence[Operation]
465465
) -> None:
466466
for op in ops:
467467
if isinstance(op, Uninstall):
@@ -471,8 +471,8 @@ def _populate_local_repo(
471471
else:
472472
package = op.package
473473

474-
if not local_repo.has_package(package):
475-
local_repo.add_package(package)
474+
if not repo.has_package(package):
475+
repo.add_package(package)
476476

477477
def _get_operations_from_lock(
478478
self, locked_repository: Repository
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
[[package]]
2+
name = "demo"
3+
version = "0.1.0"
4+
description = ""
5+
category = "main"
6+
optional = false
7+
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
8+
9+
[package.source]
10+
type = "url"
11+
url = "https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
12+
13+
[package.dependencies]
14+
pendulum = ">=1.4.4"
15+
16+
[package.extras]
17+
bar = ["tomlkit"]
18+
foo = ["cleo"]
19+
20+
[[package]]
21+
name = "demo"
22+
version = "0.1.0"
23+
description = ""
24+
category = "main"
25+
optional = false
26+
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
27+
28+
[package.source]
29+
type = "url"
30+
url = "https://python-poetry.org/distributions/demo-0.1.0.tar.gz"
31+
32+
[package.dependencies]
33+
pendulum = ">=1.4.4"
34+
35+
[package.extras]
36+
bar = ["tomlkit"]
37+
foo = ["cleo"]
38+
39+
[[package]]
40+
name = "pendulum"
41+
version = "1.4.4"
42+
description = ""
43+
category = "main"
44+
optional = false
45+
python-versions = "*"
46+
47+
[metadata]
48+
python-versions = "*"
49+
lock-version = "1.1"
50+
content-hash = "123456789"
51+
52+
[metadata.files]
53+
demo = []
54+
pendulum = []

tests/installation/test_installer.py

+50
Original file line numberDiff line numberDiff line change
@@ -2231,6 +2231,56 @@ def test_run_installs_with_url_file(
22312231
assert installer.executor.installations_count == 2
22322232

22332233

2234+
@pytest.mark.parametrize("env_platform", ["linux", "win32"])
2235+
def test_run_installs_with_same_version_url_files(
2236+
pool: Pool,
2237+
locker: Locker,
2238+
installed: CustomInstalledRepository,
2239+
config: Config,
2240+
repo: Repository,
2241+
package: ProjectPackage,
2242+
env_platform: str,
2243+
) -> None:
2244+
urls = {
2245+
"linux": "https://python-poetry.org/distributions/demo-0.1.0.tar.gz",
2246+
"win32": (
2247+
"https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl"
2248+
),
2249+
}
2250+
for platform, url in urls.items():
2251+
package.add_dependency(
2252+
Factory.create_dependency(
2253+
"demo",
2254+
{"url": url, "markers": f"sys_platform == '{platform}'"},
2255+
)
2256+
)
2257+
repo.add_package(get_package("pendulum", "1.4.4"))
2258+
2259+
installer = Installer(
2260+
NullIO(),
2261+
MockEnv(platform=env_platform),
2262+
package,
2263+
locker,
2264+
pool,
2265+
config,
2266+
installed=installed,
2267+
executor=Executor(
2268+
MockEnv(platform=env_platform),
2269+
pool,
2270+
config,
2271+
NullIO(),
2272+
),
2273+
)
2274+
installer.use_executor(True)
2275+
installer.run()
2276+
2277+
expected = fixture("with-same-version-url-dependencies")
2278+
assert locker.written_data == expected
2279+
assert installer.executor.installations_count == 2
2280+
demo_package = next(p for p in installer.executor.installations if p.name == "demo")
2281+
assert demo_package.source_url == urls[env_platform]
2282+
2283+
22342284
def test_installer_uses_prereleases_if_they_are_compatible(
22352285
installer: Installer, locker: Locker, package: ProjectPackage, repo: Repository
22362286
):

0 commit comments

Comments
 (0)