diff --git a/kubernetes/client/api_client.py b/kubernetes/client/api_client.py index 1f7c7b81e5..92b727baf3 100644 --- a/kubernetes/client/api_client.py +++ b/kubernetes/client/api_client.py @@ -285,6 +285,25 @@ def __deserialize(self, data, klass): return {k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)} + if klass.startswith('dict[') and klass.endswith(']'): + # Parse dict[key_type, value_type] respecting nested brackets + inner = klass[len('dict['):-1] + bracket_depth = 0 + comma_pos = -1 + for i, char in enumerate(inner): + if char in '([{': + bracket_depth += 1 + elif char in ')]}': + bracket_depth -= 1 + elif char == ',' and bracket_depth == 0: + comma_pos = i + break + + if comma_pos != -1: + value_type = inner[comma_pos + 1:].strip() + return {k: self.__deserialize(v, value_type) + for k, v in six.iteritems(data)} + # convert str to class if klass in self.NATIVE_TYPES_MAPPING: klass = self.NATIVE_TYPES_MAPPING[klass] diff --git a/kubernetes/test/test_api_client.py b/kubernetes/test/test_api_client.py index 486b4ac5b8..872d580784 100644 --- a/kubernetes/test/test_api_client.py +++ b/kubernetes/test/test_api_client.py @@ -25,6 +25,32 @@ def test_atexit_closes_threadpool(self): atexit._run_exitfuncs() self.assertIsNone(client._pool) + def test_deserialize_dict_syntax_compatibility(self): + """Test ApiClient.__deserialize supports both dict(str, str) and dict[str, str] syntax""" + client = kubernetes.client.ApiClient() + + # Test data + test_data = { + 'key1': 'value1', + 'key2': 'value2' + } + + # Test legacy syntax: dict(str, str) + result_legacy = client._ApiClient__deserialize(test_data, 'dict(str, str)') + self.assertEqual(result_legacy, test_data) + + # Test modern syntax: dict[str, str] + result_modern = client._ApiClient__deserialize(test_data, 'dict[str, str]') + self.assertEqual(result_modern, test_data) + + # Test nested dict: dict[str, dict[str, str]] + nested_data = { + 'outer1': {'inner1': 'value1', 'inner2': 'value2'}, + 'outer2': {'inner3': 'value3'} + } + result_nested = client._ApiClient__deserialize(nested_data, 'dict[str, dict[str, str]]') + self.assertEqual(result_nested, nested_data) + def test_rest_proxycare(self): pool = { 'proxy': urllib3.ProxyManager, 'direct': urllib3.PoolManager } diff --git a/scripts/api_client_dict_syntax.diff b/scripts/api_client_dict_syntax.diff new file mode 100644 index 0000000000..3292bdcdec --- /dev/null +++ b/scripts/api_client_dict_syntax.diff @@ -0,0 +1,28 @@ +--- a/kubernetes/client/api_client.py 2025-11-01 14:37:47 ++++ b/kubernetes/client/api_client.py 2025-11-01 15:54:48 +@@ -285,6 +285,25 @@ + return {k: self.__deserialize(v, sub_kls) + for k, v in six.iteritems(data)} + ++ if klass.startswith('dict[') and klass.endswith(']'): ++ # Parse dict[key_type, value_type] respecting nested brackets ++ inner = klass[len('dict['):-1] ++ bracket_depth = 0 ++ comma_pos = -1 ++ for i, char in enumerate(inner): ++ if char in '([{': ++ bracket_depth += 1 ++ elif char in ')]}': ++ bracket_depth -= 1 ++ elif char == ',' and bracket_depth == 0: ++ comma_pos = i ++ break ++ ++ if comma_pos != -1: ++ value_type = inner[comma_pos + 1:].strip() ++ return {k: self.__deserialize(v, value_type) ++ for k, v in six.iteritems(data)} ++ + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] diff --git a/scripts/update-client.sh b/scripts/update-client.sh index 2b0ce7be1a..eddcf95411 100755 --- a/scripts/update-client.sh +++ b/scripts/update-client.sh @@ -78,6 +78,10 @@ git apply "${SCRIPT_ROOT}/rest_client_patch.diff" # once we upgrade to a version of swagger-codegen that includes it (version>= 6.6.0). # See https://github.com/OpenAPITools/openapi-generator/pull/15283 git apply "${SCRIPT_ROOT}/rest_sni_patch.diff" +# Support dict[str, str] syntax in ApiClient deserializer alongside legacy dict(str, str) +# This enables forward compatibility with modern Python typing syntax while maintaining +# backward compatibility. Users can now convert openapi_types for Pydantic integration. +git apply "${SCRIPT_ROOT}/api_client_dict_syntax.diff" # The following is commented out due to: # AttributeError: 'RESTResponse' object has no attribute 'headers' # OpenAPI client generator prior to 6.4.0 uses deprecated urllib3 APIs.