Repository: boto/botocore
Pull request #1228 (2017-06-22 20:57:12)

Allow proxies to be set through the Config object.
Allows setting the proxy servers to be used through the Config object
when creating the client.

Refs: boto/boto3#338


Patch:
diff --git a/botocore/args.py b/botocore/args.py
index 9e69a5385..49af1c7b8 100644
--- a/botocore/args.py
+++ b/botocore/args.py
@@ -75,6 +75,7 @@ class ClientArgsCreator(object):
             endpoint_url=endpoint_config['endpoint_url'], verify=verify,
             response_parser_factory=self._response_parser_factory,
             max_pool_connections=new_config.max_pool_connections,
+            proxies=new_config.proxies,
             timeout=(new_config.connect_timeout, new_config.read_timeout))
 
         serializer = botocore.serialize.create_serializer(
@@ -129,6 +130,7 @@ class ClientArgsCreator(object):
                 connect_timeout=client_config.connect_timeout,
                 read_timeout=client_config.read_timeout,
                 max_pool_connections=client_config.max_pool_connections,
+                proxies=client_config.proxies,
             )
         s3_config = self.compute_s3_config(scoped_config,
                                            client_config)
diff --git a/botocore/config.py b/botocore/config.py
index 8c136aa70..b318941ec 100644
--- a/botocore/config.py
+++ b/botocore/config.py
@@ -54,6 +54,12 @@ class Config(object):
         keep in a connection pool.  If this value is not set, the default
         value of 10 is used.
 
+    :type proxies: dict
+    :param proxies: A dictionary of proxy servers to use by protocol or
+        endpoint, e.g.:
+        {'http': 'foo.bar:3128', 'http://hostname': 'foo.bar:4012'}.
+        The proxies are used on each request.
+
     :type s3: dict
     :param s3: A dictionary of s3 specific configurations.
         Valid keys are:
@@ -92,6 +98,7 @@ class Config(object):
         ('read_timeout', DEFAULT_TIMEOUT),
         ('parameter_validation', True),
         ('max_pool_connections', MAX_POOL_CONNECTIONS),
+        ('proxies', None),
         ('s3', None)
     ])
 
diff --git a/botocore/endpoint.py b/botocore/endpoint.py
index 228d43d43..64ccfbd2c 100644
--- a/botocore/endpoint.py
+++ b/botocore/endpoint.py
@@ -266,15 +266,18 @@ class EndpointCreator(object):
     def create_endpoint(self, service_model, region_name, endpoint_url,
                         verify=None, response_parser_factory=None,
                         timeout=DEFAULT_TIMEOUT,
-                        max_pool_connections=MAX_POOL_CONNECTIONS):
+                        max_pool_connections=MAX_POOL_CONNECTIONS,
+                        proxies=None):
         if not is_valid_endpoint_url(endpoint_url):
 
             raise ValueError("Invalid endpoint: %s" % endpoint_url)
+        if proxies is None:
+            proxies = self._get_proxies(endpoint_url)
         return Endpoint(
             endpoint_url,
             endpoint_prefix=service_model.endpoint_prefix,
             event_emitter=self._event_emitter,
-            proxies=self._get_proxies(endpoint_url),
+            proxies=proxies,
             verify=self._get_verify_value(verify),
             timeout=timeout,
             max_pool_connections=max_pool_connections,


Test patch:
diff --git a/tests/unit/test_args.py b/tests/unit/test_args.py
index 55cd0e369..af04e610a 100644
--- a/tests/unit/test_args.py
+++ b/tests/unit/test_args.py
@@ -128,7 +128,35 @@ class TestCreateClientArgs(unittest.TestCase):
             m.return_value.create_endpoint.assert_called_with(
                 mock.ANY, endpoint_url='https://ec2/', region_name='us-west-2',
                 response_parser_factory=None, timeout=(60, 60), verify=True,
-                max_pool_connections=20
+                max_pool_connections=20, proxies=None
+            )
+
+    def test_proxies_from_client_config_forwarded_to_endpoint_creator(self):
+        args_create = args.ClientArgsCreator(
+            mock.Mock(), None, None, None, None)
+        proxies = {'http': 'http://foo.bar:1234',
+                   'https': 'https://foo.bar:4321'}
+        config = botocore.config.Config(proxies=proxies)
+        service_model = mock.Mock()
+        service_model.metadata = {
+            'serviceFullName': 'MyService',
+            'protocol': 'query'
+        }
+        service_model.operation_names = []
+        bridge = mock.Mock()
+        bridge.resolve.return_value = {
+            'region_name': 'us-west-2', 'signature_version': 'v4',
+            'endpoint_url': 'https://ec2/',
+            'signing_name': 'ec2', 'signing_region': 'us-west-2',
+            'metadata': {}}
+        with mock.patch('botocore.args.EndpointCreator') as m:
+            args_create.get_client_args(
+                service_model, 'us-west-2', True, 'https://ec2/', True,
+                None, {}, config, bridge)
+            m.return_value.create_endpoint.assert_called_with(
+                mock.ANY, endpoint_url='https://ec2/', region_name='us-west-2',
+                response_parser_factory=None, timeout=(60, 60), verify=True,
+                proxies=proxies, max_pool_connections=10
             )
 
     def test_s3_with_endpoint_url_still_resolves_region(self):
diff --git a/tests/unit/test_endpoint.py b/tests/unit/test_endpoint.py
index 263db5c2d..d69fc69ff 100644
--- a/tests/unit/test_endpoint.py
+++ b/tests/unit/test_endpoint.py
@@ -139,6 +139,12 @@ class TestEndpointFeatures(TestEndpointBase):
         # but that feels like testing too much implementation detail.
         self.assertEqual(endpoint.max_pool_connections, 50)
 
+    def test_can_specify_proxies(self):
+        proxies = {'http': 'http://foo.bar:1234'}
+        endpoint = Endpoint('https://ec2.us-west-2.amazonaws.com', 'ec2',
+                            self.event_emitter, proxies=proxies)
+        self.assertEqual(endpoint.proxies, proxies)
+
 
 class TestRetryInterface(TestEndpointBase):
     def setUp(self):