Repository: Azure/msrest-for-python

Issue:
Add support to disable validation on the runtime side
Should be a flag in configuration to disable runtime validation.

This flag should be default to `client-side-validation` value from Autorest:
https://github.com/Azure/autorest/issues/1583#issuecomment-311142690

FYI @matthchr 

Comments:

Patch:
diff --git a/msrest/configuration.py b/msrest/configuration.py
index 7b47b39..4b46b56 100644
--- a/msrest/configuration.py
+++ b/msrest/configuration.py
@@ -44,6 +44,19 @@ from .pipeline import (
 from .version import msrest_version
 
 
+def default_session_configuration_callback(session, global_config, local_config, **kwargs):
+    """Configuration callback if you need to change default session configuration.
+
+    :param requests.Session session: The session.
+    :param Configuration global_config: The global configuration.
+    :param dict local_config: The on-the-fly configuration passed on the call.
+    :param dict kwargs: The current computed values for session.request method.
+    :return: Must return kwargs, to be passed to session.request. If None is return, initial kwargs will be used.
+    :rtype: dict
+    """
+    return kwargs
+
+
 class Configuration(object):
     """Client configuration.
 
@@ -79,6 +92,8 @@ class Configuration(object):
         # - kwargs['msrest']['session'] with the current session
         self.hooks = []
 
+        self.session_configuration_callback = default_session_configuration_callback
+
         self._config = configparser.ConfigParser()
         self._config.optionxform = str
 
diff --git a/msrest/serialization.py b/msrest/serialization.py
index 1902ca2..cc03063 100644
--- a/msrest/serialization.py
+++ b/msrest/serialization.py
@@ -350,6 +350,7 @@ class Serializer(object):
             }
         self.dependencies = dict(classes) if classes else {}
         self.key_transformer = full_restapi_key_transformer
+        self.client_side_validation = True
 
     def _serialize(self, target_obj, data_type=None, **kwargs):
         """Serialize data into a string according to type.
@@ -440,9 +441,10 @@ class Serializer(object):
                 raise_with_traceback(
                     SerializationError, "Unable to build a model: "+str(err), err)
 
-        errors = _recursive_validate(data_type, data)
-        if errors:
-            raise errors[0]
+        if self.client_side_validation:
+            errors = _recursive_validate(data_type, data)
+            if errors:
+                raise errors[0]
         return self._serialize(data, data_type, **kwargs)
 
     def url(self, name, data, data_type, **kwargs):
@@ -454,7 +456,8 @@ class Serializer(object):
         :raises: TypeError if serialization fails.
         :raises: ValueError if data is None
         """
-        data = self.validate(data, name, required=True, **kwargs)
+        if self.client_side_validation:
+            data = self.validate(data, name, required=True, **kwargs)
         try:
             output = self.serialize_data(data, data_type, **kwargs)
             if data_type == 'bool':
@@ -478,7 +481,8 @@ class Serializer(object):
         :raises: TypeError if serialization fails.
         :raises: ValueError if data is None
         """
-        data = self.validate(data, name, required=True, **kwargs)
+        if self.client_side_validation:
+            data = self.validate(data, name, required=True, **kwargs)
         try:
             if data_type in ['[str]']:
                 data = ["" if d is None else d for d in data]
@@ -504,7 +508,8 @@ class Serializer(object):
         :raises: TypeError if serialization fails.
         :raises: ValueError if data is None
         """
-        data = self.validate(data, name, required=True, **kwargs)
+        if self.client_side_validation:
+            data = self.validate(data, name, required=True, **kwargs)
         try:
             if data_type in ['[str]']:
                 data = ["" if d is None else d for d in data]
diff --git a/msrest/service_client.py b/msrest/service_client.py
index d0e6fdb..eed50c5 100644
--- a/msrest/service_client.py
+++ b/msrest/service_client.py
@@ -145,6 +145,11 @@ class ServiceClient(object):
         for protocol in self._protocols:
             session.mount(protocol,
                           requests.adapters.HTTPAdapter(max_retries=max_retries))
+
+        output_kwargs = self.config.session_configuration_callback(session, self.config, config, **kwargs)
+        if output_kwargs is not None:
+            kwargs = output_kwargs
+
         return kwargs
 
     def send_formdata(self, request, headers=None, content=None, **config):


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 8e73444..ee10d48 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -50,6 +50,22 @@ class TestServiceClient(unittest.TestCase):
         self.creds = mock.create_autospec(OAuthTokenAuthentication)
         return super(TestServiceClient, self).setUp()
 
+    def test_session_callback(self):
+
+        client = ServiceClient(self.creds, self.cfg)
+        local_session = requests.Session()
+
+        def callback(session, global_config, local_config, **kwargs):
+            self.assertIs(session, local_session)
+            self.assertIs(global_config, self.cfg)
+            self.assertTrue(local_config["test"])
+            return {'used_callback': True}
+
+        self.cfg.session_configuration_callback = callback
+
+        output_kwargs = client._configure_session(local_session, **{"test": True})
+        self.assertTrue(output_kwargs['used_callback'])
+
     def test_client_request(self):
 
         client = ServiceClient(self.creds, self.cfg)
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index c6cf07d..4d6a80a 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -172,6 +172,32 @@ class TestRuntimeSerialized(unittest.TestCase):
         self.s = Serializer({'TestObj': self.TestObj})
         return super(TestRuntimeSerialized, self).setUp()
 
+    def test_validation_flag(self):
+        s = Serializer()
+        s.client_side_validation = True
+
+        with self.assertRaises(ValidationError):
+            s.query("filter", "", "str", min_length=666)
+        with self.assertRaises(ValidationError):
+            s.url("filter", "", "str", min_length=666)
+        with self.assertRaises(ValidationError):
+            s.header("filter", "", "str", min_length=666)
+
+        test_obj = self.TestObj()
+        self.TestObj._validation = {
+            'attr_b': {'required': True},
+        }
+        test_obj.attr_b = None
+
+        with self.assertRaises(ValidationError):
+            self.s.body(test_obj, 'TestObj')
+
+        s.client_side_validation = False
+        s.query("filter", "", "str", min_length=666)
+        s.url("filter", "", "str", min_length=666)
+        s.header("filter", "", "str", min_length=666)
+        s.body(test_obj, 'TestObj')
+
     def test_serialize_direct_model(self):
         testobj = self.TestObj()
         testobj.attr_a = "myid"