25
25
26
26
import logging
27
27
28
+ from botocore import xform_name
29
+
28
30
29
31
logger = logging .getLogger (__name__ )
30
32
@@ -250,12 +252,162 @@ class ResourceModel(object):
250
252
def __init__ (self , name , definition , resource_defs ):
251
253
self ._definition = definition
252
254
self ._resource_defs = resource_defs
255
+ self ._renamed = {}
253
256
254
257
#: (``string``) The name of this resource
255
258
self .name = name
256
259
#: (``string``) The service shape name for this resource or ``None``
257
260
self .shape = definition .get ('shape' )
258
261
262
+ def load_rename_map (self , shape = None ):
263
+ """
264
+ Load a name translation map given a shape. This will set
265
+ up renamed values for any collisions, e.g. if the shape,
266
+ an action, and a subresource all are all named ``foo``
267
+ then the resource will have an action ``foo``, a subresource
268
+ named ``Foo`` and a property named ``foo_attribute``.
269
+ This is the order of precedence, from most important to
270
+ least important:
271
+
272
+ * Load action (resource.load)
273
+ * Identifiers
274
+ * Actions
275
+ * Subresources
276
+ * References
277
+ * Collections
278
+ * Attributes (shape members)
279
+
280
+ Batch actions are only exposed on collections, so do not
281
+ get modified here. Subresources use upper camel casing, so
282
+ are unlikely to collide with anything but other subresources.
283
+
284
+ Creates a structure like this::
285
+
286
+ renames = {
287
+ ('action', 'id'): 'id_action',
288
+ ('collection', 'id'): 'id_collection',
289
+ ('attribute', 'id'): 'id_attribute'
290
+ }
291
+
292
+ # Get the final name for an action named 'id'
293
+ name = renames.get(('action', 'id'), 'id')
294
+
295
+ :type shape: botocore.model.Shape
296
+ :param shape: The underlying shape for this resource.
297
+ """
298
+ # Meta is a reserved name for resources
299
+ names = set (['meta' ])
300
+ self ._renamed = {}
301
+
302
+ if self ._definition .get ('load' ):
303
+ names .add ('load' )
304
+
305
+ for item in self ._definition .get ('identifiers' , []):
306
+ self ._load_name_with_category (names , item ['name' ], 'identifier' )
307
+
308
+ for name in self ._definition .get ('actions' , {}).keys ():
309
+ self ._load_name_with_category (names , name , 'action' )
310
+
311
+ for name , ref in self ._get_has_definition ().items ():
312
+ # Subresources require no data members, just typically
313
+ # identifiers and user input.
314
+ data_required = False
315
+ for identifier in ref ['resource' ]['identifiers' ]:
316
+ if identifier ['source' ] == 'data' :
317
+ data_required = True
318
+ break
319
+
320
+ if not data_required :
321
+ self ._load_name_with_category (names , name , 'subresource' ,
322
+ snake_case = False )
323
+ else :
324
+ self ._load_name_with_category (names , name , 'reference' )
325
+
326
+ for name in self ._definition .get ('hasMany' , {}).keys ():
327
+ self ._load_name_with_category (names , name , 'collection' )
328
+
329
+ if shape is not None :
330
+ for name in shape .members .keys ():
331
+ self ._load_name_with_category (names , name , 'attribute' )
332
+
333
+ def _load_name_with_category (self , names , name , category ,
334
+ snake_case = True ):
335
+ """
336
+ Load a name with a given category, possibly renaming it
337
+ if that name is already in use. The name will be stored
338
+ in ``names`` and possibly be set up in ``self._renamed``.
339
+
340
+ :type names: set
341
+ :param names: Existing names (Python attributes, properties, or
342
+ methods) on the resource.
343
+ :type name: string
344
+ :param name: The original name of the value.
345
+ :type category: string
346
+ :param category: The value type, such as 'identifier' or 'action'
347
+ :type snake_case: bool
348
+ :param snake_case: True (default) if the name should be snake cased.
349
+ """
350
+ if snake_case :
351
+ name = xform_name (name )
352
+
353
+ if name in names :
354
+ logger .debug ('Renaming %s %s %s' % (self .name , category , name ))
355
+ self ._renamed [(category , name )] = name + '_' + category
356
+ name += '_' + category
357
+
358
+ if name in names :
359
+ # This isn't good, let's raise instead of trying to keep
360
+ # renaming this value.
361
+ raise ValueError ('Problem renaming {0} {1} to {2}!' .format (
362
+ self .name , category , name ))
363
+
364
+ names .add (name )
365
+
366
+ def _get_name (self , category , name , snake_case = True ):
367
+ """
368
+ Get a possibly renamed value given a category and name. This
369
+ uses the rename map set up in ``load_rename_map``, so that
370
+ method must be called once first.
371
+
372
+ :type category: string
373
+ :param category: The value type, such as 'identifier' or 'action'
374
+ :type name: string
375
+ :param name: The original name of the value
376
+ :type snake_case: bool
377
+ :param snake_case: True (default) if the name should be snake cased.
378
+ :rtype: string
379
+ :return: Either the renamed value if it is set, otherwise the
380
+ original name.
381
+ """
382
+ if snake_case :
383
+ name = xform_name (name )
384
+
385
+ return self ._renamed .get ((category , name ), name )
386
+
387
+ def get_attributes (self , shape ):
388
+ """
389
+ Get a dictionary of attribute names to shape models that
390
+ represent the attributes of this resource.
391
+
392
+ :type shape: botocore.model.Shape
393
+ :param shape: The underlying shape for this resource.
394
+ :rtype: dict
395
+ :return: Mapping of resource attributes.
396
+ """
397
+ attributes = {}
398
+ identifier_names = [i .name for i in self .identifiers ]
399
+
400
+ for name , member in shape .members .items ():
401
+ snake_cased = xform_name (name )
402
+ if snake_cased in identifier_names :
403
+ # Skip identifiers, these are set through other means
404
+ continue
405
+ snake_cased = self ._get_name ('attribute' , snake_cased ,
406
+ snake_case = False )
407
+ attributes [snake_cased ] = member
408
+
409
+ return attributes
410
+
259
411
@property
260
412
def identifiers (self ):
261
413
"""
@@ -266,7 +418,8 @@ def identifiers(self):
266
418
identifiers = []
267
419
268
420
for item in self ._definition .get ('identifiers' , []):
269
- identifiers .append (Identifier (item ['name' ]))
421
+ name = self ._get_name ('identifier' , item ['name' ])
422
+ identifiers .append (Identifier (name ))
270
423
271
424
return identifiers
272
425
@@ -294,6 +447,7 @@ def actions(self):
294
447
actions = []
295
448
296
449
for name , item in self ._definition .get ('actions' , {}).items ():
450
+ name = self ._get_name ('action' , name )
297
451
actions .append (Action (name , item , self ._resource_defs ))
298
452
299
453
return actions
@@ -308,6 +462,7 @@ def batch_actions(self):
308
462
actions = []
309
463
310
464
for name , item in self ._definition .get ('batchActions' , {}).items ():
465
+ name = self ._get_name ('batch_action' , name )
311
466
actions .append (Action (name , item , self ._resource_defs ))
312
467
313
468
return actions
@@ -387,6 +542,10 @@ def _get_related_resources(self, subresources):
387
542
resources = []
388
543
389
544
for name , definition in self ._get_has_definition ().items ():
545
+ if subresources :
546
+ name = self ._get_name ('subresource' , name , snake_case = False )
547
+ else :
548
+ name = self ._get_name ('reference' , name )
390
549
action = Action (name , definition , self ._resource_defs )
391
550
392
551
data_required = False
@@ -430,6 +589,7 @@ def collections(self):
430
589
collections = []
431
590
432
591
for name , item in self ._definition .get ('hasMany' , {}).items ():
592
+ name = self ._get_name ('collection' , name )
433
593
collections .append (Collection (name , item , self ._resource_defs ))
434
594
435
595
return collections
0 commit comments