@@ -19,15 +19,27 @@ class Bucket(object):
1919 :type name: string
2020 :param name: The name of the bucket.
2121 """
22+ # ACL rules are lazily retrieved.
23+ _acl = _default_object_acl = None
2224
2325 def __init__ (self , connection = None , name = None , metadata = None ):
2426 self .connection = connection
2527 self .name = name
2628 self .metadata = metadata
2729
28- # ACL rules are lazily retrieved.
29- self .acl = None
30- self .default_object_acl = None
30+ @property
31+ def acl (self ):
32+ """Create our ACL on demand."""
33+ if self ._acl is None :
34+ self ._acl = BucketACL (self )
35+ return self ._acl
36+
37+ @property
38+ def default_object_acl (self ):
39+ """Create our defaultObjectACL on demand."""
40+ if self ._default_object_acl is None :
41+ self ._default_object_acl = DefaultObjectACL (self )
42+ return self ._default_object_acl
3143
3244 @classmethod
3345 def from_dict (cls , bucket_dict , connection = None ):
@@ -313,17 +325,15 @@ def has_metadata(self, field=None):
313325 else :
314326 return True
315327
316- def reload_metadata (self , full = False ):
328+ def reload_metadata (self ):
317329 """Reload metadata from Cloud Storage.
318330
319- :type full: bool
320- :param full: If True, loads all data (include ACL data).
321-
322331 :rtype: :class:`Bucket`
323332 :returns: The bucket you just reloaded data for.
324333 """
325- projection = 'full' if full else 'noAcl'
326- query_params = {'projection' : projection }
334+ # Pass only '?projection=noAcl' here because 'acl'/'defaultObjectAcl'
335+ # are handled via 'get_acl()'/'get_default_object_acl()'
336+ query_params = {'projection' : 'noAcl' }
327337 self .metadata = self .connection .api_request (
328338 method = 'GET' , path = self .path , query_params = query_params )
329339 return self
@@ -344,9 +354,14 @@ def get_metadata(self, field=None, default=None):
344354 :rtype: dict or anything
345355 :returns: All metadata or the value of the specific field.
346356 """
357+ if field == 'acl' :
358+ raise KeyError ("Use 'get_acl()'" )
359+
360+ if field == 'defaultObjectAcl' :
361+ raise KeyError ("Use 'get_default_object_acl()'" )
362+
347363 if not self .has_metadata (field = field ):
348- full = (field and field in ('acl' , 'defaultObjectAcl' ))
349- self .reload_metadata (full = full )
364+ self .reload_metadata ()
350365
351366 if field :
352367 return self .metadata .get (field , default )
@@ -431,11 +446,15 @@ def reload_acl(self):
431446 :rtype: :class:`Bucket`
432447 :returns: The current bucket.
433448 """
434- self .acl = BucketACL (bucket = self )
449+ self .acl .clear ()
450+
451+ url_path = '%s/acl' % self .path
452+ found = self .connection .api_request (method = 'GET' , path = url_path )
453+ for entry in found ['items' ]:
454+ self .acl .add_entity (self .acl .entity_from_dict (entry ))
435455
436- for entry in self .get_metadata ('acl' , []):
437- entity = self .acl .entity_from_dict (entry )
438- self .acl .add_entity (entity )
456+ # Even if we fetch no entries, the ACL is still loaded.
457+ self .acl .loaded = True
439458
440459 return self
441460
@@ -445,7 +464,7 @@ def get_acl(self):
445464 :rtype: :class:`gcloud.storage.acl.BucketACL`
446465 :returns: An ACL object for the current bucket.
447466 """
448- if not self .acl :
467+ if not self .acl . loaded :
449468 self .reload_acl ()
450469 return self .acl
451470
@@ -487,12 +506,19 @@ def save_acl(self, acl=None):
487506 # both evaluate to False, but mean very different things.
488507 if acl is None :
489508 acl = self .acl
509+ dirty = acl .loaded
510+ else :
511+ dirty = True
490512
491- if acl is None :
492- return self
513+ if dirty :
514+ result = self .connection .api_request (
515+ method = 'PATCH' , path = self .path , data = {'acl' : list (acl )},
516+ query_params = {'projection' : 'full' })
517+ self .acl .clear ()
518+ for entry in result ['acl' ]:
519+ self .acl .entity (self .acl .entity_from_dict (entry ))
520+ self .acl .loaded = True
493521
494- self .patch_metadata ({'acl' : list (acl )})
495- self .reload_acl ()
496522 return self
497523
498524 def clear_acl (self ):
@@ -522,19 +548,26 @@ def clear_acl(self):
522548
523549 At this point all the custom rules you created have been removed.
524550 """
525- return self .save_acl (acl = [])
551+ # NOTE: back-end makes some ACL entries sticky (they remain even
552+ # after the PATCH succeeds.
553+ return self .save_acl ([])
526554
527555 def reload_default_object_acl (self ):
528556 """Reload the Default Object ACL rules for this bucket.
529557
530558 :rtype: :class:`Bucket`
531559 :returns: The current bucket.
532560 """
533- self .default_object_acl = DefaultObjectACL (bucket = self )
561+ doa = self .default_object_acl
562+ doa .clear ()
534563
535- for entry in self .get_metadata ('defaultObjectAcl' , []):
536- entity = self .default_object_acl .entity_from_dict (entry )
537- self .default_object_acl .add_entity (entity )
564+ url_path = '%s/defaultObjectAcl' % self .path
565+ found = self .connection .api_request (method = 'GET' , path = url_path )
566+ for entry in found ['items' ]:
567+ doa .add_entity (doa .entity_from_dict (entry ))
568+
569+ # Even if we fetch no entries, the ACL is still loaded.
570+ doa .loaded = True
538571
539572 return self
540573
@@ -547,7 +580,7 @@ def get_default_object_acl(self):
547580 :rtype: :class:`gcloud.storage.acl.DefaultObjectACL`
548581 :returns: A DefaultObjectACL object for this bucket.
549582 """
550- if not self .default_object_acl :
583+ if not self .default_object_acl . loaded :
551584 self .reload_default_object_acl ()
552585 return self .default_object_acl
553586
@@ -562,18 +595,26 @@ def save_default_object_acl(self, acl=None):
562595 """
563596 if acl is None :
564597 acl = self .default_object_acl
598+ dirty = acl .loaded
599+ else :
600+ dirty = True
601+
602+ if dirty :
603+ result = self .connection .api_request (
604+ method = 'PATCH' , path = self .path ,
605+ data = {'defaultObjectAcl' : list (acl )},
606+ query_params = {'projection' : 'full' })
607+ doa = self .default_object_acl
608+ doa .clear ()
609+ for entry in result ['defaultObjectAcl' ]:
610+ doa .entity (doa .entity_from_dict (entry ))
611+ doa .loaded = True
565612
566- if acl is None :
567- return self
568-
569- self .patch_metadata ({'defaultObjectAcl' : list (acl )})
570- self .reload_default_object_acl ()
571613 return self
572614
573615 def clear_default_object_acl (self ):
574616 """Remove the Default Object ACL from this bucket."""
575-
576- return self .save_default_object_acl (acl = [])
617+ return self .save_default_object_acl ([])
577618
578619 def make_public (self , recursive = False , future = False ):
579620 """Make a bucket public.
0 commit comments