thirdparty/google_appengine/google/appengine/api/yaml_object.py
changeset 109 620f9b141567
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 #
       
    17 
       
    18 """Builder for mapping YAML documents to object instances.
       
    19 
       
    20 ObjectBuilder is responsible for mapping a YAML document to classes defined
       
    21 using the validation mechanism (see google.appengine.api.validation.py).
       
    22 """
       
    23 
       
    24 
       
    25 
       
    26 
       
    27 
       
    28 from google.appengine.api import validation
       
    29 from google.appengine.api import yaml_listener
       
    30 from google.appengine.api import yaml_builder
       
    31 from google.appengine.api import yaml_errors
       
    32 
       
    33 import yaml
       
    34 
       
    35 
       
    36 class _ObjectMapper(object):
       
    37   """Wrapper used for mapping attributes from a yaml file to an object.
       
    38 
       
    39   This wrapper is required because objects do not know what property they are
       
    40   associated with a creation time, and therefore can not be instantiated
       
    41   with the correct class until they are mapped to their parents.
       
    42   """
       
    43 
       
    44   def __init__(self):
       
    45     """Object mapper starts off with empty value."""
       
    46     self.value = None
       
    47     self.seen = set()
       
    48 
       
    49   def set_value(self, value):
       
    50     """Set value of instance to map to.
       
    51 
       
    52     Args:
       
    53       value: Instance that this mapper maps to.
       
    54     """
       
    55     self.value = value
       
    56 
       
    57   def see(self, key):
       
    58     if key in self.seen:
       
    59       raise yaml_errors.DuplicateAttribute("Duplicate attribute '%s'." % key)
       
    60     self.seen.add(key)
       
    61 
       
    62 class _ObjectSequencer(object):
       
    63   """Wrapper used for building sequences from a yaml file to a list.
       
    64 
       
    65   This wrapper is required because objects do not know what property they are
       
    66   associated with a creation time, and therefore can not be instantiated
       
    67   with the correct class until they are mapped to their parents.
       
    68   """
       
    69 
       
    70   def __init__(self):
       
    71     """Object sequencer starts off with empty value."""
       
    72     self.value = []
       
    73     self.constructor = None
       
    74 
       
    75   def set_constructor(self, constructor):
       
    76     """Set object used for constructing new sequence instances.
       
    77 
       
    78     Args:
       
    79       constructor: Callable which can accept no arguments.  Must return
       
    80         an instance of the appropriate class for the container.
       
    81     """
       
    82     self.constructor = constructor
       
    83 
       
    84 
       
    85 class ObjectBuilder(yaml_builder.Builder):
       
    86   """Builder used for constructing validated objects.
       
    87 
       
    88   Given a class that implements validation.Validated, it will parse a YAML
       
    89   document and attempt to build an instance of the class.  It does so by mapping
       
    90   YAML keys to Python attributes.  ObjectBuilder will only map YAML fields
       
    91   to attributes defined in the Validated subclasses 'ATTRIBUTE' definitions.
       
    92   Lists are mapped to validated.  Repeated attributes and maps are mapped to
       
    93   validated.Type properties.
       
    94 
       
    95   For a YAML map to be compatible with a class, the class must have a
       
    96   constructor that can be called with no parameters.  If the provided type
       
    97   does not have such a constructor a parse time error will occur.
       
    98   """
       
    99 
       
   100   def __init__(self, default_class):
       
   101     """Initialize validated object builder.
       
   102 
       
   103     Args:
       
   104       default_class: Class that is instantiated upon the detection of a new
       
   105         document.  An instance of this class will act as the document itself.
       
   106     """
       
   107     self.default_class = default_class
       
   108 
       
   109   def _GetRepeated(self, attribute):
       
   110     """Get the ultimate type of a repeated validator.
       
   111 
       
   112     Looks for an instance of validation.Repeated, returning its constructor.
       
   113 
       
   114     Args:
       
   115       attribute: Repeated validator attribute to find type for.
       
   116 
       
   117     Returns:
       
   118       The expected class of of the Type validator, otherwise object.
       
   119     """
       
   120     if isinstance(attribute, validation.Optional):
       
   121       attribute = attribute.validator
       
   122     if isinstance(attribute, validation.Repeated):
       
   123       return attribute.constructor
       
   124     return object
       
   125 
       
   126   def BuildDocument(self):
       
   127     """Instantiate new root validated object.
       
   128 
       
   129     Returns:
       
   130       New instance of validated object.
       
   131     """
       
   132     return self.default_class()
       
   133 
       
   134   def BuildMapping(self, top_value):
       
   135     """New instance of object mapper for opening map scope.
       
   136 
       
   137     Args:
       
   138       top_value: Parent of nested object.
       
   139 
       
   140     Returns:
       
   141       New instance of object mapper.
       
   142     """
       
   143     result = _ObjectMapper()
       
   144     if isinstance(top_value, self.default_class):
       
   145       result.value = top_value
       
   146     return result
       
   147 
       
   148   def EndMapping(self, top_value, mapping):
       
   149     """When leaving scope, makes sure new object is initialized.
       
   150 
       
   151     This method is mainly for picking up on any missing required attributes.
       
   152 
       
   153     Args:
       
   154       top_value: Parent of closing mapping object.
       
   155       mapping: _ObjectMapper instance that is leaving scope.
       
   156     """
       
   157     try:
       
   158       mapping.value.CheckInitialized()
       
   159     except validation.ValidationError:
       
   160       raise
       
   161     except Exception, e:
       
   162       try:
       
   163         error_str = str(e)
       
   164       except Exception:
       
   165         error_str = '<unknown>'
       
   166 
       
   167       raise validation.ValidationError("Invalid object:\n%s" % error_str, e)
       
   168 
       
   169   def BuildSequence(self, top_value):
       
   170     """New instance of object sequence.
       
   171 
       
   172     Args:
       
   173       top_value: Object that contains the new sequence.
       
   174 
       
   175     Returns:
       
   176       A new _ObjectSequencer instance.
       
   177     """
       
   178     return _ObjectSequencer()
       
   179 
       
   180   def MapTo(self, subject, key, value):
       
   181     """Map key-value pair to an objects attribute.
       
   182 
       
   183     Args:
       
   184       subject: _ObjectMapper of object that will receive new attribute.
       
   185       key: Key of attribute.
       
   186       value: Value of new attribute.
       
   187 
       
   188     Raises:
       
   189       UnexpectedAttribute when the key is not a validated attribute of
       
   190       the subject value class.
       
   191     """
       
   192     assert subject.value is not None
       
   193     if key not in subject.value.ATTRIBUTES:
       
   194       raise yaml_errors.UnexpectedAttribute(
       
   195           'Unexpected attribute \'%s\' for object of type %s.' %
       
   196           (key, str(subject.value.__class__)))
       
   197 
       
   198     if isinstance(value, _ObjectMapper):
       
   199       value.set_value(subject.value.GetAttribute(key).expected_type())
       
   200       value = value.value
       
   201     elif isinstance(value, _ObjectSequencer):
       
   202       value.set_constructor(self._GetRepeated(subject.value.ATTRIBUTES[key]))
       
   203       value = value.value
       
   204 
       
   205     subject.see(key)
       
   206     try:
       
   207       setattr(subject.value, key, value)
       
   208     except validation.ValidationError, e:
       
   209       try:
       
   210         error_str = str(e)
       
   211       except Exception:
       
   212         error_str = '<unknown>'
       
   213 
       
   214       try:
       
   215         value_str = str(value)
       
   216       except Exception:
       
   217         value_str = '<unknown>'
       
   218 
       
   219       e.message = ("Unable to assign value '%s' to attribute '%s':\n%s" %
       
   220                    (value_str, key, error_str))
       
   221       raise e
       
   222     except Exception, e:
       
   223       try:
       
   224         error_str = str(e)
       
   225       except Exception:
       
   226         error_str = '<unknown>'
       
   227 
       
   228       try:
       
   229         value_str = str(value)
       
   230       except Exception:
       
   231         value_str = '<unknown>'
       
   232 
       
   233       message = ("Unable to assign value '%s' to attribute '%s':\n%s" %
       
   234                  (value_str, key, error_str))
       
   235       raise validation.ValidationError(message, e)
       
   236 
       
   237   def AppendTo(self, subject, value):
       
   238     """Append a value to a sequence.
       
   239 
       
   240     Args:
       
   241       subject: _ObjectSequence that is receiving new value.
       
   242       value: Value that is being appended to sequence.
       
   243     """
       
   244     if isinstance(value, _ObjectMapper):
       
   245       value.set_value(subject.constructor())
       
   246       subject.value.append(value.value)
       
   247     else:
       
   248       subject.value.append(value)
       
   249 
       
   250 
       
   251 def BuildObjects(default_class, stream, loader=yaml.loader.SafeLoader):
       
   252   """Build objects from stream.
       
   253 
       
   254   Handles the basic case of loading all the objects from a stream.
       
   255 
       
   256   Args:
       
   257     default_class: Class that is instantiated upon the detection of a new
       
   258       document.  An instance of this class will act as the document itself.
       
   259     stream: String document or open file object to process as per the
       
   260       yaml.parse method.  Any object that implements a 'read()' method which
       
   261       returns a string document will work with the YAML parser.
       
   262     loader_class: Used for dependency injection.
       
   263 
       
   264   Returns:
       
   265     List of default_class instances parsed from the stream.
       
   266   """
       
   267   builder = ObjectBuilder(default_class)
       
   268   handler = yaml_builder.BuilderHandler(builder)
       
   269   listener = yaml_listener.EventListener(handler)
       
   270 
       
   271   listener.Parse(stream, loader)
       
   272   return handler.GetResults()
       
   273 
       
   274 
       
   275 def BuildSingleObject(default_class, stream, loader=yaml.loader.SafeLoader):
       
   276   """Build object from stream.
       
   277 
       
   278   Handles the basic case of loading a single object from a stream.
       
   279 
       
   280   Args:
       
   281     default_class: Class that is instantiated upon the detection of a new
       
   282       document.  An instance of this class will act as the document itself.
       
   283     stream: String document or open file object to process as per the
       
   284       yaml.parse method.  Any object that implements a 'read()' method which
       
   285       returns a string document will work with the YAML parser.
       
   286     loader_class: Used for dependency injection.
       
   287   """
       
   288   definitions = BuildObjects(default_class, stream, loader)
       
   289 
       
   290   if len(definitions) < 1:
       
   291     raise yaml_errors.EmptyConfigurationFile()
       
   292   if len(definitions) > 1:
       
   293     raise yaml_errors.MultipleConfigurationFile()
       
   294   return definitions[0]