Skip to content

Commit

Permalink
Add --force option that overwrites existing files
Browse files Browse the repository at this point in the history
Implement the --force option that, if supplied, will make installer
overwrite any already existing package files instead of failing. With
this flag, installer can be used in an idempotent manner, i.e. the same
command can be executed multiple times with the same result:

    python -m installer --force --destdir=tmp dist/*.whl
    python -m installer --force --destdir=tmp dist/*.whl
    python -m installer --force --destdir=tmp dist/*.whl
  • Loading branch information
carlsmedstad committed Feb 13, 2024
1 parent fcc0d6f commit f718daf
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/installer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def _get_main_parser() -> argparse.ArgumentParser:
choices=["all", "entries", "none"],
help="validate the wheel against certain part of its record (default=none)",
)
parser.add_argument(
"--force",
action="store_true",
help="silently overwrite existing files",
)
return parser


Expand Down Expand Up @@ -101,6 +106,7 @@ def _main(cli_args: Sequence[str], program: Optional[str] = None) -> None:
script_kind=get_launcher_kind(),
bytecode_optimization_levels=bytecode_levels,
destdir=args.destdir,
force=args.force,
)
installer.install(source, destination, {})

Expand Down
5 changes: 4 additions & 1 deletion src/installer/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def __init__(
hash_algorithm: str = "sha256",
bytecode_optimization_levels: Collection[int] = (),
destdir: Optional[str] = None,
force: bool = False,
) -> None:
"""Construct a ``SchemeDictionaryDestination`` object.
Expand All @@ -128,13 +129,15 @@ def __init__(
:param destdir: A staging directory in which to write all files. This
is expected to be the filesystem root at runtime, so embedded paths
will be written as though this was the root.
:param force: silently overwrite existing files.
"""
self.scheme_dict = scheme_dict
self.interpreter = interpreter
self.script_kind = script_kind
self.hash_algorithm = hash_algorithm
self.bytecode_optimization_levels = bytecode_optimization_levels
self.destdir = destdir
self.force = force

def _path_with_destdir(self, scheme: Scheme, path: str) -> str:
file = os.path.join(self.scheme_dict[scheme], path)
Expand Down Expand Up @@ -162,7 +165,7 @@ def write_to_fs(
- Hashes the written content, to determine the entry in the ``RECORD`` file.
"""
target_path = self._path_with_destdir(scheme, path)
if os.path.exists(target_path):
if not self.force and os.path.exists(target_path):
message = f"File already exists: {target_path}"
raise FileExistsError(message)

Expand Down
20 changes: 20 additions & 0 deletions tests/test_destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ def destination(self, tmp_path):
scheme_dict[scheme] = str(full_path)
return SchemeDictionaryDestination(scheme_dict, "/my/python", "posix")

@pytest.fixture()
def destination_force(self, tmp_path):
scheme_dict = {}
for scheme in SCHEME_NAMES:
full_path = tmp_path / scheme
if not full_path.exists():
full_path.mkdir()
scheme_dict[scheme] = str(full_path)
return SchemeDictionaryDestination(
scheme_dict, "/my/python", "posix", force=True
)

@pytest.mark.parametrize(
("scheme", "path", "data", "expected"),
[
Expand Down Expand Up @@ -86,6 +98,14 @@ def test_write_record_duplicate(self, destination):
with pytest.raises(FileExistsError):
destination.write_file("data", "my_data.bin", io.BytesIO(b"my data"), False)

def test_write_record_duplicate_with_force(self, destination_force):
destination_force.write_file(
"data", "my_data.bin", io.BytesIO(b"my data"), False
)
destination_force.write_file(
"data", "my_data.bin", io.BytesIO(b"my data"), False
)

def test_write_script(self, destination):
script_args = ("my_entrypoint", "my_module", "my_function", "console")
record = destination.write_script(*script_args)
Expand Down

0 comments on commit f718daf

Please sign in to comment.