/appengine_django/serializer/python.py

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