From 997dd5f7481e30b6bde9f9faab4705e3aeb23ddf Mon Sep 17 00:00:00 2001 From: Abdessalam ZAIMI Date: Fri, 15 May 2026 22:42:08 +0200 Subject: [PATCH 1/2] feat: add SOCKS proxy support via aiohttp-socks ProxyConnector --- .../resources/python/asyncio/rest.mustache | 33 +++++++++++++++---- .../src/main/resources/python/rest.mustache | 16 ++------- .../openapi_client/rest.py | 16 ++------- .../echo_api/python/openapi_client/rest.py | 16 ++------- .../python-aiohttp/petstore_api/rest.py | 33 +++++++++++++++---- .../python-lazyImports/petstore_api/rest.py | 16 ++------- .../petstore/python/petstore_api/rest.py | 16 ++------- 7 files changed, 69 insertions(+), 77 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache b/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache index 6945dfd4fc95..5c12d5d74306 100644 --- a/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache @@ -17,6 +17,8 @@ from {{packageName}}.exceptions import ApiException, ApiValueError RESTResponseType = aiohttp.ClientResponse ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'}) +SUPPORTED_SOCKS_PROXIES = frozenset({'socks4', 'socks4a', 'socks5', 'socks5h'}) + class RESTResponse(io.IOBase): @@ -65,8 +67,12 @@ class RESTClientObject: self.ssl_context.check_hostname = False self.ssl_context.verify_mode = ssl.CERT_NONE - self.proxy = configuration.proxy + proxy = configuration.proxy + proxy_scheme = (proxy or "").partition("://")[0].lower() + + self.proxy = proxy self.proxy_headers = configuration.proxy_headers + self.is_socks_proxy = proxy_scheme in SUPPORTED_SOCKS_PROXIES retries = configuration.retries if retries is None: @@ -146,9 +152,9 @@ class RESTClientObject: "headers": headers } - if self.proxy: + if self.proxy and not self.is_socks_proxy: args["proxy"] = self.proxy - if self.proxy_headers: + if self.proxy_headers and not self.is_socks_proxy: args["proxy_headers"] = self.proxy_headers # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` @@ -198,10 +204,25 @@ class RESTClientObject: pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient] - # https pool manager + # https pool manager - created lazily inside async context to avoid + # event loop issues with aiohttp 3.10+ if self.pool_manager is None: + if self.is_socks_proxy: + # SOCKS proxies require ProxyConnector - aiohttp TCPConnector + # does not support SOCKS schemes + from aiohttp_socks import ProxyConnector + connector = ProxyConnector.from_url( + self.proxy, + limit=self.maxsize, + ssl=self.ssl_context, + ) + else: + connector = aiohttp.TCPConnector( + limit=self.maxsize, + ssl=self.ssl_context, + ) self.pool_manager = aiohttp.ClientSession( - connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context), + connector=connector, trust_env=True, ) pool_manager = self.pool_manager @@ -216,4 +237,4 @@ class RESTClientObject: r = await pool_manager.request(**args) - return RESTResponse(r) + return RESTResponse(r) \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python/rest.mustache b/modules/openapi-generator/src/main/resources/python/rest.mustache index 20176e7140f1..4a569f67e39b 100644 --- a/modules/openapi-generator/src/main/resources/python/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/rest.mustache @@ -12,19 +12,9 @@ import urllib3 from {{packageName}}.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"} RESTResponseType = urllib3.HTTPResponse - -def is_socks_proxy_url(url): - if url is None: - return False - split_section = url.split("://") - if len(split_section) < 2: - return False - else: - return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES - +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) class RESTResponse(io.IOBase): @@ -85,7 +75,6 @@ class RESTClientObject: if configuration.tls_server_name: pool_args['server_hostname'] = configuration.tls_server_name - if configuration.socket_options is not None: pool_args['socket_options'] = configuration.socket_options @@ -96,7 +85,8 @@ class RESTClientObject: self.pool_manager: urllib3.PoolManager if configuration.proxy: - if is_socks_proxy_url(configuration.proxy): + proxy_scheme = configuration.proxy.partition("://")[0].lower() + if proxy_scheme in SUPPORTED_SOCKS_PROXIES: from urllib3.contrib.socks import SOCKSProxyManager pool_args["proxy_url"] = configuration.proxy pool_args["headers"] = configuration.proxy_headers 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..9aa6b2dbc82b 100644 --- a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py +++ b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py @@ -22,20 +22,10 @@ from openapi_client.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"} +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse -def is_socks_proxy_url(url): - if url is None: - return False - split_section = url.split("://") - if len(split_section) < 2: - return False - else: - return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES - - class RESTResponse(io.IOBase): def __init__(self, resp) -> None: @@ -95,7 +85,6 @@ def __init__(self, configuration) -> None: if configuration.tls_server_name: pool_args['server_hostname'] = configuration.tls_server_name - if configuration.socket_options is not None: pool_args['socket_options'] = configuration.socket_options @@ -106,7 +95,8 @@ def __init__(self, configuration) -> None: self.pool_manager: urllib3.PoolManager if configuration.proxy: - if is_socks_proxy_url(configuration.proxy): + proxy_scheme = configuration.proxy.partition("://")[0].lower() + if proxy_scheme in SUPPORTED_SOCKS_PROXIES: from urllib3.contrib.socks import SOCKSProxyManager pool_args["proxy_url"] = configuration.proxy pool_args["headers"] = configuration.proxy_headers diff --git a/samples/client/echo_api/python/openapi_client/rest.py b/samples/client/echo_api/python/openapi_client/rest.py index 7f61c8fe4ab0..3f76c28ef689 100644 --- a/samples/client/echo_api/python/openapi_client/rest.py +++ b/samples/client/echo_api/python/openapi_client/rest.py @@ -22,20 +22,10 @@ from openapi_client.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"} +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse -def is_socks_proxy_url(url): - if url is None: - return False - split_section = url.split("://") - if len(split_section) < 2: - return False - else: - return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES - - class RESTResponse(io.IOBase): def __init__(self, resp) -> None: @@ -95,7 +85,6 @@ def __init__(self, configuration) -> None: if configuration.tls_server_name: pool_args['server_hostname'] = configuration.tls_server_name - if configuration.socket_options is not None: pool_args['socket_options'] = configuration.socket_options @@ -106,7 +95,8 @@ def __init__(self, configuration) -> None: self.pool_manager: urllib3.PoolManager if configuration.proxy: - if is_socks_proxy_url(configuration.proxy): + proxy_scheme = configuration.proxy.partition("://")[0].lower() + if proxy_scheme in SUPPORTED_SOCKS_PROXIES: from urllib3.contrib.socks import SOCKSProxyManager pool_args["proxy_url"] = configuration.proxy pool_args["headers"] = configuration.proxy_headers diff --git a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py index 89481af0d71d..16e8db18af5b 100644 --- a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py @@ -26,6 +26,8 @@ RESTResponseType = aiohttp.ClientResponse ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'}) +SUPPORTED_SOCKS_PROXIES = frozenset({'socks4', 'socks4a', 'socks5', 'socks5h'}) + class RESTResponse(io.IOBase): @@ -74,8 +76,12 @@ def __init__(self, configuration) -> None: self.ssl_context.check_hostname = False self.ssl_context.verify_mode = ssl.CERT_NONE - self.proxy = configuration.proxy + proxy = configuration.proxy + proxy_scheme = (proxy or "").partition("://")[0].lower() + + self.proxy = proxy self.proxy_headers = configuration.proxy_headers + self.is_socks_proxy = proxy_scheme in SUPPORTED_SOCKS_PROXIES retries = configuration.retries if retries is None: @@ -155,9 +161,9 @@ async def request( "headers": headers } - if self.proxy: + if self.proxy and not self.is_socks_proxy: args["proxy"] = self.proxy - if self.proxy_headers: + if self.proxy_headers and not self.is_socks_proxy: args["proxy_headers"] = self.proxy_headers # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` @@ -207,10 +213,25 @@ async def request( pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient] - # https pool manager + # https pool manager - created lazily inside async context to avoid + # event loop issues with aiohttp 3.10+ if self.pool_manager is None: + if self.is_socks_proxy: + # SOCKS proxies require ProxyConnector - aiohttp TCPConnector + # does not support SOCKS schemes + from aiohttp_socks import ProxyConnector + connector = ProxyConnector.from_url( + self.proxy, + limit=self.maxsize, + ssl=self.ssl_context, + ) + else: + connector = aiohttp.TCPConnector( + limit=self.maxsize, + ssl=self.ssl_context, + ) self.pool_manager = aiohttp.ClientSession( - connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context), + connector=connector, trust_env=True, ) pool_manager = self.pool_manager @@ -225,4 +246,4 @@ async def request( r = await pool_manager.request(**args) - return RESTResponse(r) + return RESTResponse(r) \ No newline at end of file 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..f4dac245ddf7 100644 --- a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py @@ -21,20 +21,10 @@ from petstore_api.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"} +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse -def is_socks_proxy_url(url): - if url is None: - return False - split_section = url.split("://") - if len(split_section) < 2: - return False - else: - return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES - - class RESTResponse(io.IOBase): def __init__(self, resp) -> None: @@ -94,7 +84,6 @@ def __init__(self, configuration) -> None: if configuration.tls_server_name: pool_args['server_hostname'] = configuration.tls_server_name - if configuration.socket_options is not None: pool_args['socket_options'] = configuration.socket_options @@ -105,7 +94,8 @@ def __init__(self, configuration) -> None: self.pool_manager: urllib3.PoolManager if configuration.proxy: - if is_socks_proxy_url(configuration.proxy): + proxy_scheme = configuration.proxy.partition("://")[0].lower() + if proxy_scheme in SUPPORTED_SOCKS_PROXIES: from urllib3.contrib.socks import SOCKSProxyManager pool_args["proxy_url"] = configuration.proxy pool_args["headers"] = configuration.proxy_headers diff --git a/samples/openapi3/client/petstore/python/petstore_api/rest.py b/samples/openapi3/client/petstore/python/petstore_api/rest.py index 29ce7d04029a..f4dac245ddf7 100755 --- a/samples/openapi3/client/petstore/python/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python/petstore_api/rest.py @@ -21,20 +21,10 @@ from petstore_api.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"} +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse -def is_socks_proxy_url(url): - if url is None: - return False - split_section = url.split("://") - if len(split_section) < 2: - return False - else: - return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES - - class RESTResponse(io.IOBase): def __init__(self, resp) -> None: @@ -94,7 +84,6 @@ def __init__(self, configuration) -> None: if configuration.tls_server_name: pool_args['server_hostname'] = configuration.tls_server_name - if configuration.socket_options is not None: pool_args['socket_options'] = configuration.socket_options @@ -105,7 +94,8 @@ def __init__(self, configuration) -> None: self.pool_manager: urllib3.PoolManager if configuration.proxy: - if is_socks_proxy_url(configuration.proxy): + proxy_scheme = configuration.proxy.partition("://")[0].lower() + if proxy_scheme in SUPPORTED_SOCKS_PROXIES: from urllib3.contrib.socks import SOCKSProxyManager pool_args["proxy_url"] = configuration.proxy pool_args["headers"] = configuration.proxy_headers From 88087351607b0c74e9c471a8db1bc385e595c6a9 Mon Sep 17 00:00:00 2001 From: Abdessalam ZAIMI Date: Sat, 16 May 2026 10:17:22 +0200 Subject: [PATCH 2/2] refactor: removing unnecessary comments --- .../src/main/resources/python/asyncio/rest.mustache | 3 +-- .../openapi_client/rest.py | 2 +- samples/client/echo_api/python/openapi_client/rest.py | 2 +- .../client/petstore/python-aiohttp/petstore_api/rest.py | 3 +-- .../client/petstore/python-lazyImports/petstore_api/rest.py | 2 +- samples/openapi3/client/petstore/python/petstore_api/rest.py | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache b/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache index 5c12d5d74306..ec9eda6b1d8f 100644 --- a/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache +++ b/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache @@ -204,8 +204,7 @@ class RESTClientObject: pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient] - # https pool manager - created lazily inside async context to avoid - # event loop issues with aiohttp 3.10+ + # https pool manager if self.pool_manager is None: if self.is_socks_proxy: # SOCKS proxies require ProxyConnector - aiohttp TCPConnector 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 9aa6b2dbc82b..ae44f0601030 100644 --- a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py +++ b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/rest.py @@ -22,9 +22,9 @@ from openapi_client.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) 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 3f76c28ef689..2517f3977909 100644 --- a/samples/client/echo_api/python/openapi_client/rest.py +++ b/samples/client/echo_api/python/openapi_client/rest.py @@ -22,9 +22,9 @@ from openapi_client.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) class RESTResponse(io.IOBase): diff --git a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py index 16e8db18af5b..aab8411ea20d 100644 --- a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/rest.py @@ -213,8 +213,7 @@ async def request( pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient] - # https pool manager - created lazily inside async context to avoid - # event loop issues with aiohttp 3.10+ + # https pool manager if self.pool_manager is None: if self.is_socks_proxy: # SOCKS proxies require ProxyConnector - aiohttp TCPConnector 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 f4dac245ddf7..7e22d76d4eb4 100644 --- a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/rest.py @@ -21,9 +21,9 @@ from petstore_api.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) 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 f4dac245ddf7..7e22d76d4eb4 100755 --- a/samples/openapi3/client/petstore/python/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python/petstore_api/rest.py @@ -21,9 +21,9 @@ from petstore_api.exceptions import ApiException, ApiValueError -SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) RESTResponseType = urllib3.HTTPResponse +SUPPORTED_SOCKS_PROXIES = frozenset({"socks5", "socks5h", "socks4", "socks4a"}) class RESTResponse(io.IOBase):