From ea2222762585180e5b808844a91a765ce01ca70f Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:13 +0300 Subject: [PATCH 01/10] Add mypy configuration Signed-off-by: Povilas Kanapickas --- pyproject.toml | 18 ++++++++++++++++++ test-requirements.txt | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 282edfb..b46cac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,3 +53,21 @@ quote-style = "preserve" directory = "misc" name = "Misc" showcontent = true + +[tool.mypy] +python_version = "3.9" +namespace_packages = true +explicit_package_bases = true +pretty = true +warn_redundant_casts = true +disallow_untyped_calls = false +disallow_untyped_defs = true +no_implicit_optional = true +mypy_path = "$MYPY_CONFIG_FILE_DIR" +exclude = "build" + +[[tool.mypy.overrides]] +module = [ + "parameterized.*", +] +ignore_missing_imports = true diff --git a/test-requirements.txt b/test-requirements.txt index e53f1d0..73927a0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,8 +4,12 @@ cryptography==44.0.3 parameterized==0.9.0 pytest==8.0.2 tox==4.13.0 +mypy==1.15.0 ruff==0.3.1 pylint==3.1.0 +types-PyYAML==6.0.12.20250402 +types-requests==2.32.0.20250328 +types-setuptools==80.7.0.20250516 # The packages below are transitive dependencies of the packages above and are included here # to make testing reproducible. @@ -24,6 +28,7 @@ filelock==3.13.1 iniconfig==2.0.0 isort==5.13.2 mccabe==0.7.0 +mypy_extensions==1.1.0 packaging==23.2 platformdirs==4.2.0 pluggy==1.4.0 @@ -32,4 +37,5 @@ python-dotenv==1.0.1 PyYAML==6.0.1 requests tomlkit==0.12.4 +typing_extensions==4.13.2 virtualenv==20.26.6 From dedb0815508828297c42e88f195e346951586931 Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:14 +0300 Subject: [PATCH 02/10] tests/unit: Add type annotations Signed-off-by: Povilas Kanapickas --- tests/unit/test_compose_exec_args.py | 8 +- tests/unit/test_compose_run_log_format.py | 45 +++++----- ..._compose_run_update_container_from_args.py | 12 +-- tests/unit/test_container_to_args.py | 84 ++++++++++--------- tests/unit/test_container_to_args_secrets.py | 36 ++++---- tests/unit/test_get_net_args.py | 45 +++++----- tests/unit/test_get_network_create_args.py | 18 ++-- tests/unit/test_is_path_git_url.py | 2 +- tests/unit/test_normalize_service.py | 10 ++- tests/unit/test_rec_subs.py | 3 +- tests/unit/test_volumes.py | 2 +- 11 files changed, 143 insertions(+), 122 deletions(-) diff --git a/tests/unit/test_compose_exec_args.py b/tests/unit/test_compose_exec_args.py index 1092bcd..5f9529a 100644 --- a/tests/unit/test_compose_exec_args.py +++ b/tests/unit/test_compose_exec_args.py @@ -7,7 +7,7 @@ from podman_compose import compose_exec_args class TestComposeExecArgs(unittest.TestCase): - def test_minimal(self): + def test_minimal(self) -> None: cnt = get_minimal_container() args = get_minimal_args() @@ -15,7 +15,7 @@ class TestComposeExecArgs(unittest.TestCase): expected = ["--interactive", "--tty", "container_name"] self.assertEqual(result, expected) - def test_additional_env_value_equals(self): + def test_additional_env_value_equals(self) -> None: cnt = get_minimal_container() args = get_minimal_args() args.env = ["key=valuepart1=valuepart2"] @@ -31,11 +31,11 @@ class TestComposeExecArgs(unittest.TestCase): self.assertEqual(result, expected) -def get_minimal_container(): +def get_minimal_container() -> dict: return {} -def get_minimal_args(): +def get_minimal_args() -> argparse.Namespace: return argparse.Namespace( T=None, cnt_command=None, diff --git a/tests/unit/test_compose_run_log_format.py b/tests/unit/test_compose_run_log_format.py index 64871a7..ee406d9 100644 --- a/tests/unit/test_compose_run_log_format.py +++ b/tests/unit/test_compose_run_log_format.py @@ -3,66 +3,67 @@ import io import unittest +from typing import Union from podman_compose import Podman class DummyReader: - def __init__(self, data=None): + def __init__(self, data: Union[list[bytes], None] = None): self.data = data or [] - async def readuntil(self, _): + async def readuntil(self, _: str) -> bytes: return self.data.pop(0) - def at_eof(self): + def at_eof(self) -> bool: return len(self.data) == 0 class TestComposeRunLogFormat(unittest.IsolatedAsyncioTestCase): - def setUp(self): + def setUp(self) -> None: self.p = get_minimal_podman() self.buffer = io.StringIO() - async def test_single_line_single_chunk(self): + async def test_single_line_single_chunk(self) -> None: reader = DummyReader([b'hello, world\n']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), 'LL: hello, world\n') - async def test_empty(self): + async def test_empty(self) -> None: reader = DummyReader([]) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), '') - async def test_empty2(self): + async def test_empty2(self) -> None: reader = DummyReader([b'']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), '') - async def test_empty_line(self): + async def test_empty_line(self) -> None: reader = DummyReader([b'\n']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), 'LL: \n') - async def test_line_split(self): + async def test_line_split(self) -> None: reader = DummyReader([b'hello,', b' world\n']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), 'LL: hello, world\n') - async def test_two_lines_in_one_chunk(self): + async def test_two_lines_in_one_chunk(self) -> None: reader = DummyReader([b'hello\nbye\n']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), 'LL: hello\nLL: bye\n') - async def test_double_blank(self): + async def test_double_blank(self) -> None: reader = DummyReader([b'hello\n\n\nbye\n']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), 'LL: hello\nLL: \nLL: \nLL: bye\n') - async def test_no_new_line_at_end(self): + async def test_no_new_line_at_end(self) -> None: reader = DummyReader([b'hello\nbye']) - await self.p._format_stream(reader, self.buffer, 'LL:') + await self.p._format_stream(reader, self.buffer, 'LL:') # type: ignore[arg-type] self.assertEqual(self.buffer.getvalue(), 'LL: hello\nLL: bye\n') -def get_minimal_podman(): - return Podman(None) +def get_minimal_podman() -> Podman: + return Podman(None) # type: ignore[arg-type] diff --git a/tests/unit/test_compose_run_update_container_from_args.py b/tests/unit/test_compose_run_update_container_from_args.py index 3bf2a13..3f0b0da 100644 --- a/tests/unit/test_compose_run_update_container_from_args.py +++ b/tests/unit/test_compose_run_update_container_from_args.py @@ -8,7 +8,7 @@ from podman_compose import compose_run_update_container_from_args class TestComposeRunUpdateContainerFromArgs(unittest.TestCase): - def test_minimal(self): + def test_minimal(self) -> None: cnt = get_minimal_container() compose = get_minimal_compose() args = get_minimal_args() @@ -18,7 +18,7 @@ class TestComposeRunUpdateContainerFromArgs(unittest.TestCase): expected_cnt = {"name": "default_name", "tty": True} self.assertEqual(cnt, expected_cnt) - def test_additional_env_value_equals(self): + def test_additional_env_value_equals(self) -> None: cnt = get_minimal_container() compose = get_minimal_compose() args = get_minimal_args() @@ -35,7 +35,7 @@ class TestComposeRunUpdateContainerFromArgs(unittest.TestCase): } self.assertEqual(cnt, expected_cnt) - def test_publish_ports(self): + def test_publish_ports(self) -> None: cnt = get_minimal_container() compose = get_minimal_compose() args = get_minimal_args() @@ -51,15 +51,15 @@ class TestComposeRunUpdateContainerFromArgs(unittest.TestCase): self.assertEqual(cnt, expected_cnt) -def get_minimal_container(): +def get_minimal_container() -> dict: return {} -def get_minimal_compose(): +def get_minimal_compose() -> PodmanCompose: return PodmanCompose() -def get_minimal_args(): +def get_minimal_args() -> argparse.Namespace: return argparse.Namespace( T=None, cnt_command=None, diff --git a/tests/unit/test_container_to_args.py b/tests/unit/test_container_to_args.py index 601c09c..62343ac 100644 --- a/tests/unit/test_container_to_args.py +++ b/tests/unit/test_container_to_args.py @@ -2,14 +2,16 @@ import os import unittest +from typing import Any from unittest import mock from parameterized import parameterized +from podman_compose import PodmanCompose from podman_compose import container_to_args -def create_compose_mock(project_name="test_project_name"): +def create_compose_mock(project_name: str = "test_project_name") -> PodmanCompose: compose = mock.Mock() compose.project_name = project_name compose.dirname = "test_dirname" @@ -19,14 +21,14 @@ def create_compose_mock(project_name="test_project_name"): compose.networks = {} compose.x_podman = {} - async def podman_output(*args, **kwargs): + async def podman_output(*args: Any, **kwargs: Any) -> None: pass compose.podman.output = mock.Mock(side_effect=podman_output) return compose -def get_minimal_container(): +def get_minimal_container() -> dict[str, Any]: return { "name": "project_name_service_name1", "service_name": "service_name", @@ -34,13 +36,13 @@ def get_minimal_container(): } -def get_test_file_path(rel_path): +def get_test_file_path(rel_path: str) -> str: repo_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) return os.path.realpath(os.path.join(repo_root, rel_path)) class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): - async def test_minimal(self): + async def test_minimal(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -56,7 +58,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_runtime(self): + async def test_runtime(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -75,7 +77,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_sysctl_list(self): + async def test_sysctl_list(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -99,7 +101,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_sysctl_map(self): + async def test_sysctl_map(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -123,7 +125,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_sysctl_wrong_type(self): + async def test_sysctl_wrong_type(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -133,7 +135,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): cnt["sysctls"] = wrong_type await container_to_args(c, cnt) - async def test_pid(self): + async def test_pid(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -152,7 +154,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_http_proxy(self): + async def test_http_proxy(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -170,7 +172,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_uidmaps_extension_old_path(self): + async def test_uidmaps_extension_old_path(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -179,7 +181,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): with self.assertRaises(ValueError): await container_to_args(c, cnt) - async def test_uidmaps_extension(self): + async def test_uidmaps_extension(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -200,7 +202,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_gidmaps_extension(self): + async def test_gidmaps_extension(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -221,7 +223,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_rootfs_extension(self): + async def test_rootfs_extension(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -240,7 +242,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_no_hosts_extension(self): + async def test_no_hosts_extension(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -258,7 +260,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_env_file_str(self): + async def test_env_file_str(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -282,7 +284,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_env_file_str_not_exists(self): + async def test_env_file_str_not_exists(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -291,7 +293,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): with self.assertRaises(ValueError): await container_to_args(c, cnt) - async def test_env_file_str_array_one_path(self): + async def test_env_file_str_array_one_path(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -315,7 +317,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_env_file_str_array_two_paths(self): + async def test_env_file_str_array_two_paths(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -344,7 +346,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_env_file_obj_required(self): + async def test_env_file_obj_required(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -368,7 +370,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_env_file_obj_required_non_existent_path(self): + async def test_env_file_obj_required_non_existent_path(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -377,7 +379,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): with self.assertRaises(ValueError): await container_to_args(c, cnt) - async def test_env_file_obj_optional(self): + async def test_env_file_obj_optional(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -394,7 +396,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_gpu_count_all(self): + async def test_gpu_count_all(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -422,7 +424,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_gpu_count_specific(self): + async def test_gpu_count_specific(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -458,7 +460,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_gpu_device_ids_all(self): + async def test_gpu_device_ids_all(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -492,7 +494,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_gpu_device_ids_specific(self): + async def test_gpu_device_ids_specific(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -534,7 +536,9 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): (True, "z", ["-v", "./foo:/mnt:z"]), (True, "Z", ["-v", "./foo:/mnt:Z"]), ]) - async def test_selinux_volume(self, prefer_volume, selinux_type, expected_additional_args): + async def test_selinux_volume( + self, prefer_volume: bool, selinux_type: str, expected_additional_args: list + ) -> None: c = create_compose_mock() c.prefer_volume_over_mount = prefer_volume @@ -572,7 +576,9 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ("compat_no_dash", True, "test_project_name", "test_project_name_network1"), ("compat_dash", True, "test_project-name", "test_projectname_network1"), ]) - async def test_network_default_name(self, name, is_compat, project_name, expected_network_name): + async def test_network_default_name( + self, name: str, is_compat: bool, project_name: str, expected_network_name: str + ) -> None: c = create_compose_mock(project_name) c.x_podman = {"default_net_name_compat": is_compat} c.networks = {'network1': {}} @@ -591,7 +597,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_device(self): + async def test_device(self) -> None: c = create_compose_mock() cnt = get_minimal_container() @@ -613,7 +619,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_cpuset(self): + async def test_cpuset(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["cpuset"] = "0-1" @@ -631,7 +637,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_pids_limit_container_level(self): + async def test_pids_limit_container_level(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["pids_limit"] = 100 @@ -649,7 +655,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_pids_limit_deploy_section(self): + async def test_pids_limit_deploy_section(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["deploy"] = {"resources": {"limits": {"pids": 100}}} @@ -667,7 +673,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_pids_limit_both_same(self): + async def test_pids_limit_both_same(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["pids_limit"] = 100 @@ -686,7 +692,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_pids_limit_both_different(self): + async def test_pids_limit_both_different(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["pids_limit"] = 100 @@ -695,7 +701,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): with self.assertRaises(ValueError): await container_to_args(c, cnt) - async def test_heathcheck_string(self): + async def test_healthcheck_string(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["healthcheck"] = { @@ -715,7 +721,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_heathcheck_cmd_args(self): + async def test_healthcheck_cmd_args(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["healthcheck"] = { @@ -735,7 +741,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_heathcheck_cmd_shell(self): + async def test_healthcheck_cmd_shell(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["healthcheck"] = { @@ -755,7 +761,7 @@ class TestContainerToArgs(unittest.IsolatedAsyncioTestCase): ], ) - async def test_heathcheck_cmd_shell_error(self): + async def test_healthcheck_cmd_shell_error(self) -> None: c = create_compose_mock() cnt = get_minimal_container() cnt["healthcheck"] = { diff --git a/tests/unit/test_container_to_args_secrets.py b/tests/unit/test_container_to_args_secrets.py index 084839b..4343bc6 100644 --- a/tests/unit/test_container_to_args_secrets.py +++ b/tests/unit/test_container_to_args_secrets.py @@ -10,12 +10,12 @@ from tests.unit.test_container_to_args import create_compose_mock from tests.unit.test_container_to_args import get_minimal_container -def repo_root(): +def repo_root() -> str: return os.path.dirname(os.path.dirname(os.path.dirname(__file__))) class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): - async def test_pass_secret_as_env_variable(self): + async def test_pass_secret_as_env_variable(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret": {"external": "true"} # must have external or name value @@ -43,7 +43,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ], ) - async def test_secret_as_env_external_true_has_no_name(self): + async def test_secret_as_env_external_true_has_no_name(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret": { @@ -74,7 +74,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ], ) - async def test_pass_secret_as_env_variable_no_external(self): + async def test_pass_secret_as_env_variable_no_external(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret": {} # must have external or name value @@ -124,7 +124,9 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): }, ), ]) - async def test_secret_name(self, test_name, declared_secrets, add_to_minimal_container): + async def test_secret_name( + self, test_name: str, declared_secrets: dict, add_to_minimal_container: dict + ) -> None: c = create_compose_mock() c.declared_secrets = declared_secrets @@ -136,7 +138,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): await container_to_args(c, cnt) self.assertIn('ERROR: undeclared secret: ', str(context.exception)) - async def test_secret_string_no_external_name_in_declared_secrets(self): + async def test_secret_string_no_external_name_in_declared_secrets(self) -> None: c = create_compose_mock() c.declared_secrets = {"my_secret_name": {"external": "true"}} cnt = get_minimal_container() @@ -157,7 +159,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ], ) - async def test_secret_string_options_external_name_in_declared_secrets(self): + async def test_secret_string_options_external_name_in_declared_secrets(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret_name": { @@ -195,7 +197,9 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ], ) - async def test_secret_string_external_name_in_declared_secrets_does_not_match_secret(self): + async def test_secret_string_external_name_in_declared_secrets_does_not_match_secret( + self, + ) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret_name": { @@ -213,7 +217,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): await container_to_args(c, cnt) self.assertIn('ERROR: Custom name/target reference ', str(context.exception)) - async def test_secret_target_does_not_match_secret_name_secret_type_not_env(self): + async def test_secret_target_does_not_match_secret_name_secret_type_not_env(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret_name": { @@ -234,7 +238,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): await container_to_args(c, cnt) self.assertIn('ERROR: Custom name/target reference ', str(context.exception)) - async def test_secret_target_does_not_match_secret_name_secret_type_env(self): + async def test_secret_target_does_not_match_secret_name_secret_type_env(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret_name": { @@ -260,7 +264,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ], ) - async def test_secret_target_matches_secret_name_secret_type_not_env(self): + async def test_secret_target_matches_secret_name_secret_type_not_env(self) -> None: c = create_compose_mock() c.declared_secrets = { "my_secret_name": { @@ -342,8 +346,12 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ), ]) async def test_file_secret( - self, test_name, declared_secrets, add_to_minimal_container, expected_volume_ref - ): + self, + test_name: str, + declared_secrets: dict, + add_to_minimal_container: dict, + expected_volume_ref: str, + ) -> None: c = create_compose_mock() c.declared_secrets = declared_secrets cnt = get_minimal_container() @@ -362,7 +370,7 @@ class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase): ], ) - async def test_file_secret_unused_params_warning(self): + async def test_file_secret_unused_params_warning(self) -> None: c = create_compose_mock() c.declared_secrets = { "file_secret": { diff --git a/tests/unit/test_get_net_args.py b/tests/unit/test_get_net_args.py index 015e60d..2bf881d 100644 --- a/tests/unit/test_get_net_args.py +++ b/tests/unit/test_get_net_args.py @@ -2,6 +2,7 @@ import unittest from parameterized import parameterized +from podman_compose import PodmanCompose from podman_compose import get_net_args from tests.unit.test_container_to_args import create_compose_mock @@ -10,7 +11,7 @@ SERVICE_NAME = "service_name" CONTAINER_NAME = f"{PROJECT_NAME}_{SERVICE_NAME}_1" -def get_networked_compose(num_networks=1): +def get_networked_compose(num_networks: int = 1) -> PodmanCompose: compose = create_compose_mock(PROJECT_NAME) for network in range(num_networks): compose.networks[f"net{network}"] = { @@ -30,7 +31,7 @@ def get_networked_compose(num_networks=1): return compose -def get_minimal_container(): +def get_minimal_container() -> dict: return { "name": CONTAINER_NAME, "service_name": SERVICE_NAME, @@ -39,7 +40,7 @@ def get_minimal_container(): class TestGetNetArgs(unittest.TestCase): - def test_minimal(self): + def test_minimal(self) -> None: compose = get_networked_compose() container = get_minimal_container() @@ -49,7 +50,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_default_net_is_None(self): + def test_default_net_is_None(self) -> None: compose = get_networked_compose() container = get_minimal_container() @@ -64,7 +65,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_one_net(self): + def test_one_net(self) -> None: compose = get_networked_compose() container = get_minimal_container() container["networks"] = {"net0": {}} @@ -75,7 +76,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_alias(self): + def test_alias(self) -> None: compose = get_networked_compose() container = get_minimal_container() container["networks"] = {"net0": {}} @@ -87,7 +88,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_aliases_on_network_scope(self): + def test_aliases_on_network_scope(self) -> None: compose = get_networked_compose() container = get_minimal_container() container["networks"] = {"net0": {"aliases": ["alias1"]}} @@ -98,7 +99,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_one_ipv4(self): + def test_one_ipv4(self) -> None: ip = "192.168.0.42" compose = get_networked_compose() container = get_minimal_container() @@ -110,7 +111,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertEqual(expected_args, args) - def test_one_ipv6(self): + def test_one_ipv6(self) -> None: ipv6_address = "fd00:0::42" compose = get_networked_compose() container = get_minimal_container() @@ -122,7 +123,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_one_mac(self): + def test_one_mac(self) -> None: mac = "00:11:22:33:44:55" compose = get_networked_compose() container = get_minimal_container() @@ -135,7 +136,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_one_mac_two_nets(self): + def test_one_mac_two_nets(self) -> None: mac = "00:11:22:33:44:55" compose = get_networked_compose(num_networks=6) container = get_minimal_container() @@ -153,7 +154,7 @@ class TestGetNetArgs(unittest.TestCase): "mac_address", "x-podman.mac_address", ]) - def test_mac_on_network(self, mac_attr): + def test_mac_on_network(self, mac_attr: str) -> None: mac = "00:11:22:33:44:55" compose = get_networked_compose() container = get_minimal_container() @@ -165,7 +166,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_two_nets_as_dict(self): + def test_two_nets_as_dict(self) -> None: compose = get_networked_compose(num_networks=2) container = get_minimal_container() container["networks"] = {"net0": {}, "net1": {}} @@ -177,7 +178,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_two_nets_as_list(self): + def test_two_nets_as_list(self) -> None: compose = get_networked_compose(num_networks=2) container = get_minimal_container() container["networks"] = ["net0", "net1"] @@ -189,7 +190,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_two_ipv4(self): + def test_two_ipv4(self) -> None: ip0 = "192.168.0.42" ip1 = "192.168.1.42" compose = get_networked_compose(num_networks=2) @@ -203,7 +204,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_two_ipv6(self): + def test_two_ipv6(self) -> None: ip0 = "fd00:0::42" ip1 = "fd00:1::42" compose = get_networked_compose(num_networks=2) @@ -218,7 +219,7 @@ class TestGetNetArgs(unittest.TestCase): self.assertListEqual(expected_args, args) # custom extension; not supported by docker-compose - def test_two_mac(self): + def test_two_mac(self) -> None: mac0 = "00:00:00:00:00:01" mac1 = "00:00:00:00:00:02" compose = get_networked_compose(num_networks=2) @@ -235,7 +236,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_mixed_mac(self): + def test_mixed_mac(self) -> None: ip4_0 = "192.168.0.42" ip4_1 = "192.168.1.42" ip4_2 = "192.168.2.42" @@ -256,7 +257,7 @@ class TestGetNetArgs(unittest.TestCase): ) self.assertRaisesRegex(RuntimeError, expected_exception, get_net_args, compose, container) - def test_mixed_config(self): + def test_mixed_config(self) -> None: ip4_0 = "192.168.0.42" ip4_1 = "192.168.1.42" ip6_0 = "fd00:0::42" @@ -297,7 +298,7 @@ class TestGetNetArgs(unittest.TestCase): ("ns:my_namespace", ["--network=ns:my_namespace"]), ("container:my_container", ["--network=container:my_container"]), ]) - def test_network_modes(self, network_mode, expected_args): + def test_network_modes(self, network_mode: str, expected_args: list) -> None: compose = get_networked_compose() container = get_minimal_container() container["network_mode"] = network_mode @@ -309,7 +310,7 @@ class TestGetNetArgs(unittest.TestCase): args = get_net_args(compose, container) self.assertListEqual(expected_args, args) - def test_network_mode_invalid(self): + def test_network_mode_invalid(self) -> None: compose = get_networked_compose() container = get_minimal_container() container["network_mode"] = "invalid_mode" @@ -317,7 +318,7 @@ class TestGetNetArgs(unittest.TestCase): with self.assertRaises(SystemExit): get_net_args(compose, container) - def test_network__mode_service(self): + def test_network__mode_service(self) -> None: compose = get_networked_compose() compose.container_names_by_service = { "service_1": ["container_1"], diff --git a/tests/unit/test_get_network_create_args.py b/tests/unit/test_get_network_create_args.py index 8951138..b06f4fc 100644 --- a/tests/unit/test_get_network_create_args.py +++ b/tests/unit/test_get_network_create_args.py @@ -4,7 +4,7 @@ from podman_compose import get_network_create_args class TestGetNetworkCreateArgs(unittest.TestCase): - def test_minimal(self): + def test_minimal(self) -> None: net_desc = { "labels": [], "internal": False, @@ -26,7 +26,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_ipv6(self): + def test_ipv6(self) -> None: net_desc = { "labels": [], "internal": False, @@ -49,7 +49,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_bridge(self): + def test_bridge(self) -> None: net_desc = { "labels": [], "internal": False, @@ -77,7 +77,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_ipam_driver_default(self): + def test_ipam_driver_default(self) -> None: net_desc = { "labels": [], "internal": False, @@ -113,7 +113,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_ipam_driver(self): + def test_ipam_driver(self) -> None: net_desc = { "labels": [], "internal": False, @@ -151,7 +151,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_complete(self): + def test_complete(self) -> None: net_desc = { "labels": ["label1", "label2"], "internal": True, @@ -202,7 +202,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_disable_dns(self): + def test_disable_dns(self) -> None: net_desc = { "labels": [], "internal": False, @@ -226,7 +226,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_dns_string(self): + def test_dns_string(self) -> None: net_desc = { "labels": [], "internal": False, @@ -251,7 +251,7 @@ class TestGetNetworkCreateArgs(unittest.TestCase): args = get_network_create_args(net_desc, proj_name, net_name) self.assertEqual(args, expected_args) - def test_dns_list(self): + def test_dns_list(self) -> None: net_desc = { "labels": [], "internal": False, diff --git a/tests/unit/test_is_path_git_url.py b/tests/unit/test_is_path_git_url.py index 831a7a7..80df447 100644 --- a/tests/unit/test_is_path_git_url.py +++ b/tests/unit/test_is_path_git_url.py @@ -18,5 +18,5 @@ class TestIsPathGitUrl(unittest.TestCase): ("suffix_and_prefix", "git://host.xz/path/to/repo.git", True), ("empty_url_path", "http://#fragment", False), ]) - def test_is_path_git_url(self, test_name, path, result): + def test_is_path_git_url(self, test_name: str, path: str, result: bool) -> None: self.assertEqual(is_path_git_url(path), result) diff --git a/tests/unit/test_normalize_service.py b/tests/unit/test_normalize_service.py index 925af4e..b58d606 100644 --- a/tests/unit/test_normalize_service.py +++ b/tests/unit/test_normalize_service.py @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 import unittest +from typing import Any +from typing import Union from parameterized import parameterized @@ -29,7 +31,7 @@ class TestNormalizeService(unittest.TestCase): {"build": {"additional_contexts": ["ctx=../ctx", "ctx2=../ctx2"]}}, ), ]) - def test_simple(self, input, expected): + def test_simple(self, input: dict[str, Any], expected: dict[str, Any]) -> None: self.assertEqual(normalize_service(input), expected) @parameterized.expand([ @@ -46,7 +48,9 @@ class TestNormalizeService(unittest.TestCase): {"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}}, ), ]) - def test_normalize_service_with_sub_dir(self, input, expected): + def test_normalize_service_with_sub_dir( + self, input: dict[str, Any], expected: dict[str, Any] + ) -> None: self.assertEqual(normalize_service(input, sub_dir="./sub_dir"), expected) @parameterized.expand([ @@ -60,7 +64,7 @@ class TestNormalizeService(unittest.TestCase): ["bash", "-c", "sleep infinity"], ), ]) - def test_command_like(self, input, expected): + def test_command_like(self, input: Union[list[str], str], expected: list[str]) -> None: for key in ['command', 'entrypoint']: input_service = {} input_service[key] = input diff --git a/tests/unit/test_rec_subs.py b/tests/unit/test_rec_subs.py index 8d0401a..82a358d 100644 --- a/tests/unit/test_rec_subs.py +++ b/tests/unit/test_rec_subs.py @@ -2,6 +2,7 @@ # pylint: disable=protected-access import unittest +from typing import Any from parameterized import parameterized @@ -66,7 +67,7 @@ class TestRecSubs(unittest.TestCase): ] @parameterized.expand(substitutions) - def test_rec_subs(self, desc, input, expected): + def test_rec_subs(self, desc: str, input: Any, expected: Any) -> None: sub_dict = {"v1": "high priority", "empty": ""} result = rec_subs(input, sub_dict) self.assertEqual(result, expected, msg=desc) diff --git a/tests/unit/test_volumes.py b/tests/unit/test_volumes.py index 4c6a366..26fe509 100644 --- a/tests/unit/test_volumes.py +++ b/tests/unit/test_volumes.py @@ -6,7 +6,7 @@ from podman_compose import parse_short_mount class ParseShortMountTests(unittest.TestCase): - def test_multi_propagation(self): + def test_multi_propagation(self) -> None: self.assertEqual( parse_short_mount("/foo/bar:/baz:U,Z", "/"), { From a3f48f830d700b9c044f10bee2fea393d78e17ce Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:15 +0300 Subject: [PATCH 03/10] tests/integration: Add type annotations Signed-off-by: Povilas Kanapickas --- tests/integration/__init__.py | 2 +- .../abort/test_podman_compose_abort.py | 4 +-- ...test_podman_compose_additional_contexts.py | 4 +-- ...est_podman_compose_default_net_behavior.py | 8 ++--- .../deps/test_podman_compose_deps.py | 20 +++++------ .../test_podman_compose_env_file.py | 16 ++++----- .../env_tests/test_podman_compose_env.py | 8 ++--- .../test_podman_compose_exit_from.py | 8 ++--- .../extends/test_podman_compose_extends.py | 8 ++--- ..._podman_compose_extends_w_empty_service.py | 6 ++-- .../test_podman_compose_extends_w_file.py | 4 +-- ...st_podman_compose_extends_w_file_subdir.py | 6 ++-- .../test_podman_compose_filesystem.py | 2 +- .../in_pod/test_podman_compose_in_pod.py | 36 +++++++++---------- .../include/test_podman_compose_include.py | 2 +- .../test_podman_compose_interpolation.py | 4 +-- .../test_podman_compose_ipam_default.py | 4 +-- tests/integration/lifetime/test_lifetime.py | 4 +-- ...t_podman_compose_override_tag_attribute.py | 4 +-- ...est_podman_compose_override_tag_service.py | 4 +-- ...test_podman_compose_reset_tag_attribute.py | 4 +-- .../test_podman_compose_reset_tag_service.py | 4 +-- .../test_podman_compose_volumes_merge.py | 4 +-- .../test_podman_compose_multicompose.py | 4 +-- .../nethost/test_podman_compose_nethost.py | 8 ++--- .../test_podman_compose_nets_test1.py | 4 +-- .../test_podman_compose_nets_test2.py | 4 +-- .../test_podman_compose_nets_test3.py | 10 ++++-- .../test_podman_compose_nets_test_ip.py | 4 +-- .../network/test_podman_compose_network.py | 9 ++--- ...t_podman_compose_network_interface_name.py | 8 ++--- ...t_podman_compose_network_scoped_aliases.py | 12 +++---- .../test_podman_compose_no_services.py | 4 +-- .../pod_args/test_podman_compose_pod_args.py | 28 +++++++-------- .../ports/test_podman_compose_ports.py | 2 +- .../profile/test_podman_compose_config.py | 8 ++--- .../profile/test_podman_compose_up_down.py | 7 ++-- .../seccomp/test_podman_compose_seccomp.py | 4 +-- .../secrets/test_podman_compose_secrets.py | 8 ++--- .../selinux/test_podman_compose_selinux.py | 2 +- .../test_podman_compose_scale.py | 8 ++--- tests/integration/test_utils.py | 16 +++++---- .../uidmaps/test_podman_compose_uidmaps.py | 2 +- .../ulimit/test_podman_compose_ulimit.py | 2 +- .../up_down/test_podman_compose_up_down.py | 20 +++++------ .../vol/test_podman_compose_vol.py | 2 +- 46 files changed, 175 insertions(+), 167 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index dbd5d2f..ea2070b 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -2,7 +2,7 @@ import os import subprocess -def create_base_test_image(): +def create_base_test_image() -> None: subprocess.check_call( ['podman', 'build', '-t', 'nopush/podman-compose-test', '.'], cwd=os.path.join(os.path.dirname(__file__), "base_image"), diff --git a/tests/integration/abort/test_podman_compose_abort.py b/tests/integration/abort/test_podman_compose_abort.py index 905b339..d0659a5 100644 --- a/tests/integration/abort/test_podman_compose_abort.py +++ b/tests/integration/abort/test_podman_compose_abort.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(failure_order): +def compose_yaml_path(failure_order: str) -> str: return os.path.join(test_path(), "abort", f"docker-compose-fail-{failure_order}.yaml") @@ -25,7 +25,7 @@ class TestComposeAbort(unittest.TestCase, RunSubprocessMixin): ("exit", "none", 0), ("failure", "none", 0), ]) - def test_abort(self, abort_type, failure_order, expected_exit_code): + def test_abort(self, abort_type: str, failure_order: str, expected_exit_code: int) -> None: try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/additional_contexts/test_podman_compose_additional_contexts.py b/tests/integration/additional_contexts/test_podman_compose_additional_contexts.py index 74600d6..1e96c24 100644 --- a/tests/integration/additional_contexts/test_podman_compose_additional_contexts.py +++ b/tests/integration/additional_contexts/test_podman_compose_additional_contexts.py @@ -11,13 +11,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: """ "Returns the path to the compose file used for this test module""" return os.path.join(test_path(), "additional_contexts", "project") class TestComposeBuildAdditionalContexts(unittest.TestCase): - def test_build_additional_context(self): + def test_build_additional_context(self) -> None: """podman build should receive additional contexts as --build-context See additional_context/project/docker-compose.yaml for context paths diff --git a/tests/integration/default_net_behavior/test_podman_compose_default_net_behavior.py b/tests/integration/default_net_behavior/test_podman_compose_default_net_behavior.py index 680f874..33909f4 100644 --- a/tests/integration/default_net_behavior/test_podman_compose_default_net_behavior.py +++ b/tests/integration/default_net_behavior/test_podman_compose_default_net_behavior.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(scenario): +def compose_yaml_path(scenario: str) -> str: return os.path.join( os.path.join(test_path(), "default_net_behavior"), f"docker-compose_{scenario}.yaml" ) @@ -27,13 +27,13 @@ class TestComposeDefaultNetBehavior(unittest.TestCase, RunSubprocessMixin): ('two_nets_compat', 'default_net_behavior_default'), ('with_default_compat', 'default_net_behavior_default'), ]) - def test_nethost(self, scenario, default_net): + def test_nethost(self, scenario: str, default_net: str) -> None: try: self.run_subprocess_assert_returncode( [podman_compose_path(), "-f", compose_yaml_path(scenario), "up", "-d"], ) - container_id, _ = self.run_subprocess_assert_returncode( + container_id_out, _ = self.run_subprocess_assert_returncode( [ podman_compose_path(), "-f", @@ -43,7 +43,7 @@ class TestComposeDefaultNetBehavior(unittest.TestCase, RunSubprocessMixin): '{{.ID}}', ], ) - container_id = container_id.decode('utf-8').split('\n')[0] + container_id = container_id_out.decode('utf-8').split('\n')[0] output, _ = self.run_subprocess_assert_returncode( [ "podman", diff --git a/tests/integration/deps/test_podman_compose_deps.py b/tests/integration/deps/test_podman_compose_deps.py index 9a4bfa5..6ea11dc 100644 --- a/tests/integration/deps/test_podman_compose_deps.py +++ b/tests/integration/deps/test_podman_compose_deps.py @@ -9,12 +9,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(suffix=""): +def compose_yaml_path(suffix: str = "") -> str: return os.path.join(os.path.join(test_path(), "deps"), f"docker-compose{suffix}.yaml") class TestComposeBaseDeps(unittest.TestCase, RunSubprocessMixin): - def test_deps(self): + def test_deps(self) -> None: try: output, _ = self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -37,7 +37,7 @@ class TestComposeBaseDeps(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_run_nodeps(self): + def test_run_nodeps(self) -> None: try: output, _ = self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -62,7 +62,7 @@ class TestComposeBaseDeps(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_up_nodeps(self): + def test_up_nodeps(self) -> None: try: self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -89,7 +89,7 @@ class TestComposeBaseDeps(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_podman_compose_run(self): + def test_podman_compose_run(self) -> None: """ This will test depends_on as well """ @@ -143,7 +143,7 @@ class TestComposeBaseDeps(unittest.TestCase, RunSubprocessMixin): class TestComposeConditionalDeps(unittest.TestCase, RunSubprocessMixin): - def test_deps_succeeds(self): + def test_deps_succeeds(self) -> None: suffix = "-conditional-succeeds" try: output, _ = self.run_subprocess_assert_returncode([ @@ -167,7 +167,7 @@ class TestComposeConditionalDeps(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_deps_fails(self): + def test_deps_fails(self) -> None: suffix = "-conditional-fails" try: output, _ = self.run_subprocess_assert_returncode([ @@ -188,10 +188,10 @@ class TestComposeConditionalDeps(unittest.TestCase, RunSubprocessMixin): class TestComposeConditionalDepsHealthy(unittest.TestCase, PodmanAwareRunSubprocessMixin): - def setUp(self): + def setUp(self) -> None: self.podman_version = self.retrieve_podman_version() - def test_up_deps_healthy(self): + def test_up_deps_healthy(self) -> None: suffix = "-conditional-healthy" try: self.run_subprocess_assert_returncode([ @@ -261,6 +261,6 @@ class TestComposeConditionalDepsHealthy(unittest.TestCase, PodmanAwareRunSubproc self.run_subprocess_assert_returncode([ podman_compose_path(), "-f", - compose_yaml_path(), + compose_yaml_path(suffix), "down", ]) diff --git a/tests/integration/env_file_tests/test_podman_compose_env_file.py b/tests/integration/env_file_tests/test_podman_compose_env_file.py index e3ab83b..73607f3 100644 --- a/tests/integration/env_file_tests/test_podman_compose_env_file.py +++ b/tests/integration/env_file_tests/test_podman_compose_env_file.py @@ -8,12 +8,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_base_path(): +def compose_base_path() -> str: return os.path.join(test_path(), "env_file_tests") class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): - def test_path_env_file_inline(self): + def test_path_env_file_inline(self) -> None: # Test taking env variable value directly from env-file when its path is inline path base_path = compose_base_path() path_compose_file = os.path.join(base_path, "project/container-compose.yaml") @@ -42,7 +42,7 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_path_env_file_flat_in_compose_file(self): + def test_path_env_file_flat_in_compose_file(self) -> None: # Test taking env variable value from env-file/project-1.env which was declared in # compose file's env_file base_path = compose_base_path() @@ -74,7 +74,7 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_path_env_file_obj_in_compose_file(self): + def test_path_env_file_obj_in_compose_file(self) -> None: # take variable value from env-file project-1.env which was declared in compose # file's env_file by -path: ... base_path = compose_base_path() @@ -106,7 +106,7 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_exists_optional_env_file_path_in_compose_file(self): + def test_exists_optional_env_file_path_in_compose_file(self) -> None: # test taking env variable values from several env-files when one of them is optional # and exists base_path = compose_base_path() @@ -139,7 +139,7 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_missing_optional_env_file_path_in_compose_file(self): + def test_missing_optional_env_file_path_in_compose_file(self) -> None: # test taking env variable values from several env-files when one of them is optional and # is missing (silently skip it) base_path = compose_base_path() @@ -173,7 +173,7 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_var_value_inline_overrides_env_file_path_inline(self): + def test_var_value_inline_overrides_env_file_path_inline(self) -> None: # Test overriding env value when value is declared in inline command base_path = compose_base_path() path_compose_file = os.path.join(base_path, "project/container-compose.yaml") @@ -204,7 +204,7 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_taking_env_variables_from_env_files_from_different_directories(self): + def test_taking_env_variables_from_env_files_from_different_directories(self) -> None: # FIXME: It is not clear what this test actually tests, but from README.md it looks like: # Test overriding env values by directory env-files-tests/.env file values # and only take value from project/.env, when it does not exist in env-files-tests/.env diff --git a/tests/integration/env_tests/test_podman_compose_env.py b/tests/integration/env_tests/test_podman_compose_env.py index fc2020b..49af508 100644 --- a/tests/integration/env_tests/test_podman_compose_env.py +++ b/tests/integration/env_tests/test_podman_compose_env.py @@ -8,14 +8,14 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "env_tests"), "container-compose.yml") class TestComposeEnv(unittest.TestCase, RunSubprocessMixin): """Test that inline environment variable overrides environment variable from compose file.""" - def test_env(self): + def test_env(self) -> None: try: output, _ = self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -50,7 +50,7 @@ class TestComposeEnv(unittest.TestCase, RunSubprocessMixin): - https://github.com/compose-spec/compose-spec/blob/main/04-version-and-name.md """ - def test_project_name(self): + def test_project_name(self) -> None: try: output, _ = self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -68,7 +68,7 @@ class TestComposeEnv(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_project_name_override(self): + def test_project_name_override(self) -> None: try: output, _ = self.run_subprocess_assert_returncode([ podman_compose_path(), diff --git a/tests/integration/exit_from/test_podman_compose_exit_from.py b/tests/integration/exit_from/test_podman_compose_exit_from.py index c0b2d3e..326c081 100644 --- a/tests/integration/exit_from/test_podman_compose_exit_from.py +++ b/tests/integration/exit_from/test_podman_compose_exit_from.py @@ -8,12 +8,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "exit_from"), "docker-compose.yaml") class TestComposeExitFrom(unittest.TestCase, RunSubprocessMixin): - def test_exit_code_sh1(self): + def test_exit_code_sh1(self) -> None: try: self.run_subprocess_assert_returncode( [ @@ -33,7 +33,7 @@ class TestComposeExitFrom(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_exit_code_sh2(self): + def test_exit_code_sh2(self) -> None: try: self.run_subprocess_assert_returncode( [ @@ -53,7 +53,7 @@ class TestComposeExitFrom(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_podman_compose_exit_from(self): + def test_podman_compose_exit_from(self) -> None: up_cmd = [ "coverage", "run", diff --git a/tests/integration/extends/test_podman_compose_extends.py b/tests/integration/extends/test_podman_compose_extends.py index f4002d8..88d7ccc 100644 --- a/tests/integration/extends/test_podman_compose_extends.py +++ b/tests/integration/extends/test_podman_compose_extends.py @@ -8,12 +8,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "extends"), "docker-compose.yaml") class TestComposeExteds(unittest.TestCase, RunSubprocessMixin): - def test_extends_service_launch_echo(self): + def test_extends_service_launch_echo(self) -> None: try: self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -38,7 +38,7 @@ class TestComposeExteds(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_extends_service_launch_echo1(self): + def test_extends_service_launch_echo1(self) -> None: try: self.run_subprocess_assert_returncode([ podman_compose_path(), @@ -63,7 +63,7 @@ class TestComposeExteds(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_extends_service_launch_env1(self): + def test_extends_service_launch_env1(self) -> None: try: self.run_subprocess_assert_returncode([ podman_compose_path(), diff --git a/tests/integration/extends_w_empty_service/test_podman_compose_extends_w_empty_service.py b/tests/integration/extends_w_empty_service/test_podman_compose_extends_w_empty_service.py index 8be90b1..4729133 100644 --- a/tests/integration/extends_w_empty_service/test_podman_compose_extends_w_empty_service.py +++ b/tests/integration/extends_w_empty_service/test_podman_compose_extends_w_empty_service.py @@ -9,12 +9,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "extends_w_empty_service"), "docker-compose.yml") class TestComposeExtendsWithEmptyService(unittest.TestCase, RunSubprocessMixin): - def test_extends_w_empty_service(self): + def test_extends_w_empty_service(self) -> None: try: self.run_subprocess_assert_returncode( [ @@ -39,7 +39,7 @@ class TestComposeExtendsWithEmptyService(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_podman_compose_extends_w_empty_service(self): + def test_podman_compose_extends_w_empty_service(self) -> None: """ Test that podman-compose can execute podman-compose -f up with extended File which includes an empty service. (e.g. if the file is used as placeholder for more complex diff --git a/tests/integration/extends_w_file/test_podman_compose_extends_w_file.py b/tests/integration/extends_w_file/test_podman_compose_extends_w_file.py index 406617c..bd72703 100644 --- a/tests/integration/extends_w_file/test_podman_compose_extends_w_file.py +++ b/tests/integration/extends_w_file/test_podman_compose_extends_w_file.py @@ -8,12 +8,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "extends_w_file"), "docker-compose.yml") class TestComposeExtendsWithFile(unittest.TestCase, RunSubprocessMixin): - def test_extends_w_file(self): # when file is Dockerfile for building the image + def test_extends_w_file(self) -> None: # when file is Dockerfile for building the image try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/extends_w_file_subdir/test_podman_compose_extends_w_file_subdir.py b/tests/integration/extends_w_file_subdir/test_podman_compose_extends_w_file_subdir.py index a188d06..fa388e8 100644 --- a/tests/integration/extends_w_file_subdir/test_podman_compose_extends_w_file_subdir.py +++ b/tests/integration/extends_w_file_subdir/test_podman_compose_extends_w_file_subdir.py @@ -9,12 +9,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "extends_w_file_subdir"), "docker-compose.yml") class TestComposeExtendsWithFileSubdir(unittest.TestCase, RunSubprocessMixin): - def test_extends_w_file_subdir(self): # when file is Dockerfile for building the image + def test_extends_w_file_subdir(self) -> None: # when file is Dockerfile for building the image try: self.run_subprocess_assert_returncode( [ @@ -39,7 +39,7 @@ class TestComposeExtendsWithFileSubdir(unittest.TestCase, RunSubprocessMixin): "down", ]) - def test_podman_compose_extends_w_file_subdir(self): + def test_podman_compose_extends_w_file_subdir(self) -> None: """ Test that podman-compose can execute podman-compose -f up with extended File which includes a build context diff --git a/tests/integration/filesystem/test_podman_compose_filesystem.py b/tests/integration/filesystem/test_podman_compose_filesystem.py index 9539e6e..1647cc6 100644 --- a/tests/integration/filesystem/test_podman_compose_filesystem.py +++ b/tests/integration/filesystem/test_podman_compose_filesystem.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import test_path class TestFilesystem(unittest.TestCase, RunSubprocessMixin): - def test_compose_symlink(self): + def test_compose_symlink(self) -> None: """The context of podman-compose.yml should come from the same directory as the file even if it is a symlink """ diff --git a/tests/integration/in_pod/test_podman_compose_in_pod.py b/tests/integration/in_pod/test_podman_compose_in_pod.py index 0bfe78b..099a684 100644 --- a/tests/integration/in_pod/test_podman_compose_in_pod.py +++ b/tests/integration/in_pod/test_podman_compose_in_pod.py @@ -6,26 +6,26 @@ import unittest from tests.integration.test_utils import RunSubprocessMixin -def base_path(): +def base_path() -> str: """Returns the base path for the project""" return os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) -def test_path(): +def test_path() -> str: """Returns the path to the tests directory""" return os.path.join(base_path(), "tests/integration") -def podman_compose_path(): +def podman_compose_path() -> str: """Returns the path to the podman compose script""" return os.path.join(base_path(), "podman_compose.py") -def is_root(): +def is_root() -> bool: return os.geteuid() == 0 -def failure_exitcode_when_rootful(): +def failure_exitcode_when_rootful() -> int: if is_root(): return 125 return 0 @@ -37,7 +37,7 @@ def failure_exitcode_when_rootful(): # Test all combinations of command line argument in_pod and compose file argument in_pod. class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # compose file provides x-podman in_pod=false - def test_x_podman_in_pod_false_command_line_in_pod_not_exists(self): + def test_x_podman_in_pod_false_command_line_in_pod_not_exists(self) -> None: """ Test that podman-compose will not create a pod, when x-podman in_pod=false and command line does not provide this option @@ -82,7 +82,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # throws an error, can not actually find this pod because it was not created self.run_subprocess_assert_returncode(command_rm_pod, expected_returncode=1) - def test_x_podman_in_pod_false_command_line_in_pod_true(self): + def test_x_podman_in_pod_false_command_line_in_pod_true(self) -> None: """ Test that podman-compose does not allow pod creating even with command line in_pod=True when --userns and --pod are set together: throws an error @@ -115,7 +115,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # been created) and have expected_returncode=1 (see FIXME above) self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_in_pod_false_command_line_in_pod_false(self): + def test_x_podman_in_pod_false_command_line_in_pod_false(self) -> None: """ Test that podman-compose will not create a pod as command line sets in_pod=False """ @@ -160,7 +160,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # can not actually find this pod because it was not created self.run_subprocess_assert_returncode(command_rm_pod, 1) - def test_x_podman_in_pod_false_command_line_in_pod_empty_string(self): + def test_x_podman_in_pod_false_command_line_in_pod_empty_string(self) -> None: """ Test that podman-compose will not create a pod, when x-podman in_pod=false and command line command line in_pod="" @@ -207,7 +207,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): self.run_subprocess_assert_returncode(command_rm_pod, 1) # compose file provides x-podman in_pod=true - def test_x_podman_in_pod_true_command_line_in_pod_not_exists(self): + def test_x_podman_in_pod_true_command_line_in_pod_not_exists(self) -> None: """ Test that podman-compose does not allow pod creating when --userns and --pod are set together even when x-podman in_pod=true: throws an error @@ -240,7 +240,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # created) and have expected_returncode=1 (see FIXME above) self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_in_pod_true_command_line_in_pod_true(self): + def test_x_podman_in_pod_true_command_line_in_pod_true(self) -> None: """ Test that podman-compose does not allow pod creating when --userns and --pod are set together even when x-podman in_pod=true and and command line in_pod=True: throws an error @@ -274,7 +274,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # been created) and have expected_returncode=1 (see FIXME above) self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_in_pod_true_command_line_in_pod_false(self): + def test_x_podman_in_pod_true_command_line_in_pod_false(self) -> None: """ Test that podman-compose will not create a pod as command line sets in_pod=False """ @@ -319,7 +319,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # can not actually find this pod because it was not created self.run_subprocess_assert_returncode(command_rm_pod, 1) - def test_x_podman_in_pod_true_command_line_in_pod_empty_string(self): + def test_x_podman_in_pod_true_command_line_in_pod_empty_string(self) -> None: """ Test that podman-compose does not allow pod creating when --userns and --pod are set together even when x-podman in_pod=true and command line in_pod="": throws an error @@ -354,7 +354,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): self.run_subprocess_assert_returncode(command_rm_pod) # compose file does not provide x-podman in_pod - def test_x_podman_in_pod_not_exists_command_line_in_pod_not_exists(self): + def test_x_podman_in_pod_not_exists_command_line_in_pod_not_exists(self) -> None: """ Test that podman-compose does not allow pod creating when --userns and --pod are set together: throws an error @@ -387,7 +387,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # created) and have expected_returncode=1 (see FIXME above) self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_in_pod_not_exists_command_line_in_pod_true(self): + def test_x_podman_in_pod_not_exists_command_line_in_pod_true(self) -> None: """ Test that podman-compose does not allow pod creating when --userns and --pod are set together even when x-podman in_pod=true: throws an error @@ -421,7 +421,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # been created) and have expected_returncode=1 (see FIXME above) self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_in_pod_not_exists_command_line_in_pod_false(self): + def test_x_podman_in_pod_not_exists_command_line_in_pod_false(self) -> None: """ Test that podman-compose will not create a pod as command line sets in_pod=False """ @@ -467,7 +467,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): # can not actually find this pod because it was not created self.run_subprocess_assert_returncode(command_rm_pod, 1) - def test_x_podman_in_pod_custom_name(self): + def test_x_podman_in_pod_custom_name(self) -> None: """ Test that podman-compose will create a pod with a custom name """ @@ -494,7 +494,7 @@ class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin): command_rm_pod = ["podman", "pod", "rm", "custom_test_pod_name"] self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_in_pod_not_exists_command_line_in_pod_empty_string(self): + def test_x_podman_in_pod_not_exists_command_line_in_pod_empty_string(self) -> None: """ Test that podman-compose does not allow pod creating when --userns and --pod are set together: throws an error diff --git a/tests/integration/include/test_podman_compose_include.py b/tests/integration/include/test_podman_compose_include.py index e5e915c..bdd1857 100644 --- a/tests/integration/include/test_podman_compose_include.py +++ b/tests/integration/include/test_podman_compose_include.py @@ -7,7 +7,7 @@ from tests.integration.test_utils import RunSubprocessMixin class TestPodmanComposeInclude(unittest.TestCase, RunSubprocessMixin): - def test_podman_compose_include(self): + def test_podman_compose_include(self) -> None: """ Test that podman-compose can execute podman-compose -f up with include :return: diff --git a/tests/integration/interpolation/test_podman_compose_interpolation.py b/tests/integration/interpolation/test_podman_compose_interpolation.py index 4dbf682..14c8a54 100644 --- a/tests/integration/interpolation/test_podman_compose_interpolation.py +++ b/tests/integration/interpolation/test_podman_compose_interpolation.py @@ -8,12 +8,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "interpolation"), "docker-compose.yml") class TestComposeInterpolation(unittest.TestCase, RunSubprocessMixin): - def test_interpolation(self): + def test_interpolation(self) -> None: try: self.run_subprocess_assert_returncode([ "env", diff --git a/tests/integration/ipam_default/test_podman_compose_ipam_default.py b/tests/integration/ipam_default/test_podman_compose_ipam_default.py index 18ad0d0..e174bc8 100644 --- a/tests/integration/ipam_default/test_podman_compose_ipam_default.py +++ b/tests/integration/ipam_default/test_podman_compose_ipam_default.py @@ -9,12 +9,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "ipam_default"), "docker-compose.yaml") class TestComposeIpamDefault(unittest.TestCase, RunSubprocessMixin): - def test_ipam_default(self): + def test_ipam_default(self) -> None: try: self.run_subprocess_assert_returncode( [podman_compose_path(), "-f", compose_yaml_path(), "up", "-d"], diff --git a/tests/integration/lifetime/test_lifetime.py b/tests/integration/lifetime/test_lifetime.py index 6d29f05..54648e0 100644 --- a/tests/integration/lifetime/test_lifetime.py +++ b/tests/integration/lifetime/test_lifetime.py @@ -12,7 +12,7 @@ from tests.integration.test_utils import test_path class TestLifetime(unittest.TestCase, RunSubprocessMixin): - def test_up_single_container(self): + def test_up_single_container(self) -> None: """Podman compose up should be able to start containers one after another""" compose_path = os.path.join(test_path(), "lifetime/up_single_container/docker-compose.yml") @@ -68,7 +68,7 @@ class TestLifetime(unittest.TestCase, RunSubprocessMixin): ("no_ports", "up_single_container_many_times"), ("with_ports", "up_single_container_many_times_with_ports"), ]) - def test_up_single_container_many_times(self, name, subdir): + def test_up_single_container_many_times(self, name: str, subdir: str) -> None: """Podman compose up should be able to start a container many times after it finishes running. """ diff --git a/tests/integration/merge/reset_and_override_tags/override_tag_attribute/test_podman_compose_override_tag_attribute.py b/tests/integration/merge/reset_and_override_tags/override_tag_attribute/test_podman_compose_override_tag_attribute.py index 2fe1339..62358a8 100644 --- a/tests/integration/merge/reset_and_override_tags/override_tag_attribute/test_podman_compose_override_tag_attribute.py +++ b/tests/integration/merge/reset_and_override_tags/override_tag_attribute/test_podman_compose_override_tag_attribute.py @@ -9,7 +9,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join( test_path(), "merge/reset_and_override_tags/override_tag_attribute/docker-compose.yaml", @@ -18,7 +18,7 @@ def compose_yaml_path(): class TestComposeOverrideTagAttribute(unittest.TestCase, RunSubprocessMixin): # test if a service attribute from docker-compose.yaml file is overridden - def test_override_tag_attribute(self): + def test_override_tag_attribute(self) -> None: override_file = os.path.join( test_path(), "merge/reset_and_override_tags/override_tag_attribute/docker-compose.override_attribute.yaml", diff --git a/tests/integration/merge/reset_and_override_tags/override_tag_service/test_podman_compose_override_tag_service.py b/tests/integration/merge/reset_and_override_tags/override_tag_service/test_podman_compose_override_tag_service.py index 115d59d..63d61d4 100644 --- a/tests/integration/merge/reset_and_override_tags/override_tag_service/test_podman_compose_override_tag_service.py +++ b/tests/integration/merge/reset_and_override_tags/override_tag_service/test_podman_compose_override_tag_service.py @@ -9,7 +9,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join( test_path(), "merge/reset_and_override_tags/override_tag_service/docker-compose.yaml", @@ -18,7 +18,7 @@ def compose_yaml_path(): class TestComposeOverrideTagService(unittest.TestCase, RunSubprocessMixin): # test if whole service from docker-compose.yaml file is overridden in another file - def test_override_tag_service(self): + def test_override_tag_service(self) -> None: override_file = os.path.join( test_path(), "merge/reset_and_override_tags/override_tag_service/docker-compose.override_service.yaml", diff --git a/tests/integration/merge/reset_and_override_tags/reset_tag_attribute/test_podman_compose_reset_tag_attribute.py b/tests/integration/merge/reset_and_override_tags/reset_tag_attribute/test_podman_compose_reset_tag_attribute.py index 154afea..81e7cb2 100644 --- a/tests/integration/merge/reset_and_override_tags/reset_tag_attribute/test_podman_compose_reset_tag_attribute.py +++ b/tests/integration/merge/reset_and_override_tags/reset_tag_attribute/test_podman_compose_reset_tag_attribute.py @@ -8,7 +8,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join( test_path(), "merge/reset_and_override_tags/reset_tag_attribute/docker-compose.yaml", @@ -17,7 +17,7 @@ def compose_yaml_path(): class TestComposeResetTagAttribute(unittest.TestCase, RunSubprocessMixin): # test if the attribute of the service is correctly reset - def test_reset_tag_attribute(self): + def test_reset_tag_attribute(self) -> None: reset_file = os.path.join( test_path(), "merge/reset_and_override_tags/reset_tag_attribute/docker-compose.reset_attribute.yaml", diff --git a/tests/integration/merge/reset_and_override_tags/reset_tag_service/test_podman_compose_reset_tag_service.py b/tests/integration/merge/reset_and_override_tags/reset_tag_service/test_podman_compose_reset_tag_service.py index d062621..9491123 100644 --- a/tests/integration/merge/reset_and_override_tags/reset_tag_service/test_podman_compose_reset_tag_service.py +++ b/tests/integration/merge/reset_and_override_tags/reset_tag_service/test_podman_compose_reset_tag_service.py @@ -8,7 +8,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join( test_path(), "merge/reset_and_override_tags/reset_tag_service/docker-compose.yaml" ) @@ -16,7 +16,7 @@ def compose_yaml_path(): class TestComposeResetTagService(unittest.TestCase, RunSubprocessMixin): # test if whole service from docker-compose.yaml file is reset - def test_reset_tag_service(self): + def test_reset_tag_service(self) -> None: reset_file = os.path.join( test_path(), "merge/reset_and_override_tags/reset_tag_service/docker-compose.reset_service.yaml", diff --git a/tests/integration/merge/volumes_merge/test_podman_compose_volumes_merge.py b/tests/integration/merge/volumes_merge/test_podman_compose_volumes_merge.py index 4b3d361..b0db9d8 100644 --- a/tests/integration/merge/volumes_merge/test_podman_compose_volumes_merge.py +++ b/tests/integration/merge/volumes_merge/test_podman_compose_volumes_merge.py @@ -9,14 +9,14 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(compose_name): +def compose_yaml_path(compose_name: str) -> str: """ "Returns the path to the compose file used for this test module""" base_path = os.path.join(test_path(), "merge/volumes_merge/") return os.path.join(base_path, compose_name) class TestComposeVolumesMerge(unittest.TestCase, RunSubprocessMixin): - def test_volumes_merge(self): + def test_volumes_merge(self) -> None: # test if additional compose file overrides host path and access mode of a volume try: self.run_subprocess_assert_returncode([ diff --git a/tests/integration/multicompose/test_podman_compose_multicompose.py b/tests/integration/multicompose/test_podman_compose_multicompose.py index d400ecf..29f9831 100644 --- a/tests/integration/multicompose/test_podman_compose_multicompose.py +++ b/tests/integration/multicompose/test_podman_compose_multicompose.py @@ -8,12 +8,12 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "multicompose"), "docker-compose.yml") class TestComposeMulticompose(unittest.TestCase, RunSubprocessMixin): - def test_multicompose(self): + def test_multicompose(self) -> None: try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/nethost/test_podman_compose_nethost.py b/tests/integration/nethost/test_podman_compose_nethost.py index e8c232d..cbb6011 100644 --- a/tests/integration/nethost/test_podman_compose_nethost.py +++ b/tests/integration/nethost/test_podman_compose_nethost.py @@ -10,20 +10,20 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "nethost"), "docker-compose.yaml") class TestComposeNethost(unittest.TestCase, RunSubprocessMixin): # check if container listens for http requests and sends response back # as network_mode: host allows to connect to container easily - def test_nethost(self): + def test_nethost(self) -> None: try: self.run_subprocess_assert_returncode( [podman_compose_path(), "-f", compose_yaml_path(), "up", "-d"], ) - container_id, _ = self.run_subprocess_assert_returncode( + container_id_out, _ = self.run_subprocess_assert_returncode( [ podman_compose_path(), "-f", @@ -33,7 +33,7 @@ class TestComposeNethost(unittest.TestCase, RunSubprocessMixin): '{{.ID}}', ], ) - container_id = container_id.decode('utf-8').split('\n')[0] + container_id = container_id_out.decode('utf-8').split('\n')[0] output, _ = self.run_subprocess_assert_returncode( [ "podman", diff --git a/tests/integration/nets_test1/test_podman_compose_nets_test1.py b/tests/integration/nets_test1/test_podman_compose_nets_test1.py index 62db8fe..9bd9737 100644 --- a/tests/integration/nets_test1/test_podman_compose_nets_test1.py +++ b/tests/integration/nets_test1/test_podman_compose_nets_test1.py @@ -11,13 +11,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "nets_test1"), "docker-compose.yml") class TestComposeNetsTest1(unittest.TestCase, RunSubprocessMixin): # test if port mapping works as expected - def test_nets_test1(self): + def test_nets_test1(self) -> None: try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/nets_test2/test_podman_compose_nets_test2.py b/tests/integration/nets_test2/test_podman_compose_nets_test2.py index 93e6d4e..93c53f1 100644 --- a/tests/integration/nets_test2/test_podman_compose_nets_test2.py +++ b/tests/integration/nets_test2/test_podman_compose_nets_test2.py @@ -11,13 +11,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "nets_test2"), "docker-compose.yml") class TestComposeNetsTest2(unittest.TestCase, RunSubprocessMixin): # test if port mapping works as expected with networks top-level element - def test_nets_test2(self): + def test_nets_test2(self) -> None: try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/nets_test3/test_podman_compose_nets_test3.py b/tests/integration/nets_test3/test_podman_compose_nets_test3.py index 6d4a043..d3389cc 100644 --- a/tests/integration/nets_test3/test_podman_compose_nets_test3.py +++ b/tests/integration/nets_test3/test_podman_compose_nets_test3.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "nets_test3"), "docker-compose.yml") @@ -28,8 +28,12 @@ class TestComposeNetsTest3(unittest.TestCase, RunSubprocessMixin): ("nets_test3_web1_1", "alias21", b"", 1), ]) def test_nets_test3( - self, container_name, nework_alias_name, expected_text, expected_returncode - ): + self, + container_name: str, + nework_alias_name: str, + expected_text: bytes, + expected_returncode: int, + ) -> None: try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/nets_test_ip/test_podman_compose_nets_test_ip.py b/tests/integration/nets_test_ip/test_podman_compose_nets_test_ip.py index 0dcf259..2713540 100644 --- a/tests/integration/nets_test_ip/test_podman_compose_nets_test_ip.py +++ b/tests/integration/nets_test_ip/test_podman_compose_nets_test_ip.py @@ -8,14 +8,14 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "nets_test_ip"), "docker-compose.yml") class TestComposeNetsTestIp(unittest.TestCase, RunSubprocessMixin): # test if services retain custom ipv4_address and mac_address matching the subnet provided # in networks top-level element - def test_nets_test_ip(self): + def test_nets_test_ip(self) -> None: try: self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/network/test_podman_compose_network.py b/tests/integration/network/test_podman_compose_network.py index b9dc4d9..d88c7bd 100644 --- a/tests/integration/network/test_podman_compose_network.py +++ b/tests/integration/network/test_podman_compose_network.py @@ -9,6 +9,7 @@ Tests the podman networking parameters # pylint: disable=redefined-outer-name import os import unittest +from typing import Generator from tests.integration.test_utils import RunSubprocessMixin from tests.integration.test_utils import podman_compose_path @@ -17,11 +18,11 @@ from tests.integration.test_utils import test_path class TestPodmanComposeNetwork(RunSubprocessMixin, unittest.TestCase): @staticmethod - def compose_file(): + def compose_file() -> str: """Returns the path to the compose file used for this test module""" return os.path.join(test_path(), "nets_test_ip", "docker-compose.yml") - def teardown(self): + def teardown(self) -> Generator[None, None, None]: """ Ensures that the services within the "profile compose file" are removed between each test case. @@ -40,7 +41,7 @@ class TestPodmanComposeNetwork(RunSubprocessMixin, unittest.TestCase): ] self.run_subprocess(down_cmd) - def test_networks(self): + def test_networks(self) -> None: up_cmd = [ "coverage", "run", @@ -115,7 +116,7 @@ class TestPodmanComposeNetwork(RunSubprocessMixin, unittest.TestCase): self.assertIn(f"ether {mac}", out.decode('utf-8')) self.assertIn(f"inet {ip}/", out.decode('utf-8')) - def test_down_with_network(self): + def test_down_with_network(self) -> None: try: self.run_subprocess_assert_returncode([ "coverage", diff --git a/tests/integration/network_interface_name/test_podman_compose_network_interface_name.py b/tests/integration/network_interface_name/test_podman_compose_network_interface_name.py index b87faef..2e3ccaf 100644 --- a/tests/integration/network_interface_name/test_podman_compose_network_interface_name.py +++ b/tests/integration/network_interface_name/test_podman_compose_network_interface_name.py @@ -10,10 +10,10 @@ from tests.integration.test_utils import test_path class TestPodmanComposeNetworkInterfaceName(RunSubprocessMixin, unittest.TestCase): - def compose_file(self): + def compose_file(self) -> str: return os.path.join(test_path(), "network_interface_name", "docker-compose.yml") - def up(self): + def up(self) -> None: up_cmd = [ "coverage", "run", @@ -26,7 +26,7 @@ class TestPodmanComposeNetworkInterfaceName(RunSubprocessMixin, unittest.TestCas ] self.run_subprocess_assert_returncode(up_cmd) - def down(self): + def down(self) -> None: down_cmd = [ "coverage", "run", @@ -38,7 +38,7 @@ class TestPodmanComposeNetworkInterfaceName(RunSubprocessMixin, unittest.TestCas ] self.run_subprocess(down_cmd) - def test_interface_name(self): + def test_interface_name(self) -> None: try: self.up() diff --git a/tests/integration/network_scoped_aliases/test_podman_compose_network_scoped_aliases.py b/tests/integration/network_scoped_aliases/test_podman_compose_network_scoped_aliases.py index 804d77a..35b8941 100644 --- a/tests/integration/network_scoped_aliases/test_podman_compose_network_scoped_aliases.py +++ b/tests/integration/network_scoped_aliases/test_podman_compose_network_scoped_aliases.py @@ -11,18 +11,18 @@ from tests.integration.test_utils import test_path class TestPodmanComposeNetworkScopedAliases(RunSubprocessMixin, unittest.TestCase): @staticmethod - def compose_file(): + def compose_file() -> str: """Returns the path to the compose file used for this test module""" return os.path.join(test_path(), "network_scoped_aliases", "docker-compose.yaml") - def test_network_scoped_aliases(self): + def test_network_scoped_aliases(self) -> None: try: self.up() self.verify() finally: self.down() - def up(self): + def up(self) -> None: up_cmd = [ "coverage", "run", @@ -36,7 +36,7 @@ class TestPodmanComposeNetworkScopedAliases(RunSubprocessMixin, unittest.TestCas self.run_subprocess_assert_returncode(up_cmd) - def down(self): + def down(self) -> None: down_cmd = [ "coverage", "run", @@ -48,7 +48,7 @@ class TestPodmanComposeNetworkScopedAliases(RunSubprocessMixin, unittest.TestCas ] self.run_subprocess(down_cmd) - def verify(self): + def verify(self) -> None: expected_results = [ ("utils-net0", "web1", ["172.19.3.11"]), ("utils-net0", "secure-web", ["172.19.3.11"]), @@ -72,7 +72,7 @@ class TestPodmanComposeNetworkScopedAliases(RunSubprocessMixin, unittest.TestCas addresses = self.parse_dnslookup(out.decode()) self.assertEqual(addresses, expected_result) - def parse_dnslookup(self, output): + def parse_dnslookup(self, output: str) -> list[str]: lines = output.splitlines() addresses = [] for line in lines: diff --git a/tests/integration/no_services/test_podman_compose_no_services.py b/tests/integration/no_services/test_podman_compose_no_services.py index 2f3d44e..0bb056d 100644 --- a/tests/integration/no_services/test_podman_compose_no_services.py +++ b/tests/integration/no_services/test_podman_compose_no_services.py @@ -8,13 +8,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "no_services"), "docker-compose.yaml") class TestComposeNoServices(unittest.TestCase, RunSubprocessMixin): # test if a network was created, but not the services - def test_no_services(self): + def test_no_services(self) -> None: try: output, return_code = self.run_subprocess_assert_returncode( [ diff --git a/tests/integration/pod_args/test_podman_compose_pod_args.py b/tests/integration/pod_args/test_podman_compose_pod_args.py index 77facd1..1ab779a 100644 --- a/tests/integration/pod_args/test_podman_compose_pod_args.py +++ b/tests/integration/pod_args/test_podman_compose_pod_args.py @@ -7,23 +7,23 @@ import unittest from tests.integration.test_utils import RunSubprocessMixin -def base_path(): +def base_path() -> str: """Returns the base path for the project""" return os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) -def test_path(): +def test_path() -> str: """Returns the path to the tests directory""" return os.path.join(base_path(), "tests/integration") -def podman_compose_path(): +def podman_compose_path() -> str: """Returns the path to the podman compose script""" return os.path.join(base_path(), "podman_compose.py") class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): - def load_pod_info(self, pod_name): + def load_pod_info(self, pod_name: str) -> dict: output, _ = self.run_subprocess_assert_returncode([ "podman", "pod", @@ -37,7 +37,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): return pod_info[0] return pod_info - def run_pod_args_test(self, config, args, expected): + def run_pod_args_test(self, config: str, args: list, expected: list) -> None: """ Helper to run podman up with a docker-compose.yml config, additional (--pod-args) arguments and compare the CreateCommand of the resulting @@ -78,7 +78,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): command_rm_pod = ["podman", "pod", "rm", pod_name] self.run_subprocess_assert_returncode(command_rm_pod) - def test_x_podman_pod_args_unset_unset(self): + def test_x_podman_pod_args_unset_unset(self) -> None: """ Test that podman-compose will use the default pod-args when unset in both docker-compose.yml and command line @@ -89,7 +89,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): ["--infra=false", "--share="], ) - def test_x_podman_pod_args_unset_empty(self): + def test_x_podman_pod_args_unset_empty(self) -> None: """ Test that podman-compose will use empty pod-args when unset in docker-compose.yml and passing an empty value on the command line @@ -100,7 +100,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): [], ) - def test_x_podman_pod_args_unset_set(self): + def test_x_podman_pod_args_unset_set(self) -> None: """ Test that podman-compose will use the passed pod-args when unset in docker-compose.yml and passing a non-empty value on the command line @@ -111,7 +111,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): ["--infra=false", "--share=", "--cpus=1"], ) - def test_x_podman_pod_args_empty_unset(self): + def test_x_podman_pod_args_empty_unset(self) -> None: """ Test that podman-compose will use empty pod-args when set to an empty value in docker-compose.yml and unset on the command line @@ -122,7 +122,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): [], ) - def test_x_podman_pod_args_empty_empty(self): + def test_x_podman_pod_args_empty_empty(self) -> None: """ Test that podman-compose will use empty pod-args when set to an empty value in both docker-compose.yml and command line @@ -133,7 +133,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): [], ) - def test_x_podman_pod_args_empty_set(self): + def test_x_podman_pod_args_empty_set(self) -> None: """ Test that podman-compose will use the passed pod-args when set to an empty value in docker-compose.yml and passing a non-empty value on the @@ -145,7 +145,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): ["--infra=false", "--share=", "--cpus=1"], ) - def test_x_podman_pod_args_set_unset(self): + def test_x_podman_pod_args_set_unset(self) -> None: """ Test that podman-compose will use the set pod-args when set to a non-empty value in docker-compose.yml and unset on the command line @@ -156,7 +156,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): ["--infra=false", "--share=", "--cpus=2"], ) - def test_x_podman_pod_args_set_empty(self): + def test_x_podman_pod_args_set_empty(self) -> None: """ Test that podman-compose will use empty pod-args when set to a non-empty value in docker-compose.yml and passing an empty value on @@ -168,7 +168,7 @@ class TestPodmanComposePodArgs(unittest.TestCase, RunSubprocessMixin): [], ) - def test_x_podman_pod_args_set_set(self): + def test_x_podman_pod_args_set_set(self) -> None: """ Test that podman-compose will use the passed pod-args when set to a non-empty value in both docker-compose.yml and command line diff --git a/tests/integration/ports/test_podman_compose_ports.py b/tests/integration/ports/test_podman_compose_ports.py index 3aa630d..8b2f279 100644 --- a/tests/integration/ports/test_podman_compose_ports.py +++ b/tests/integration/ports/test_podman_compose_ports.py @@ -15,7 +15,7 @@ from tests.integration.test_utils import test_path class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): - def test_up_with_ports(self): + def test_up_with_ports(self) -> None: up_cmd = [ "coverage", "run", diff --git a/tests/integration/profile/test_podman_compose_config.py b/tests/integration/profile/test_podman_compose_config.py index c0d86d7..989b3ef 100644 --- a/tests/integration/profile/test_podman_compose_config.py +++ b/tests/integration/profile/test_podman_compose_config.py @@ -17,13 +17,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def profile_compose_file(): +def profile_compose_file() -> str: """ "Returns the path to the `profile` compose file used for this test module""" return os.path.join(test_path(), "profile", "docker-compose.yml") class TestComposeConfig(unittest.TestCase, RunSubprocessMixin): - def test_config_no_profiles(self): + def test_config_no_profiles(self) -> None: """ Tests podman-compose config command without profile enablement. """ @@ -59,7 +59,7 @@ class TestComposeConfig(unittest.TestCase, RunSubprocessMixin): ), ], ) - def test_config_profiles(self, profiles, expected_services): + def test_config_profiles(self, profiles: list, expected_services: dict) -> None: """ Tests podman-compose :param profiles: The enabled profiles for the parameterized test. @@ -81,7 +81,7 @@ class TestComposeConfig(unittest.TestCase, RunSubprocessMixin): self.assertEqual(expected_services, actual_services) - def test_config_quiet(self): + def test_config_quiet(self) -> None: """ Tests podman-compose config command with the --quiet flag. """ diff --git a/tests/integration/profile/test_podman_compose_up_down.py b/tests/integration/profile/test_podman_compose_up_down.py index be95f13..63e5050 100644 --- a/tests/integration/profile/test_podman_compose_up_down.py +++ b/tests/integration/profile/test_podman_compose_up_down.py @@ -9,6 +9,7 @@ Tests the podman compose up and down commands used to create and remove services # pylint: disable=redefined-outer-name import os import unittest +from typing import List from parameterized import parameterized @@ -17,13 +18,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def profile_compose_file(): +def profile_compose_file() -> str: """ "Returns the path to the `profile` compose file used for this test module""" return os.path.join(test_path(), "profile", "docker-compose.yml") class TestUpDown(unittest.TestCase, RunSubprocessMixin): - def tearDown(self): + def tearDown(self) -> None: """ Ensures that the services within the "profile compose file" are removed between each test case. @@ -60,7 +61,7 @@ class TestUpDown(unittest.TestCase, RunSubprocessMixin): ), ], ) - def test_up(self, profiles, expected_services): + def test_up(self, profiles: List[str], expected_services: dict) -> None: up_cmd = [ "coverage", "run", diff --git a/tests/integration/seccomp/test_podman_compose_seccomp.py b/tests/integration/seccomp/test_podman_compose_seccomp.py index b14bb15..8c906fa 100644 --- a/tests/integration/seccomp/test_podman_compose_seccomp.py +++ b/tests/integration/seccomp/test_podman_compose_seccomp.py @@ -8,7 +8,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "seccomp"), "docker-compose.yml") @@ -20,7 +20,7 @@ class TestComposeSeccomp(unittest.TestCase, RunSubprocessMixin): ) # test if seccomp uses custom seccomp profile file 'default.json' where command mkdir is not # allowed - def test_seccomp(self): + def test_seccomp(self) -> None: try: output, _, return_code = self.run_subprocess( [podman_compose_path(), "-f", compose_yaml_path(), "run", "--rm", "web1"], diff --git a/tests/integration/secrets/test_podman_compose_secrets.py b/tests/integration/secrets/test_podman_compose_secrets.py index dedb266..2682078 100644 --- a/tests/integration/secrets/test_podman_compose_secrets.py +++ b/tests/integration/secrets/test_podman_compose_secrets.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(): +def compose_yaml_path() -> str: return os.path.join(os.path.join(test_path(), "secrets"), "docker-compose.yaml") @@ -22,12 +22,12 @@ class TestComposeNoSecrets(unittest.TestCase, RunSubprocessMixin): "podman_compose_test_secret_custom_name", ] - def setUp(self): + def setUp(self) -> None: for secret in self.created_secrets: p = Popen(["podman", "secret", "create", secret, "-"], stdin=PIPE) p.communicate(secret.encode('utf-8')) - def tearDown(self): + def tearDown(self) -> None: for secret in self.created_secrets: self.run_subprocess_assert_returncode([ "podman", @@ -37,7 +37,7 @@ class TestComposeNoSecrets(unittest.TestCase, RunSubprocessMixin): ]) # test if secrets are saved and available in respective files of a container - def test_secrets(self): + def test_secrets(self) -> None: try: _, error, _ = self.run_subprocess( [ diff --git a/tests/integration/selinux/test_podman_compose_selinux.py b/tests/integration/selinux/test_podman_compose_selinux.py index 2c04d18..b88f0af 100644 --- a/tests/integration/selinux/test_podman_compose_selinux.py +++ b/tests/integration/selinux/test_podman_compose_selinux.py @@ -11,7 +11,7 @@ from tests.integration.test_utils import test_path class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): - def test_selinux(self): + def test_selinux(self) -> None: # test if when using volumes type:bind with selinux:z option, container ackquires a # respective host:source:z mapping in CreateCommand list compose_path = os.path.join(test_path(), "selinux", "docker-compose.yml") diff --git a/tests/integration/service_scale/test_podman_compose_scale.py b/tests/integration/service_scale/test_podman_compose_scale.py index 34c3cf9..cbee415 100644 --- a/tests/integration/service_scale/test_podman_compose_scale.py +++ b/tests/integration/service_scale/test_podman_compose_scale.py @@ -8,13 +8,13 @@ from tests.integration.test_utils import podman_compose_path from tests.integration.test_utils import test_path -def compose_yaml_path(test_ref_folder): +def compose_yaml_path(test_ref_folder: str) -> str: return os.path.join(test_path(), "service_scale", test_ref_folder, "docker-compose.yml") class TestComposeScale(unittest.TestCase, RunSubprocessMixin): # scale-up using `scale` prarmeter in docker-compose.yml - def test_scaleup_scale_parameter(self): + def test_scaleup_scale_parameter(self) -> None: try: output, _, return_code = self.run_subprocess([ podman_compose_path(), @@ -43,7 +43,7 @@ class TestComposeScale(unittest.TestCase, RunSubprocessMixin): ]) # scale-up using `deploy => replicas` prarmeter in docker-compose.yml - def test_scaleup_deploy_replicas_parameter(self): + def test_scaleup_deploy_replicas_parameter(self) -> None: try: output, _, return_code = self.run_subprocess([ podman_compose_path(), @@ -72,7 +72,7 @@ class TestComposeScale(unittest.TestCase, RunSubprocessMixin): ]) # scale-up using `--scale =` argument in CLI - def test_scaleup_cli(self): + def test_scaleup_cli(self) -> None: try: output, _, return_code = self.run_subprocess([ podman_compose_path(), diff --git a/tests/integration/test_utils.py b/tests/integration/test_utils.py index 5d170ba..3f3d42f 100644 --- a/tests/integration/test_utils.py +++ b/tests/integration/test_utils.py @@ -7,17 +7,17 @@ import time from pathlib import Path -def base_path(): +def base_path() -> Path: """Returns the base path for the project""" return Path(__file__).parent.parent.parent -def test_path(): +def test_path() -> str: """Returns the path to the tests directory""" return os.path.join(base_path(), "tests/integration") -def podman_compose_path(): +def podman_compose_path() -> str: """Returns the path to the podman compose script""" return os.path.join(base_path(), "podman_compose.py") @@ -31,10 +31,10 @@ def is_systemd_available(): class RunSubprocessMixin: - def is_debug_enabled(self): + def is_debug_enabled(self) -> bool: return "TESTS_DEBUG" in os.environ - def run_subprocess(self, args): + def run_subprocess(self, args: list[str]) -> tuple[bytes, bytes, int]: begin = time.time() if self.is_debug_enabled(): print("TEST_CALL", args) @@ -50,11 +50,13 @@ class RunSubprocessMixin: print("STDERR:", err.decode('utf-8')) return out, err, proc.returncode - def run_subprocess_assert_returncode(self, args, expected_returncode=0): + def run_subprocess_assert_returncode( + self, args: list[str], expected_returncode: int = 0 + ) -> tuple[bytes, bytes]: out, err, returncode = self.run_subprocess(args) decoded_out = out.decode('utf-8') decoded_err = err.decode('utf-8') - self.assertEqual( + self.assertEqual( # type: ignore[attr-defined] returncode, expected_returncode, f"Invalid return code of process {returncode} != {expected_returncode}\n" diff --git a/tests/integration/uidmaps/test_podman_compose_uidmaps.py b/tests/integration/uidmaps/test_podman_compose_uidmaps.py index 8a3b453..a21f7cd 100644 --- a/tests/integration/uidmaps/test_podman_compose_uidmaps.py +++ b/tests/integration/uidmaps/test_podman_compose_uidmaps.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import test_path class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): - def test_uidmaps(self): + def test_uidmaps(self) -> None: compose_path = os.path.join(test_path(), "uidmaps", "docker-compose.yml") try: self.run_subprocess_assert_returncode([ diff --git a/tests/integration/ulimit/test_podman_compose_ulimit.py b/tests/integration/ulimit/test_podman_compose_ulimit.py index 4d2ef28..5c12254 100644 --- a/tests/integration/ulimit/test_podman_compose_ulimit.py +++ b/tests/integration/ulimit/test_podman_compose_ulimit.py @@ -10,7 +10,7 @@ from tests.integration.test_utils import test_path class TestUlimit(unittest.TestCase, RunSubprocessMixin): - def test_ulimit(self): + def test_ulimit(self) -> None: compose_path = os.path.join(test_path(), "ulimit/docker-compose.yaml") try: self.run_subprocess_assert_returncode([ diff --git a/tests/integration/up_down/test_podman_compose_up_down.py b/tests/integration/up_down/test_podman_compose_up_down.py index 2169bde..d44f5d6 100644 --- a/tests/integration/up_down/test_podman_compose_up_down.py +++ b/tests/integration/up_down/test_podman_compose_up_down.py @@ -26,7 +26,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): "--force-recreate", ] - def setUp(self): + def setUp(self) -> None: """ Retag the debian image before each test to no mess with the other integration tests when testing the `--rmi` argument @@ -40,7 +40,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): self.run_subprocess_assert_returncode(tag_cmd) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: """ Ensures that the images that were created for this tests will be removed """ @@ -54,7 +54,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): ] cls().run_subprocess_assert_returncode(rmi_cmd) - def test_down(self): + def test_down(self) -> None: down_cmd = [ "coverage", "run", @@ -116,7 +116,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): "docker.io/library/debian:up-down-test", ]) - def test_down_with_volumes(self): + def test_down_with_volumes(self) -> None: down_cmd = [ "coverage", "run", @@ -179,7 +179,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): "docker.io/library/debian:up-down-test", ]) - def test_down_without_orphans(self): + def test_down_without_orphans(self) -> None: down_cmd = [ "coverage", "run", @@ -258,7 +258,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): ) self.run_subprocess_assert_returncode(["podman", "volume", "exists", "up_down_web2_vol"], 1) - def test_down_with_orphans(self): + def test_down_with_orphans(self) -> None: down_cmd = [ "coverage", "run", @@ -322,7 +322,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): "docker.io/library/debian:up-down-test", ]) - def test_down_with_images_default(self): + def test_down_with_images_default(self) -> None: down_cmd = [ "coverage", "run", @@ -379,7 +379,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): ["podman", "image", "exists", "docker.io/library/debian:up-down-test"], 1 ) - def test_down_with_images_all(self): + def test_down_with_images_all(self) -> None: down_cmd = [ "coverage", "run", @@ -437,7 +437,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): ["podman", "image", "exists", "docker.io/library/debian:up-down-test"], 1 ) - def test_down_with_images_all_and_orphans(self): + def test_down_with_images_all_and_orphans(self) -> None: down_cmd = [ "coverage", "run", @@ -497,7 +497,7 @@ class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): ["podman", "image", "exists", "docker.io/library/debian:up-down-test"], 1 ) - def test_down_with_images_local(self): + def test_down_with_images_local(self) -> None: down_cmd = [ "coverage", "run", diff --git a/tests/integration/vol/test_podman_compose_vol.py b/tests/integration/vol/test_podman_compose_vol.py index 31c1e8b..f879246 100644 --- a/tests/integration/vol/test_podman_compose_vol.py +++ b/tests/integration/vol/test_podman_compose_vol.py @@ -16,7 +16,7 @@ from tests.integration.test_utils import test_path class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): - def test_down_with_vols(self): + def test_down_with_vols(self) -> None: up_cmd = [ "coverage", "run", From 6c466780822e4bb450405eea0aa5c71c894dce8e Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:16 +0300 Subject: [PATCH 04/10] Fix mypy warnings Signed-off-by: Povilas Kanapickas --- podman_compose.py | 519 +++++++++++++++++++++++++++------------------- 1 file changed, 300 insertions(+), 219 deletions(-) diff --git a/podman_compose.py b/podman_compose.py index 4fe044e..05bdcd1 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -28,6 +28,11 @@ import tempfile import urllib.parse from asyncio import Task from enum import Enum +from typing import Any +from typing import Callable +from typing import Iterable +from typing import Union +from typing import overload # import fnmatch # fnmatch.fnmatchcase(env, "*_HOST") @@ -41,7 +46,7 @@ script = os.path.realpath(sys.argv[0]) # helper functions -def is_list(list_object): +def is_list(list_object: Any) -> bool: return ( not isinstance(list_object, str) and not isinstance(list_object, dict) @@ -50,11 +55,17 @@ def is_list(list_object): # identity filter -def filteri(a): - return filter(lambda i: i, a) +def filteri(a: list[str]) -> list[str]: + return list(filter(lambda i: i, a)) -def try_int(i, fallback=None): +@overload +def try_int(i: int | str, fallback: int) -> int: ... +@overload +def try_int(i: int | str, fallback: None) -> int | None: ... + + +def try_int(i: int | str, fallback: int | None = None) -> int | None: try: return int(i) except ValueError: @@ -64,7 +75,7 @@ def try_int(i, fallback=None): return fallback -def try_float(i, fallback=None): +def try_float(i: int | str, fallback: float | None = None) -> float | None: try: return float(i) except ValueError: @@ -100,7 +111,7 @@ t_re = re.compile(r"^(?:(\d+)[m:])?(?:(\d+(?:\.\d+)?)s?)?$") STOP_GRACE_PERIOD = "10" -def str_to_seconds(txt): +def str_to_seconds(txt: int | str | None) -> int | None: if not txt: return None if isinstance(txt, (int, float)): @@ -117,19 +128,19 @@ def str_to_seconds(txt): return int(mins * 60.0 + sec) -def ver_as_list(a): +def ver_as_list(a: str) -> list[int]: return [try_int(i, i) for i in num_split_re.findall(a)] -def strverscmp_lt(a, b): +def strverscmp_lt(a: str, b: str) -> bool: a_ls = ver_as_list(a or "") b_ls = ver_as_list(b or "") return a_ls < b_ls -def parse_short_mount(mount_str, basedir): +def parse_short_mount(mount_str: str, basedir: str) -> dict[str, Any]: mount_a = mount_str.split(":") - mount_opt_dict = {} + mount_opt_dict: dict[str, Any] = {} mount_opt = None if len(mount_a) == 1: # Anonymous: Just specify a path and let the engine creates the volume @@ -189,7 +200,9 @@ def parse_short_mount(mount_str, basedir): # unless it's anonymous-volume -def fix_mount_dict(compose, mount_dict, srv_name): +def fix_mount_dict( + compose: PodmanCompose, mount_dict: dict[str, Any], srv_name: str +) -> dict[str, Any]: """ in-place fix mount dictionary to: - define _vol to be the corresponding top-level volume @@ -197,12 +210,14 @@ def fix_mount_dict(compose, mount_dict, srv_name): - if no source it would be generated """ # if already applied nothing todo + assert compose.project_name is not None + if "_vol" in mount_dict: return mount_dict if mount_dict["type"] == "volume": vols = compose.vols source = mount_dict.get("source") - vol = (vols.get(source, {}) or {}) if source else {} + vol = (vols.get(source, {}) or {}) if source else {} # type: ignore[union-attr] name = vol.get("name") mount_dict["_vol"] = vol # handle anonymous or implied volume @@ -254,7 +269,15 @@ var_re = re.compile( ) -def rec_subs(value, subs_dict): +@overload +def rec_subs(value: dict, subs_dict: dict[str, Any]) -> dict: ... +@overload +def rec_subs(value: str, subs_dict: dict[str, Any]) -> str: ... +@overload +def rec_subs(value: Iterable, subs_dict: dict[str, Any]) -> Iterable: ... + + +def rec_subs(value: dict | str | Iterable, subs_dict: dict[str, Any]) -> dict | str | Iterable: """ do bash-like substitution in value and if list of dictionary do that recursively """ @@ -271,7 +294,7 @@ def rec_subs(value, subs_dict): value = {k: rec_subs(v, subs_dict) for k, v in value.items()} elif isinstance(value, str): - def convert(m): + def convert(m: re.Match) -> str: if m.group("escaped") is not None: return "$" name = m.group("named") or m.group("braced") @@ -290,12 +313,12 @@ def rec_subs(value, subs_dict): return value -def norm_as_list(src): +def norm_as_list(src: dict[str, Any] | list[Any] | None) -> list[Any]: """ given a dictionary {key1:value1, key2: None} or list return a list of ["key1=value1", "key2"] """ - dst: list[str] + dst: list[Any] if src is None: dst = [] elif isinstance(src, dict): @@ -307,7 +330,7 @@ def norm_as_list(src): return dst -def norm_as_dict(src): +def norm_as_dict(src: None | dict[str, str | None] | list[str] | str) -> dict[str, str | None]: """ given a list ["key1=value1", "key2"] return a dictionary {key1:value1, key2: None} @@ -317,8 +340,8 @@ def norm_as_dict(src): elif isinstance(src, dict): dst = dict(src) elif is_list(src): - dst = [i.split("=", 1) for i in src if i] - dst = [(a if len(a) == 2 else (a[0], None)) for a in dst] + dst = [i.split("=", 1) for i in src if i] # type: ignore[assignment] + dst = [(a if len(a) == 2 else (a[0], None)) for a in dst] # type: ignore[assignment] dst = dict(dst) elif isinstance(src, str): key, value = src.split("=", 1) if "=" in src else (src, None) @@ -328,7 +351,7 @@ def norm_as_dict(src): return dst -def norm_ulimit(inner_value): +def norm_ulimit(inner_value: dict | list | int | str) -> str: if isinstance(inner_value, dict): if not inner_value.keys() & {"soft", "hard"}: raise ValueError("expected at least one soft or hard limit") @@ -336,15 +359,17 @@ def norm_ulimit(inner_value): hard = inner_value.get("hard", inner_value.get("soft")) return f"{soft}:{hard}" if is_list(inner_value): - return norm_ulimit(norm_as_dict(inner_value)) + return norm_ulimit(norm_as_dict(inner_value)) # type: ignore[arg-type] # if int or string return as is - return inner_value + return inner_value # type: ignore[return-value] -def default_network_name_for_project(compose, net, is_ext): +def default_network_name_for_project(compose: PodmanCompose, net: str, is_ext: Any) -> str: if is_ext: return net + assert compose.project_name is not None + default_net_name_compat = compose.x_podman.get("default_net_name_compat", False) if default_net_name_compat is True: return f"{compose.project_name.replace('-', '')}_{net}" @@ -360,7 +385,9 @@ def default_network_name_for_project(compose, net, is_ext): # return [pod], containers -def transform(args, project_name, given_containers): +def transform( + args: Any, project_name: str, given_containers: list[Any] +) -> tuple[list[dict], list[dict]]: in_pod = str(args.in_pod).lower() pod_name = None pods = [] @@ -379,7 +406,7 @@ def transform(args, project_name, given_containers): return pods, containers -async def assert_volume(compose, mount_dict): +async def assert_volume(compose: PodmanCompose, mount_dict: dict[str, Any]) -> None: """ inspect volume to get directory create volume if needed @@ -429,8 +456,11 @@ async def assert_volume(compose, mount_dict): _ = (await compose.podman.output([], "volume", ["inspect", vol_name])).decode("utf-8") -def mount_desc_to_mount_args(compose, mount_desc, srv_name, cnt_name): # pylint: disable=unused-argument - mount_type = mount_desc.get("type") +def mount_desc_to_mount_args( + compose: PodmanCompose, mount_desc: dict[str, Any], srv_name: str, cnt_name: str +) -> str: # pylint: disable=unused-argument + mount_type: str | None = mount_desc.get("type") + assert mount_type is not None vol = mount_desc.get("_vol") if mount_type == "volume" else None source = vol["name"] if vol else mount_desc.get("source") target = mount_desc["target"] @@ -465,7 +495,7 @@ def mount_desc_to_mount_args(compose, mount_desc, srv_name, cnt_name): # pylint raise ValueError("unknown mount type:" + mount_type) -def ulimit_to_ulimit_args(ulimit, podman_args): +def ulimit_to_ulimit_args(ulimit: str | dict[str, Any] | list[Any], podman_args: list[str]) -> None: if ulimit is not None: # ulimit can be a single value, i.e. ulimit: host if isinstance(ulimit, str): @@ -474,25 +504,27 @@ def ulimit_to_ulimit_args(ulimit, podman_args): else: ulimit = norm_as_dict(ulimit) ulimit = [ - "{}={}".format(ulimit_key, norm_ulimit(inner_value)) - for ulimit_key, inner_value in ulimit.items() + "{}={}".format(ulimit_key, norm_ulimit(inner_value)) # type: ignore[arg-type] + for ulimit_key, inner_value in ulimit.items() # type: ignore[union-attr] ] for i in ulimit: podman_args.extend(["--ulimit", i]) -def container_to_ulimit_args(cnt, podman_args): +def container_to_ulimit_args(cnt: dict[str, Any], podman_args: list[str]) -> None: ulimit_to_ulimit_args(cnt.get("ulimits", []), podman_args) -def container_to_ulimit_build_args(cnt, podman_args): +def container_to_ulimit_build_args(cnt: dict[str, Any], podman_args: list[str]) -> None: build = cnt.get("build") if build is not None: ulimit_to_ulimit_args(build.get("ulimits", []), podman_args) -def mount_desc_to_volume_args(compose, mount_desc, srv_name, cnt_name): # pylint: disable=unused-argument +def mount_desc_to_volume_args( + compose: PodmanCompose, mount_desc: dict[str, Any], srv_name: str, cnt_name: str +) -> str: # pylint: disable=unused-argument mount_type = mount_desc["type"] if mount_type not in ("bind", "volume"): raise ValueError("unknown mount type:" + mount_type) @@ -501,7 +533,7 @@ def mount_desc_to_volume_args(compose, mount_desc, srv_name, cnt_name): # pylin if not source: raise ValueError(f"missing mount source for {mount_type} on {srv_name}") target = mount_desc["target"] - opts = [] + opts: list[str] = [] propagations = set(filteri(mount_desc.get(mount_type, {}).get("propagation", "").split(","))) if mount_type != "bind": @@ -532,7 +564,9 @@ def mount_desc_to_volume_args(compose, mount_desc, srv_name, cnt_name): # pylin return args -def get_mnt_dict(compose, cnt, volume): +def get_mnt_dict( + compose: PodmanCompose, cnt: dict[str, Any], volume: str | dict[str, Any] +) -> dict[str, Any]: srv_name = cnt["_service"] basedir = compose.dirname if isinstance(volume, str): @@ -540,7 +574,9 @@ def get_mnt_dict(compose, cnt, volume): return fix_mount_dict(compose, volume, srv_name) -async def get_mount_args(compose, cnt, volume): +async def get_mount_args( + compose: PodmanCompose, cnt: dict[str, Any], volume: str | dict[str, Any] +) -> list[str]: volume = get_mnt_dict(compose, cnt, volume) srv_name = cnt["_service"] mount_type = volume["type"] @@ -566,11 +602,18 @@ async def get_mount_args(compose, cnt, volume): return ["--mount", args] -def get_secret_args(compose, cnt, secret, podman_is_building=False): +def get_secret_args( + compose: PodmanCompose, + cnt: dict[str, Any], + secret: str | dict[str, Any], + podman_is_building: bool = False, +) -> list[str]: """ podman_is_building: True if we are preparing arguments for an invocation of "podman build" False if we are preparing for something else like "podman run" """ + assert compose.declared_secrets is not None + secret_name = secret if isinstance(secret, str) else secret.get("source") if not secret_name or secret_name not in compose.declared_secrets.keys(): raise ValueError(f'ERROR: undeclared secret: "{secret}", service: {cnt["_service"]}') @@ -671,12 +714,12 @@ def get_secret_args(compose, cnt, secret, podman_is_building=False): ) -def container_to_res_args(cnt, podman_args): +def container_to_res_args(cnt: dict[str, Any], podman_args: list[str]) -> None: container_to_cpu_res_args(cnt, podman_args) container_to_gpu_res_args(cnt, podman_args) -def container_to_gpu_res_args(cnt, podman_args): +def container_to_gpu_res_args(cnt: dict[str, Any], podman_args: list[str]) -> None: # https://docs.docker.com/compose/gpu-support/ # https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/cdi-support.html @@ -727,11 +770,11 @@ def container_to_gpu_res_args(cnt, podman_args): podman_args.append("--security-opt=label=disable") -def container_to_cpu_res_args(cnt, podman_args): +def container_to_cpu_res_args(cnt: dict[str, Any], podman_args: list[str]) -> None: # v2: https://docs.docker.com/compose/compose-file/compose-file-v2/#cpu-and-other-resources # cpus, cpu_shares, mem_limit, mem_reservation - cpus_limit_v2 = try_float(cnt.get("cpus"), None) - cpu_shares_v2 = try_int(cnt.get("cpu_shares"), None) + cpus_limit_v2 = try_float(cnt.get("cpus"), None) # type: ignore[arg-type] + cpu_shares_v2 = try_int(cnt.get("cpu_shares"), None) # type: ignore[arg-type] mem_limit_v2 = cnt.get("mem_limit") mem_res_v2 = cnt.get("mem_reservation") # v3: https://docs.docker.com/compose/compose-file/compose-file-v3/#resources @@ -787,7 +830,7 @@ def container_to_cpu_res_args(cnt, podman_args): podman_args.extend(["--pids-limit", str(final_pids_limit)]) -def port_dict_to_str(port_desc): +def port_dict_to_str(port_desc: dict[str, Any]) -> str: # NOTE: `mode: host|ingress` is ignored cnt_port = port_desc.get("target") published = port_desc.get("published", "") @@ -804,11 +847,14 @@ def port_dict_to_str(port_desc): return ret -def norm_ports(ports_in): +def norm_ports( + ports_in: None | str | list[str | dict[str, Any] | int] | dict[str, Any] | int, +) -> list[str]: if not ports_in: ports_in = [] if isinstance(ports_in, str): ports_in = [ports_in] + assert isinstance(ports_in, list) ports_out = [] for port in ports_in: if isinstance(port, dict): @@ -821,7 +867,7 @@ def norm_ports(ports_in): return ports_out -def get_network_create_args(net_desc, proj_name, net_name): +def get_network_create_args(net_desc: dict[str, Any], proj_name: str, net_name: str) -> list[str]: args = [ "create", "--label", @@ -873,7 +919,7 @@ def get_network_create_args(net_desc, proj_name, net_name): return args -async def assert_cnt_nets(compose, cnt): +async def assert_cnt_nets(compose: PodmanCompose, cnt: dict[str, Any]) -> None: """ create missing networks """ @@ -881,10 +927,12 @@ async def assert_cnt_nets(compose, cnt): if net: return + assert compose.project_name is not None + cnt_nets = cnt.get("networks") if cnt_nets and isinstance(cnt_nets, dict): cnt_nets = list(cnt_nets.keys()) - cnt_nets = norm_as_list(cnt_nets or compose.default_net) + cnt_nets = norm_as_list(cnt_nets or compose.default_net) # type: ignore[arg-type] for net in cnt_nets: net_desc = compose.networks[net] or {} is_ext = net_desc.get("external") @@ -901,9 +949,10 @@ async def assert_cnt_nets(compose, cnt): await compose.podman.output([], "network", ["exists", net_name]) -def get_net_args_from_network_mode(compose, cnt): +def get_net_args_from_network_mode(compose: PodmanCompose, cnt: dict[str, Any]) -> list[str]: net_args = [] net = cnt.get("network_mode") + assert isinstance(net, str) service_name = cnt["service_name"] if "networks" in cnt: @@ -933,13 +982,13 @@ def get_net_args_from_network_mode(compose, cnt): elif net.startswith("bridge"): aliases_on_container = [service_name] if cnt.get("_aliases"): - aliases_on_container.extend(cnt.get("_aliases")) + aliases_on_container.extend(cnt.get("_aliases")) # type: ignore[arg-type] net_options = [f"alias={alias}" for alias in aliases_on_container] mac_address = cnt.get("mac_address") if mac_address: net_options.append(f"mac={mac_address}") - net = f"{net}," if ":" in net else f"{net}:" + net = f"{net}," if ":" in net else f"{net}:" # type: ignore[operator] net_args.append(f"--network={net}{','.join(net_options)}") else: log.fatal("unknown network_mode [%s]", net) @@ -948,7 +997,7 @@ def get_net_args_from_network_mode(compose, cnt): return net_args -def get_net_args(compose, cnt): +def get_net_args(compose: PodmanCompose, cnt: dict[str, Any]) -> list[str]: net = cnt.get("network_mode") if net: return get_net_args_from_network_mode(compose, cnt) @@ -956,7 +1005,7 @@ def get_net_args(compose, cnt): return get_net_args_from_networks(compose, cnt) -def get_net_args_from_networks(compose, cnt): +def get_net_args_from_networks(compose: PodmanCompose, cnt: dict[str, Any]) -> list[str]: net_args = [] mac_address = cnt.get("mac_address") service_name = cnt["service_name"] @@ -1000,8 +1049,8 @@ def get_net_args_from_networks(compose, cnt): for net_, net_config_ in multiple_nets.items(): net_desc = compose.networks.get(net_) or {} is_ext = net_desc.get("external") - ext_desc = is_ext if isinstance(is_ext, str) else {} - default_net_name = default_network_name_for_project(compose, net_, is_ext) + ext_desc: dict[str, Any] = is_ext if isinstance(is_ext, str) else {} # type: ignore[assignment] + default_net_name = default_network_name_for_project(compose, net_, is_ext) # type: ignore[arg-type] net_name = ext_desc.get("name") or net_desc.get("name") or default_net_name interface_name = net_config_.get("x-podman.interface_name") @@ -1045,7 +1094,9 @@ def get_net_args_from_networks(compose, cnt): return net_args -async def container_to_args(compose, cnt, detached=True, no_deps=False): +async def container_to_args( + compose: PodmanCompose, cnt: dict[str, Any], detached: bool = True, no_deps: bool = False +) -> list[str]: # TODO: double check -e , --add-host, -v, --read-only dirname = compose.dirname name = cnt["name"] @@ -1301,7 +1352,7 @@ class ServiceDependencyCondition(Enum): UNHEALTHY = "unhealthy" @classmethod - def from_value(cls, value): + def from_value(cls, value: str) -> 'ServiceDependencyCondition': # Check if the value exists in the enum for member in cls: if member.value == value: @@ -1321,30 +1372,32 @@ class ServiceDependencyCondition(Enum): class ServiceDependency: - def __init__(self, name, condition): + def __init__(self, name: str, condition: str) -> None: self._name = name self._condition = ServiceDependencyCondition.from_value(condition) @property - def name(self): + def name(self) -> str: return self._name @property - def condition(self): + def condition(self) -> 'ServiceDependencyCondition': return self._condition - def __hash__(self): + def __hash__(self) -> int: # Compute hash based on the frozenset of items to ensure order does not matter return hash(('name', self._name) + ('condition', self._condition)) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: # Compare equality based on dictionary content if isinstance(other, ServiceDependency): return self._name == other.name and self._condition == other.condition return False -def rec_deps(services, service_name, start_point=None): +def rec_deps( + services: dict[str, Any], service_name: str, start_point: str | None = None +) -> set['ServiceDependency']: """ return all dependencies of service_name recursively """ @@ -1366,13 +1419,13 @@ def rec_deps(services, service_name, start_point=None): return deps -def flat_deps(services, with_extends=False): +def flat_deps(services: dict[str, Any], with_extends: bool = False) -> None: """ create dependencies "_deps" or update it recursively for all services """ for name, srv in services.items(): # parse dependencies for each service - deps = set() + deps: set[ServiceDependency] = set() srv["_deps"] = deps # TODO: manage properly the dependencies coming from base services when extended if with_extends: @@ -1414,7 +1467,8 @@ class OverrideTag(yaml.YAMLObject): yaml_loader = yaml.SafeLoader yaml_tag = '!override' - def __init__(self, value): + def __init__(self, value: Any) -> None: + self.value: Union[dict[Any, Any], list[Any]] # type: ignore[no-redef] if len(value) > 0 and isinstance(value[0], tuple): self.value = {} # item is a tuple representing service's lower level key and value @@ -1422,18 +1476,18 @@ class OverrideTag(yaml.YAMLObject): # value can actually be a list, then all the elements from the list have to be # collected if isinstance(item[1].value, list): - self.value[item[0].value] = [item.value for item in item[1].value] + self.value[item[0].value] = [i.value for i in item[1].value] # type: ignore[index] else: - self.value[item[0].value] = item[1].value + self.value[item[0].value] = item[1].value # type: ignore[index] else: - self.value = [item.value for item in value] + self.value = [item.value for item in value] # type: ignore[union-attr] @classmethod - def from_yaml(cls, loader, node): + def from_yaml(cls, loader: Any, node: Any) -> 'OverrideTag': return OverrideTag(node.value) @classmethod - def to_yaml(cls, dumper, data): + def to_yaml(cls, dumper: Any, data: 'OverrideTag') -> str: return dumper.represent_scalar(cls.yaml_tag, data.value) @@ -1443,19 +1497,19 @@ class ResetTag(yaml.YAMLObject): yaml_tag = '!reset' @classmethod - def to_json(cls): + def to_json(cls) -> str: return cls.yaml_tag @classmethod - def from_yaml(cls, loader, node): + def from_yaml(cls, loader: Any, node: Any) -> 'ResetTag': return ResetTag() @classmethod - def to_yaml(cls, dumper, data): + def to_yaml(cls, dumper: Any, data: 'ResetTag') -> str: return dumper.represent_scalar(cls.yaml_tag, '') -async def wait_with_timeout(coro, timeout): +async def wait_with_timeout(coro: Any, timeout: int | float) -> Any: """ Asynchronously waits for the given coroutine to complete with a timeout. @@ -1480,17 +1534,19 @@ async def wait_with_timeout(coro, timeout): class Podman: def __init__( self, - compose, - podman_path="podman", - dry_run=False, + compose: PodmanCompose, + podman_path: str = "podman", + dry_run: bool = False, semaphore: asyncio.Semaphore = asyncio.Semaphore(sys.maxsize), - ): + ) -> None: self.compose = compose self.podman_path = podman_path self.dry_run = dry_run self.semaphore = semaphore - async def output(self, podman_args, cmd="", cmd_args=None): + async def output( + self, podman_args: list[str], cmd: str = "", cmd_args: list[str] | None = None + ) -> bytes: async with self.semaphore: cmd_args = cmd_args or [] xargs = self.compose.get_podman_args(cmd) if cmd else [] @@ -1501,12 +1557,13 @@ class Podman: ) stdout_data, stderr_data = await p.communicate() + assert p.returncode is not None if p.returncode == 0: return stdout_data raise subprocess.CalledProcessError(p.returncode, " ".join(cmd_ls), stderr_data) - async def _readchunk(self, reader): + async def _readchunk(self, reader: asyncio.StreamReader) -> bytes: try: return await reader.readuntil(b"\n") except asyncio.exceptions.IncompleteReadError as e: @@ -1514,16 +1571,18 @@ class Podman: except asyncio.exceptions.LimitOverrunError as e: return await reader.read(e.consumed) - async def _format_stream(self, reader, sink, log_formatter): + async def _format_stream( + self, reader: asyncio.StreamReader, sink: Any, log_formatter: str + ) -> None: line_ongoing = False - def _formatted_print_with_nl(s): + def _formatted_print_with_nl(s: str) -> None: if line_ongoing: print(s, file=sink, end="\n") else: print(log_formatter, s, file=sink, end="\n") - def _formatted_print_without_nl(s): + def _formatted_print_without_nl(s: str) -> None: if line_ongoing: print(s, file=sink, end="") else: @@ -1547,10 +1606,10 @@ class Podman: def exec( self, - podman_args, - cmd="", - cmd_args=None, - ): + podman_args: list[str], + cmd: str = "", + cmd_args: list[str] | None = None, + ) -> None: cmd_args = list(map(str, cmd_args or [])) xargs = self.compose.get_podman_args(cmd) if cmd else [] cmd_ls = [self.podman_path, *podman_args, cmd] + xargs + cmd_args @@ -1559,14 +1618,14 @@ class Podman: async def run( # pylint: disable=dangerous-default-value self, - podman_args, - cmd="", - cmd_args=None, - log_formatter=None, + podman_args: list[str], + cmd: str = "", + cmd_args: list[str] | None = None, + log_formatter: str | None = None, *, # Intentionally mutable default argument to hold references to tasks - task_reference=set(), - ) -> int: + task_reference: set[asyncio.Task] = set(), + ) -> int | None: async with self.semaphore: cmd_args = list(map(str, cmd_args or [])) xargs = self.compose.get_podman_args(cmd) if cmd else [] @@ -1583,6 +1642,9 @@ class Podman: close_fds=False, ) # pylint: disable=consider-using-with + assert p.stdout is not None + assert p.stderr is not None + # This is hacky to make the tasks not get garbage collected # https://github.com/python/cpython/issues/91887 out_t = asyncio.create_task( @@ -1615,7 +1677,7 @@ class Podman: log.info("exit code: %s", exit_code) return exit_code - async def network_ls(self): + async def network_ls(self) -> list[str]: output = ( await self.output( [], @@ -1633,7 +1695,7 @@ class Podman: networks = output.splitlines() return networks - async def volume_ls(self): + async def volume_ls(self) -> list[str]: output = ( await self.output( [], @@ -1652,12 +1714,12 @@ class Podman: return volumes -def normalize_service(service, sub_dir=""): +def normalize_service(service: dict[str, Any], sub_dir: str = "") -> dict[str, Any]: if isinstance(service, ResetTag): return service if isinstance(service, OverrideTag): - service = service.value + service = service.value # type: ignore[assignment] if "build" in service: build = service["build"] @@ -1725,7 +1787,7 @@ def normalize_service(service, sub_dir=""): return service -def normalize(compose): +def normalize(compose: dict[str, Any]) -> dict[str, Any]: """ convert compose dict of some keys from string or dicts into arrays """ @@ -1735,7 +1797,7 @@ def normalize(compose): return compose -def normalize_service_final(service: dict, project_dir: str) -> dict: +def normalize_service_final(service: dict[str, Any], project_dir: str) -> dict[str, Any]: if "build" in service: build = service["build"] context = build if isinstance(build, str) else build.get("context", ".") @@ -1748,18 +1810,18 @@ def normalize_service_final(service: dict, project_dir: str) -> dict: return service -def normalize_final(compose: dict, project_dir: str) -> dict: +def normalize_final(compose: dict[str, Any], project_dir: str) -> dict[str, Any]: services = compose.get("services", {}) for service in services.values(): normalize_service_final(service, project_dir) return compose -def clone(value): +def clone(value: Any) -> Any: return value.copy() if is_list(value) or isinstance(value, dict) else value -def rec_merge_one(target, source): +def rec_merge_one(target: dict[str, Any], source: dict[str, Any]) -> dict[str, Any]: """ update target from source recursively """ @@ -1829,7 +1891,7 @@ def rec_merge_one(target, source): return target -def rec_merge(target, *sources): +def rec_merge(target: dict[str, Any], *sources: dict[str, Any]) -> dict[str, Any]: """ update target recursively from sources """ @@ -1838,7 +1900,9 @@ def rec_merge(target, *sources): return ret -def resolve_extends(services, service_names, environ): +def resolve_extends( + services: dict[str, Any], service_names: list[str], environ: dict[str, Any] +) -> None: for name in service_names: service = services[name] ext = service.get("extends", {}) @@ -1870,7 +1934,7 @@ def resolve_extends(services, service_names, environ): services[name] = new_service -def dotenv_to_dict(dotenv_path): +def dotenv_to_dict(dotenv_path: str) -> dict[str, str | None]: if not os.path.isfile(dotenv_path): return {} return dotenv_values(dotenv_path) @@ -1895,28 +1959,28 @@ COMPOSE_DEFAULT_LS = [ class PodmanCompose: - def __init__(self): + def __init__(self) -> None: self.podman: Podman - self.podman_version = None - self.environ = {} + self.podman_version: str | None = None + self.environ: dict[str, str] = {} self.exit_code = None - self.commands = {} + self.commands: dict[str, Any] = {} self.global_args = argparse.Namespace() - self.project_name = None - self.dirname = None - self.pods = None - self.containers = [] - self.vols = None - self.networks = {} - self.default_net = "default" - self.declared_secrets = None - self.container_names_by_service = None - self.container_by_name = None - self.services = None - self.all_services = set() + self.project_name: str | None = None + self.dirname: str + self.pods: list[Any] + self.containers: list[Any] = [] + self.vols: dict[str, Any] | None = None + self.networks: dict[str, Any] = {} + self.default_net: str | None = "default" + self.declared_secrets: dict[str, Any] | None = None + self.container_names_by_service: dict[str, list[str]] + self.container_by_name: dict[str, Any] + self.services: dict[str, Any] + self.all_services: set[Any] = set() self.prefer_volume_over_mount = True - self.x_podman = {} - self.merged_yaml = None + self.x_podman: dict[str, Any] = {} + self.merged_yaml: Any self.yaml_hash = "" self.console_colors = [ "\x1b[1;32m", @@ -1926,7 +1990,7 @@ class PodmanCompose: "\x1b[1;36m", ] - def assert_services(self, services): + def assert_services(self, services: dict[str, Any]) -> None: if isinstance(services, str): services = [services] given = set(services or []) @@ -1936,7 +2000,7 @@ class PodmanCompose: log.warning("missing services [%s]", missing_csv) sys.exit(1) - def get_podman_args(self, cmd): + def get_podman_args(self, cmd: str) -> list[str]: xargs = [] for args in self.global_args.podman_args: xargs.extend(shlex.split(args)) @@ -1946,7 +2010,7 @@ class PodmanCompose: xargs.extend(shlex.split(args)) return xargs - async def run(self, argv=None): + async def run(self, argv: list[str] | None = None) -> None: log.info("podman-compose version: %s", __version__) args = self._parse_args(argv) podman_path = args.podman_path @@ -1984,13 +2048,13 @@ class PodmanCompose: if isinstance(retcode, int): sys.exit(retcode) - def resolve_in_pod(self): + def resolve_in_pod(self) -> bool: if self.global_args.in_pod in (None, ''): self.global_args.in_pod = self.x_podman.get("in_pod", "1") # otherwise use `in_pod` value provided by command line return self.global_args.in_pod - def resolve_pod_args(self): + def resolve_pod_args(self) -> list[str]: # Priorities: # - Command line --pod-args # - docker-compose.yml x-podman.pod_args @@ -1999,7 +2063,7 @@ class PodmanCompose: return shlex.split(self.global_args.pod_args) return self.x_podman.get("pod_args", ["--infra=false", "--share="]) - def _parse_compose_file(self): + def _parse_compose_file(self) -> None: args = self.global_args # cmd = args.command project_dir = os.environ.get("COMPOSE_PROJECT_DIR") @@ -2033,7 +2097,7 @@ class PodmanCompose: # no_cleanup = args.no_cleanup # dry_run = args.dry_run # host_env = None - dirname = os.path.realpath(os.path.dirname(filename)) + dirname: str = os.path.realpath(os.path.dirname(filename)) dir_basename = os.path.basename(dirname) self.dirname = dirname @@ -2050,9 +2114,11 @@ class PodmanCompose: dotenv_dict.update(dotenv_to_dict(dotenv_path)) os.environ.update({ - key: value for key, value in dotenv_dict.items() if key.startswith("PODMAN_") + key: value # type: ignore[misc] + for key, value in dotenv_dict.items() + if key.startswith("PODMAN_") # type: ignore[misc] }) - self.environ = dotenv_dict + self.environ = dotenv_dict # type: ignore[assignment] self.environ.update(dict(os.environ)) # see: https://docs.docker.com/compose/reference/envvars/ # see: https://docs.docker.com/compose/env-file/ @@ -2064,9 +2130,9 @@ class PodmanCompose: if args and 'env' in args and args.env: env_vars = norm_as_dict(args.env) - self.environ.update(env_vars) + self.environ.update(env_vars) # type: ignore[arg-type] - compose = {} + compose: dict[str, Any] = {} # Iterate over files primitively to allow appending to files in-loop files_iter = iter(files) @@ -2106,11 +2172,12 @@ class PodmanCompose: project_name = project_name_normalized self.project_name = project_name + assert self.project_name is not None self.environ.update({"COMPOSE_PROJECT_NAME": self.project_name}) content = rec_subs(content, self.environ) - if isinstance(services := content.get('services'), dict): - for service in services.values(): + if isinstance(content_services := content.get('services'), dict): + for service in content_services.values(): if not isinstance(service, OverrideTag) and not isinstance(service, ResetTag): if 'extends' in service and ( service_file := service['extends'].get('file') @@ -2143,7 +2210,7 @@ class PodmanCompose: log.debug(" ** merged:\n%s", json.dumps(compose, indent=2)) # ver = compose.get('version') - services = compose.get("services") + services: dict | None = compose.get("services") if services is None: services = {} log.warning("WARNING: No services defined") @@ -2153,8 +2220,7 @@ class PodmanCompose: # NOTE: maybe add "extends.service" to _deps at this stage flat_deps(services, with_extends=True) service_names = sorted([(len(srv["_deps"]), name) for name, srv in services.items()]) - service_names = [name for _, name in service_names] - resolve_extends(services, service_names, self.environ) + resolve_extends(services, [name for _, name in service_names], self.environ) flat_deps(services) nets = compose.get("networks", {}) if not nets: @@ -2208,7 +2274,7 @@ class PodmanCompose: # configs: {...} self.declared_secrets = compose.get("secrets", {}) given_containers = [] - container_names_by_service = {} + container_names_by_service: dict[str, list[str]] = {} self.services = services for service_name, service_desc in services.items(): replicas = 1 @@ -2265,7 +2331,7 @@ class PodmanCompose: if ( mnt_dict.get("type") == "volume" and mnt_dict["source"] - and mnt_dict["source"] not in self.vols + and mnt_dict["source"] not in self.vols # type: ignore[operator] ): vol_name = mnt_dict["source"] raise RuntimeError(f"volume [{vol_name}] not defined in top level") @@ -2286,7 +2352,9 @@ class PodmanCompose: self.containers = containers self.container_by_name = {c["name"]: c for c in containers} - def _resolve_profiles(self, defined_services, requested_profiles=None): + def _resolve_profiles( + self, defined_services: dict[str, Any], requested_profiles: set[str] | None = None + ) -> dict[str, Any]: """ Returns a service dictionary (key = service name, value = service config) compatible with the requested_profiles list. @@ -2308,7 +2376,7 @@ class PodmanCompose: services[name] = config return services - def _parse_args(self, argv=None): + def _parse_args(self, argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) self._init_global_parser(parser) subparsers = parser.add_subparsers(title="command", dest="command") @@ -2329,7 +2397,7 @@ class PodmanCompose: return self.global_args @staticmethod - def _init_global_parser(parser): + def _init_global_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument("-v", "--version", help="show version", action="store_true") parser.add_argument( "--in-pod", @@ -2436,32 +2504,32 @@ class PodmanComposeError(Exception): class cmd_run: # pylint: disable=invalid-name,too-few-public-methods - def __init__(self, compose, cmd_name, cmd_desc=None): + def __init__(self, compose: PodmanCompose, cmd_name: str, cmd_desc: str | None = None) -> None: self.compose = compose self.cmd_name = cmd_name self.cmd_desc = cmd_desc - def __call__(self, func): - def wrapped(*args, **kw): + def __call__(self, func: Callable) -> Callable: + def wrapped(*args: Any, **kw: Any) -> Any: return func(*args, **kw) if not asyncio.iscoroutinefunction(func): raise PodmanComposeError("Command must be async") - wrapped._compose = self.compose + wrapped._compose = self.compose # type: ignore[attr-defined] # Trim extra indentation at start of multiline docstrings. - wrapped.desc = self.cmd_desc or re.sub(r"^\s+", "", func.__doc__) - wrapped._parse_args = [] + wrapped.desc = self.cmd_desc or re.sub(r"^\s+", "", func.__doc__ or "") # type: ignore[attr-defined] + wrapped._parse_args = [] # type: ignore[attr-defined] self.compose.commands[self.cmd_name] = wrapped return wrapped class cmd_parse: # pylint: disable=invalid-name,too-few-public-methods - def __init__(self, compose, cmd_names): + def __init__(self, compose: PodmanCompose, cmd_names: list[str] | str) -> None: self.compose = compose - self.cmd_names = cmd_names if is_list(cmd_names) else [cmd_names] + self.cmd_names = cmd_names if is_list(cmd_names) else [cmd_names] # type: ignore[list-item] - def __call__(self, func): - def wrapped(*args, **kw): + def __call__(self, func: Callable) -> Callable: + def wrapped(*args: Any, **kw: Any) -> Any: return func(*args, **kw) for cmd_name in self.cmd_names: @@ -2475,7 +2543,7 @@ class cmd_parse: # pylint: disable=invalid-name,too-few-public-methods @cmd_run(podman_compose, "version", "show version") -async def compose_version(compose, args): +async def compose_version(compose: PodmanCompose, args: argparse.Namespace) -> None: if getattr(args, "short", False): print(__version__) return @@ -2500,7 +2568,7 @@ def is_local(container: dict) -> bool: @cmd_run(podman_compose, "wait", "wait running containers to stop") -async def compose_wait(compose, args): # pylint: disable=unused-argument +async def compose_wait(compose: PodmanCompose, args: argparse.Namespace) -> None: containers = [cnt["name"] for cnt in compose.containers] cmd_args = ["--"] cmd_args.extend(containers) @@ -2508,7 +2576,7 @@ async def compose_wait(compose, args): # pylint: disable=unused-argument @cmd_run(podman_compose, "systemd") -async def compose_systemd(compose, args): +async def compose_systemd(compose: PodmanCompose, args: argparse.Namespace) -> None: """ create systemd unit file and register its compose stacks @@ -2613,7 +2681,7 @@ while in your project type `podman-compose systemd -a register` @cmd_run(podman_compose, "pull", "pull stack images") -async def compose_pull(compose, args): +async def compose_pull(compose: PodmanCompose, args: argparse.Namespace) -> None: img_containers = [cnt for cnt in compose.containers if "image" in cnt] if args.services: services = set(args.services) @@ -2627,7 +2695,7 @@ async def compose_pull(compose, args): @cmd_run(podman_compose, "push", "push stack images") -async def compose_push(compose, args): +async def compose_push(compose: PodmanCompose, args: argparse.Namespace) -> None: services = set(args.services) for cnt in compose.containers: if "build" not in cnt: @@ -2637,12 +2705,12 @@ async def compose_push(compose, args): await compose.podman.run([], "push", [cnt["image"]]) -def is_path_git_url(path): +def is_path_git_url(path: str) -> bool: r = urllib.parse.urlparse(path) return r.scheme == 'git' or r.path.endswith('.git') -def adjust_build_ssh_key_paths(compose, agent_or_key): +def adjust_build_ssh_key_paths(compose: PodmanCompose, agent_or_key: str) -> str: # when using a custom id for ssh property, path to a local SSH key is provided after "=" parts = agent_or_key.split("=", 1) if len(parts) == 1: @@ -2652,7 +2720,13 @@ def adjust_build_ssh_key_paths(compose, agent_or_key): return name + "=" + os.path.join(compose.dirname, path) -def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=None): +def container_to_build_args( + compose: PodmanCompose, + cnt: dict[str, Any], + args: argparse.Namespace, + path_exists: Callable[[str], bool], + cleanup_callbacks: list[Callable] | None = None, +) -> list[str]: build_desc = cnt["build"] if not hasattr(build_desc, "items"): build_desc = {"context": build_desc} @@ -2669,7 +2743,7 @@ def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=N dockerfile.close() dockerfile = dockerfile.name - def cleanup_temp_dockfile(): + def cleanup_temp_dockfile() -> None: if os.path.exists(dockerfile): os.remove(dockerfile) @@ -2743,7 +2817,7 @@ def container_to_build_args(compose, cnt, args, path_exists, cleanup_callbacks=N return build_args -async def build_one(compose, args, cnt): +async def build_one(compose: PodmanCompose, args: argparse.Namespace, cnt: dict) -> int | None: if "build" not in cnt: return None if getattr(args, "if_not_exists", None): @@ -2756,7 +2830,7 @@ async def build_one(compose, args, cnt): if img_id: return None - cleanup_callbacks = [] + cleanup_callbacks: list[Callable] = [] build_args = container_to_build_args( compose, cnt, args, os.path.exists, cleanup_callbacks=cleanup_callbacks ) @@ -2767,7 +2841,7 @@ async def build_one(compose, args, cnt): @cmd_run(podman_compose, "build", "build stack images") -async def compose_build(compose, args): +async def compose_build(compose: PodmanCompose, args: argparse.Namespace) -> int: tasks = [] if args.services: @@ -2790,12 +2864,12 @@ async def compose_build(compose, args): return status -async def pod_exists(compose, name): +async def pod_exists(compose: PodmanCompose, name: str) -> bool: exit_code = await compose.podman.run([], "pod", ["exists", name]) return exit_code == 0 -async def create_pods(compose, args): # pylint: disable=unused-argument +async def create_pods(compose: PodmanCompose, args: argparse.Namespace) -> None: for pod in compose.pods: if await pod_exists(compose, pod["name"]): continue @@ -2814,7 +2888,7 @@ async def create_pods(compose, args): # pylint: disable=unused-argument await compose.podman.run([], "pod", podman_args) -def get_excluded(compose, args): +def get_excluded(compose: PodmanCompose, args: argparse.Namespace) -> set[str]: excluded = set() if args.services: excluded = set(compose.services) @@ -2839,7 +2913,10 @@ async def check_dep_conditions(compose: PodmanCompose, deps: set) -> None: if ( d.condition in (ServiceDependencyCondition.HEALTHY, ServiceDependencyCondition.UNHEALTHY) - ) and strverscmp_lt(compose.podman_version, "4.6.0"): + ) and ( + compose.podman_version is not None + and strverscmp_lt(compose.podman_version, "4.6.0") + ): log.warning( "Ignored %s condition check due to podman %s doesn't support %s!", d.name, @@ -2877,8 +2954,8 @@ async def check_dep_conditions(compose: PodmanCompose, deps: set) -> None: async def run_container( - compose: PodmanCompose, name: str, deps: set, command: tuple, log_formatter: str = None -): + compose: PodmanCompose, name: str, deps: set, command: tuple, log_formatter: str | None = None +) -> int | None: """runs a container after waiting for its dependencies to be fulfilled""" # wait for the dependencies to be fulfilled @@ -2888,17 +2965,17 @@ async def run_container( # start the container log.debug("Starting task for container %s", name) - return await compose.podman.run(*command, log_formatter=log_formatter) + return await compose.podman.run(*command, log_formatter=log_formatter or "text") # type: ignore[misc] -def deps_from_container(args, cnt): +def deps_from_container(args: argparse.Namespace, cnt: dict) -> set: if args.no_deps: return set() return cnt['_deps'] @cmd_run(podman_compose, "up", "Create and start the entire stack or some of its services") -async def compose_up(compose: PodmanCompose, args): +async def compose_up(compose: PodmanCompose, args: argparse.Namespace) -> int | None: excluded = get_excluded(compose, args) if not args.no_build: @@ -2971,9 +3048,9 @@ async def compose_up(compose: PodmanCompose, args): curr_length = len(cnt["_service"]) max_service_length = curr_length if curr_length > max_service_length else max_service_length - tasks = set() + tasks: set[asyncio.Task] = set() - async def handle_sigint(): + async def handle_sigint() -> None: log.info("Caught SIGINT or Ctrl+C, shutting down...") try: log.info("Shutting down gracefully, please wait...") @@ -3057,7 +3134,7 @@ async def compose_up(compose: PodmanCompose, args): return exit_code -def get_volume_names(compose, cnt): +def get_volume_names(compose: PodmanCompose, cnt: dict) -> list[str]: basedir = compose.dirname srv_name = cnt["_service"] ls = [] @@ -3074,9 +3151,9 @@ def get_volume_names(compose, cnt): @cmd_run(podman_compose, "down", "tear down entire stack") -async def compose_down(compose: PodmanCompose, args): +async def compose_down(compose: PodmanCompose, args: argparse.Namespace) -> None: excluded = get_excluded(compose, args) - podman_args = [] + podman_args: list[str] = [] timeout_global = getattr(args, "timeout", None) containers = list(reversed(compose.containers)) @@ -3159,7 +3236,7 @@ async def compose_down(compose: PodmanCompose, args): @cmd_run(podman_compose, "ps", "show status of containers") -async def compose_ps(compose, args): +async def compose_ps(compose: PodmanCompose, args: argparse.Namespace) -> None: ps_args = ["-a", "--filter", f"label=io.podman.compose.project={compose.project_name}"] if args.quiet is True: ps_args.extend(["--format", "{{.ID}}"]) @@ -3178,7 +3255,7 @@ async def compose_ps(compose, args): "run", "create a container similar to a service to run a one-off command", ) -async def compose_run(compose, args): +async def compose_run(compose: PodmanCompose, args: argparse.Namespace) -> None: await create_pods(compose, args) compose.assert_services(args.service) container_names = compose.container_names_by_service[args.service] @@ -3220,7 +3297,9 @@ async def compose_run(compose, args): sys.exit(p) -def compose_run_update_container_from_args(compose, cnt, args): +def compose_run_update_container_from_args( + compose: PodmanCompose, cnt: dict, args: argparse.Namespace +) -> None: # adjust one-off container options name0 = "{}_{}_tmp{}".format(compose.project_name, args.service, random.randrange(0, 65536)) cnt["name"] = args.name or name0 @@ -3259,7 +3338,7 @@ def compose_run_update_container_from_args(compose, cnt, args): @cmd_run(podman_compose, "exec", "execute a command in a running container") -async def compose_exec(compose, args): +async def compose_exec(compose: PodmanCompose, args: argparse.Namespace) -> None: compose.assert_services(args.service) container_names = compose.container_names_by_service[args.service] container_name = container_names[args.index - 1] @@ -3269,7 +3348,7 @@ async def compose_exec(compose, args): sys.exit(p) -def compose_exec_args(cnt, container_name, args): +def compose_exec_args(cnt: dict, container_name: str, args: argparse.Namespace) -> list[str]: podman_args = ["--interactive"] if args.privileged: podman_args += ["--privileged"] @@ -3293,7 +3372,9 @@ def compose_exec_args(cnt, container_name, args): return podman_args -async def transfer_service_status(compose, args, action): +async def transfer_service_status( + compose: PodmanCompose, args: argparse.Namespace, action: str +) -> None: # TODO: handle dependencies, handle creations container_names_by_service = compose.container_names_by_service if not args.services: @@ -3324,22 +3405,22 @@ async def transfer_service_status(compose, args, action): @cmd_run(podman_compose, "start", "start specific services") -async def compose_start(compose, args): +async def compose_start(compose: PodmanCompose, args: argparse.Namespace) -> None: await transfer_service_status(compose, args, "start") @cmd_run(podman_compose, "stop", "stop specific services") -async def compose_stop(compose, args): +async def compose_stop(compose: PodmanCompose, args: argparse.Namespace) -> None: await transfer_service_status(compose, args, "stop") @cmd_run(podman_compose, "restart", "restart specific services") -async def compose_restart(compose, args): +async def compose_restart(compose: PodmanCompose, args: argparse.Namespace) -> None: await transfer_service_status(compose, args, "restart") @cmd_run(podman_compose, "logs", "show logs from services") -async def compose_logs(compose, args): +async def compose_logs(compose: PodmanCompose, args: argparse.Namespace) -> None: container_names_by_service = compose.container_names_by_service if not args.services and not args.latest: args.services = container_names_by_service.keys() @@ -3370,7 +3451,7 @@ async def compose_logs(compose, args): @cmd_run(podman_compose, "config", "displays the compose file") -async def compose_config(compose, args): +async def compose_config(compose: PodmanCompose, args: argparse.Namespace) -> None: if args.services: for service in compose.services: if not args.quiet: @@ -3381,7 +3462,7 @@ async def compose_config(compose, args): @cmd_run(podman_compose, "port", "Prints the public port for a port binding.") -async def compose_port(compose, args): +async def compose_port(compose: PodmanCompose, args: argparse.Namespace) -> None: compose.assert_services(args.service) containers = compose.container_names_by_service[args.service] output = await compose.podman.output([], "inspect", [containers[args.index - 1]]) @@ -3392,7 +3473,7 @@ async def compose_port(compose, args): @cmd_run(podman_compose, "pause", "Pause all running containers") -async def compose_pause(compose, args): +async def compose_pause(compose: PodmanCompose, args: argparse.Namespace) -> None: container_names_by_service = compose.container_names_by_service if not args.services: args.services = container_names_by_service.keys() @@ -3403,7 +3484,7 @@ async def compose_pause(compose, args): @cmd_run(podman_compose, "unpause", "Unpause all running containers") -async def compose_unpause(compose, args): +async def compose_unpause(compose: PodmanCompose, args: argparse.Namespace) -> None: container_names_by_service = compose.container_names_by_service if not args.services: args.services = container_names_by_service.keys() @@ -3414,7 +3495,7 @@ async def compose_unpause(compose, args): @cmd_run(podman_compose, "kill", "Kill one or more running containers with a specific signal") -async def compose_kill(compose, args): +async def compose_kill(compose: PodmanCompose, args: argparse.Namespace) -> None: # to ensure that the user did not execute the command by mistake if not args.services and not args.all: log.fatal( @@ -3450,7 +3531,7 @@ async def compose_kill(compose, args): "stats", "Display percentage of CPU, memory, network I/O, block I/O and PIDs for services.", ) -async def compose_stats(compose, args): +async def compose_stats(compose: PodmanCompose, args: argparse.Namespace) -> None: container_names_by_service = compose.container_names_by_service if not args.services: args.services = container_names_by_service.keys() @@ -3477,7 +3558,7 @@ async def compose_stats(compose, args): @cmd_run(podman_compose, "images", "List images used by the created containers") -async def compose_images(compose, args): +async def compose_images(compose: PodmanCompose, args: argparse.Namespace) -> None: img_containers = [cnt for cnt in compose.containers if "image" in cnt] data = [] if args.quiet is True: @@ -3518,7 +3599,7 @@ async def compose_images(compose, args): @cmd_parse(podman_compose, "version") -def compose_version_parse(parser): +def compose_version_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-f", "--format", @@ -3534,7 +3615,7 @@ def compose_version_parse(parser): @cmd_parse(podman_compose, "up") -def compose_up_parse(parser): +def compose_up_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-d", "--detach", @@ -3628,7 +3709,7 @@ def compose_up_parse(parser): @cmd_parse(podman_compose, "down") -def compose_down_parse(parser): +def compose_down_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-v", "--volumes", @@ -3654,7 +3735,7 @@ def compose_down_parse(parser): @cmd_parse(podman_compose, "run") -def compose_run_parse(parser): +def compose_run_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--build", action="store_true", help="Build images before starting containers." ) @@ -3733,7 +3814,7 @@ def compose_run_parse(parser): @cmd_parse(podman_compose, "exec") -def compose_exec_parse(parser): +def compose_exec_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-d", "--detach", @@ -3784,7 +3865,7 @@ def compose_exec_parse(parser): @cmd_parse(podman_compose, ["down", "stop", "restart"]) -def compose_parse_timeout(parser): +def compose_parse_timeout(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-t", "--timeout", @@ -3795,7 +3876,7 @@ def compose_parse_timeout(parser): @cmd_parse(podman_compose, ["logs"]) -def compose_logs_parse(parser): +def compose_logs_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-f", "--follow", @@ -3829,7 +3910,7 @@ def compose_logs_parse(parser): @cmd_parse(podman_compose, "systemd") -def compose_systemd_parse(parser): +def compose_systemd_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-a", "--action", @@ -3840,7 +3921,7 @@ def compose_systemd_parse(parser): @cmd_parse(podman_compose, "pull") -def compose_pull_parse(parser): +def compose_pull_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--force-local", action="store_true", @@ -3851,7 +3932,7 @@ def compose_pull_parse(parser): @cmd_parse(podman_compose, "push") -def compose_push_parse(parser): +def compose_push_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--ignore-push-failures", action="store_true", @@ -3861,12 +3942,12 @@ def compose_push_parse(parser): @cmd_parse(podman_compose, "ps") -def compose_ps_parse(parser): +def compose_ps_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument("-q", "--quiet", help="Only display container IDs", action="store_true") @cmd_parse(podman_compose, ["build", "up"]) -def compose_build_up_parse(parser): +def compose_build_up_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--pull", help="attempt to pull a newer version of the image", @@ -3893,7 +3974,7 @@ def compose_build_up_parse(parser): @cmd_parse(podman_compose, ["build", "up", "down", "start", "stop", "restart"]) -def compose_build_parse(parser): +def compose_build_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "services", metavar="services", @@ -3904,7 +3985,7 @@ def compose_build_parse(parser): @cmd_parse(podman_compose, "config") -def compose_config_parse(parser): +def compose_config_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--no-normalize", help="Don't normalize compose model.", action="store_true" ) @@ -3920,7 +4001,7 @@ def compose_config_parse(parser): @cmd_parse(podman_compose, "port") -def compose_port_parse(parser): +def compose_port_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--index", type=int, @@ -3944,14 +4025,14 @@ def compose_port_parse(parser): @cmd_parse(podman_compose, ["pause", "unpause"]) -def compose_pause_unpause_parse(parser): +def compose_pause_unpause_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "services", metavar="services", nargs="*", default=None, help="service names" ) @cmd_parse(podman_compose, ["kill"]) -def compose_kill_parse(parser): +def compose_kill_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "services", metavar="services", nargs="*", default=None, help="service names" ) @@ -3970,12 +4051,12 @@ def compose_kill_parse(parser): @cmd_parse(podman_compose, "images") -def compose_images_parse(parser): +def compose_images_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument("-q", "--quiet", help="Only display images IDs", action="store_true") @cmd_parse(podman_compose, ["stats"]) -def compose_stats_parse(parser): +def compose_stats_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "services", metavar="services", nargs="*", default=None, help="service names" ) @@ -3998,7 +4079,7 @@ def compose_stats_parse(parser): @cmd_parse(podman_compose, ["ps", "stats"]) -def compose_format_parse(parser): +def compose_format_parse(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-f", "--format", @@ -4007,11 +4088,11 @@ def compose_format_parse(parser): ) -async def async_main(): +async def async_main() -> None: await podman_compose.run() -def main(): +def main() -> None: asyncio.run(async_main()) From 1eae76ddca96633aa3f69b5863e451ac4c35f160 Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:17 +0300 Subject: [PATCH 05/10] Add return type annotations to test_utils.py Signed-off-by: Povilas Kanapickas --- tests/integration/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_utils.py b/tests/integration/test_utils.py index 3f3d42f..d8adedb 100644 --- a/tests/integration/test_utils.py +++ b/tests/integration/test_utils.py @@ -22,7 +22,7 @@ def podman_compose_path() -> str: return os.path.join(base_path(), "podman_compose.py") -def is_systemd_available(): +def is_systemd_available() -> bool: try: with open("/proc/1/comm", "r", encoding="utf-8") as fh: return fh.read().strip() == "systemd" @@ -66,7 +66,7 @@ class RunSubprocessMixin: class PodmanAwareRunSubprocessMixin(RunSubprocessMixin): - def retrieve_podman_version(self): + def retrieve_podman_version(self) -> tuple[int, int, int]: out, _ = self.run_subprocess_assert_returncode(["podman", "--version"]) matcher = re.match(r"\D*(\d+)\.(\d+)\.(\d+)", out.decode('utf-8')) if matcher: From 0be50ffdfb2e2eeb4b441a88254767e6fbc37c32 Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:18 +0300 Subject: [PATCH 06/10] Fix return value from compose_systemd() Signed-off-by: Povilas Kanapickas --- podman_compose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podman_compose.py b/podman_compose.py index 05bdcd1..3d5b0db 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -2638,7 +2638,7 @@ you can stop and disable the service with: except OSError as e: log.error("failed to remove file %s: %s", fn, e) print(f"Failed to remove registration file for project '{proj_name}'") - return 1 + sys.exit(1) else: log.warning("registration file not found: %s", fn) print(f"Project '{proj_name}' is not registered") From 5765e5306b6b547cbae5c0feb7baaa1cb05722c3 Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:19 +0300 Subject: [PATCH 07/10] Use correct logging methods Signed-off-by: Povilas Kanapickas --- podman_compose.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/podman_compose.py b/podman_compose.py index 3d5b0db..c458bae 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -1838,11 +1838,11 @@ def rec_merge_one(target: dict[str, Any], source: dict[str, Any]) -> dict[str, A continue if key not in source: if isinstance(value, ResetTag): - log("INFO: Unneeded !reset found for [{key}]") + log.info("Unneeded !reset found for [%s]", key) remove.add(key) if isinstance(value, OverrideTag): - log("INFO: Unneeded !override found for [{key}] with value '{value}'") + log.info("Unneeded !override found for [%s] with value '%s'", key, value) target[key] = clone(value.value) continue From 3c2978c9ca21e1dd1bab36403e9a20e1133f709f Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:10:20 +0300 Subject: [PATCH 08/10] examples: Add type annotations Signed-off-by: Povilas Kanapickas --- examples/hello-python/app/web.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/hello-python/app/web.py b/examples/hello-python/app/web.py index 6173ab7..10746b8 100644 --- a/examples/hello-python/app/web.py +++ b/examples/hello-python/app/web.py @@ -3,8 +3,8 @@ import asyncio # noqa: F401 import os -import aioredis -from aiohttp import web +import aioredis # type: ignore[import-not-found] +from aiohttp import web # type: ignore[import-not-found] REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379")) @@ -16,13 +16,13 @@ routes = web.RouteTableDef() @routes.get("/") -async def hello(request): # pylint: disable=unused-argument +async def hello(request: web.Request) -> web.Response: # pylint: disable=unused-argument counter = await redis.incr("mycounter") return web.Response(text=f"counter={counter}") @routes.get("/hello.json") -async def hello_json(request): # pylint: disable=unused-argument +async def hello_json(request: web.Request) -> web.Response: # pylint: disable=unused-argument counter = await redis.incr("mycounter") data = {"counter": counter} return web.json_response(data) @@ -31,7 +31,7 @@ async def hello_json(request): # pylint: disable=unused-argument app.add_routes(routes) -def main(): +def main() -> None: web.run_app(app, port=8080) From efea0ee652ffa3b9326aafc9a134acc6fd71bdfc Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:24:52 +0300 Subject: [PATCH 09/10] Address unused argument warnings Signed-off-by: Povilas Kanapickas --- podman_compose.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/podman_compose.py b/podman_compose.py index c458bae..28408d9 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -456,9 +456,7 @@ async def assert_volume(compose: PodmanCompose, mount_dict: dict[str, Any]) -> N _ = (await compose.podman.output([], "volume", ["inspect", vol_name])).decode("utf-8") -def mount_desc_to_mount_args( - compose: PodmanCompose, mount_desc: dict[str, Any], srv_name: str, cnt_name: str -) -> str: # pylint: disable=unused-argument +def mount_desc_to_mount_args(mount_desc: dict[str, Any]) -> str: mount_type: str | None = mount_desc.get("type") assert mount_type is not None vol = mount_desc.get("_vol") if mount_type == "volume" else None @@ -522,9 +520,7 @@ def container_to_ulimit_build_args(cnt: dict[str, Any], podman_args: list[str]) ulimit_to_ulimit_args(build.get("ulimits", []), podman_args) -def mount_desc_to_volume_args( - compose: PodmanCompose, mount_desc: dict[str, Any], srv_name: str, cnt_name: str -) -> str: # pylint: disable=unused-argument +def mount_desc_to_volume_args(mount_desc: dict[str, Any], srv_name: str) -> str: mount_type = mount_desc["type"] if mount_type not in ("bind", "volume"): raise ValueError("unknown mount type:" + mount_type) @@ -596,9 +592,9 @@ async def get_mount_args( if opts: args += ":" + ",".join(opts) return ["--tmpfs", args] - args = mount_desc_to_volume_args(compose, volume, srv_name, cnt["name"]) + args = mount_desc_to_volume_args(volume, srv_name) return ["-v", args] - args = mount_desc_to_mount_args(compose, volume, srv_name, cnt["name"]) + args = mount_desc_to_mount_args(volume) return ["--mount", args] @@ -2568,7 +2564,10 @@ def is_local(container: dict) -> bool: @cmd_run(podman_compose, "wait", "wait running containers to stop") -async def compose_wait(compose: PodmanCompose, args: argparse.Namespace) -> None: +async def compose_wait( + compose: PodmanCompose, + args: argparse.Namespace, # pylint: disable=unused-argument +) -> None: containers = [cnt["name"] for cnt in compose.containers] cmd_args = ["--"] cmd_args.extend(containers) From 248a63ebb0c207088517df2d7660d1e1473dca6b Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Sat, 24 May 2025 17:26:38 +0300 Subject: [PATCH 10/10] test-requirements: Upgrade ruff Signed-off-by: Povilas Kanapickas --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 73927a0..e97ea06 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ parameterized==0.9.0 pytest==8.0.2 tox==4.13.0 mypy==1.15.0 -ruff==0.3.1 +ruff==0.11.11 pylint==3.1.0 types-PyYAML==6.0.12.20250402 types-requests==2.32.0.20250328