From 22eeb20689ccfbaafe07929a8afa7a4d4cf618ae Mon Sep 17 00:00:00 2001 From: Yuriy Novostavskiy Date: Sun, 16 Aug 2026 12:41:44 +0200 Subject: [PATCH 1/2] [python] re-resolve the environment proxy when the host is assigned The scheme-specific proxy is read in __init__ from self.host, which is empty for specs without a server URL until the caller assigns one. A host set after construction therefore never matched HTTP_PROXY or HTTPS_PROXY and requests went direct; only ALL_PROXY worked. Re-resolve in the host setter when the proxy came from the environment. An assigned proxy still wins. --- .../resources/python/configuration.mustache | 30 +++++++-- .../openapi_client/configuration.py | 23 ++++++- .../python/openapi_client/configuration.py | 23 ++++++- .../legacy_model_dict_client/configuration.py | 23 ++++++- .../petstore_api/configuration.py | 23 ++++++- .../python/petstore_api/configuration.py | 23 ++++++- .../python/tests/test_configuration.py | 62 +++++++++++++++++++ 7 files changed, 188 insertions(+), 19 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/configuration.mustache b/modules/openapi-generator/src/main/resources/python/configuration.mustache index 7019fa6c7dd2..2864ee04d94b 100644 --- a/modules/openapi-generator/src/main/resources/python/configuration.mustache +++ b/modules/openapi-generator/src/main/resources/python/configuration.mustache @@ -495,6 +495,9 @@ conf = {{{packageName}}}.Configuration( {{^async}} # urllib3 does not read proxy environment variables itself: # https://github.com/urllib3/urllib3/issues/1785 + # A proxy taken from the environment is re-resolved when the host is + # assigned; see the host setter. + self._proxy_from_env = proxy is None if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: @@ -502,14 +505,15 @@ conf = {{{packageName}}}.Configuration( proxy = proxies.get(scheme) or proxies.get("all") if no_proxy is None: no_proxy = proxies.get("no") + self._proxy = proxy + self.no_proxy = no_proxy + """Hosts that bypass the proxy + """ {{/async}} +{{#async}} self.proxy = proxy """Proxy URL """ -{{^async}} - self.no_proxy = no_proxy - """Hosts that bypass the proxy - """ {{/async}} self.proxy_headers = proxy_headers """Proxy headers @@ -991,3 +995,21 @@ conf = {{{packageName}}}.Configuration( """Fix base path.""" self._base_path = value self.server_index = None +{{^async}} + if self._proxy_from_env: + # the scheme-specific proxy depends on the host, which is + # commonly assigned after construction + proxies = getproxies() + self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + + @property + def proxy(self) -> Optional[str]: + """Proxy URL + """ + return self._proxy + + @proxy.setter + def proxy(self, value: Optional[str]) -> None: + self._proxy = value + self._proxy_from_env = False +{{/async}} diff --git a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py index 2bbf005e307a..464489500a0a 100644 --- a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py +++ b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py @@ -337,6 +337,9 @@ def __init__( # urllib3 does not read proxy environment variables itself: # https://github.com/urllib3/urllib3/issues/1785 + # A proxy taken from the environment is re-resolved when the host is + # assigned; see the host setter. + self._proxy_from_env = proxy is None if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: @@ -344,9 +347,7 @@ def __init__( proxy = proxies.get(scheme) or proxies.get("all") if no_proxy is None: no_proxy = proxies.get("no") - self.proxy = proxy - """Proxy URL - """ + self._proxy = proxy self.no_proxy = no_proxy """Hosts that bypass the proxy """ @@ -657,3 +658,19 @@ def host(self, value: str) -> None: """Fix base path.""" self._base_path = value self.server_index = None + if self._proxy_from_env: + # the scheme-specific proxy depends on the host, which is + # commonly assigned after construction + proxies = getproxies() + self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + + @property + def proxy(self) -> Optional[str]: + """Proxy URL + """ + return self._proxy + + @proxy.setter + def proxy(self, value: Optional[str]) -> None: + self._proxy = value + self._proxy_from_env = False diff --git a/samples/client/echo_api/python/openapi_client/configuration.py b/samples/client/echo_api/python/openapi_client/configuration.py index 2bbf005e307a..464489500a0a 100644 --- a/samples/client/echo_api/python/openapi_client/configuration.py +++ b/samples/client/echo_api/python/openapi_client/configuration.py @@ -337,6 +337,9 @@ def __init__( # urllib3 does not read proxy environment variables itself: # https://github.com/urllib3/urllib3/issues/1785 + # A proxy taken from the environment is re-resolved when the host is + # assigned; see the host setter. + self._proxy_from_env = proxy is None if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: @@ -344,9 +347,7 @@ def __init__( proxy = proxies.get(scheme) or proxies.get("all") if no_proxy is None: no_proxy = proxies.get("no") - self.proxy = proxy - """Proxy URL - """ + self._proxy = proxy self.no_proxy = no_proxy """Hosts that bypass the proxy """ @@ -657,3 +658,19 @@ def host(self, value: str) -> None: """Fix base path.""" self._base_path = value self.server_index = None + if self._proxy_from_env: + # the scheme-specific proxy depends on the host, which is + # commonly assigned after construction + proxies = getproxies() + self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + + @property + def proxy(self) -> Optional[str]: + """Proxy URL + """ + return self._proxy + + @proxy.setter + def proxy(self, value: Optional[str]) -> None: + self._proxy = value + self._proxy_from_env = False diff --git a/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py b/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py index 8e689e45eb90..f1dba3a880c1 100644 --- a/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py +++ b/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py @@ -317,6 +317,9 @@ def __init__( # urllib3 does not read proxy environment variables itself: # https://github.com/urllib3/urllib3/issues/1785 + # A proxy taken from the environment is re-resolved when the host is + # assigned; see the host setter. + self._proxy_from_env = proxy is None if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: @@ -324,9 +327,7 @@ def __init__( proxy = proxies.get(scheme) or proxies.get("all") if no_proxy is None: no_proxy = proxies.get("no") - self.proxy = proxy - """Proxy URL - """ + self._proxy = proxy self.no_proxy = no_proxy """Hosts that bypass the proxy """ @@ -623,3 +624,19 @@ def host(self, value: str) -> None: """Fix base path.""" self._base_path = value self.server_index = None + if self._proxy_from_env: + # the scheme-specific proxy depends on the host, which is + # commonly assigned after construction + proxies = getproxies() + self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + + @property + def proxy(self) -> Optional[str]: + """Proxy URL + """ + return self._proxy + + @proxy.setter + def proxy(self, value: Optional[str]) -> None: + self._proxy = value + self._proxy_from_env = False diff --git a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py index 8f0b80ef4aed..4078b33f9d7c 100644 --- a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py +++ b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py @@ -407,6 +407,9 @@ def __init__( # urllib3 does not read proxy environment variables itself: # https://github.com/urllib3/urllib3/issues/1785 + # A proxy taken from the environment is re-resolved when the host is + # assigned; see the host setter. + self._proxy_from_env = proxy is None if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: @@ -414,9 +417,7 @@ def __init__( proxy = proxies.get(scheme) or proxies.get("all") if no_proxy is None: no_proxy = proxies.get("no") - self.proxy = proxy - """Proxy URL - """ + self._proxy = proxy self.no_proxy = no_proxy """Hosts that bypass the proxy """ @@ -805,3 +806,19 @@ def host(self, value: str) -> None: """Fix base path.""" self._base_path = value self.server_index = None + if self._proxy_from_env: + # the scheme-specific proxy depends on the host, which is + # commonly assigned after construction + proxies = getproxies() + self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + + @property + def proxy(self) -> Optional[str]: + """Proxy URL + """ + return self._proxy + + @proxy.setter + def proxy(self, value: Optional[str]) -> None: + self._proxy = value + self._proxy_from_env = False diff --git a/samples/openapi3/client/petstore/python/petstore_api/configuration.py b/samples/openapi3/client/petstore/python/petstore_api/configuration.py index fcbc987747b6..fbb89d5e74ef 100755 --- a/samples/openapi3/client/petstore/python/petstore_api/configuration.py +++ b/samples/openapi3/client/petstore/python/petstore_api/configuration.py @@ -407,6 +407,9 @@ def __init__( # urllib3 does not read proxy environment variables itself: # https://github.com/urllib3/urllib3/issues/1785 + # A proxy taken from the environment is re-resolved when the host is + # assigned; see the host setter. + self._proxy_from_env = proxy is None if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: @@ -414,9 +417,7 @@ def __init__( proxy = proxies.get(scheme) or proxies.get("all") if no_proxy is None: no_proxy = proxies.get("no") - self.proxy = proxy - """Proxy URL - """ + self._proxy = proxy self.no_proxy = no_proxy """Hosts that bypass the proxy """ @@ -801,3 +802,19 @@ def host(self, value: str) -> None: """Fix base path.""" self._base_path = value self.server_index = None + if self._proxy_from_env: + # the scheme-specific proxy depends on the host, which is + # commonly assigned after construction + proxies = getproxies() + self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + + @property + def proxy(self) -> Optional[str]: + """Proxy URL + """ + return self._proxy + + @proxy.setter + def proxy(self, value: Optional[str]) -> None: + self._proxy = value + self._proxy_from_env = False diff --git a/samples/openapi3/client/petstore/python/tests/test_configuration.py b/samples/openapi3/client/petstore/python/tests/test_configuration.py index 3dfa32ae4f99..146900e92a5d 100644 --- a/samples/openapi3/client/petstore/python/tests/test_configuration.py +++ b/samples/openapi3/client/petstore/python/tests/test_configuration.py @@ -101,6 +101,68 @@ def test_explicit_proxy_does_not_resolve_generated_host(self): self.assertEqual(config.proxy, "http://proxy.example") + def test_proxy_settings_follow_host_assigned_after_construction(self): + environment_proxies = { + "http": "http://plain-proxy.example", + "https": "http://secure-proxy.example", + } + with patch( + "petstore_api.configuration.getproxies", + return_value=environment_proxies, + ): + config = petstore_api.Configuration() + + config.host = "https://api.example" + self.assertEqual(config.proxy, "http://secure-proxy.example") + + config.host = "http://api.example" + self.assertEqual(config.proxy, "http://plain-proxy.example") + + def test_proxy_settings_resolve_for_a_host_unknown_at_construction(self): + # a spec without a server URL leaves the host empty until the caller + # assigns one, which is when the scheme first becomes known + with patch( + "petstore_api.configuration.getproxies", + return_value={"https": "http://secure-proxy.example"}, + ): + config = petstore_api.Configuration(host="") + self.assertIsNone(config.proxy) + + config.host = "https://api.example" + self.assertEqual(config.proxy, "http://secure-proxy.example") + + def test_assigned_proxy_survives_later_host_assignment(self): + with patch( + "petstore_api.configuration.getproxies", + return_value={"https": "http://secure-proxy.example"}, + ): + config = petstore_api.Configuration() + config.proxy = "http://assigned-proxy.example" + config.host = "https://api.example" + + self.assertEqual(config.proxy, "http://assigned-proxy.example") + + def test_assigned_none_proxy_disables_the_environment(self): + with patch( + "petstore_api.configuration.getproxies", + return_value={"https": "http://secure-proxy.example"}, + ): + config = petstore_api.Configuration() + config.proxy = None + config.host = "https://api.example" + + self.assertIsNone(config.proxy) + + def test_assigned_host_falls_back_to_the_all_proxy(self): + with patch( + "petstore_api.configuration.getproxies", + return_value={"all": "http://any-proxy.example"}, + ): + config = petstore_api.Configuration() + config.host = "https://api.example" + + self.assertEqual(config.proxy, "http://any-proxy.example") + def test_ignore_operation_servers(self): self.config.ignore_operation_servers = True self.assertTrue(self.config.ignore_operation_servers) From 2437d2fdf8f2870a39d3e3dbba5ba494793a07cf Mon Sep 17 00:00:00 2001 From: Yuriy Novostavskiy Date: Sun, 16 Aug 2026 13:20:11 +0200 Subject: [PATCH 2/2] [python] extract the environment proxy lookup The scheme lookup and the ALL_PROXY fallback were spelled out both in __init__ and in the host setter. Keep the rule in one place so the two cannot drift. --- .../src/main/resources/python/configuration.mustache | 11 +++++++---- .../openapi_client/configuration.py | 11 +++++++---- .../echo_api/python/openapi_client/configuration.py | 11 +++++++---- .../legacy_model_dict_client/configuration.py | 11 +++++++---- .../python-lazyImports/petstore_api/configuration.py | 11 +++++++---- .../petstore/python/petstore_api/configuration.py | 11 +++++++---- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python/configuration.mustache b/modules/openapi-generator/src/main/resources/python/configuration.mustache index 2864ee04d94b..81d1d8da60d9 100644 --- a/modules/openapi-generator/src/main/resources/python/configuration.mustache +++ b/modules/openapi-generator/src/main/resources/python/configuration.mustache @@ -501,8 +501,7 @@ conf = {{{packageName}}}.Configuration( if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: - scheme = urlparse(self.host).scheme - proxy = proxies.get(scheme) or proxies.get("all") + proxy = self._env_proxy(proxies, self.host) if no_proxy is None: no_proxy = proxies.get("no") self._proxy = proxy @@ -999,8 +998,12 @@ conf = {{{packageName}}}.Configuration( if self._proxy_from_env: # the scheme-specific proxy depends on the host, which is # commonly assigned after construction - proxies = getproxies() - self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + self._proxy = self._env_proxy(getproxies(), value) + + @staticmethod + def _env_proxy(proxies: Dict[str, str], host: str) -> Optional[str]: + """Pick the environment proxy that applies to `host`.""" + return proxies.get(urlparse(host).scheme) or proxies.get("all") @property def proxy(self) -> Optional[str]: diff --git a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py index 464489500a0a..f2411fe6bd9d 100644 --- a/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py +++ b/samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/configuration.py @@ -343,8 +343,7 @@ def __init__( if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: - scheme = urlparse(self.host).scheme - proxy = proxies.get(scheme) or proxies.get("all") + proxy = self._env_proxy(proxies, self.host) if no_proxy is None: no_proxy = proxies.get("no") self._proxy = proxy @@ -661,8 +660,12 @@ def host(self, value: str) -> None: if self._proxy_from_env: # the scheme-specific proxy depends on the host, which is # commonly assigned after construction - proxies = getproxies() - self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + self._proxy = self._env_proxy(getproxies(), value) + + @staticmethod + def _env_proxy(proxies: Dict[str, str], host: str) -> Optional[str]: + """Pick the environment proxy that applies to `host`.""" + return proxies.get(urlparse(host).scheme) or proxies.get("all") @property def proxy(self) -> Optional[str]: diff --git a/samples/client/echo_api/python/openapi_client/configuration.py b/samples/client/echo_api/python/openapi_client/configuration.py index 464489500a0a..f2411fe6bd9d 100644 --- a/samples/client/echo_api/python/openapi_client/configuration.py +++ b/samples/client/echo_api/python/openapi_client/configuration.py @@ -343,8 +343,7 @@ def __init__( if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: - scheme = urlparse(self.host).scheme - proxy = proxies.get(scheme) or proxies.get("all") + proxy = self._env_proxy(proxies, self.host) if no_proxy is None: no_proxy = proxies.get("no") self._proxy = proxy @@ -661,8 +660,12 @@ def host(self, value: str) -> None: if self._proxy_from_env: # the scheme-specific proxy depends on the host, which is # commonly assigned after construction - proxies = getproxies() - self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + self._proxy = self._env_proxy(getproxies(), value) + + @staticmethod + def _env_proxy(proxies: Dict[str, str], host: str) -> Optional[str]: + """Pick the environment proxy that applies to `host`.""" + return proxies.get(urlparse(host).scheme) or proxies.get("all") @property def proxy(self) -> Optional[str]: diff --git a/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py b/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py index f1dba3a880c1..b99862654f04 100644 --- a/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py +++ b/samples/client/others/python-legacy-model-dictionaries/legacy_model_dict_client/configuration.py @@ -323,8 +323,7 @@ def __init__( if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: - scheme = urlparse(self.host).scheme - proxy = proxies.get(scheme) or proxies.get("all") + proxy = self._env_proxy(proxies, self.host) if no_proxy is None: no_proxy = proxies.get("no") self._proxy = proxy @@ -627,8 +626,12 @@ def host(self, value: str) -> None: if self._proxy_from_env: # the scheme-specific proxy depends on the host, which is # commonly assigned after construction - proxies = getproxies() - self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + self._proxy = self._env_proxy(getproxies(), value) + + @staticmethod + def _env_proxy(proxies: Dict[str, str], host: str) -> Optional[str]: + """Pick the environment proxy that applies to `host`.""" + return proxies.get(urlparse(host).scheme) or proxies.get("all") @property def proxy(self) -> Optional[str]: diff --git a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py index 4078b33f9d7c..7e9c9d177921 100644 --- a/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py +++ b/samples/openapi3/client/petstore/python-lazyImports/petstore_api/configuration.py @@ -413,8 +413,7 @@ def __init__( if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: - scheme = urlparse(self.host).scheme - proxy = proxies.get(scheme) or proxies.get("all") + proxy = self._env_proxy(proxies, self.host) if no_proxy is None: no_proxy = proxies.get("no") self._proxy = proxy @@ -809,8 +808,12 @@ def host(self, value: str) -> None: if self._proxy_from_env: # the scheme-specific proxy depends on the host, which is # commonly assigned after construction - proxies = getproxies() - self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + self._proxy = self._env_proxy(getproxies(), value) + + @staticmethod + def _env_proxy(proxies: Dict[str, str], host: str) -> Optional[str]: + """Pick the environment proxy that applies to `host`.""" + return proxies.get(urlparse(host).scheme) or proxies.get("all") @property def proxy(self) -> Optional[str]: diff --git a/samples/openapi3/client/petstore/python/petstore_api/configuration.py b/samples/openapi3/client/petstore/python/petstore_api/configuration.py index fbb89d5e74ef..3fdb5641b4d3 100755 --- a/samples/openapi3/client/petstore/python/petstore_api/configuration.py +++ b/samples/openapi3/client/petstore/python/petstore_api/configuration.py @@ -413,8 +413,7 @@ def __init__( if proxy is None or no_proxy is None: proxies = getproxies() if proxy is None: - scheme = urlparse(self.host).scheme - proxy = proxies.get(scheme) or proxies.get("all") + proxy = self._env_proxy(proxies, self.host) if no_proxy is None: no_proxy = proxies.get("no") self._proxy = proxy @@ -805,8 +804,12 @@ def host(self, value: str) -> None: if self._proxy_from_env: # the scheme-specific proxy depends on the host, which is # commonly assigned after construction - proxies = getproxies() - self._proxy = proxies.get(urlparse(value).scheme) or proxies.get("all") + self._proxy = self._env_proxy(getproxies(), value) + + @staticmethod + def _env_proxy(proxies: Dict[str, str], host: str) -> Optional[str]: + """Pick the environment proxy that applies to `host`.""" + return proxies.get(urlparse(host).scheme) or proxies.get("all") @property def proxy(self) -> Optional[str]: