PageRenderTime 27ms CodeModel.GetById 10ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/appengine_django/serializer/python.py

http://google-app-engine-django.googlecode.com/
Python | 170 lines | 153 code | 1 blank | 16 comment | 0 complexity | 6c039e81c3d7fd49605702fbac756163 MD5 | raw file
  1#!/usr/bin/python2.4
  2#
  3# Copyright 2008 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"""
 18A Python "serializer", based on the default Django python serializer.
 19
 20The only customisation is in the deserialization process which needs to take
 21special care to resolve the name and parent attributes of the key for each
 22entity and also recreate the keys for any references appropriately.
 23"""
 24import datetime
 25import re
 26
 27from django.conf import settings
 28from django.core.serializers import base
 29from django.core.serializers import python
 30from django.db import models
 31
 32from google.appengine.api import datastore_types
 33from google.appengine.ext import db
 34
 35from django.utils.encoding import smart_unicode
 36
 37Serializer = python.Serializer
 38
 39
 40class FakeParent(object):
 41  """Fake parent 'model' like object.
 42
 43  This class exists to allow a parent object to be provided to a new model
 44  without having to load the parent instance itself.
 45  """
 46
 47  def __init__(self, parent_key):
 48    self._entity = parent_key
 49
 50
 51def Deserializer(object_list, **options):
 52  """Deserialize simple Python objects back into Model instances.
 53
 54  It's expected that you pass the Python objects themselves (instead of a
 55  stream or a string) to the constructor
 56  """
 57  models.get_apps()
 58  for d in object_list:
 59    # Look up the model and starting build a dict of data for it.
 60    Model = python._get_model(d["model"])
 61    data = {}
 62    key = resolve_key(Model._meta.module_name, d["pk"])
 63    if key.name():
 64      data["key_name"] = key.name()
 65    parent = None
 66    if key.parent():
 67      parent = FakeParent(key.parent())
 68    m2m_data = {}
 69
 70    # Handle each field
 71    for (field_name, field_value) in d["fields"].iteritems():
 72      if isinstance(field_value, str):
 73        field_value = smart_unicode(
 74            field_value, options.get("encoding",
 75                                     settings.DEFAULT_CHARSET),
 76            strings_only=True)
 77      field = Model.properties()[field_name]
 78
 79      if isinstance(field, db.Reference):
 80        # Resolve foreign key references.
 81        data[field.name] = resolve_key(Model._meta.module_name, field_value)
 82      else:
 83        # Handle converting strings to more specific formats.
 84        if isinstance(field_value, basestring):
 85          if isinstance(field, db.DateProperty):
 86            field_value = datetime.datetime.strptime(
 87                field_value, '%Y-%m-%d').date()
 88          elif isinstance(field, db.TimeProperty):
 89            field_value = parse_datetime_with_microseconds(field_value,
 90                                                           '%H:%M:%S').time()
 91          elif isinstance(field, db.DateTimeProperty):
 92            field_value = parse_datetime_with_microseconds(field_value,
 93                                                           '%Y-%m-%d %H:%M:%S')
 94        # Handle pyyaml datetime.time deserialization - it returns a datetime
 95        # instead of a time.
 96        if (isinstance(field_value, datetime.datetime) and
 97            isinstance(field, db.TimeProperty)):
 98          field_value = field_value.time()
 99        data[field.name] = field.validate(field_value)
100    # Create the new model instance with all it's data, but no parent.
101    object = Model(**data)
102    # Now add the parent into the hidden attribute, bypassing the type checks
103    # in the Model's __init__ routine.
104    object._parent = parent
105    # When the deserialized object is saved our replacement DeserializedObject
106    # class will set object._parent to force the real parent model to be loaded
107    # the first time it is referenced.
108    yield base.DeserializedObject(object, m2m_data)
109
110
111def parse_datetime_with_microseconds(field_value, format):
112  """Parses a string to a datetime object including microseconds.
113
114  Args:
115    field_value: The string to parse.
116    format: The format string to parse to datetime.strptime. Not including a
117      format specifier for the expected microseconds component.
118
119  Returns:
120    A datetime instance.
121  """
122  try:
123    # This will only return if no microseconds were availanle.
124    return datetime.datetime.strptime(field_value, format)
125  except ValueError, e:
126    # Hack to deal with microseconds.
127    match = re.match(r'unconverted data remains: \.([0-9]+)$',
128                     str(e))
129    if not match:
130      raise
131    ms_str = match.group(1)
132    without_ms = field_value[:-(len(ms_str)+1)]
133    new_value = datetime.datetime.strptime(without_ms, format)
134    return new_value.replace(microsecond=int(ms_str))
135
136
137def resolve_key(model, key_data):
138  """Creates a Key instance from a some data.
139
140  Args:
141    model: The name of the model this key is being resolved for. Only used in
142      the fourth case below (a plain key_name string).
143    key_data: The data to create a key instance from. May be in four formats:
144      * The str() output of a key instance. Eg. A base64 encoded string.
145      * The repr() output of a key instance. Eg. A string for eval().
146      * A list of arguments to pass to db.Key.from_path.
147      * A single string value, being the key_name of the instance. When this
148        format is used the resulting key has no parent, and is for the model
149        named in the model parameter.
150
151  Returns:
152    An instance of db.Key. If the data cannot be used to create a Key instance
153    an error will be raised.
154  """
155  if isinstance(key_data, list):
156    # The key_data is a from_path sequence.
157    return db.Key.from_path(*key_data)
158  elif isinstance(key_data, basestring):
159    if key_data.find("from_path") != -1:
160      # key_data is encoded in repr(key) format
161      return eval(key_data)
162    else:
163      try:
164        # key_data encoded a str(key) format
165        return db.Key(key_data)
166      except datastore_types.datastore_errors.BadKeyError, e:
167        # Final try, assume it's a plain key name for the model.
168        return db.Key.from_path(model, key_data)
169  else:
170    raise base.DeserializationError(u"Invalid key data: '%s'" % key_data)