Repository: boto/botocore
Pull request #703 (2015-10-26 22:44:10)

Generalize special casing of pluralized acronyms
Given that this is in the critical path for making API calls,
I did some benchmarking to ensure that this doesn't cause any
serious performance issues.  Short answer is that this seems
acceptable.

I timed how long it took to xform every single operation and
top level member name against this branch and the develop
branch.

Given 3034 xform's, I'm seeing:

```
dev branch   - 0.0930128 seconds averaged over 10 runs of 3034
fix-arn-name - 0.0944845 seconds averaged over 10 runs of 3034
```

Or in terms of ops/sec:

```
before - 32619.2 ops/sec
after  - 32111.1 ops/sec
```

The reason I think this should be generalized is that some
services use this pattern for particular types of resources
(mostly ACLs and ARNs), and we'll need to possibly update
our special cases every time we pull in a service update.

With this change, we won't need to do this.  I will need
to make a corresponding PR to the AWS CLI to put in
backward compat shims so we can still support both.

Using [this script](https://gist.github.com/jamesls/0d1005415d30d2f9f44a),
the delta between the develop branch and this PR is:

```
Model Name                                          xform name

-OpenIdConnectProviderARNs                          open_id_connect_provider_ar_ns
+OpenIdConnectProviderARNs                          open_id_connect_provider_arns

-TapeARNs                                           tape_ar_ns
+TapeARNs                                           tape_arns

-VTLDeviceARNs                                      vtl_device_ar_ns
+VTLDeviceARNs                                      vtl_device_arns

-VolumeARNs                                         volume_ar_ns
+VolumeARNs                                         volume_arns

```


cc @kyleknap @mtdowling @rayluo @JordonPhillips 


Patch:
diff --git a/botocore/__init__.py b/botocore/__init__.py
index e2a3b825f..a1963df00 100644
--- a/botocore/__init__.py
+++ b/botocore/__init__.py
@@ -31,11 +31,12 @@ log.addHandler(NullHandler())
 _first_cap_regex = re.compile('(.)([A-Z][a-z]+)')
 _number_cap_regex = re.compile('([a-z])([0-9]+)')
 _end_cap_regex = re.compile('([a-z0-9])([A-Z])')
+# The regex below handles the special case where some acryonym
+# name is pluralized, e.g GatewayARNs, ListWebACLs, SomeCNAMEs.
+_special_case_transform = re.compile('[A-Z]{3,}s$')
 # Prepopulate the cache with special cases that don't match
 # our regular transformation.
 _xform_cache = {
-    ('SwapEnvironmentCNAMEs', '_'): 'swap_environment_cnames',
-    ('SwapEnvironmentCNAMEs', '-'): 'swap-environment-cnames',
     ('CreateCachediSCSIVolume', '_'): 'create_cached_iscsi_volume',
     ('CreateCachediSCSIVolume', '-'): 'create-cached-iscsi-volume',
     ('DescribeCachediSCSIVolumes', '_'): 'describe_cached_iscsi_volumes',
@@ -44,10 +45,6 @@ _xform_cache = {
     ('DescribeStorediSCSIVolumes', '-'): 'describe-stored-iscsi-volumes',
     ('CreateStorediSCSIVolume', '_'): 'create_stored_iscsi_volume',
     ('CreateStorediSCSIVolume', '-'): 'create-stored-iscsi-volume',
-    ('NotificationARNs', '_'): 'notification_arns',
-    ('NotificationARNs', '-'): 'notification-arns',
-    ('ListWebACLs', '_'): 'list_web_acls',
-    ('ListWebACLs', '-'): 'list-web-acls',
 }
 ScalarTypes = ('string', 'integer', 'boolean', 'timestamp', 'float', 'double')
 
@@ -70,6 +67,11 @@ def xform_name(name, sep='_', _xform_cache=_xform_cache):
         return name
     key = (name, sep)
     if key not in _xform_cache:
+        if _special_case_transform.search(name) is not None:
+            is_special = _special_case_transform.search(name)
+            matched = is_special.group()
+            # Replace something like ARNs, ACLs with _arns, _acls.
+            name = name[:-len(matched)] + sep + matched.lower()
         s1 = _first_cap_regex.sub(r'\1' + sep + r'\2', name)
         s2 = _number_cap_regex.sub(r'\1' + sep + r'\2', s1)
         transformed = _end_cap_regex.sub(r'\1' + sep + r'\2', s2).lower()


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 9818c6b64..436b15db8 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -102,6 +102,9 @@ class TestTransformName(unittest.TestCase):
         self.assertEqual(xform_name('DescribeStorediSCSIVolumes', '-'), 'describe-stored-iscsi-volumes')
         self.assertEqual(xform_name('CreateStorediSCSIVolume', '-'), 'create-stored-iscsi-volume')
 
+    def test_special_case_ends_with_s(self):
+        self.assertEqual(xform_name('GatewayARNs', '-'), 'gateway-arns')
+
 
 class TestValidateJMESPathForSet(unittest.TestCase):
     def setUp(self):