From 2f59146da3258989f57a021320059edf7c26c8cb Mon Sep 17 00:00:00 2001 From: Ro Date: Mon, 12 Feb 2024 17:08:01 -0500 Subject: [PATCH] Add dependency on types-pexpect (mypy) --- export/poetry.lock | 25 +++++-- export/pyproject.toml | 1 + export/securedrop_export/disk/cli.py | 104 ++++++++++++++++++--------- export/securedrop_export/main.py | 1 - export/tests/disk/test_cli.py | 17 ++--- export/tests/disk/test_service.py | 4 +- 6 files changed, 98 insertions(+), 54 deletions(-) diff --git a/export/poetry.lock b/export/poetry.lock index be0a8a841f..d8a62416f1 100644 --- a/export/poetry.lock +++ b/export/poetry.lock @@ -925,24 +925,24 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, @@ -950,7 +950,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, @@ -958,7 +958,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, @@ -966,7 +966,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, @@ -1061,6 +1061,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-pexpect" +version = "4.9.0.20240207" +description = "Typing stubs for pexpect" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pexpect-4.9.0.20240207.tar.gz", hash = "sha256:910e20f0f177aeee5f2808d1b3221e3a23dfa1ca3bb02f685c2788fce6ddeb73"}, + {file = "types_pexpect-4.9.0.20240207-py3-none-any.whl", hash = "sha256:22b3fdccf253a8627bac0d3169845743fe0b1dbc87e5d33a438faaf879eb1f7a"}, +] + [[package]] name = "types-setuptools" version = "68.2.0.0" @@ -1116,4 +1127,4 @@ bracex = ">=2.1.1" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "e92c2069a65e238bbacf26305df6214362b5963d662e9caabce44d568389d9ec" +content-hash = "fd72e0cbacca4e86d5d6d2129a76d5c751e68e9ee52f4357f901719cb0dbb58c" diff --git a/export/pyproject.toml b/export/pyproject.toml index 51e0a12253..77daa6ed13 100644 --- a/export/pyproject.toml +++ b/export/pyproject.toml @@ -20,6 +20,7 @@ pytest-cov = "^4.1.0" pytest-mock = "^3.11.1" semgrep = "^1.31.2" safety = "*" +types-pexpect = "^4.9.0.20240207" [tool.mypy] python_version = "3.9" diff --git a/export/securedrop_export/disk/cli.py b/export/securedrop_export/disk/cli.py index 1148e17b6a..ef380cb3b1 100644 --- a/export/securedrop_export/disk/cli.py +++ b/export/securedrop_export/disk/cli.py @@ -2,10 +2,10 @@ import logging import os import pexpect -import re import subprocess import time +from re import Pattern from typing import Optional, Union from securedrop_export.exceptions import ExportException @@ -22,6 +22,26 @@ "--------------------------------------------------------------------------\n" ) +# pexpect allows for a complex type to be passed to `expect` in order to match with input +# that includes regular expressions, byte or string patterns, *or* pexpect.EOF and pexpect.TIMEOUT, +# but mypy needs a little help with it, so the below alias is used as a typehint. +# See https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.expect +PexpectList = Union[ + Pattern[str], + Pattern[bytes], + str, + bytes, + type[pexpect.EOF], + type[pexpect.TIMEOUT], + list[ + Union[ + Pattern[str], + Pattern[bytes], + Union[str, bytes, Union[type[pexpect.EOF], type[pexpect.TIMEOUT]]], + ] + ], +] + class CLI: """ @@ -83,7 +103,9 @@ def get_volume(self) -> Union[Volume, MountedVolume]: logger.error("Unrecoverable: could not parse lsblk.") raise ExportException(sdstatus=Status.DEVICE_ERROR) - volumes = [] + # mypy complains that this is a list[str], but it is a + # list[Union[Volume, MountedVolume]] + volumes = [] # type: ignore for device in lsblk_json.get("blockdevices"): if device.get("name") in targets and device.get("ro") is False: logger.debug( @@ -94,21 +116,21 @@ def get_volume(self) -> Union[Volume, MountedVolume]: if "children" in device: for partition in device.get("children"): # /dev/sdX1, /dev/sdX2 etc - item = self._get_supported_volume(partition) + item = self._get_supported_volume(partition) # type: ignore if item: - volumes.append(item) + volumes.append(item) # type: ignore # /dev/sdX else: - item = self._get_supported_volume(device) + item = self._get_supported_volume(device) # type: ignore if item: - volumes.append(item) + volumes.append(item) # type: ignore if len(volumes) != 1: logger.error(f"Need one target, got {len(volumes)}") raise ExportException(sdstatus=Status.INVALID_DEVICE_DETECTED) else: - logger.debug(f"Export target is {volumes[0].device_name}") - return volumes[0] + logger.debug(f"Export target is {volumes[0].device_name}") # type: ignore + return volumes[0] # type: ignore except json.JSONDecodeError as err: logger.error(err) @@ -232,16 +254,24 @@ def unlock_volume(self, volume: Volume, encryption_key: str) -> MountedVolume: logger.debug("Unlocking volume {}".format(volume.device_name)) command = f"udisksctl unlock --block-device {volume.device_name}" - prompt = ["Passphrase: ", pexpect.EOF, pexpect.TIMEOUT] + + # pexpect allows for a match list that contains pexpect.EOF and pexpect.TIMEOUT + # as well as string/regex matches: + # https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.expect + prompt = [ + "Passphrase: ", + pexpect.EOF, + pexpect.TIMEOUT, + ] # type: PexpectList expected = [ - f"Unlocked {volume.device_name} as (.*)\.", + f"Unlocked {volume.device_name} as (.*).", "GDBus.Error:org.freedesktop.UDisks2.Error.Failed: Device " # string continues - f"{volume.device_name} is already unlocked as (.*)\.", + f"{volume.device_name} is already unlocked as (.*).", "GDBus.Error:org.freedesktop.UDisks2.Error.Failed: Error " # string continues f"unlocking {volume.device_name}: Failed to activate device: Incorrect passphrase", pexpect.EOF, pexpect.TIMEOUT, - ] + ] # type: PexpectList unlock_error = Status.ERROR_UNLOCK_GENERIC child = pexpect.spawn(command) @@ -254,8 +284,10 @@ def unlock_volume(self, volume: Volume, encryption_key: str) -> MountedVolume: child.sendline(encryption_key) index = child.expect(expected) if index == 0 or index == 1: - # We know what format the string is in - dm_name = child.match.group(1).decode("utf-8").strip() + # We know what format the string is in. + # Pexpect includes a re.Match object at `child.match`, but this freaks mypy out: + # see https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.expect + dm_name = child.match.group(1).decode("utf-8").strip() # type: ignore logger.debug(f"Device is unlocked as {dm_name}") child.close() @@ -298,31 +330,35 @@ def _mount_volume(self, volume: Volume, full_unlocked_name: str) -> MountedVolum "*Error looking up object for device*", pexpect.EOF, pexpect.TIMEOUT, - ] + ] # type: PexpectList max_retries = 3 - unlock = f"udisksctl mount --block-device {full_unlocked_name}" + mount = f"udisksctl mount --block-device {full_unlocked_name}" # We can't pass {full_unlocked_name} in the match statement since even if we # pass in /dev/mapper/xxx, udisks2 may refer to the disk as /dev/dm-X. - expected_unlock = [ - f"Mounted * at (.*)", - f"Error mounting *: GDBus.Error:org." # string continues + expected_mount = [ + "Mounted * at (.*)", + "Error mounting *: GDBus.Error:org." # string continues "freedesktop.UDisks2.Error.AlreadyMounted: " # string continues - "Device .* is already mounted at `(.*)'", - f"Error looking up object for device *.", + "Device (.*) is already mounted at `(.*)'", + "Error looking up object for device *.", pexpect.EOF, pexpect.TIMEOUT, - ] + ] # type: PexpectList mountpoint = None - logger.debug(f"Check to make sure udisks identified {volume.device_name} " - "(unlocked as {full_unlocked_name})") + logger.debug( + f"Check to make sure udisks identified {volume.device_name} " + f"(unlocked as {full_unlocked_name})" + ) for _ in range(max_retries): child = pexpect.spawn(info) index = child.expect(expected_info) - logger.debug(f"Results from udisks info: {volume.device_name}, " - "before: {child.before}, after: {child.after}") + logger.debug( + f"Results from udisks info: {volume.device_name}, " + f"before: {child.before}, after: {child.after}" + ) child.close() if index != 0: @@ -336,22 +372,24 @@ def _mount_volume(self, volume: Volume, full_unlocked_name: str) -> MountedVolum break logger.info(f"Mount {full_unlocked_name} using udisksctl") - child = pexpect.spawn(unlock) - index = child.expect(expected_unlock) + child = pexpect.spawn(mount) + index = child.expect(expected_mount) logger.debug( f"child: {str(child.match)}, before: {child.before}, after: {child.after}" ) if index == 0: - # As above, we know the format - mountpoint = child.match.group(1).decode("utf-8").strip() + # As above, we know the format. + # Per https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.expect, + # `child.match` is a re.Match object + mountpoint = child.match.group(1).decode("utf-8").strip() # type: ignore logger.debug(f"Successfully mounted device at {mountpoint}") elif index == 1: - # Mountpoint needs a bit of help. It arrives in the form `/path/to/mountpoint'. - # including the one backtick, single quote, and the period - mountpoint = child.match.group(1).decode("utf-8").strip() + # Use udisks unlocked name + full_unlocked_name = child.match.group(1).decode("utf-8").strip() # type: ignore + mountpoint = child.match.group(2).decode("utf-8").strip() # type: ignore logger.debug(f"Device already mounted at {mountpoint}") elif index == 2: diff --git a/export/securedrop_export/main.py b/export/securedrop_export/main.py index 4535a1dc7f..d04787c3b9 100755 --- a/export/securedrop_export/main.py +++ b/export/securedrop_export/main.py @@ -5,7 +5,6 @@ import platform import logging import sys -from typing import Optional from securedrop_export.archive import Archive, Metadata from securedrop_export.command import Command diff --git a/export/tests/disk/test_cli.py b/export/tests/disk/test_cli.py index 2723d5f368..9b6b3cb774 100644 --- a/export/tests/disk/test_cli.py +++ b/export/tests/disk/test_cli.py @@ -1,9 +1,7 @@ import pytest -from pexpect import ExceptionPexpect from unittest import mock import subprocess -import pexpect import re from securedrop_export.disk.cli import CLI @@ -410,7 +408,6 @@ def test_cleanup_error_reports_exporterror_if_flagged(self, mock_popen): self.cli.cleanup(mock_volume, submission.tmpdir, is_error=True) assert ex.value.sdstatus is Status.ERROR_EXPORT - @mock.patch("os.path.exists", return_value=False) @mock.patch("subprocess.check_call", return_value=0) def test_cleanup(self, mock_subprocess, mocked_path): @@ -444,14 +441,14 @@ def test_cleanup(self, mock_subprocess, mocked_path): def test_parse_correct_mountpoint_from_pexpect(self, mock_pexpect): child = mock_pexpect() child.expect.return_value = 1 - child.match.return_value = re.match( - r"`(\w+)'\.\r\n".encode("utf-8"), - "Error mounting /dev/dm-1: GDBus.Error:org." - "freedesktop.UDisks2.Error.AlreadyMounted: " - "Device /dev/sda1 is already mounted at `/dev/dm-0'.\r\n".encode("utf-8"), - ) + child.match = mock.MagicMock() + child.match.group.side_effect = [ + "/dev/dm-0".encode("utf-8"), + "/media/usb".encode("utf-8"), + ] mv = self.cli._mount_volume( - Volume("/dev/sda1", EncryptionScheme.VERACRYPT), "/dev/dm-1" + Volume("/dev/sda1", EncryptionScheme.VERACRYPT), "/dev/mapper/vc" ) assert mv.unlocked_name == "/dev/dm-0" + assert mv.mountpoint == "/media/usb" diff --git a/export/tests/disk/test_service.py b/export/tests/disk/test_service.py index 73dc0210ac..3b52fe9854 100644 --- a/export/tests/disk/test_service.py +++ b/export/tests/disk/test_service.py @@ -59,9 +59,7 @@ def _setup_submission(cls) -> Archive: temp_folder = tempfile.mkdtemp() metadata = os.path.join(temp_folder, Metadata.METADATA_FILE) with open(metadata, "w") as f: - f.write( - '{"device": "disk", "encryption_key": "hunter1"}' - ) + f.write('{"device": "disk", "encryption_key": "hunter1"}') return submission.set_metadata(Metadata(temp_folder).validate())