diff --git a/changes/476.feature.rst b/changes/476.feature.rst new file mode 100644 index 000000000..b8eb366de --- /dev/null +++ b/changes/476.feature.rst @@ -0,0 +1 @@ +Support for pinned requirements files has been added diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst index fcd167941..d644f510c 100644 --- a/docs/reference/configuration.rst +++ b/docs/reference/configuration.rst @@ -302,6 +302,21 @@ application level, *and* platform level, the final set of requirements will be the *concatenation* of requirements from all levels, starting from least to most specific. +``requires_lock`` +~~~~~~~~~~~~~~~~~ + +The name of a lock file. +If the file exists, +its contents will be used instead of the +value of the +``requires`` +field. + +Use +``briefcase update -r --relock`` +to recompute the lock file. + + ``revision`` ~~~~~~~~~~~~ @@ -415,6 +430,21 @@ level, application level, *and* platform level, the final set of requirements will be the *concatenation* of requirements from all levels, starting from least to most specific. + +``test_requires_lock`` +~~~~~~~~~~~~~~~~~~~~~~ + +The name of a lock file. +If the file exists, +its contents will be used instead of the +value of the +``test_requires`` +field. + +Use +``briefcase update -r --test --relock`` +to update the lock file. + ``test_sources`` ~~~~~~~~~~~~~~~~ diff --git a/setup.cfg b/setup.cfg index fb74560df..c4bb66a14 100644 --- a/setup.cfg +++ b/setup.cfg @@ -79,6 +79,7 @@ install_requires = cookiecutter >= 2.1, < 3.0 dmgbuild >= 1.6, < 2.0; sys_platform == "darwin" GitPython >= 3.1, < 4.0 + pip-tools >= 6.13, < 7.0 platformdirs >= 2.6, < 4.0 psutil >= 5.9, < 6.0 requests >= 2.28, < 3.0 diff --git a/src/briefcase/commands/base.py b/src/briefcase/commands/base.py index ea8f8882a..27ed82c10 100644 --- a/src/briefcase/commands/base.py +++ b/src/briefcase/commands/base.py @@ -673,6 +673,12 @@ def _add_update_options( help=f"Update requirements for the app{context_label}", ) + parser.add_argument( + "--relock", + action="store_true", + help=f"Recalculate locked requirements for the app{context_label}", + ) + parser.add_argument( "--update-resources", action="store_true", diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py index 88e671f62..a5ff097c2 100644 --- a/src/briefcase/commands/create.py +++ b/src/briefcase/commands/create.py @@ -4,6 +4,7 @@ import shutil import subprocess import sys +import tempfile from datetime import date from pathlib import Path from typing import List, Optional @@ -487,7 +488,48 @@ def _install_app_requirements( else: self.logger.info("No application requirements.") - def install_app_requirements(self, app: BaseConfig, test_mode: bool): + def _calc_app_requirements(self, app: BaseConfig, test_mode: bool, relock: bool): + requires = app.requires.copy() if app.requires else [] + if test_mode and app.test_requires: + requires.extend(app.test_requires) + + lock_attr = "requires_lock" if not test_mode else "test_requires_lock" + try: + lock_value = getattr(app, lock_attr) + except AttributeError: + return requires + else: + lock_file = self.base_path / lock_value + + if relock: + with tempfile.NamedTemporaryFile(mode="w+") as fpout: + fpout.writelines(line + "\n" for line in requires) + fpout.flush() + res = self.tools[app].app_context.run( + [ + sys.executable, + "-u", + "-m", + "piptools", + "compile", + fpout.name, + "--output-file=-", + ], + text=True, + capture_output=True, + check=True, + ) + lock_file.write_text(res.stdout) + + requires = [ + line.split("#", 1)[0].strip() for line in lock_file.read_text().splitlines() + ] + requires = [potential for potential in requires if potential != ""] + return requires + + def install_app_requirements( + self, app: BaseConfig, test_mode: bool, relock: bool = False + ): """Handle requirements for the app. This will result in either (in preferential order): @@ -504,9 +546,7 @@ def install_app_requirements(self, app: BaseConfig, test_mode: bool): :param app: The config object for the app :param test_mode: Should the test requirements be installed? """ - requires = app.requires.copy() if app.requires else [] - if test_mode and app.test_requires: - requires.extend(app.test_requires) + requires = self._calc_app_requirements(app, test_mode, relock) try: requirements_path = self.app_requirements_path(app) @@ -766,7 +806,7 @@ def create_app(self, app: BaseConfig, test_mode: bool = False, **options): self.install_app_code(app=app, test_mode=test_mode) self.logger.info("Installing requirements...", prefix=app.app_name) - self.install_app_requirements(app=app, test_mode=test_mode) + self.install_app_requirements(app=app, test_mode=test_mode, relock=True) self.logger.info("Installing application resources...", prefix=app.app_name) self.install_app_resources(app=app) diff --git a/src/briefcase/commands/update.py b/src/briefcase/commands/update.py index c8c2da6d8..e2434e726 100644 --- a/src/briefcase/commands/update.py +++ b/src/briefcase/commands/update.py @@ -23,6 +23,7 @@ def update_app( update_requirements: bool, update_resources: bool, test_mode: bool, + relock: bool, **options, ): """Update an existing application bundle. @@ -47,7 +48,7 @@ def update_app( if update_requirements: self.logger.info("Updating requirements...", prefix=app.app_name) - self.install_app_requirements(app=app, test_mode=test_mode) + self.install_app_requirements(app=app, test_mode=test_mode, relock=relock) if update_resources: self.logger.info("Updating application resources...", prefix=app.app_name) diff --git a/tests/commands/build/test_call.py b/tests/commands/build/test_call.py index ee7b4836a..b0df073af 100644 --- a/tests/commands/build/test_call.py +++ b/tests/commands/build/test_call.py @@ -3,6 +3,14 @@ from briefcase.exceptions import BriefcaseCommandError +def _clean_relock(actions): + for cmd, *rest in actions: + if cmd not in {"run", "build", "create", "update"}: + continue + rest[-1].pop("relock", None) + return actions + + def test_specific_app(build_command, first_app, second_app): """If a specific app is requested, build it.""" # Add two apps @@ -18,7 +26,7 @@ def test_specific_app(build_command, first_app, second_app): build_command(first_app, **options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -47,7 +55,7 @@ def test_multiple_apps(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -81,7 +89,7 @@ def test_non_existent(build_command, first_app_config, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -121,7 +129,7 @@ def test_unbuilt(build_command, first_app_unbuilt, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -155,7 +163,7 @@ def test_update_app(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -213,7 +221,7 @@ def test_update_app_requirements(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -271,7 +279,7 @@ def test_update_app_resources(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -329,7 +337,7 @@ def test_update_non_existent(build_command, first_app_config, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -384,7 +392,7 @@ def test_update_unbuilt(build_command, first_app_unbuilt, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -442,7 +450,7 @@ def test_build_test(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -501,7 +509,7 @@ def test_build_test_no_update(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -540,7 +548,7 @@ def test_build_test_update_dependencies(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -599,7 +607,7 @@ def test_build_test_update_resources(build_command, first_app, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -716,7 +724,7 @@ def test_test_app_non_existent(build_command, first_app_config, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -772,7 +780,7 @@ def test_test_app_unbuilt(build_command, first_app_unbuilt, second_app): build_command(**options) # The right sequence of things will be done - assert build_command.actions == [ + assert _clean_relock(build_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified diff --git a/tests/commands/create/conftest.py b/tests/commands/create/conftest.py index 1ae0033fe..103dc8c7b 100644 --- a/tests/commands/create/conftest.py +++ b/tests/commands/create/conftest.py @@ -118,7 +118,7 @@ def generate_app_template(self, app): def install_app_support_package(self, app): self.actions.append(("support", app.app_name)) - def install_app_requirements(self, app, test_mode): + def install_app_requirements(self, app, test_mode, relock=False): self.actions.append(("requirements", app.app_name, test_mode)) def install_app_code(self, app, test_mode): diff --git a/tests/commands/create/test_install_app_requirements.py b/tests/commands/create/test_install_app_requirements.py index 2dcbbee1c..29555b764 100644 --- a/tests/commands/create/test_install_app_requirements.py +++ b/tests/commands/create/test_install_app_requirements.py @@ -146,6 +146,103 @@ def test_app_packages_valid_requires( assert myapp.test_requires is None +def test_app_packages_relock_requires( + create_command, + myapp, + app_packages_path, + app_packages_path_index, +): + """If an app has a valid list of requirements, pip is invoked.""" + myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp.requires_lock = "requirements-test-lock.txt" + create_command.tools[myapp].app_context.run.return_value.stdout = ( + "first==1.1.1\n" "second==1.2.3\n" "third==4.1.1\n" + ) + + create_command.install_app_requirements(myapp, test_mode=False, relock=True) + + # A request was made to install requirements + create_command.tools[myapp].app_context.run.assert_called_with( + [ + sys.executable, + "-u", + "-m", + "pip", + "install", + "--upgrade", + "--no-user", + f"--target={app_packages_path}", + "first==1.1.1", + "second==1.2.3", + "third==4.1.1", + ], + check=True, + env={ + "PYTHONPATH": str( + create_command.bundle_path(myapp) + / "path" + / "to" + / "support" + / "platform-site" + ) + }, + ) + + # Original app definitions haven't changed + assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp.test_requires is None + + +def test_app_packages_prelocked_requires( + create_command, + myapp, + app_packages_path, + app_packages_path_index, +): + """If an app has a valid list of requirements, pip is invoked.""" + myapp.requires = ["first", "second==1.2.3", "third>=3.2.1"] + myapp.requires_lock = "requirements-test-lock.txt" + create_command.tools[myapp].app_context.run.return_value.stdout = ( + "first==1.1.1\n" "second==1.2.3\n" "third==4.1.1\n" + ) + (create_command.base_path / myapp.requires_lock).write_text( + "first==1.1.1\n" "second==1.2.3\n" "third==4.0.0\n" + ) + + create_command.install_app_requirements(myapp, test_mode=False, relock=False) + + # A request was made to install requirements + create_command.tools[myapp].app_context.run.assert_called_with( + [ + sys.executable, + "-u", + "-m", + "pip", + "install", + "--upgrade", + "--no-user", + f"--target={app_packages_path}", + "first==1.1.1", + "second==1.2.3", + "third==4.0.0", + ], + check=True, + env={ + "PYTHONPATH": str( + create_command.bundle_path(myapp) + / "path" + / "to" + / "support" + / "platform-site" + ) + }, + ) + + # Original app definitions haven't changed + assert myapp.requires == ["first", "second==1.2.3", "third>=3.2.1"] + assert myapp.test_requires is None + + def test_app_packages_valid_requires_no_support_package( create_command, myapp, diff --git a/tests/commands/run/test_call.py b/tests/commands/run/test_call.py index 4ea34d817..80054327d 100644 --- a/tests/commands/run/test_call.py +++ b/tests/commands/run/test_call.py @@ -3,6 +3,14 @@ from briefcase.exceptions import BriefcaseCommandError +def _clean_relock(actions): + for cmd, *rest in actions: + if cmd not in {"run", "build"}: + continue + rest[-1].pop("relock", None) + return actions + + def test_no_args_one_app(run_command, first_app): """If there is one app, run starts that app by default.""" # Add a single app @@ -17,7 +25,7 @@ def test_no_args_one_app(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -46,7 +54,7 @@ def test_no_args_one_app_with_passthrough(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -93,7 +101,7 @@ def test_with_arg_one_app(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -122,7 +130,7 @@ def test_with_arg_two_apps(run_command, first_app, second_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -170,7 +178,7 @@ def test_create_app_before_start(run_command, first_app_config): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -215,7 +223,7 @@ def test_build_app_before_start(run_command, first_app_unbuilt): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -259,7 +267,7 @@ def test_update_app(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -303,7 +311,7 @@ def test_update_app_requirements(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -347,7 +355,7 @@ def test_update_app_resources(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -391,7 +399,7 @@ def test_update_unbuilt_app(run_command, first_app_unbuilt): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -436,7 +444,7 @@ def test_update_non_existent(run_command, first_app_config): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -481,7 +489,7 @@ def test_test_mode_existing_app(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -525,7 +533,7 @@ def test_test_mode_existing_app_with_passthrough(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -573,7 +581,7 @@ def test_test_mode_existing_app_no_update(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -606,7 +614,7 @@ def test_test_mode_existing_app_update_requirements(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -650,7 +658,7 @@ def test_test_mode_existing_app_update_resources(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -694,7 +702,7 @@ def test_test_mode_update_existing_app(run_command, first_app): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified @@ -738,7 +746,7 @@ def test_test_mode_non_existent(run_command, first_app_config): run_command(**options) # The right sequence of things will be done - assert run_command.actions == [ + assert _clean_relock(run_command.actions) == [ # Host OS is verified ("verify-host",), # Tools are verified diff --git a/tests/commands/update/conftest.py b/tests/commands/update/conftest.py index 275a30b51..3dbfe9b4a 100644 --- a/tests/commands/update/conftest.py +++ b/tests/commands/update/conftest.py @@ -46,7 +46,7 @@ def verify_app_tools(self, app): # Override all the body methods of a UpdateCommand # with versions that we can use to track actions performed. - def install_app_requirements(self, app, test_mode): + def install_app_requirements(self, app, test_mode, relock=False): self.actions.append(("requirements", app.app_name, test_mode)) create_file(self.bundle_path(app) / "requirements", "app requirements") diff --git a/tests/commands/update/test_update_app.py b/tests/commands/update/test_update_app.py index f96c377ab..3127d1c7c 100644 --- a/tests/commands/update/test_update_app.py +++ b/tests/commands/update/test_update_app.py @@ -5,6 +5,7 @@ def test_update_app(update_command, first_app, tmp_path): update_requirements=False, update_resources=False, test_mode=False, + relock=False, ) # The right sequence of things will be done @@ -39,6 +40,7 @@ def test_update_non_existing_app(update_command, tmp_path): update_requirements=False, update_resources=False, test_mode=False, + relock=False, ) # No app creation actions will be performed @@ -60,6 +62,7 @@ def test_update_app_with_requirements(update_command, first_app, tmp_path): update_requirements=True, update_resources=False, test_mode=False, + relock=False, ) # The right sequence of things will be done @@ -94,6 +97,7 @@ def test_update_app_with_resources(update_command, first_app, tmp_path): update_requirements=False, update_resources=True, test_mode=False, + relock=False, ) # The right sequence of things will be done @@ -127,6 +131,7 @@ def test_update_app_test_mode(update_command, first_app, tmp_path): update_command.update_app( update_command.apps["first"], test_mode=True, + relock=False, update_requirements=False, update_resources=False, ) @@ -161,6 +166,7 @@ def test_update_app_test_mode_requirements(update_command, first_app, tmp_path): update_command.update_app( update_command.apps["first"], test_mode=True, + relock=False, update_requirements=True, update_resources=False, ) @@ -196,6 +202,7 @@ def test_update_app_test_mode_resources(update_command, first_app, tmp_path): update_command.update_app( update_command.apps["first"], test_mode=True, + relock=False, update_requirements=False, update_resources=True, ) diff --git a/tests/platforms/android/gradle/test_run.py b/tests/platforms/android/gradle/test_run.py index d95c1272d..a8b9be5fa 100644 --- a/tests/platforms/android/gradle/test_run.py +++ b/tests/platforms/android/gradle/test_run.py @@ -88,6 +88,7 @@ def test_device_option(run_command): "update_resources": False, "no_update": False, "test_mode": False, + "relock": False, "passthrough": [], "extra_emulator_args": None, "shutdown_on_exit": False, @@ -108,6 +109,7 @@ def test_extra_emulator_args_option(run_command): "update_resources": False, "no_update": False, "test_mode": False, + "relock": False, "passthrough": [], "extra_emulator_args": ["-no-window", "-no-audio"], "shutdown_on_exit": False, @@ -126,6 +128,7 @@ def test_shutdown_on_exit_option(run_command): "update_resources": False, "no_update": False, "test_mode": False, + "relock": False, "passthrough": [], "extra_emulator_args": None, "shutdown_on_exit": True, diff --git a/tests/platforms/iOS/xcode/test_run.py b/tests/platforms/iOS/xcode/test_run.py index bb53ecaf7..d86613ca8 100644 --- a/tests/platforms/iOS/xcode/test_run.py +++ b/tests/platforms/iOS/xcode/test_run.py @@ -44,6 +44,7 @@ def test_device_option(run_command): "update_resources": False, "no_update": False, "test_mode": False, + "relock": False, "passthrough": [], "appname": None, } diff --git a/tests/platforms/web/static/test_run.py b/tests/platforms/web/static/test_run.py index 1730511d3..2f60da429 100644 --- a/tests/platforms/web/static/test_run.py +++ b/tests/platforms/web/static/test_run.py @@ -37,6 +37,7 @@ def test_default_options(run_command): "update_resources": False, "no_update": False, "test_mode": False, + "relock": False, "passthrough": [], "host": "localhost", "port": 8080, @@ -57,6 +58,7 @@ def test_options(run_command): "update_resources": False, "no_update": False, "test_mode": False, + "relock": False, "passthrough": [], "host": "myhost", "port": 1234, diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index da9a263a8..5f9bb3986 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -31,6 +31,11 @@ def console() -> Console: return Console() +def _clean_relock(options): + options.pop("relock", None) + return options + + def do_cmdline_parse(args: list, logger: Log, console: Console): """Simulate process to parse command line.""" Command, extra_cmdline = cmdline.parse_cmdline(args) @@ -104,7 +109,7 @@ def test_new_command(logger, console): assert cmd.logger.verbosity == 1 assert cmd.logger is logger assert cmd.input is console - assert options == {"template": None, "template_branch": None} + assert _clean_relock(options) == {"template": None, "template_branch": None} # Common tests for dev and run commands. @@ -153,7 +158,7 @@ def test_dev_command(monkeypatch, logger, console, cmdline, expected_options): assert cmd.logger.verbosity == 1 assert cmd.logger is logger assert cmd.input is console - assert options == { + assert _clean_relock(options) == { "appname": None, "update_requirements": False, "run_app": True, @@ -187,7 +192,7 @@ def test_run_command(monkeypatch, logger, console, cmdline, expected_options): assert cmd.logger.verbosity == 1 assert cmd.logger is logger assert cmd.input is console - assert options == { + assert _clean_relock(options) == { "appname": None, "update": False, "update_requirements": False, @@ -213,7 +218,7 @@ def test_upgrade_command(monkeypatch, logger, console): assert cmd.logger.verbosity == 1 assert cmd.logger is logger assert cmd.input is console - assert options == { + assert _clean_relock(options) == { "list_tools": False, "tool_list": [], }