Repository: boto/botocore
Pull request #537 (2015-05-21 20:48:21)

Add client-method-called event to botocore
This event will help with the issue with the boto3 dynamodb customizations where if a dictionary gets passed to botocore, it gets modified.

cc @jamesls 

coveralls: 
[![Coverage Status](https://coveralls.io/builds/2621906/badge)](https://coveralls.io/builds/2621906)

Coverage increased (+0.0%) to 95.88% when pulling **90f384acf7b54ff481918519d20b61c8d35cc2e6 on kyleknap:add-event** into **c156e1343c2959ccf8c90ed0c26eb3a19d0a5ab3 on boto:develop**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/2621906/badge)](https://coveralls.io/builds/2621906)

Coverage increased (+0.0%) to 95.88% when pulling **90f384acf7b54ff481918519d20b61c8d35cc2e6 on kyleknap:add-event** into **c156e1343c2959ccf8c90ed0c26eb3a19d0a5ab3 on boto:develop**.

jamesls: :shipit:  My only feedback is that I'd prefer to call this event name something else.  Ideally this event name communicates the fact that this is the place to construct the api params that will be used for serialization.  Something like, `want-api-params`, `client-params-wanted`, etc.
kyleknap: How about ``provide-client-params`` or ``client-params-provided``? I think for consistency the later is better based on verb tense
jamesls: My vote would be the first choice.  I see the providing of the client params as something the event is asking the handlers to do, so the past tense doesn't make sense yet since the handlers haven't yet provided the client params.
kyleknap: Sounds good. I will make the change.

Patch:
diff --git a/botocore/client.py b/botocore/client.py
index fcbbffa03..f2aac68fa 100644
--- a/botocore/client.py
+++ b/botocore/client.py
@@ -20,6 +20,7 @@ from botocore.awsrequest import prepare_request_dict
 from botocore.endpoint import EndpointCreator
 from botocore.exceptions import ClientError, DataNotFoundError
 from botocore.exceptions import OperationNotPageableError
+from botocore.hooks import first_non_none_response
 from botocore.model import ServiceModel
 from botocore.paginate import Paginator
 from botocore.signers import RequestSigner
@@ -316,6 +317,17 @@ class BaseClient(object):
         # Given the API params provided by the user and the operation_model
         # we can serialize the request to a request_dict.
         operation_name = operation_model.name
+
+        # Emit an event that allows users to modify the parameters at the
+        # beginning of the method. It allows handlers to modify existing
+        # parameters or return a new set of parameters to use.
+        responses = self.meta.events.emit(
+            'provide-client-params.{endpoint_prefix}.{operation_name}'.format(
+                endpoint_prefix=self._service_model.endpoint_prefix,
+                operation_name=operation_name),
+            params=api_params, model=operation_model)
+        api_params = first_non_none_response(responses, default=api_params)
+
         event_name = (
             'before-parameter-build.{endpoint_prefix}.{operation_name}')
         self.meta.events.emit(


Test patch:
diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py
index 6422306bc..c04b01afc 100644
--- a/tests/unit/test_client.py
+++ b/tests/unit/test_client.py
@@ -752,3 +752,29 @@ class TestAutoGeneratedClient(unittest.TestCase):
 
         self.assertEqual(len(called), 1)
         self.assertIn('test_operation', called[0])
+
+    def test_client_method_called_event(self):
+        event_emitter = hooks.HierarchicalEmitter()
+
+        def inject_params(params, **kwargs):
+            new_params = params.copy()
+            new_params['Foo'] = 'zero'
+            return new_params
+
+        event_emitter.register(
+            'provide-client-params.myservice.TestOperation', inject_params)
+
+        wrapped_emitter = mock.Mock(wraps=event_emitter)
+        creator = self.create_client_creator(event_emitter=wrapped_emitter)
+        service_client = creator.create_client(
+            'myservice', 'us-west-2', credentials=self.credentials)
+
+        params = {'Foo': 'one', 'Bar': 'two'}
+        service_client.test_operation(**params)
+
+        # Ensure that the initial params were not modified in the handler
+        self.assertEqual(params, {'Foo': 'one', 'Bar': 'two'})
+
+        # Ensure the handler passed on the correct param values.
+        body = self.endpoint.make_request.call_args[0][1]['body']
+        self.assertEqual(body['Foo'], 'zero')