--- /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 ''