1 """
2 container operations
3
4 Containers are storage compartments where you put your data (objects).
5 A container is similar to a directory or folder on a conventional filesystem
6 with the exception that they exist in a flat namespace, you can not create
7 containers inside of containers.
8
9 See COPYING for license information.
10 """
11
12 from storage_object import Object, ObjectResults
13 from errors import ResponseError, InvalidContainerName, InvalidObjectName, \
14 ContainerNotPublic, CDNNotEnabled
15 from utils import requires_name
16 import consts
17 from fjson import json_loads
24 """
25 Container object and Object instance factory.
26
27 If your account has the feature enabled, containers can be publically
28 shared over a global content delivery network.
29
30 @ivar name: the container's name (generally treated as read-only)
31 @type name: str
32 @ivar object_count: the number of objects in this container (cached)
33 @type object_count: number
34 @ivar size_used: the sum of the sizes of all objects in this container
35 (cached)
36 @type size_used: number
37 @ivar cdn_ttl: the time-to-live of the CDN's public cache of this container
38 (cached, use make_public to alter)
39 @type cdn_ttl: number
40 @ivar cdn_log_retention: retention of the logs in the container.
41 @type cdn_log_retention: bool
42 @ivar cdn_acl_user_agent: enable ACL restriction by User Agent
43 for this container.
44 @type cdn_acl_user_agent: str
45 @ivar cdn_acl_referrer: enable ACL restriction by Referrer
46 for this container.
47 @type cdn_acl_referrer: str
48
49 @undocumented: _fetch_cdn_data
50 @undocumented: _list_objects_raw
51 """
58
59 name = property(fget=lambda self: self._name, fset=__set_name,
60 doc="the name of the container (read-only)")
61
62 - def __init__(self, connection=None, name=None, count=None, size=None):
63 """
64 Containers will rarely if ever need to be instantiated directly by the
65 user.
66
67 Instead, use the L{create_container<Connection.create_container>},
68 L{get_container<Connection.get_container>},
69 L{list_containers<Connection.list_containers>} and
70 other methods on a valid Connection object.
71 """
72 self._name = None
73 self.name = name
74 self.conn = connection
75 self.object_count = count
76 self.size_used = size
77 self.cdn_uri = None
78 self.cdn_ttl = None
79 self.cdn_log_retention = None
80 self.cdn_acl_user_agent = None
81 self.cdn_acl_referrer = None
82 if connection.cdn_enabled:
83 self._fetch_cdn_data()
84
85 @requires_name(InvalidContainerName)
87 """
88 Fetch the object's CDN data from the CDN service
89 """
90 response = self.conn.cdn_request('HEAD', [self.name])
91 if (response.status >= 200) and (response.status < 300):
92 for hdr in response.getheaders():
93 if hdr[0].lower() == 'x-cdn-uri':
94 self.cdn_uri = hdr[1]
95 if hdr[0].lower() == 'x-ttl':
96 self.cdn_ttl = int(hdr[1])
97 if hdr[0].lower() == 'x-log-retention':
98 self.cdn_log_retention = hdr[1] == "True" and True or False
99 if hdr[0].lower() == 'x-user-agent-acl':
100 self.cdn_acl_user_agent = hdr[1]
101 if hdr[0].lower() == 'x-referrer-acl':
102 self.cdn_acl_referrer = hdr[1]
103
104
105 @requires_name(InvalidContainerName)
107 """
108 Either publishes the current container to the CDN or updates its
109 CDN attributes. Requires CDN be enabled on the account.
110
111 >>> container.make_public(ttl=604800) # expire in 1 week
112
113 @param ttl: cache duration in seconds of the CDN server
114 @type ttl: number
115 """
116 if not self.conn.cdn_enabled:
117 raise CDNNotEnabled()
118 if self.cdn_uri:
119 request_method = 'POST'
120 else:
121 request_method = 'PUT'
122 hdrs = {'X-TTL': str(ttl), 'X-CDN-Enabled': 'True'}
123 response = self.conn.cdn_request(request_method, [self.name], hdrs=hdrs)
124 if (response.status < 200) or (response.status >= 300):
125 raise ResponseError(response.status, response.reason)
126 self.cdn_ttl = ttl
127 for hdr in response.getheaders():
128 if hdr[0].lower() == 'x-cdn-uri':
129 self.cdn_uri = hdr[1]
130
131 @requires_name(InvalidContainerName)
133 """
134 Disables CDN access to this container.
135 It may continue to be available until its TTL expires.
136
137 >>> container.make_private()
138 """
139 if not self.conn.cdn_enabled:
140 raise CDNNotEnabled()
141 hdrs = {'X-CDN-Enabled': 'False'}
142 self.cdn_uri = None
143 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
144 if (response.status < 200) or (response.status >= 300):
145 raise ResponseError(response.status, response.reason)
146
147 @requires_name(InvalidContainerName)
148 - def acl_user_agent(self, cdn_acl_user_agent=consts.cdn_acl_user_agent):
149 """
150 Enable ACL restriction by User Agent for this container.
151
152 >>> container.acl_user_agent("Mozilla")
153
154 @param cdn_acl_user_agent: Set the user agent ACL
155 @type cdn_acl_user_agent: str
156 """
157 if not self.conn.cdn_enabled:
158 raise CDNNotEnabled()
159
160 hdrs = {'X-User-Agent-ACL': cdn_acl_user_agent}
161 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
162 if (response.status < 200) or (response.status >= 300):
163 raise ResponseError(response.status, response.reason)
164
165 self.cdn_acl_user_agent = cdn_acl_user_agent
166
167 @requires_name(InvalidContainerName)
168 - def acl_referrer(self, cdn_acl_referrer=consts.cdn_acl_referrer):
169 """
170 Enable ACL restriction by referrer for this container.
171
172 >>> container.acl_referrer("http://www.example.com")
173
174 @param cdn_acl_user_agent: Set the referrer ACL
175 @type cdn_acl_user_agent: str
176 """
177 if not self.conn.cdn_enabled:
178 raise CDNNotEnabled()
179
180 hdrs = {'X-Referrer-ACL': cdn_acl_referrer}
181 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
182 if (response.status < 200) or (response.status >= 300):
183 raise ResponseError(response.status, response.reason)
184
185 self.cdn_acl_referrer = cdn_acl_referrer
186
187
188 @requires_name(InvalidContainerName)
189 - def log_retention(self, log_retention=consts.cdn_log_retention):
190 """
191 Enable CDN log retention on the container. If enabled logs will be
192 periodically (at unpredictable intervals) compressed and uploaded to
193 a ".CDN_ACCESS_LOGS" container in the form of
194 "container_name.YYYYMMDDHH-XXXX.gz". Requires CDN be enabled on the
195 account.
196
197 >>> container.log_retention(True)
198
199 @param log_retention: Enable or disable logs retention.
200 @type log_retention: bool
201 """
202 if not self.conn.cdn_enabled:
203 raise CDNNotEnabled()
204
205 hdrs = {'X-Log-Retention': log_retention}
206 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
207 if (response.status < 200) or (response.status >= 300):
208 raise ResponseError(response.status, response.reason)
209
210 self.cdn_log_retention = log_retention
211
213 """
214 Returns a boolean indicating whether or not this container is
215 publically accessible via the CDN.
216
217 >>> container.is_public()
218 False
219 >>> container.make_public()
220 >>> container.is_public()
221 True
222
223 @rtype: bool
224 @return: whether or not this container is published to the CDN
225 """
226 if not self.conn.cdn_enabled:
227 raise CDNNotEnabled()
228 return self.cdn_uri is not None
229
230 @requires_name(InvalidContainerName)
232 """
233 Return the URI for this container, if it is publically
234 accessible via the CDN.
235
236 >>> connection['container1'].public_uri()
237 'http://c00061.cdn.cloudfiles.rackspacecloud.com'
238
239 @rtype: str
240 @return: the public URI for this container
241 """
242 if not self.is_public():
243 raise ContainerNotPublic()
244 return self.cdn_uri
245
246 @requires_name(InvalidContainerName)
248 """
249 Return an L{Object} instance, creating it if necessary.
250
251 When passed the name of an existing object, this method will
252 return an instance of that object, otherwise it will create a
253 new one.
254
255 >>> container.create_object('new_object')
256 <cloudfiles.storage_object.Object object at 0xb778366c>
257 >>> obj = container.create_object('new_object')
258 >>> obj.name
259 'new_object'
260
261 @type object_name: str
262 @param object_name: the name of the object to create
263 @rtype: L{Object}
264 @return: an object representing the newly created storage object
265 """
266 return Object(self, object_name)
267
268 @requires_name(InvalidContainerName)
269 - def get_objects(self, prefix=None, limit=None, marker=None,
270 path=None, **parms):
271 """
272 Return a result set of all Objects in the Container.
273
274 Keyword arguments are treated as HTTP query parameters and can
275 be used to limit the result set (see the API documentation).
276
277 >>> container.get_objects(limit=2)
278 ObjectResults: 2 objects
279 >>> for obj in container.get_objects():
280 ... print obj.name
281 new_object
282 old_object
283
284 @param prefix: filter the results using this prefix
285 @type prefix: str
286 @param limit: return the first "limit" objects found
287 @type limit: int
288 @param marker: return objects whose names are greater than "marker"
289 @type marker: str
290 @param path: return all objects in "path"
291 @type path: str
292
293 @rtype: L{ObjectResults}
294 @return: an iterable collection of all storage objects in the container
295 """
296 return ObjectResults(self, self.list_objects_info(
297 prefix, limit, marker, path, **parms))
298
299 @requires_name(InvalidContainerName)
301 """
302 Return an L{Object} instance for an existing storage object.
303
304 If an object with a name matching object_name does not exist
305 then a L{NoSuchObject} exception is raised.
306
307 >>> obj = container.get_object('old_object')
308 >>> obj.name
309 'old_object'
310
311 @param object_name: the name of the object to retrieve
312 @type object_name: str
313 @rtype: L{Object}
314 @return: an Object representing the storage object requested
315 """
316 return Object(self, object_name, force_exists=True)
317
318 @requires_name(InvalidContainerName)
319 - def list_objects_info(self, prefix=None, limit=None, marker=None,
320 path=None, **parms):
321 """
322 Return information about all objects in the Container.
323
324 Keyword arguments are treated as HTTP query parameters and can
325 be used limit the result set (see the API documentation).
326
327 >>> conn['container1'].list_objects_info(limit=2)
328 [{u'bytes': 4820,
329 u'content_type': u'application/octet-stream',
330 u'hash': u'db8b55400b91ce34d800e126e37886f8',
331 u'last_modified': u'2008-11-05T00:56:00.406565',
332 u'name': u'new_object'},
333 {u'bytes': 1896,
334 u'content_type': u'application/octet-stream',
335 u'hash': u'1b49df63db7bc97cd2a10e391e102d4b',
336 u'last_modified': u'2008-11-05T00:56:27.508729',
337 u'name': u'old_object'}]
338
339 @param prefix: filter the results using this prefix
340 @type prefix: str
341 @param limit: return the first "limit" objects found
342 @type limit: int
343 @param marker: return objects with names greater than "marker"
344 @type marker: str
345 @param path: return all objects in "path"
346 @type path: str
347
348 @rtype: list({"name":"...", "hash":..., "size":..., "type":...})
349 @return: a list of all container info as dictionaries with the
350 keys "name", "hash", "size", and "type"
351 """
352 parms['format'] = 'json'
353 resp = self._list_objects_raw(
354 prefix, limit, marker, path, **parms)
355 return json_loads(resp)
356
357 @requires_name(InvalidContainerName)
358 - def list_objects(self, prefix=None, limit=None, marker=None,
359 path=None, **parms):
360 """
361 Return names of all L{Object}s in the L{Container}.
362
363 Keyword arguments are treated as HTTP query parameters and can
364 be used to limit the result set (see the API documentation).
365
366 >>> container.list_objects()
367 ['new_object', 'old_object']
368
369 @param prefix: filter the results using this prefix
370 @type prefix: str
371 @param limit: return the first "limit" objects found
372 @type limit: int
373 @param marker: return objects with names greater than "marker"
374 @type marker: str
375 @param path: return all objects in "path"
376 @type path: str
377
378 @rtype: list(str)
379 @return: a list of all container names
380 """
381 resp = self._list_objects_raw(prefix=prefix, limit=limit,
382 marker=marker, path=path, **parms)
383 return resp.splitlines()
384
385 @requires_name(InvalidContainerName)
386 - def _list_objects_raw(self, prefix=None, limit=None, marker=None,
387 path=None, **parms):
388 """
389 Returns a chunk list of storage object info.
390 """
391 if prefix: parms['prefix'] = prefix
392 if limit: parms['limit'] = limit
393 if marker: parms['marker'] = marker
394 if not path is None: parms['path'] = path
395 response = self.conn.make_request('GET', [self.name], parms=parms)
396 if (response.status < 200) or (response.status > 299):
397 buff = response.read()
398 raise ResponseError(response.status, response.reason)
399 return response.read()
400
403
406
407 @requires_name(InvalidContainerName)
409 """
410 Permanently remove a storage object.
411
412 >>> container.list_objects()
413 ['new_object', 'old_object']
414 >>> container.delete_object('old_object')
415 >>> container.list_objects()
416 ['new_object']
417
418 @param object_name: the name of the object to retrieve
419 @type object_name: str
420 """
421 if isinstance(object_name, Object):
422 object_name = object_name.name
423 if not object_name:
424 raise InvalidObjectName(object_name)
425 response = self.conn.make_request('DELETE', [self.name, object_name])
426 if (response.status < 200) or (response.status > 299):
427 buff = response.read()
428 raise ResponseError(response.status, response.reason)
429 buff = response.read()
430
432 """
433 An iterable results set object for Containers.
434
435 This class implements dictionary- and list-like interfaces.
436 """
437 - def __init__(self, conn, containers=list()):
438 self._containers = containers
439 self._names = [k['name'] for k in containers]
440 self.conn = conn
441
443 return Container(self.conn,
444 self._containers[key]['name'],
445 self._containers[key]['count'],
446 self._containers[key]['bytes'])
447
449 return [Container(self.conn, k['name'], k['count'], k['size']) for k in self._containers[i:j] ]
450
452 return item in self._names
453
455 return 'ContainerResults: %s containers' % len(self._containers)
456 __str__ = __repr__
457
459 return len(self._containers)
460
461 - def index(self, value, *args):
462 """
463 returns an integer for the first index of value
464 """
465 return self._names.index(value, *args)
466
468 """
469 returns the number of occurrences of value
470 """
471 return self._names.count(value)
472
473
474