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
25 """
26 Container object and Object instance factory.
27
28 If your account has the feature enabled, containers can be publically
29 shared over a global content delivery network.
30
31 @ivar name: the container's name (generally treated as read-only)
32 @type name: str
33 @ivar object_count: the number of objects in this container (cached)
34 @type object_count: number
35 @ivar size_used: the sum of the sizes of all objects in this container
36 (cached)
37 @type size_used: number
38 @ivar cdn_ttl: the time-to-live of the CDN's public cache of this container
39 (cached, use make_public to alter)
40 @type cdn_ttl: number
41 @ivar cdn_log_retention: retention of the logs in the container.
42 @type cdn_log_retention: bool
43 @ivar cdn_acl_user_agent: enable ACL restriction by User Agent
44 for this container.
45 @type cdn_acl_user_agent: str
46 @ivar cdn_acl_referrer: enable ACL restriction by Referrer
47 for this container.
48 @type cdn_acl_referrer: str
49
50 @undocumented: _fetch_cdn_data
51 @undocumented: _list_objects_raw
52 """
59
60 name = property(fget=lambda self: self._name, fset=__set_name,
61 doc="the name of the container (read-only)")
62
63 - def __init__(self, connection=None, name=None, count=None, size=None):
64 """
65 Containers will rarely if ever need to be instantiated directly by the
66 user.
67
68 Instead, use the L{create_container<Connection.create_container>},
69 L{get_container<Connection.get_container>},
70 L{list_containers<Connection.list_containers>} and
71 other methods on a valid Connection object.
72 """
73 self._name = None
74 self.name = name
75 self.conn = connection
76 self.object_count = count
77 self.size_used = size
78 self.cdn_uri = None
79 self.cdn_ttl = None
80 self.cdn_log_retention = None
81 self.cdn_acl_user_agent = None
82 self.cdn_acl_referrer = None
83 if connection.cdn_enabled:
84 self._fetch_cdn_data()
85
86 @requires_name(InvalidContainerName)
88 """
89 Fetch the object's CDN data from the CDN service
90 """
91 response = self.conn.cdn_request('HEAD', [self.name])
92 if (response.status >= 200) and (response.status < 300):
93 for hdr in response.getheaders():
94 if hdr[0].lower() == 'x-cdn-uri':
95 self.cdn_uri = hdr[1]
96 if hdr[0].lower() == 'x-ttl':
97 self.cdn_ttl = int(hdr[1])
98 if hdr[0].lower() == 'x-log-retention':
99 self.cdn_log_retention = hdr[1] == "True" and True or False
100 if hdr[0].lower() == 'x-user-agent-acl':
101 self.cdn_acl_user_agent = hdr[1]
102 if hdr[0].lower() == 'x-referrer-acl':
103 self.cdn_acl_referrer = hdr[1]
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, \
124 [self.name], hdrs=hdrs)
125 if (response.status < 200) or (response.status >= 300):
126 raise ResponseError(response.status, response.reason)
127 self.cdn_ttl = ttl
128 for hdr in response.getheaders():
129 if hdr[0].lower() == 'x-cdn-uri':
130 self.cdn_uri = hdr[1]
131
132 @requires_name(InvalidContainerName)
134 """
135 Disables CDN access to this container.
136 It may continue to be available until its TTL expires.
137
138 >>> container.make_private()
139 """
140 if not self.conn.cdn_enabled:
141 raise CDNNotEnabled()
142 hdrs = {'X-CDN-Enabled': 'False'}
143 self.cdn_uri = None
144 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
145 if (response.status < 200) or (response.status >= 300):
146 raise ResponseError(response.status, response.reason)
147
148 @requires_name(InvalidContainerName)
149 - def acl_user_agent(self, cdn_acl_user_agent=consts.cdn_acl_user_agent):
150 """
151 Enable ACL restriction by User Agent for this container.
152
153 >>> container.acl_user_agent("Mozilla")
154
155 @param cdn_acl_user_agent: Set the user agent ACL
156 @type cdn_acl_user_agent: str
157 """
158 if not self.conn.cdn_enabled:
159 raise CDNNotEnabled()
160
161 hdrs = {'X-User-Agent-ACL': cdn_acl_user_agent}
162 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
163 if (response.status < 200) or (response.status >= 300):
164 raise ResponseError(response.status, response.reason)
165
166 self.cdn_acl_user_agent = cdn_acl_user_agent
167
168 @requires_name(InvalidContainerName)
169 - def acl_referrer(self, cdn_acl_referrer=consts.cdn_acl_referrer):
170 """
171 Enable ACL restriction by referrer for this container.
172
173 >>> container.acl_referrer("http://www.example.com")
174
175 @param cdn_acl_user_agent: Set the referrer ACL
176 @type cdn_acl_user_agent: str
177 """
178 if not self.conn.cdn_enabled:
179 raise CDNNotEnabled()
180
181 hdrs = {'X-Referrer-ACL': cdn_acl_referrer}
182 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
183 if (response.status < 200) or (response.status >= 300):
184 raise ResponseError(response.status, response.reason)
185
186 self.cdn_acl_referrer = cdn_acl_referrer
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, delimiter=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 @param delimiter: use this character as a delimiter for subdirectories
293 @type delimiter: char
294
295 @rtype: L{ObjectResults}
296 @return: an iterable collection of all storage objects in the container
297 """
298 return ObjectResults(self, self.list_objects_info(
299 prefix, limit, marker, path, delimiter, **parms))
300
301 @requires_name(InvalidContainerName)
303 """
304 Return an L{Object} instance for an existing storage object.
305
306 If an object with a name matching object_name does not exist
307 then a L{NoSuchObject} exception is raised.
308
309 >>> obj = container.get_object('old_object')
310 >>> obj.name
311 'old_object'
312
313 @param object_name: the name of the object to retrieve
314 @type object_name: str
315 @rtype: L{Object}
316 @return: an Object representing the storage object requested
317 """
318 return Object(self, object_name, force_exists=True)
319
320 @requires_name(InvalidContainerName)
321 - def list_objects_info(self, prefix=None, limit=None, marker=None,
322 path=None, delimiter=None, **parms):
323 """
324 Return information about all objects in the Container.
325
326 Keyword arguments are treated as HTTP query parameters and can
327 be used limit the result set (see the API documentation).
328
329 >>> conn['container1'].list_objects_info(limit=2)
330 [{u'bytes': 4820,
331 u'content_type': u'application/octet-stream',
332 u'hash': u'db8b55400b91ce34d800e126e37886f8',
333 u'last_modified': u'2008-11-05T00:56:00.406565',
334 u'name': u'new_object'},
335 {u'bytes': 1896,
336 u'content_type': u'application/octet-stream',
337 u'hash': u'1b49df63db7bc97cd2a10e391e102d4b',
338 u'last_modified': u'2008-11-05T00:56:27.508729',
339 u'name': u'old_object'}]
340
341 @param prefix: filter the results using this prefix
342 @type prefix: str
343 @param limit: return the first "limit" objects found
344 @type limit: int
345 @param marker: return objects with names greater than "marker"
346 @type marker: str
347 @param path: return all objects in "path"
348 @type path: str
349 @param delimiter: use this character as a delimiter for subdirectories
350 @type delimiter: char
351
352 @rtype: list({"name":"...", "hash":..., "size":..., "type":...})
353 @return: a list of all container info as dictionaries with the
354 keys "name", "hash", "size", and "type"
355 """
356 parms['format'] = 'json'
357 resp = self._list_objects_raw(
358 prefix, limit, marker, path, delimiter, **parms)
359 return json_loads(resp)
360
361 @requires_name(InvalidContainerName)
362 - def list_objects(self, prefix=None, limit=None, marker=None,
363 path=None, delimiter=None, **parms):
364 """
365 Return names of all L{Object}s in the L{Container}.
366
367 Keyword arguments are treated as HTTP query parameters and can
368 be used to limit the result set (see the API documentation).
369
370 >>> container.list_objects()
371 ['new_object', 'old_object']
372
373 @param prefix: filter the results using this prefix
374 @type prefix: str
375 @param limit: return the first "limit" objects found
376 @type limit: int
377 @param marker: return objects with names greater than "marker"
378 @type marker: str
379 @param path: return all objects in "path"
380 @type path: str
381 @param delimiter: use this character as a delimiter for subdirectories
382 @type delimiter: char
383
384 @rtype: list(str)
385 @return: a list of all container names
386 """
387 resp = self._list_objects_raw(prefix=prefix, limit=limit,
388 marker=marker, path=path,
389 delimiter=delimiter, **parms)
390 return resp.splitlines()
391
392 @requires_name(InvalidContainerName)
393 - def _list_objects_raw(self, prefix=None, limit=None, marker=None,
394 path=None, delimiter=None, **parms):
395 """
396 Returns a chunk list of storage object info.
397 """
398 if prefix:
399 parms['prefix'] = prefix
400 if limit:
401 parms['limit'] = limit
402 if marker:
403 parms['marker'] = marker
404 if delimiter:
405 parms['delimiter'] = delimiter
406 if not path is None:
407 parms['path'] = path
408 response = self.conn.make_request('GET', [self.name], parms=parms)
409 if (response.status < 200) or (response.status > 299):
410 response.read()
411 raise ResponseError(response.status, response.reason)
412 return response.read()
413
416
419
420 @requires_name(InvalidContainerName)
422 """
423 Permanently remove a storage object.
424
425 >>> container.list_objects()
426 ['new_object', 'old_object']
427 >>> container.delete_object('old_object')
428 >>> container.list_objects()
429 ['new_object']
430
431 @param object_name: the name of the object to retrieve
432 @type object_name: str
433 """
434 if isinstance(object_name, Object):
435 object_name = object_name.name
436 if not object_name:
437 raise InvalidObjectName(object_name)
438 response = self.conn.make_request('DELETE', [self.name, object_name])
439 if (response.status < 200) or (response.status > 299):
440 response.read()
441 raise ResponseError(response.status, response.reason)
442 response.read()
443
446 """
447 An iterable results set object for Containers.
448
449 This class implements dictionary- and list-like interfaces.
450 """
451 - def __init__(self, conn, containers=list()):
452 self._containers = containers
453 self._names = [k['name'] for k in containers]
454 self.conn = conn
455
457 return Container(self.conn,
458 self._containers[key]['name'],
459 self._containers[key]['count'],
460 self._containers[key]['bytes'])
461
463 return [Container(self.conn, k['name'], k['count'], \
464 k['size']) for k in self._containers[i:j]]
465
467 return item in self._names
468
470 return 'ContainerResults: %s containers' % len(self._containers)
471 __str__ = __repr__
472
474 return len(self._containers)
475
476 - def index(self, value, *args):
477 """
478 returns an integer for the first index of value
479 """
480 return self._names.index(value, *args)
481
483 """
484 returns the number of occurrences of value
485 """
486 return self._names.count(value)
487
488
489