32 self._storage: Dict[pathlib.Path, S3Object] = {}
35 def _generate_etag(data):
36 return hashlib.md5(data).hexdigest()
42 last_modified: Optional[Union[dt.datetime, str]] =
None,
44 key_path = pathlib.Path(key)
45 if last_modified
is None:
48 dt.datetime.now().replace(tzinfo=tz.tzlocal()).isoformat()
50 elif isinstance(last_modified, dt.datetime):
51 last_modified = last_modified.isoformat()
56 'Last-Modified': last_modified,
57 'Size': str(sys.getsizeof(data)),
59 self._storage[key_path] =
S3Object(data, meta)
62 def get_object(self, key: str) -> Optional[S3Object]:
63 key_path = pathlib.Path(key)
64 return self._storage.get(key_path)
66 def get_objects(self, parent_dir='') -> Dict[str, S3Object]:
68 str(key_path): value
for key_path, value
in self._storage.items()
76 for key, value
in all_objects.items()
77 if key.startswith(str(pathlib.Path(parent_dir)))
80 def delete_object(self, key) -> Optional[S3Object]:
81 key = pathlib.Path(key)
82 if key
not in self._storage:
84 return self._storage.pop(key)
88 def __init__(self, mockserver, s3_mock_storage, mock_base_url):
93 def _get_bucket_name(self, request):
94 return request.headers[
'Host'].split(
'.')[0]
96 def _extract_key(self, request):
97 return request.path[len(self.
_base_url) + 1 :]
99 def _generate_get_objects_result(
101 s3_objects_dict: Dict[str, S3Object],
103 marker: Optional[str],
105 empty_result = {
'result_objects': [],
'is_truncated':
False}
106 keys = list(s3_objects_dict.keys())
112 if marker > keys[-1]:
114 for i, key
in enumerate(keys):
121 for key
in keys[from_index : from_index + max_keys]
123 is_truncated = from_index + max_keys >= len(keys)
124 return {
'result_objects': result_objects,
'is_truncated': is_truncated}
126 def _generate_get_objects_xml(
128 s3_objects: List[S3Object],
131 max_keys: Optional[int],
132 marker: Optional[str],
136 for s3_object
in s3_objects:
139 <ETag>{s3_object.meta['ETag']}</ETag>
140 <Key>{s3_object.meta['Key']}</Key>
141 <LastModified>{s3_object.meta['Last-Modified']}</LastModified>
142 <Size>{s3_object.meta['Size']}</Size>
143 <StorageClass>STANDARD</StorageClass>
147 <?xml version="1.0" encoding="UTF-8"?>
149 <Name>{bucket_name}</Name>
150 <Prefix>{prefix}</Prefix>
151 <Marker>{marker or ''}</Marker>
152 <MaxKeys>{max_keys or ''}</MaxKeys>
153 <IsTruncated>{is_truncated}</IsTruncated>
158 def get_object(self, request):
163 s3_object = bucket_storage.get_object(key)
165 return self.
_mockserver.make_response(
'Object not found', 404)
167 s3_object.data, 200, headers=s3_object.meta,
170 def put_object(self, request):
175 data = request.get_data()
176 meta = bucket_storage.put_object(key, data)
177 return self.
_mockserver.make_response(
'OK', 200, headers=meta)
179 def copy_object(self, request):
182 source_bucket_name, source_key = request.headers.get(
186 src_bucket_storage = self.
_storage[source_bucket_name]
187 dst_bucket_storage = self.
_storage[dest_bucket_name]
189 data = src_bucket_storage.get_object(source_key).data
190 meta = dst_bucket_storage.put_object(key, data)
191 return self.
_mockserver.make_response(
'OK', 200, headers=meta)
193 def get_objects(self, request):
194 prefix = request.query[
'prefix']
196 max_keys = int(request.query.get(
'max-keys', 1000))
197 marker = request.query.get(
'marker')
200 bucket_storage = self.
_storage[bucket_name]
202 s3_objects_dict = bucket_storage.get_objects(parent_dir=prefix)
204 s3_objects_dict=s3_objects_dict, max_keys=max_keys, marker=marker,
207 s3_objects=result[
'result_objects'],
208 bucket_name=bucket_name,
212 is_truncated=result[
'is_truncated'],
214 return self.
_mockserver.make_response(result_xml, 200)
216 def delete_object(self, request):
221 bucket_storage.delete_object(key)
225 def get_object_head(self, request):
230 s3_object = bucket_storage.get_object(key)
232 return self.
_mockserver.make_response(
'Object not found', 404)
234 'OK', 200, headers=s3_object.meta,
238@pytest.fixture(name='s3_mock_storage')