| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2012 Google Inc. All Rights Reserved. | 2 # Copyright 2012 Google Inc. All Rights Reserved. |
| 2 # | 3 # |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # copy of this software and associated documentation files (the | 5 # you may not use this file except in compliance with the License. |
| 5 # "Software"), to deal in the Software without restriction, including | 6 # You may obtain a copy of the License at |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- | |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit | |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- | |
| 9 # lowing conditions: | |
| 10 # | 7 # |
| 11 # The above copyright notice and this permission notice shall be included | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 12 # in all copies or substantial portions of the Software. | |
| 13 # | 9 # |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 10 # Unless required by applicable law or agreed to in writing, software |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | 13 # See the License for the specific language governing permissions and |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 14 # limitations under the License. |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | 15 """Classes for cloud/file references yielded by gsutil iterators.""" |
| 20 # IN THE SOFTWARE. | |
| 21 | 16 |
| 22 import time | 17 from __future__ import absolute_import |
| 23 | 18 |
| 24 | 19 |
| 25 class BucketListingRef(object): | 20 class BucketListingRef(object): |
| 26 """ | 21 """Base class for a reference to one fully expanded iterator result. |
| 27 Container that holds a reference to one result from a bucket listing, allowing | |
| 28 polymorphic iteration over wildcard-iterated URIs, Keys, or Prefixes. At a | |
| 29 minimum, every reference contains a StorageUri. If the reference came from a | |
| 30 bucket listing (as opposed to a manually instantiated ref that might populate | |
| 31 only the StorageUri), it will additionally contain either a Key or a Prefix, | |
| 32 depending on whether it was a reference to an object or was just a prefix of a | |
| 33 path (i.e., bucket subdirectory). The latter happens when the bucket was | |
| 34 listed using delimiter='/'. | |
| 35 | 22 |
| 36 Note that Keys are shallow-populated, based on the contents extracted from | 23 This allows polymorphic iteration over wildcard-iterated URLs. The |
| 37 parsing a bucket listing. This includes name, length, and other fields | 24 reference contains a fully expanded URL string containing no wildcards and |
| 38 (basically, the info listed by gsutil ls -l), but does not include information | 25 referring to exactly one entity (if a wildcard is contained, it is assumed |
| 39 like ACL and location (which require separate server requests, which is why | 26 this is part of the raw string and should never be treated as a wildcard). |
| 40 there's a separate gsutil ls -L option to get this more detailed info). | 27 |
| 28 Each reference represents a Bucket, Object, or Prefix. For filesystem URLs, |
| 29 Objects represent files and Prefixes represent directories. |
| 30 |
| 31 The root_object member contains the underlying object as it was retrieved. |
| 32 It is populated by the calling iterator, which may only request certain |
| 33 fields to reduce the number of server requests. |
| 34 |
| 35 For filesystem URLs, root_object is not populated. |
| 41 """ | 36 """ |
| 42 | 37 |
| 43 def __init__(self, uri, key=None, prefix=None, headers=None): | 38 class _BucketListingRefType(object): |
| 44 """Instantiate BucketListingRef from uri and (if available) key or prefix. | 39 """Enum class for describing BucketListingRefs.""" |
| 40 BUCKET = 'bucket' # Cloud bucket |
| 41 OBJECT = 'object' # Cloud object or filesystem file |
| 42 PREFIX = 'prefix' # Cloud bucket subdir or filesystem directory |
| 43 |
| 44 @property |
| 45 def url_string(self): |
| 46 return self._url_string |
| 47 |
| 48 @property |
| 49 def type_name(self): |
| 50 return self._ref_type |
| 51 |
| 52 def IsBucket(self): |
| 53 return self._ref_type == self._BucketListingRefType.BUCKET |
| 54 |
| 55 def IsObject(self): |
| 56 return self._ref_type == self._BucketListingRefType.OBJECT |
| 57 |
| 58 def IsPrefix(self): |
| 59 return self._ref_type == self._BucketListingRefType.PREFIX |
| 60 |
| 61 def __str__(self): |
| 62 return self._url_string |
| 63 |
| 64 |
| 65 class BucketListingBucket(BucketListingRef): |
| 66 """BucketListingRef subclass for buckets.""" |
| 67 |
| 68 def __init__(self, storage_url, root_object=None): |
| 69 """Creates a BucketListingRef of type bucket. |
| 45 | 70 |
| 46 Args: | 71 Args: |
| 47 uri: StorageUri for the object (required). | 72 storage_url: StorageUrl containing a bucket. |
| 48 key: Key for the object, or None if not available. | 73 root_object: Underlying object metadata, if available. |
| 49 prefix: Prefix for the subdir, or None if not available. | |
| 50 headers: Dictionary containing optional HTTP headers to pass to boto | |
| 51 (which happens when GetKey() is called on an BucketListingRef which | |
| 52 has no constructor-populated Key), or None if not available. | |
| 53 | |
| 54 At most one of key and prefix can be populated. | |
| 55 """ | 74 """ |
| 56 assert key is None or prefix is None | 75 super(BucketListingBucket, self).__init__() |
| 57 self.uri = uri | 76 self._ref_type = self._BucketListingRefType.BUCKET |
| 58 self.key = key | 77 self._url_string = storage_url.url_string |
| 59 self.prefix = prefix | 78 self.storage_url = storage_url |
| 60 self.headers = headers or {} | 79 self.root_object = root_object |
| 61 | |
| 62 def GetUri(self): | |
| 63 """Get URI form of listed URI. | |
| 64 | |
| 65 Returns: | |
| 66 StorageUri. | |
| 67 """ | |
| 68 return self.uri | |
| 69 | |
| 70 def GetUriString(self): | |
| 71 """Get string URI form of listed URI. | |
| 72 | |
| 73 Returns: | |
| 74 String. | |
| 75 """ | |
| 76 return self.uri.uri | |
| 77 | |
| 78 def NamesBucket(self): | |
| 79 """Determines if this BucketListingRef names a bucket. | |
| 80 | |
| 81 Returns: | |
| 82 bool indicator. | |
| 83 """ | |
| 84 return self.key is None and self.prefix is None and self.uri.names_bucket() | |
| 85 | |
| 86 def IsLatest(self): | |
| 87 """Determines if this BucketListingRef names the latest version of an | |
| 88 object. | |
| 89 | |
| 90 Returns: | |
| 91 bool indicator. | |
| 92 """ | |
| 93 return hasattr(self.uri, 'is_latest') and self.uri.is_latest | |
| 94 | |
| 95 def GetRStrippedUriString(self): | |
| 96 """Get string URI form of listed URI, stripped of any right trailing | |
| 97 delims, and without version string. | |
| 98 | |
| 99 Returns: | |
| 100 String. | |
| 101 """ | |
| 102 return self.uri.versionless_uri.rstrip('/') | |
| 103 | |
| 104 def HasKey(self): | |
| 105 """Return bool indicator of whether this BucketListingRef has a Key.""" | |
| 106 return bool(self.key) | |
| 107 | |
| 108 def HasPrefix(self): | |
| 109 """Return bool indicator of whether this BucketListingRef has a Prefix.""" | |
| 110 return bool(self.prefix) | |
| 111 | |
| 112 def GetKey(self): | |
| 113 """Get Key form of listed URI. | |
| 114 | |
| 115 Returns: | |
| 116 Subclass of boto.s3.key.Key. | |
| 117 | |
| 118 Raises: | |
| 119 BucketListingRefException: for bucket-only uri. | |
| 120 """ | |
| 121 # For gsutil ls -l gs://bucket self.key will be populated from (boto) | |
| 122 # parsing the bucket listing. But as noted and handled below there are | |
| 123 # cases where self.key isn't populated. | |
| 124 if not self.key: | |
| 125 if not self.uri.names_object(): | |
| 126 raise BucketListingRefException( | |
| 127 'Attempt to call GetKey() on Key-less BucketListingRef (uri=%s) ' % | |
| 128 self.uri) | |
| 129 # This case happens when we do gsutil ls -l on a object name-ful | |
| 130 # StorageUri with no object-name wildcard. Since the ls command | |
| 131 # implementation only reads bucket info we need to read the object | |
| 132 # for this case. | |
| 133 self.key = self.uri.get_key(validate=False, headers=self.headers) | |
| 134 # When we retrieve the object this way its last_modified timestamp | |
| 135 # is formatted in RFC 1123 format, which is different from when we | |
| 136 # retrieve from the bucket listing (which uses ISO 8601 format), so | |
| 137 # convert so we consistently return ISO 8601 format. | |
| 138 tuple_time = (time.strptime(self.key.last_modified, | |
| 139 '%a, %d %b %Y %H:%M:%S %Z')) | |
| 140 self.key.last_modified = time.strftime('%Y-%m-%dT%H:%M:%S', tuple_time) | |
| 141 return self.key | |
| 142 | |
| 143 def GetPrefix(self): | |
| 144 """Get Prefix form of listed URI. | |
| 145 | |
| 146 Returns: | |
| 147 boto.s3.prefix.Prefix. | |
| 148 | |
| 149 Raises: | |
| 150 BucketListingRefException: if this object has no Prefix. | |
| 151 """ | |
| 152 if not self.prefix: | |
| 153 raise BucketListingRefException( | |
| 154 'Attempt to call GetPrefix() on Prefix-less BucketListingRef ' | |
| 155 '(uri=%s)' % self.uri) | |
| 156 return self.prefix | |
| 157 | |
| 158 def __repr__(self): | |
| 159 """Returns string representation of BucketListingRef.""" | |
| 160 return 'BucketListingRef(%s, HasKey=%s, HasPrefix=%s)' % ( | |
| 161 self.uri, self.HasKey(), self.HasPrefix()) | |
| 162 | 80 |
| 163 | 81 |
| 164 class BucketListingRefException(StandardError): | 82 class BucketListingPrefix(BucketListingRef): |
| 165 """Exception thrown for invalid BucketListingRef requests.""" | 83 """BucketListingRef subclass for prefixes.""" |
| 166 | 84 |
| 167 def __init__(self, reason): | 85 def __init__(self, storage_url, root_object=None): |
| 168 StandardError.__init__(self) | 86 """Creates a BucketListingRef of type prefix. |
| 169 self.reason = reason | |
| 170 | 87 |
| 171 def __repr__(self): | 88 Args: |
| 172 return 'BucketListingRefException: %s' % self.reason | 89 storage_url: StorageUrl containing a prefix. |
| 90 root_object: Underlying object metadata, if available. |
| 91 """ |
| 92 super(BucketListingPrefix, self).__init__() |
| 93 self._ref_type = self._BucketListingRefType.PREFIX |
| 94 self._url_string = storage_url.url_string |
| 95 self.storage_url = storage_url |
| 96 self.root_object = root_object |
| 173 | 97 |
| 174 def __str__(self): | 98 |
| 175 return 'BucketListingRefException: %s' % self.reason | 99 class BucketListingObject(BucketListingRef): |
| 100 """BucketListingRef subclass for objects.""" |
| 101 |
| 102 def __init__(self, storage_url, root_object=None): |
| 103 """Creates a BucketListingRef of type object. |
| 104 |
| 105 Args: |
| 106 storage_url: StorageUrl containing an object. |
| 107 root_object: Underlying object metadata, if available. |
| 108 """ |
| 109 super(BucketListingObject, self).__init__() |
| 110 self._ref_type = self._BucketListingRefType.OBJECT |
| 111 self._url_string = storage_url.url_string |
| 112 self.storage_url = storage_url |
| 113 self.root_object = root_object |
| 114 |
| OLD | NEW |