1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"). You
4# may not use this file except in compliance with the License. A copy of
5# the License is located at
6#
7# https://aws.amazon.com/apache2.0/
8#
9# or in the "license" file accompanying this file. This file is
10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11# ANY KIND, either express or implied. See the License for the specific
12# language governing permissions and limitations under the License.
13
14import logging
15
16import boto3
17
18logger = logging.getLogger(__name__)
19
20
21class ResourceMeta:
22 """
23 An object containing metadata about a resource.
24 """
25
26 def __init__(
27 self,
28 service_name,
29 identifiers=None,
30 client=None,
31 data=None,
32 resource_model=None,
33 ):
34 #: (``string``) The service name, e.g. 's3'
35 self.service_name = service_name
36
37 if identifiers is None:
38 identifiers = []
39 #: (``list``) List of identifier names
40 self.identifiers = identifiers
41
42 #: (:py:class:`~botocore.client.BaseClient`) Low-level Botocore client
43 self.client = client
44 #: (``dict``) Loaded resource data attributes
45 self.data = data
46
47 # The resource model for that resource
48 self.resource_model = resource_model
49
50 def __repr__(self):
51 return f'ResourceMeta(\'{self.service_name}\', identifiers={self.identifiers})'
52
53 def __eq__(self, other):
54 # Two metas are equal if their components are all equal
55 if other.__class__.__name__ != self.__class__.__name__:
56 return False
57
58 return self.__dict__ == other.__dict__
59
60 def copy(self):
61 """
62 Create a copy of this metadata object.
63 """
64 params = self.__dict__.copy()
65 service_name = params.pop('service_name')
66 return ResourceMeta(service_name, **params)
67
68
69class ServiceResource:
70 """
71 A base class for resources.
72
73 :type client: botocore.client
74 :param client: A low-level Botocore client instance
75 """
76
77 meta = None
78 """
79 Stores metadata about this resource instance, such as the
80 ``service_name``, the low-level ``client`` and any cached ``data``
81 from when the instance was hydrated. For example::
82
83 # Get a low-level client from a resource instance
84 client = resource.meta.client
85 response = client.operation(Param='foo')
86
87 # Print the resource instance's service short name
88 print(resource.meta.service_name)
89
90 See :py:class:`ResourceMeta` for more information.
91 """
92
93 def __init__(self, *args, **kwargs):
94 # Always work on a copy of meta, otherwise we would affect other
95 # instances of the same subclass.
96 self.meta = self.meta.copy()
97
98 # Create a default client if none was passed
99 if kwargs.get('client') is not None:
100 self.meta.client = kwargs.get('client')
101 else:
102 self.meta.client = boto3.client(self.meta.service_name)
103
104 # Allow setting identifiers as positional arguments in the order
105 # in which they were defined in the ResourceJSON.
106 for i, value in enumerate(args):
107 setattr(self, '_' + self.meta.identifiers[i], value)
108
109 # Allow setting identifiers via keyword arguments. Here we need
110 # extra logic to ignore other keyword arguments like ``client``.
111 for name, value in kwargs.items():
112 if name == 'client':
113 continue
114
115 if name not in self.meta.identifiers:
116 raise ValueError(f'Unknown keyword argument: {name}')
117
118 setattr(self, '_' + name, value)
119
120 # Validate that all identifiers have been set.
121 for identifier in self.meta.identifiers:
122 if getattr(self, identifier) is None:
123 raise ValueError(f'Required parameter {identifier} not set')
124
125 def __repr__(self):
126 identifiers = []
127 for identifier in self.meta.identifiers:
128 identifiers.append(
129 f'{identifier}={repr(getattr(self, identifier))}'
130 )
131 return "{}({})".format(
132 self.__class__.__name__,
133 ', '.join(identifiers),
134 )
135
136 def __eq__(self, other):
137 # Should be instances of the same resource class
138 if other.__class__.__name__ != self.__class__.__name__:
139 return False
140
141 # Each of the identifiers should have the same value in both
142 # instances, e.g. two buckets need the same name to be equal.
143 for identifier in self.meta.identifiers:
144 if getattr(self, identifier) != getattr(other, identifier):
145 return False
146
147 return True
148
149 def __hash__(self):
150 identifiers = []
151 for identifier in self.meta.identifiers:
152 identifiers.append(getattr(self, identifier))
153 return hash((self.__class__.__name__, tuple(identifiers)))