thirdparty/google_appengine/google/appengine/api/appinfo.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/api/appinfo.py	Tue Aug 26 21:49:54 2008 +0000
@@ -0,0 +1,378 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""AppInfo tools
+
+Library for working with AppInfo records in memory, store and load from
+configuration files.
+"""
+
+
+
+
+
+import re
+
+from google.appengine.api import appinfo_errors
+from google.appengine.api import validation
+from google.appengine.api import yaml_listener
+from google.appengine.api import yaml_builder
+from google.appengine.api import yaml_object
+
+
+_URL_REGEX = r'(?!\^)/|\.|(\(.).*(?!\$).'
+_FILES_REGEX = r'(?!\^).*(?!\$).'
+
+_DELTA_REGEX = r'([1-9][0-9]*)([DdHhMm]|[sS]?)'
+_EXPIRATION_REGEX = r'\s*(%s)(\s+%s)*\s*' % (_DELTA_REGEX, _DELTA_REGEX)
+
+APP_ID_MAX_LEN = 100
+MAJOR_VERSION_ID_MAX_LEN = 100
+MAX_URL_MAPS = 100
+
+APPLICATION_RE_STRING = r'(?!-)[a-z\d\-]{1,%d}' % APP_ID_MAX_LEN
+VERSION_RE_STRING = r'(?!-)[a-z\d\-]{1,%d}' % MAJOR_VERSION_ID_MAX_LEN
+
+HANDLER_STATIC_FILES = 'static_files'
+HANDLER_STATIC_DIR = 'static_dir'
+HANDLER_SCRIPT = 'script'
+
+LOGIN_OPTIONAL = 'optional'
+LOGIN_REQUIRED = 'required'
+LOGIN_ADMIN = 'admin'
+
+RUNTIME_PYTHON = 'python'
+
+DEFAULT_SKIP_FILES = (r"^(.*/)?("
+                      r"(app\.yaml)|"
+                      r"(app\.yml)|"
+                      r"(index\.yaml)|"
+                      r"(index\.yml)|"
+                      r"(#.*#)|"
+                      r"(.*~)|"
+                      r"(.*\.py[co])|"
+                      r"(.*/RCS/.*)|"
+                      r"(\..*)|"
+                      r")$")
+
+LOGIN = 'login'
+URL = 'url'
+STATIC_FILES = 'static_files'
+UPLOAD = 'upload'
+STATIC_DIR = 'static_dir'
+MIME_TYPE = 'mime_type'
+SCRIPT = 'script'
+EXPIRATION = 'expiration'
+
+APPLICATION = 'application'
+VERSION = 'version'
+RUNTIME = 'runtime'
+API_VERSION = 'api_version'
+HANDLERS = 'handlers'
+DEFAULT_EXPIRATION = 'default_expiration'
+SKIP_FILES = 'skip_files'
+
+
+class URLMap(validation.Validated):
+  """Mapping from URLs to handlers.
+
+  This class acts like something of a union type.  Its purpose is to
+  describe a mapping between a set of URLs and their handlers.  What
+  handler type a given instance has is determined by which handler-id
+  attribute is used.
+
+  Each mapping can have one and only one handler type.  Attempting to
+  use more than one handler-id attribute will cause an UnknownHandlerType
+  to be raised during validation.  Failure to provide any handler-id
+  attributes will cause MissingHandlerType to be raised during validation.
+
+  The regular expression used by the url field will be used to match against
+  the entire URL path and query string of the request.  This means that
+  partial maps will not be matched.  Specifying a url, say /admin, is the
+  same as matching against the regular expression '^/admin$'.  Don't begin
+  your matching url with ^ or end them with $.  These regular expressions
+  won't be accepted and will raise ValueError.
+
+  Attributes:
+    login: Whether or not login is required to access URL.  Defaults to
+      'optional'.
+    url: Regular expression used to fully match against the request URLs path.
+      See Special Cases for using static_dir.
+    static_files: Handler id attribute that maps URL to the appropriate
+      file.  Can use back regex references to the string matched to url.
+    upload: Regular expression used by the application configuration
+      program to know which files are uploaded as blobs.  It's very
+      difficult to determine this using just the url and static_files
+      so this attribute must be included.  Required when defining a
+      static_files mapping.
+      A matching file name must fully match against the upload regex, similar
+      to how url is matched against the request path.  Do not begin upload
+      with ^ or end it with $.
+    static_dir: Handler id that maps the provided url to a sub-directory
+      within the application directory.  See Special Cases.
+    mime_type: When used with static_files and static_dir the mime-type
+      of files served from those directories are overridden with this
+      value.
+    script: Handler id that maps URLs to scipt handler within the application
+      directory that will run using CGI.
+    expiration: When used with static files and directories, the time delta to
+      use for cache expiration. Has the form '4d 5h 30m 15s', where each letter
+      signifies days, hours, minutes, and seconds, respectively. The 's' for
+      seconds may be omitted. Only one amount must be specified, combining
+      multiple amounts is optional. Example good values: '10', '1d 6h',
+      '1h 30m', '7d 7d 7d', '5m 30'.
+
+  Special cases:
+    When defining a static_dir handler, do not use a regular expression
+    in the url attribute.  Both the url and static_dir attributes are
+    automatically mapped to these equivalents:
+
+      <url>/(.*)
+      <static_dir>/\1
+
+    For example:
+
+      url: /images
+      static_dir: images_folder
+
+    Is the same as this static_files declaration:
+
+      url: /images/(.*)
+      static_files: images/\1
+      upload: images/(.*)
+  """
+
+  ATTRIBUTES = {
+
+    URL: validation.Optional(_URL_REGEX),
+    LOGIN: validation.Options(LOGIN_OPTIONAL,
+                              LOGIN_REQUIRED,
+                              LOGIN_ADMIN,
+                              default=LOGIN_OPTIONAL),
+
+
+
+    HANDLER_STATIC_FILES: validation.Optional(_FILES_REGEX),
+    UPLOAD: validation.Optional(_FILES_REGEX),
+
+
+    HANDLER_STATIC_DIR: validation.Optional(_FILES_REGEX),
+
+
+    MIME_TYPE: validation.Optional(str),
+    EXPIRATION: validation.Optional(_EXPIRATION_REGEX),
+
+
+    HANDLER_SCRIPT: validation.Optional(_FILES_REGEX),
+  }
+
+  COMMON_FIELDS = set([URL, LOGIN])
+
+  ALLOWED_FIELDS = {
+    HANDLER_STATIC_FILES: (MIME_TYPE, UPLOAD, EXPIRATION),
+    HANDLER_STATIC_DIR: (MIME_TYPE, EXPIRATION),
+    HANDLER_SCRIPT: (),
+  }
+
+  def GetHandler(self):
+    """Get handler for mapping.
+
+    Returns:
+      Value of the handler (determined by handler id attribute).
+    """
+    return getattr(self, self.GetHandlerType())
+
+  def GetHandlerType(self):
+    """Get handler type of mapping.
+
+    Returns:
+      Handler type determined by which handler id attribute is set.
+
+    Raises:
+      UnknownHandlerType when none of the no handler id attributes
+      are set.
+
+      UnexpectedHandlerAttribute when an unexpected attribute
+      is set for the discovered handler type.
+
+      HandlerTypeMissingAttribute when the handler is missing a
+      required attribute for its handler type.
+    """
+    for id_field in URLMap.ALLOWED_FIELDS.iterkeys():
+      if getattr(self, id_field) is not None:
+        mapping_type = id_field
+        break
+    else:
+      raise appinfo_errors.UnknownHandlerType(
+          'Unknown url handler type.\n%s' % str(self))
+
+    allowed_fields = URLMap.ALLOWED_FIELDS[mapping_type]
+
+    for attribute in self.ATTRIBUTES.iterkeys():
+      if (getattr(self, attribute) is not None and
+          not (attribute in allowed_fields or
+               attribute in URLMap.COMMON_FIELDS or
+               attribute == mapping_type)):
+            raise appinfo_errors.UnexpectedHandlerAttribute(
+                'Unexpected attribute "%s" for mapping type %s.' %
+                (attribute, mapping_type))
+
+    if mapping_type == HANDLER_STATIC_FILES and not self.upload:
+      raise appinfo_errors.MissingHandlerAttribute(
+          'Missing "%s" attribute for URL "%s".' % (UPLOAD, self.url))
+
+    return mapping_type
+
+  def CheckInitialized(self):
+    """Adds additional checking to make sure handler has correct fields.
+
+    In addition to normal ValidatedCheck calls GetHandlerType
+    which validates all the handler fields are configured
+    properly.
+
+    Raises:
+      UnknownHandlerType when none of the no handler id attributes
+      are set.
+
+      UnexpectedHandlerAttribute when an unexpected attribute
+      is set for the discovered handler type.
+
+      HandlerTypeMissingAttribute when the handler is missing a
+      required attribute for its handler type.
+    """
+    super(URLMap, self).CheckInitialized()
+    self.GetHandlerType()
+
+
+class AppInfoExternal(validation.Validated):
+  """Class representing users application info.
+
+  This class is passed to a yaml_object builder to provide the validation
+  for the application information file format parser.
+
+  Attributes:
+    application: Unique identifier for application.
+    version: Application's major version number.
+    runtime: Runtime used by application.
+    api_version: Which version of APIs to use.
+    handlers: List of URL handlers.
+    default_expiration: Default time delta to use for cache expiration for
+      all static files, unless they have their own specific 'expiration' set.
+      See the URLMap.expiration field's documentation for more information.
+    skip_files: An re object.  Files that match this regular expression will
+      not be uploaded by appcfg.py.  For example:
+        skip_files: |
+          .svn.*|
+          #.*#
+  """
+
+  ATTRIBUTES = {
+
+
+    APPLICATION: APPLICATION_RE_STRING,
+    VERSION: VERSION_RE_STRING,
+    RUNTIME: validation.Options(RUNTIME_PYTHON),
+
+
+    API_VERSION: validation.Options('1', 'beta'),
+    HANDLERS: validation.Optional(validation.Repeated(URLMap)),
+    DEFAULT_EXPIRATION: validation.Optional(_EXPIRATION_REGEX),
+    SKIP_FILES: validation.RegexStr(default=DEFAULT_SKIP_FILES)
+  }
+
+  def CheckInitialized(self):
+    """Ensures that at least one url mapping is provided.
+
+    Raises:
+      MissingURLMapping when no URLMap objects are present in object.
+      TooManyURLMappings when there are too many URLMap entries.
+    """
+    super(AppInfoExternal, self).CheckInitialized()
+    if not self.handlers:
+      raise appinfo_errors.MissingURLMapping(
+          'No URLMap entries found in application configuration')
+    if len(self.handlers) > MAX_URL_MAPS:
+      raise appinfo_errors.TooManyURLMappings(
+          'Found more than %d URLMap entries in application configuration' %
+          MAX_URL_MAPS)
+
+
+def LoadSingleAppInfo(app_info):
+  """Load a single AppInfo object where one and only one is expected.
+
+  Args:
+    app_info: A file-like object or string.  If it is a string, parse it as
+    a configuration file.  If it is a file-like object, read in data and
+    parse.
+
+  Returns:
+    An instance of AppInfoExternal as loaded from a YAML file.
+
+  Raises:
+    EmptyConfigurationFile when there are no documents in YAML file.
+    MultipleConfigurationFile when there is more than one document in YAML
+    file.
+  """
+  builder = yaml_object.ObjectBuilder(AppInfoExternal)
+  handler = yaml_builder.BuilderHandler(builder)
+  listener = yaml_listener.EventListener(handler)
+  listener.Parse(app_info)
+
+  app_infos = handler.GetResults()
+  if len(app_infos) < 1:
+    raise appinfo_errors.EmptyConfigurationFile()
+  if len(app_infos) > 1:
+    raise appinfo_errors.MultipleConfigurationFile()
+  return app_infos[0]
+
+
+_file_path_positive_re = re.compile(r'^[ 0-9a-zA-Z\._\+/\$-]{1,256}$')
+
+_file_path_negative_1_re = re.compile(r'\.\.|^\./|\.$|/\./|^-')
+
+_file_path_negative_2_re = re.compile(r'//|/$')
+
+_file_path_negative_3_re = re.compile(r'^ | $|/ | /')
+
+
+def ValidFilename(filename):
+  """Determines if filename is valid.
+
+  filename must be a valid pathname.
+  - It must contain only letters, numbers, _, +, /, $, ., and -.
+  - It must be less than 256 chars.
+  - It must not contain "/./", "/../", or "//".
+  - It must not end in "/".
+  - All spaces must be in the middle of a directory or file name.
+
+  Args:
+    filename: The filename to validate.
+
+  Returns:
+    An error string if the filename is invalid.  Returns '' if the filename
+    is valid.
+  """
+  if _file_path_positive_re.match(filename) is None:
+    return 'Invalid character in filename: %s' % filename
+  if _file_path_negative_1_re.search(filename) is not None:
+    return ('Filename cannot contain "." or ".." or start with "-": %s' %
+            filename)
+  if _file_path_negative_2_re.search(filename) is not None:
+    return 'Filename cannot have trailing / or contain //: %s' % filename
+  if _file_path_negative_3_re.search(filename) is not None:
+    return 'Any spaces must be in the middle of a filename: %s' % filename
+  return ''