15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2012 Google Inc. All Rights Reserved.
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License");
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# you may not use this file except in compliance with the License.
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# You may obtain a copy of the License at
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#    http://www.apache.org/licenses/LICENSE-2.0
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Unless required by applicable law or agreed to in writing,
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# software distributed under the License is distributed on an
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# either express or implied. See the License for the specific
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# language governing permissions and limitations under the License.
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""Helpers shared by cloudstorage_stub and cloudstorage_api."""
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)__all__ = ['CS_XML_NS',
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'CSFileStat',
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'dt_str_to_posix',
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'local_api_url',
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'LOCAL_GCS_ENDPOINT',
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'local_run',
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'get_access_token',
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'get_metadata',
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'GCSFileStat',
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'http_time_to_posix',
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'memory_usage',
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'posix_time_to_http',
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'posix_to_dt_str',
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'set_access_token',
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'validate_options',
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'validate_bucket_name',
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'validate_bucket_path',
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           'validate_file_path',
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          ]
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import calendar
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import datetime
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from email import utils as email_utils
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import logging
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import os
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import re
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)try:
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  from google.appengine.api import runtime
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)except ImportError:
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  from google.appengine.api import runtime
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_BUCKET_REGEX_BASE = r'[a-z0-9\.\-_]{3,63}'
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_BUCKET_REGEX = re.compile(_GCS_BUCKET_REGEX_BASE + r'$')
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_BUCKET_PATH_REGEX = re.compile(r'/' + _GCS_BUCKET_REGEX_BASE + r'$')
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_PATH_PREFIX_REGEX = re.compile(r'/' + _GCS_BUCKET_REGEX_BASE + r'.*')
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_FULLPATH_REGEX = re.compile(r'/' + _GCS_BUCKET_REGEX_BASE + r'/.*')
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_METADATA = ['x-goog-meta-',
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 'content-disposition',
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 'cache-control',
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 'content-encoding']
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_GCS_OPTIONS = _GCS_METADATA + ['x-goog-acl']
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)CS_XML_NS = 'http://doc.s3.amazonaws.com/2006-03-01'
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)LOCAL_GCS_ENDPOINT = '/_ah/gcs'
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_access_token = ''
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_MAX_GET_BUCKET_RESULT = 1000
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def set_access_token(access_token):
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Set the shared access token to authenticate with Google Cloud Storage.
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  When set, the library will always attempt to communicate with the
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  real Google Cloud Storage with this token even when running on dev appserver.
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Note the token could expire so it's up to you to renew it.
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  When absent, the library will automatically request and refresh a token
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  on appserver, or when on dev appserver, talk to a Google Cloud Storage
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  stub.
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    access_token: you can get one by run 'gsutil -d ls' and copy the
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      str after 'Bearer'.
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  global _access_token
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  _access_token = access_token
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def get_access_token():
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Returns the shared access token."""
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return _access_token
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class GCSFileStat(object):
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Container for GCS file stat."""
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self,
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               filename,
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               st_size,
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               etag,
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               st_ctime,
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               content_type=None,
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               metadata=None,
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               is_dir=False):
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Initialize.
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    For files, the non optional arguments are always set.
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    For directories, only filename and is_dir is set.
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      filename: a Google Cloud Storage filename of form '/bucket/filename'.
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      st_size: file size in bytes. long compatible.
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      etag: hex digest of the md5 hash of the file's content. str.
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      st_ctime: posix file creation time. float compatible.
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      content_type: content type. str.
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      metadata: a str->str dict of user specified options when creating
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        the file. Possible keys are x-goog-meta-, content-disposition,
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        content-encoding, and cache-control.
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      is_dir: True if this represents a directory. False if this is a real file.
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.filename = filename
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.is_dir = is_dir
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.st_size = None
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.st_ctime = None
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.etag = None
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.content_type = content_type
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.metadata = metadata
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not is_dir:
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self.st_size = long(st_size)
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self.st_ctime = float(st_ctime)
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if etag[0] == '"' and etag[-1] == '"':
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        etag = etag[1:-1]
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self.etag = etag
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __repr__(self):
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if self.is_dir:
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return '(directory: %s)' % self.filename
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return (
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        '(filename: %(filename)s, st_size: %(st_size)s, '
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        'st_ctime: %(st_ctime)s, etag: %(etag)s, '
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        'content_type: %(content_type)s, '
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        'metadata: %(metadata)s)' %
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        dict(filename=self.filename,
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)             st_size=self.st_size,
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)             st_ctime=self.st_ctime,
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)             etag=self.etag,
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)             content_type=self.content_type,
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)             metadata=self.metadata))
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __cmp__(self, other):
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not isinstance(other, self.__class__):
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise ValueError('Argument to cmp must have the same type. '
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       'Expect %s, got %s', self.__class__.__name__,
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       other.__class__.__name__)
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if self.filename > other.filename:
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return 1
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    elif self.filename < other.filename:
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return -1
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return 0
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __hash__(self):
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if self.etag:
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return hash(self.etag)
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return hash(self.filename)
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)CSFileStat = GCSFileStat
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def get_metadata(headers):
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Get user defined options from HTTP response headers."""
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return dict((k, v) for k, v in headers.iteritems()
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)              if any(k.lower().startswith(valid) for valid in _GCS_METADATA))
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def validate_bucket_name(name):
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Validate a Google Storage bucket name.
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    name: a Google Storage bucket name with no prefix or suffix.
1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Raises:
1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ValueError: if name is invalid.
1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  _validate_path(name)
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not _GCS_BUCKET_REGEX.match(name):
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise ValueError('Bucket should be 3-63 characters long using only a-z,'
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                     '0-9, underscore, dash or dot but got %s' % name)
1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def validate_bucket_path(path):
1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Validate a Google Cloud Storage bucket path.
1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    path: a Google Storage bucket path. It should have form '/bucket'.
2015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Raises:
2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ValueError: if path is invalid.
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  _validate_path(path)
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not _GCS_BUCKET_PATH_REGEX.match(path):
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise ValueError('Bucket should have format /bucket '
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                     'but got %s' % path)
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def validate_file_path(path):
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Validate a Google Cloud Storage file path.
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    path: a Google Storage file path. It should have form '/bucket/filename'.
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Raises:
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ValueError: if path is invalid.
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  _validate_path(path)
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not _GCS_FULLPATH_REGEX.match(path):
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise ValueError('Path should have format /bucket/filename '
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                     'but got %s' % path)
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def _process_path_prefix(path_prefix):
2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Validate and process a Google Cloud Stoarge path prefix.
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    path_prefix: a Google Cloud Storage path prefix of format '/bucket/prefix'
2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      or '/bucket/' or '/bucket'.
2325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Raises:
2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ValueError: if path is invalid.
2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    a tuple of /bucket and prefix. prefix can be None.
2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  _validate_path(path_prefix)
2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not _GCS_PATH_PREFIX_REGEX.match(path_prefix):
2415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise ValueError('Path prefix should have format /bucket, /bucket/, '
2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                     'or /bucket/prefix but got %s.' % path_prefix)
2435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  bucket_name_end = path_prefix.find('/', 1)
2445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  bucket = path_prefix
2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  prefix = None
2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if bucket_name_end != -1:
2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    bucket = path_prefix[:bucket_name_end]
2485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    prefix = path_prefix[bucket_name_end + 1:] or None
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return bucket, prefix
2505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def _validate_path(path):
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Basic validation of Google Storage paths.
2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    path: a Google Storage path. It should have form '/bucket/filename'
2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      or '/bucket'.
2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Raises:
2605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ValueError: if path is invalid.
2615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    TypeError: if path is not of type basestring.
2625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
2635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not path:
2645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise ValueError('Path is empty')
2655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not isinstance(path, basestring):
2665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise TypeError('Path should be a string but is %s (%s).' %
2675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    (path.__class__, path))
2685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def validate_options(options):
2715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Validate Google Cloud Storage options.
2725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
2745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    options: a str->basestring dict of options to pass to Google Cloud Storage.
2755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Raises:
2775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ValueError: if option is not supported.
2785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    TypeError: if option is not of type str or value of an option
2795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      is not of type basestring.
2805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
2815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if not options:
2825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return
2835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for k, v in options.iteritems():
2855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not isinstance(k, str):
2865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise TypeError('option %r should be a str.' % k)
2875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not any(k.lower().startswith(valid) for valid in _GCS_OPTIONS):
2885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise ValueError('option %s is not supported.' % k)
2895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not isinstance(v, basestring):
2905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise TypeError('value %r for option %s should be of type basestring.' %
2915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                      (v, k))
2925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def http_time_to_posix(http_time):
2955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Convert HTTP time format to posix time.
2965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
2985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for http time format.
2995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
3015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    http_time: time in RFC 2616 format. e.g.
3025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      "Mon, 20 Nov 1995 19:12:08 GMT".
3035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
3055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    A float of secs from unix epoch.
3065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
3075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if http_time is not None:
3085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return email_utils.mktime_tz(email_utils.parsedate_tz(http_time))
3095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def posix_time_to_http(posix_time):
3125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Convert posix time to HTML header time format.
3135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
3155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    posix_time: unix time.
3165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
3185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    A datatime str in RFC 2616 format.
3195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
3205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if posix_time:
3215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return email_utils.formatdate(posix_time, usegmt=True)
3225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_DT_FORMAT = '%Y-%m-%dT%H:%M:%S'
3255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def dt_str_to_posix(dt_str):
3285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """format str to posix.
3295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  datetime str is of format %Y-%m-%dT%H:%M:%S.%fZ,
3315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  e.g. 2013-04-12T00:22:27.978Z. According to ISO 8601, T is a separator
3325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  between date and time when they are on the same line.
3335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Z indicates UTC (zero meridian).
3345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  A pointer: http://www.cl.cam.ac.uk/~mgk25/iso-time.html
3365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  This is used to parse LastModified node from GCS's GET bucket XML response.
3385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
3405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    dt_str: A datetime str.
3415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
3435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    A float of secs from unix epoch. By posix definition, epoch is midnight
3445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    1970/1/1 UTC.
3455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
3465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  parsable, _ = dt_str.split('.')
3475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  dt = datetime.datetime.strptime(parsable, _DT_FORMAT)
3485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return calendar.timegm(dt.utctimetuple())
3495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def posix_to_dt_str(posix):
3525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Reverse of str_to_datetime.
3535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  This is used by GCS stub to generate GET bucket XML response.
3555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
3575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    posix: A float of secs from unix epoch.
3585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
3605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    A datetime str.
3615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
3625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  dt = datetime.datetime.utcfromtimestamp(posix)
3635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  dt_str = dt.strftime(_DT_FORMAT)
3645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return dt_str + '.000Z'
3655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def local_run():
3685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Whether we should hit GCS dev appserver stub."""
3695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  server_software = os.environ.get('SERVER_SOFTWARE')
3705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if server_software is None:
3715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return True
3725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if 'remote_api' in server_software:
3735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return False
3745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if server_software.startswith(('Development', 'testutil')):
3755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return True
3765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return False
3775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def local_api_url():
3805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Return URL for GCS emulation on dev appserver."""
3815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return 'http://%s%s' % (os.environ.get('HTTP_HOST'), LOCAL_GCS_ENDPOINT)
3825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def memory_usage(method):
3855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Log memory usage before and after a method."""
3865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def wrapper(*args, **kwargs):
3875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    logging.info('Memory before method %s is %s.',
3885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 method.__name__, runtime.memory_usage().current())
3895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    result = method(*args, **kwargs)
3905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    logging.info('Memory after method %s is %s',
3915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 method.__name__, runtime.memory_usage().current())
3925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return result
3935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return wrapper
3945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def _add_ns(tagname):
3975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return '{%(ns)s}%(tag)s' % {'ns': CS_XML_NS,
3985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                              'tag': tagname}
3995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
4005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
4015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_CONTENTS = _add_ns('Contents')
4025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_LAST_MODIFIED = _add_ns('LastModified')
4035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_ETAG = _add_ns('ETag')
4045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_KEY = _add_ns('Key')
4055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_SIZE = _add_ns('Size')
4065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_PREFIX = _add_ns('Prefix')
4075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_COMMON_PREFIXES = _add_ns('CommonPrefixes')
4085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_NEXT_MARKER = _add_ns('NextMarker')
4095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)_T_IS_TRUNCATED = _add_ns('IsTruncated')
410