From 5045e04893b3cb858b55813e028af3cb5eb05554 Mon Sep 17 00:00:00 2001 From: Svet Date: Tue, 11 Feb 2025 14:15:59 +0200 Subject: [PATCH] Fix for multiple container start invocations with custom labels When invoking .start() multiple times on the same DockerContainer instance, the call fails with "ValueError: The org.testcontainers namespace is reserved for internal use" error. Example code: ``` from testcontainers.core.container import DockerContainer container = DockerContainer("alpine:latest").with_kwargs(labels={}) container.start() container.stop() container.start() ``` The fix is to update labels for the container in a copy of the user-provided dictionary, so that: * the code doesn't mutate user structures * avoid side effects, allowing for multiple .start() invocations --- core/testcontainers/core/labels.py | 15 +++++++++------ core/tests/test_labels.py | 7 +++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/core/testcontainers/core/labels.py b/core/testcontainers/core/labels.py index 0570b22cb..1c45b79cf 100644 --- a/core/testcontainers/core/labels.py +++ b/core/testcontainers/core/labels.py @@ -21,12 +21,15 @@ def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str if k.startswith(TESTCONTAINERS_NAMESPACE): raise ValueError("The org.testcontainers namespace is reserved for internal use") - labels[LABEL_LANG] = "python" - labels[LABEL_TESTCONTAINERS] = "true" - labels[LABEL_VERSION] = importlib.metadata.version("testcontainers") + tc_labels = { + **labels, + LABEL_LANG: "python", + LABEL_TESTCONTAINERS: "true", + LABEL_VERSION: importlib.metadata.version("testcontainers"), + } if image == c.ryuk_image: - return labels + return tc_labels - labels[LABEL_SESSION_ID] = SESSION_ID - return labels + tc_labels[LABEL_SESSION_ID] = SESSION_ID + return tc_labels diff --git a/core/tests/test_labels.py b/core/tests/test_labels.py index 425aee7dd..bbd72409d 100644 --- a/core/tests/test_labels.py +++ b/core/tests/test_labels.py @@ -56,3 +56,10 @@ def test_session_are_module_import_scoped(): assert LABEL_SESSION_ID in first_labels assert LABEL_SESSION_ID in second_labels assert first_labels[LABEL_SESSION_ID] == second_labels[LABEL_SESSION_ID] + + +def test_create_no_side_effects(): + input_labels = {"key": "value"} + expected_labels = input_labels.copy() + create_labels("not-ryuk", {"key": "value"}) + assert input_labels == expected_labels, input_labels