Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
model.py633 linesDownload Raw Back to resources
1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License"). You4# may not use this file except in compliance with the License. A copy of5# the License is located at6#7# https://aws.amazon.com/apache2.0/8#9# or in the "license" file accompanying this file. This file is10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF11# ANY KIND, either express or implied. See the License for the specific12# language governing permissions and limitations under the License.13 14"""15The models defined in this file represent the resource JSON description16format and provide a layer of abstraction from the raw JSON. The advantages17of this are:18 19* Pythonic interface (e.g. ``action.request.operation``)20* Consumers need not change for minor JSON changes (e.g. renamed field)21 22These models are used both by the resource factory to generate resource23classes as well as by the documentation generator.24"""25 26import logging27 28from botocore import xform_name29 30logger = logging.getLogger(__name__)31 32 33class Identifier:34    """35    A resource identifier, given by its name.36 37    :type name: string38    :param name: The name of the identifier39    """40 41    def __init__(self, name, member_name=None):42        #: (``string``) The name of the identifier43        self.name = name44        self.member_name = member_name45 46 47class Action:48    """49    A service operation action.50 51    :type name: string52    :param name: The name of the action53    :type definition: dict54    :param definition: The JSON definition55    :type resource_defs: dict56    :param resource_defs: All resources defined in the service57    """58 59    def __init__(self, name, definition, resource_defs):60        self._definition = definition61 62        #: (``string``) The name of the action63        self.name = name64        #: (:py:class:`Request`) This action's request or ``None``65        self.request = None66        if 'request' in definition:67            self.request = Request(definition.get('request', {}))68        #: (:py:class:`ResponseResource`) This action's resource or ``None``69        self.resource = None70        if 'resource' in definition:71            self.resource = ResponseResource(72                definition.get('resource', {}), resource_defs73            )74        #: (``string``) The JMESPath search path or ``None``75        self.path = definition.get('path')76 77 78class DefinitionWithParams:79    """80    An item which has parameters exposed via the ``params`` property.81    A request has an operation and parameters, while a waiter has82    a name, a low-level waiter name and parameters.83 84    :type definition: dict85    :param definition: The JSON definition86    """87 88    def __init__(self, definition):89        self._definition = definition90 91    @property92    def params(self):93        """94        Get a list of auto-filled parameters for this request.95 96        :type: list(:py:class:`Parameter`)97        """98        params = []99 100        for item in self._definition.get('params', []):101            params.append(Parameter(**item))102 103        return params104 105 106class Parameter:107    """108    An auto-filled parameter which has a source and target. For example,109    the ``QueueUrl`` may be auto-filled from a resource's ``url`` identifier110    when making calls to ``queue.receive_messages``.111 112    :type target: string113    :param target: The destination parameter name, e.g. ``QueueUrl``114    :type source_type: string115    :param source_type: Where the source is defined.116    :type source: string117    :param source: The source name, e.g. ``Url``118    """119 120    def __init__(121        self, target, source, name=None, path=None, value=None, **kwargs122    ):123        #: (``string``) The destination parameter name124        self.target = target125        #: (``string``) Where the source is defined126        self.source = source127        #: (``string``) The name of the source, if given128        self.name = name129        #: (``string``) The JMESPath query of the source130        self.path = path131        #: (``string|int|float|bool``) The source constant value132        self.value = value133 134        # Complain if we encounter any unknown values.135        if kwargs:136            logger.warning('Unknown parameter options found: %s', kwargs)137 138 139class Request(DefinitionWithParams):140    """141    A service operation action request.142 143    :type definition: dict144    :param definition: The JSON definition145    """146 147    def __init__(self, definition):148        super().__init__(definition)149 150        #: (``string``) The name of the low-level service operation151        self.operation = definition.get('operation')152 153 154class Waiter(DefinitionWithParams):155    """156    An event waiter specification.157 158    :type name: string159    :param name: Name of the waiter160    :type definition: dict161    :param definition: The JSON definition162    """163 164    PREFIX = 'WaitUntil'165 166    def __init__(self, name, definition):167        super().__init__(definition)168 169        #: (``string``) The name of this waiter170        self.name = name171 172        #: (``string``) The name of the underlying event waiter173        self.waiter_name = definition.get('waiterName')174 175 176class ResponseResource:177    """178    A resource response to create after performing an action.179 180    :type definition: dict181    :param definition: The JSON definition182    :type resource_defs: dict183    :param resource_defs: All resources defined in the service184    """185 186    def __init__(self, definition, resource_defs):187        self._definition = definition188        self._resource_defs = resource_defs189 190        #: (``string``) The name of the response resource type191        self.type = definition.get('type')192 193        #: (``string``) The JMESPath search query or ``None``194        self.path = definition.get('path')195 196    @property197    def identifiers(self):198        """199        A list of resource identifiers.200 201        :type: list(:py:class:`Identifier`)202        """203        identifiers = []204 205        for item in self._definition.get('identifiers', []):206            identifiers.append(Parameter(**item))207 208        return identifiers209 210    @property211    def model(self):212        """213        Get the resource model for the response resource.214 215        :type: :py:class:`ResourceModel`216        """217        return ResourceModel(218            self.type, self._resource_defs[self.type], self._resource_defs219        )220 221 222class Collection(Action):223    """224    A group of resources. See :py:class:`Action`.225 226    :type name: string227    :param name: The name of the collection228    :type definition: dict229    :param definition: The JSON definition230    :type resource_defs: dict231    :param resource_defs: All resources defined in the service232    """233 234    @property235    def batch_actions(self):236        """237        Get a list of batch actions supported by the resource type238        contained in this action. This is a shortcut for accessing239        the same information through the resource model.240 241        :rtype: list(:py:class:`Action`)242        """243        return self.resource.model.batch_actions244 245 246class ResourceModel:247    """248    A model representing a resource, defined via a JSON description249    format. A resource has identifiers, attributes, actions,250    sub-resources, references and collections. For more information251    on resources, see :ref:`guide_resources`.252 253    :type name: string254    :param name: The name of this resource, e.g. ``sqs`` or ``Queue``255    :type definition: dict256    :param definition: The JSON definition257    :type resource_defs: dict258    :param resource_defs: All resources defined in the service259    """260 261    def __init__(self, name, definition, resource_defs):262        self._definition = definition263        self._resource_defs = resource_defs264        self._renamed = {}265 266        #: (``string``) The name of this resource267        self.name = name268        #: (``string``) The service shape name for this resource or ``None``269        self.shape = definition.get('shape')270 271    def load_rename_map(self, shape=None):272        """273        Load a name translation map given a shape. This will set274        up renamed values for any collisions, e.g. if the shape,275        an action, and a subresource all are all named ``foo``276        then the resource will have an action ``foo``, a subresource277        named ``Foo`` and a property named ``foo_attribute``.278        This is the order of precedence, from most important to279        least important:280 281        * Load action (resource.load)282        * Identifiers283        * Actions284        * Subresources285        * References286        * Collections287        * Waiters288        * Attributes (shape members)289 290        Batch actions are only exposed on collections, so do not291        get modified here. Subresources use upper camel casing, so292        are unlikely to collide with anything but other subresources.293 294        Creates a structure like this::295 296            renames = {297                ('action', 'id'): 'id_action',298                ('collection', 'id'): 'id_collection',299                ('attribute', 'id'): 'id_attribute'300            }301 302            # Get the final name for an action named 'id'303            name = renames.get(('action', 'id'), 'id')304 305        :type shape: botocore.model.Shape306        :param shape: The underlying shape for this resource.307        """308        # Meta is a reserved name for resources309        names = {'meta'}310        self._renamed = {}311 312        if self._definition.get('load'):313            names.add('load')314 315        for item in self._definition.get('identifiers', []):316            self._load_name_with_category(names, item['name'], 'identifier')317 318        for name in self._definition.get('actions', {}):319            self._load_name_with_category(names, name, 'action')320 321        for name, ref in self._get_has_definition().items():322            # Subresources require no data members, just typically323            # identifiers and user input.324            data_required = False325            for identifier in ref['resource']['identifiers']:326                if identifier['source'] == 'data':327                    data_required = True328                    break329 330            if not data_required:331                self._load_name_with_category(332                    names, name, 'subresource', snake_case=False333                )334            else:335                self._load_name_with_category(names, name, 'reference')336 337        for name in self._definition.get('hasMany', {}):338            self._load_name_with_category(names, name, 'collection')339 340        for name in self._definition.get('waiters', {}):341            self._load_name_with_category(342                names, Waiter.PREFIX + name, 'waiter'343            )344 345        if shape is not None:346            for name in shape.members.keys():347                self._load_name_with_category(names, name, 'attribute')348 349    def _load_name_with_category(self, names, name, category, snake_case=True):350        """351        Load a name with a given category, possibly renaming it352        if that name is already in use. The name will be stored353        in ``names`` and possibly be set up in ``self._renamed``.354 355        :type names: set356        :param names: Existing names (Python attributes, properties, or357                      methods) on the resource.358        :type name: string359        :param name: The original name of the value.360        :type category: string361        :param category: The value type, such as 'identifier' or 'action'362        :type snake_case: bool363        :param snake_case: True (default) if the name should be snake cased.364        """365        if snake_case:366            name = xform_name(name)367 368        if name in names:369            logger.debug(f'Renaming {self.name} {category} {name}')370            self._renamed[(category, name)] = name + '_' + category371            name += '_' + category372 373            if name in names:374                # This isn't good, let's raise instead of trying to keep375                # renaming this value.376                raise ValueError(377                    'Problem renaming {} {} to {}!'.format(378                        self.name, category, name379                    )380                )381 382        names.add(name)383 384    def _get_name(self, category, name, snake_case=True):385        """386        Get a possibly renamed value given a category and name. This387        uses the rename map set up in ``load_rename_map``, so that388        method must be called once first.389 390        :type category: string391        :param category: The value type, such as 'identifier' or 'action'392        :type name: string393        :param name: The original name of the value394        :type snake_case: bool395        :param snake_case: True (default) if the name should be snake cased.396        :rtype: string397        :return: Either the renamed value if it is set, otherwise the398                 original name.399        """400        if snake_case:401            name = xform_name(name)402 403        return self._renamed.get((category, name), name)404 405    def get_attributes(self, shape):406        """407        Get a dictionary of attribute names to original name and shape408        models that represent the attributes of this resource. Looks409        like the following:410 411            {412                'some_name': ('SomeName', <Shape...>)413            }414 415        :type shape: botocore.model.Shape416        :param shape: The underlying shape for this resource.417        :rtype: dict418        :return: Mapping of resource attributes.419        """420        attributes = {}421        identifier_names = [i.name for i in self.identifiers]422 423        for name, member in shape.members.items():424            snake_cased = xform_name(name)425            if snake_cased in identifier_names:426                # Skip identifiers, these are set through other means427                continue428            snake_cased = self._get_name(429                'attribute', snake_cased, snake_case=False430            )431            attributes[snake_cased] = (name, member)432 433        return attributes434 435    @property436    def identifiers(self):437        """438        Get a list of resource identifiers.439 440        :type: list(:py:class:`Identifier`)441        """442        identifiers = []443 444        for item in self._definition.get('identifiers', []):445            name = self._get_name('identifier', item['name'])446            member_name = item.get('memberName', None)447            if member_name:448                member_name = self._get_name('attribute', member_name)449            identifiers.append(Identifier(name, member_name))450 451        return identifiers452 453    @property454    def load(self):455        """456        Get the load action for this resource, if it is defined.457 458        :type: :py:class:`Action` or ``None``459        """460        action = self._definition.get('load')461 462        if action is not None:463            action = Action('load', action, self._resource_defs)464 465        return action466 467    @property468    def actions(self):469        """470        Get a list of actions for this resource.471 472        :type: list(:py:class:`Action`)473        """474        actions = []475 476        for name, item in self._definition.get('actions', {}).items():477            name = self._get_name('action', name)478            actions.append(Action(name, item, self._resource_defs))479 480        return actions481 482    @property483    def batch_actions(self):484        """485        Get a list of batch actions for this resource.486 487        :type: list(:py:class:`Action`)488        """489        actions = []490 491        for name, item in self._definition.get('batchActions', {}).items():492            name = self._get_name('batch_action', name)493            actions.append(Action(name, item, self._resource_defs))494 495        return actions496 497    def _get_has_definition(self):498        """499        Get a ``has`` relationship definition from a model, where the500        service resource model is treated special in that it contains501        a relationship to every resource defined for the service. This502        allows things like ``s3.Object('bucket-name', 'key')`` to503        work even though the JSON doesn't define it explicitly.504 505        :rtype: dict506        :return: Mapping of names to subresource and reference507                 definitions.508        """509        if self.name not in self._resource_defs:510            # This is the service resource, so let us expose all of511            # the defined resources as subresources.512            definition = {}513 514            for name, resource_def in self._resource_defs.items():515                # It's possible for the service to have renamed a516                # resource or to have defined multiple names that517                # point to the same resource type, so we need to518                # take that into account.519                found = False520                has_items = self._definition.get('has', {}).items()521                for has_name, has_def in has_items:522                    if has_def.get('resource', {}).get('type') == name:523                        definition[has_name] = has_def524                        found = True525 526                if not found:527                    # Create a relationship definition and attach it528                    # to the model, such that all identifiers must be529                    # supplied by the user. It will look something like:530                    #531                    # {532                    #   'resource': {533                    #     'type': 'ResourceName',534                    #     'identifiers': [535                    #       {'target': 'Name1', 'source': 'input'},536                    #       {'target': 'Name2', 'source': 'input'},537                    #       ...538                    #     ]539                    #   }540                    # }541                    #542                    fake_has = {'resource': {'type': name, 'identifiers': []}}543 544                    for identifier in resource_def.get('identifiers', []):545                        fake_has['resource']['identifiers'].append(546                            {'target': identifier['name'], 'source': 'input'}547                        )548 549                    definition[name] = fake_has550        else:551            definition = self._definition.get('has', {})552 553        return definition554 555    def _get_related_resources(self, subresources):556        """557        Get a list of sub-resources or references.558 559        :type subresources: bool560        :param subresources: ``True`` to get sub-resources, ``False`` to561                             get references.562        :rtype: list(:py:class:`Action`)563        """564        resources = []565 566        for name, definition in self._get_has_definition().items():567            if subresources:568                name = self._get_name('subresource', name, snake_case=False)569            else:570                name = self._get_name('reference', name)571            action = Action(name, definition, self._resource_defs)572 573            data_required = False574            for identifier in action.resource.identifiers:575                if identifier.source == 'data':576                    data_required = True577                    break578 579            if subresources and not data_required:580                resources.append(action)581            elif not subresources and data_required:582                resources.append(action)583 584        return resources585 586    @property587    def subresources(self):588        """589        Get a list of sub-resources.590 591        :type: list(:py:class:`Action`)592        """593        return self._get_related_resources(True)594 595    @property596    def references(self):597        """598        Get a list of reference resources.599 600        :type: list(:py:class:`Action`)601        """602        return self._get_related_resources(False)603 604    @property605    def collections(self):606        """607        Get a list of collections for this resource.608 609        :type: list(:py:class:`Collection`)610        """611        collections = []612 613        for name, item in self._definition.get('hasMany', {}).items():614            name = self._get_name('collection', name)615            collections.append(Collection(name, item, self._resource_defs))616 617        return collections618 619    @property620    def waiters(self):621        """622        Get a list of waiters for this resource.623 624        :type: list(:py:class:`Waiter`)625        """626        waiters = []627 628        for name, item in self._definition.get('waiters', {}).items():629            name = self._get_name('waiter', Waiter.PREFIX + name)630            waiters.append(Waiter(name, item))631 632        return waiters633 
codekingpro/portable-devtools · Team Ai