From c960a14b9a173e4a01837f853b38dd104afaab3b Mon Sep 17 00:00:00 2001 From: Marcel Konrad Date: Wed, 10 Jun 2026 16:34:52 +0200 Subject: [PATCH 1/5] Account for content-type parameters on request preparation --- .../src/main/resources/python/rest.mustache | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/rest.mustache b/modules/openapi-generator/src/main/resources/python/rest.mustache index 20176e7140f1..f6fcec62dfa5 100644 --- a/modules/openapi-generator/src/main/resources/python/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/rest.mustache @@ -108,6 +108,10 @@ class RESTClientObject: else: self.pool_manager = urllib3.PoolManager(**pool_args) + def _contenttype_matches(contenttype, maintype, subtype): + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None + def request( self, method, @@ -171,7 +175,7 @@ class RESTClientObject: content_type = headers.get('Content-Type') if ( not content_type - or re.search('json', content_type, re.IGNORECASE) + or _contenttype_matches(content_type, 'application', 'json') ): request_body = None if body is not None: @@ -184,7 +188,7 @@ class RESTClientObject: headers=headers, preload_content=False ) - elif content_type == 'application/x-www-form-urlencoded': + elif _contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): r = self.pool_manager.request( method, url, @@ -194,7 +198,7 @@ class RESTClientObject: headers=headers, preload_content=False ) - elif content_type == 'multipart/form-data': + elif _contenttype_matches(content_type, 'multipart', 'form-data'): # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. @@ -222,7 +226,7 @@ class RESTClientObject: headers=headers, preload_content=False ) - elif headers['Content-Type'].startswith('text/') and isinstance(body, bool): + elif content_type.startswith('text/') and isinstance(body, bool): request_body = "true" if body else "false" r = self.pool_manager.request( method, From b5416cbcc80a07fe9c3ced48a1def3d5670ed116 Mon Sep 17 00:00:00 2001 From: Marcel Konrad Date: Thu, 11 Jun 2026 10:58:47 +0200 Subject: [PATCH 2/5] Move helper method to outer scope --- .../src/main/resources/python/rest.mustache | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/rest.mustache b/modules/openapi-generator/src/main/resources/python/rest.mustache index f6fcec62dfa5..99ceb3a2f5eb 100644 --- a/modules/openapi-generator/src/main/resources/python/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/rest.mustache @@ -25,6 +25,9 @@ def is_socks_proxy_url(url): else: return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES +def contenttype_matches(contenttype, maintype, subtype): + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): @@ -108,10 +111,6 @@ class RESTClientObject: else: self.pool_manager = urllib3.PoolManager(**pool_args) - def _contenttype_matches(contenttype, maintype, subtype): - pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) - return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None - def request( self, method, @@ -175,7 +174,7 @@ class RESTClientObject: content_type = headers.get('Content-Type') if ( not content_type - or _contenttype_matches(content_type, 'application', 'json') + or contenttype_matches(content_type, 'application', 'json') ): request_body = None if body is not None: @@ -188,7 +187,7 @@ class RESTClientObject: headers=headers, preload_content=False ) - elif _contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): + elif contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): r = self.pool_manager.request( method, url, @@ -198,7 +197,7 @@ class RESTClientObject: headers=headers, preload_content=False ) - elif _contenttype_matches(content_type, 'multipart', 'form-data'): + elif contenttype_matches(content_type, 'multipart', 'form-data'): # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. From c112d336657ca26d0a38e421254322a2b3e06ee9 Mon Sep 17 00:00:00 2001 From: Marcel Konrad Date: Thu, 11 Jun 2026 11:02:26 +0200 Subject: [PATCH 3/5] Update examples --- .../openapi_client/rest.py | 11 +++++++---- samples/client/echo_api/python/openapi_client/rest.py | 11 +++++++---- .../petstore/python-lazyImports/petstore_api/rest.py | 11 +++++++---- .../client/petstore/python/petstore_api/rest.py | 11 +++++++---- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py index 4375566a583b..e69a1b10b6c5 100644 --- a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py +++ b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py @@ -35,6 +35,9 @@ def is_socks_proxy_url(url): else: return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES +def contenttype_matches(contenttype, maintype, subtype): + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): @@ -181,7 +184,7 @@ def request( content_type = headers.get('Content-Type') if ( not content_type - or re.search('json', content_type, re.IGNORECASE) + or contenttype_matches(content_type, 'application', 'json') ): request_body = None if body is not None: @@ -194,7 +197,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'application/x-www-form-urlencoded': + elif contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): r = self.pool_manager.request( method, url, @@ -204,7 +207,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'multipart/form-data': + elif contenttype_matches(content_type, 'multipart', 'form-data'): # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. @@ -232,7 +235,7 @@ def request( headers=headers, preload_content=False ) - elif headers['Content-Type'].startswith('text/') and isinstance(body, bool): + elif content_type.startswith('text/') and isinstance(body, bool): request_body = "true" if body else "false" r = self.pool_manager.request( method, diff --git a/samples/client/echo_api/python/openapi_client/rest.py b/samples/client/echo_api/python/openapi_client/rest.py index 7f61c8fe4ab0..29171aca8c37 100644 --- a/samples/client/echo_api/python/openapi_client/rest.py +++ b/samples/client/echo_api/python/openapi_client/rest.py @@ -35,6 +35,9 @@ def is_socks_proxy_url(url): else: return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES +def contenttype_matches(contenttype, maintype, subtype): + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): @@ -181,7 +184,7 @@ def request( content_type = headers.get('Content-Type') if ( not content_type - or re.search('json', content_type, re.IGNORECASE) + or contenttype_matches(content_type, 'application', 'json') ): request_body = None if body is not None: @@ -194,7 +197,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'application/x-www-form-urlencoded': + elif contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): r = self.pool_manager.request( method, url, @@ -204,7 +207,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'multipart/form-data': + elif contenttype_matches(content_type, 'multipart', 'form-data'): # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. @@ -232,7 +235,7 @@ def request( headers=headers, preload_content=False ) - elif headers['Content-Type'].startswith('text/') and isinstance(body, bool): + elif content_type.startswith('text/') and isinstance(body, bool): request_body = "true" if body else "false" r = self.pool_manager.request( method, diff --git a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py index 29ce7d04029a..71b21b952ff6 100644 --- a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py @@ -34,6 +34,9 @@ def is_socks_proxy_url(url): else: return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES +def contenttype_matches(contenttype, maintype, subtype): + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): @@ -180,7 +183,7 @@ def request( content_type = headers.get('Content-Type') if ( not content_type - or re.search('json', content_type, re.IGNORECASE) + or contenttype_matches(content_type, 'application', 'json') ): request_body = None if body is not None: @@ -193,7 +196,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'application/x-www-form-urlencoded': + elif contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): r = self.pool_manager.request( method, url, @@ -203,7 +206,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'multipart/form-data': + elif contenttype_matches(content_type, 'multipart', 'form-data'): # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. @@ -231,7 +234,7 @@ def request( headers=headers, preload_content=False ) - elif headers['Content-Type'].startswith('text/') and isinstance(body, bool): + elif content_type.startswith('text/') and isinstance(body, bool): request_body = "true" if body else "false" r = self.pool_manager.request( method, diff --git a/samples/openapi3/client/petstore/python/petstore_api/rest.py b/samples/openapi3/client/petstore/python/petstore_api/rest.py index 29ce7d04029a..71b21b952ff6 100755 --- a/samples/openapi3/client/petstore/python/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python/petstore_api/rest.py @@ -34,6 +34,9 @@ def is_socks_proxy_url(url): else: return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES +def contenttype_matches(contenttype, maintype, subtype): + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): @@ -180,7 +183,7 @@ def request( content_type = headers.get('Content-Type') if ( not content_type - or re.search('json', content_type, re.IGNORECASE) + or contenttype_matches(content_type, 'application', 'json') ): request_body = None if body is not None: @@ -193,7 +196,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'application/x-www-form-urlencoded': + elif contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'): r = self.pool_manager.request( method, url, @@ -203,7 +206,7 @@ def request( headers=headers, preload_content=False ) - elif content_type == 'multipart/form-data': + elif contenttype_matches(content_type, 'multipart', 'form-data'): # must del headers['Content-Type'], or the correct # Content-Type which generated by urllib3 will be # overwritten. @@ -231,7 +234,7 @@ def request( headers=headers, preload_content=False ) - elif headers['Content-Type'].startswith('text/') and isinstance(body, bool): + elif content_type.startswith('text/') and isinstance(body, bool): request_body = "true" if body else "false" r = self.pool_manager.request( method, From 4b7d1f07891e2d9ead1b190fdfd6507d90d09e81 Mon Sep 17 00:00:00 2001 From: Marcel Konrad Date: Mon, 15 Jun 2026 09:54:32 +0200 Subject: [PATCH 4/5] Allow optional whitespace before parameters and wrap function arguments --- .../src/main/resources/python/rest.mustache | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/resources/python/rest.mustache b/modules/openapi-generator/src/main/resources/python/rest.mustache index 99ceb3a2f5eb..e00ed4169473 100644 --- a/modules/openapi-generator/src/main/resources/python/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/rest.mustache @@ -26,7 +26,10 @@ def is_socks_proxy_url(url): return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES def contenttype_matches(contenttype, maintype, subtype): - pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:[ \t]*;.*)?'.format( + type = re.escape(maintype), + subtype = re.escape(subtype), + ) return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): From f34fbbfe52c0776a58ec8924d5230229d94a3176 Mon Sep 17 00:00:00 2001 From: Marcel Konrad Date: Mon, 15 Jun 2026 11:57:46 +0200 Subject: [PATCH 5/5] Update examples --- .../openapi_client/rest.py | 5 ++++- samples/client/echo_api/python/openapi_client/rest.py | 5 ++++- .../client/petstore/python-lazyImports/petstore_api/rest.py | 5 ++++- samples/openapi3/client/petstore/python/petstore_api/rest.py | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py index e69a1b10b6c5..829aa8246272 100644 --- a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py +++ b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py @@ -36,7 +36,10 @@ def is_socks_proxy_url(url): return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES def contenttype_matches(contenttype, maintype, subtype): - pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:[ \t]*;.*)?'.format( + type = re.escape(maintype), + subtype = re.escape(subtype), + ) return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): diff --git a/samples/client/echo_api/python/openapi_client/rest.py b/samples/client/echo_api/python/openapi_client/rest.py index 29171aca8c37..e1c4c95d6a45 100644 --- a/samples/client/echo_api/python/openapi_client/rest.py +++ b/samples/client/echo_api/python/openapi_client/rest.py @@ -36,7 +36,10 @@ def is_socks_proxy_url(url): return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES def contenttype_matches(contenttype, maintype, subtype): - pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:[ \t]*;.*)?'.format( + type = re.escape(maintype), + subtype = re.escape(subtype), + ) return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): diff --git a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py index 71b21b952ff6..a1a8166e1b65 100644 --- a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py @@ -35,7 +35,10 @@ def is_socks_proxy_url(url): return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES def contenttype_matches(contenttype, maintype, subtype): - pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:[ \t]*;.*)?'.format( + type = re.escape(maintype), + subtype = re.escape(subtype), + ) return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase): diff --git a/samples/openapi3/client/petstore/python/petstore_api/rest.py b/samples/openapi3/client/petstore/python/petstore_api/rest.py index 71b21b952ff6..a1a8166e1b65 100755 --- a/samples/openapi3/client/petstore/python/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python/petstore_api/rest.py @@ -35,7 +35,10 @@ def is_socks_proxy_url(url): return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES def contenttype_matches(contenttype, maintype, subtype): - pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:;.*)?'.format(type = re.escape(maintype), subtype = re.escape(subtype)) + pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:[ \t]*;.*)?'.format( + type = re.escape(maintype), + subtype = re.escape(subtype), + ) return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None class RESTResponse(io.IOBase):