blob: 5f3ad9499db346a3ee225e15f5c210208c3e9d5b [file] [log] [blame]
# Copyright 2012 Google Inc. All Rights Reserved.
"""Utility module for converting NDB properties to ProtoRPC messages/fields.
In the dictionary NDB_PROPERTY_TO_PROTO, each property defined by NDB is
registered. The registry values can either be a ProtoRPC field for simple
types/properties or a custom method for converting a property into a
ProtoRPC field.
Some properties have no corresponding implementation. These fields are
registered with a method that will raise a NotImplementedError. As of right now,
these are:
Property -- this is the base property class and shouldn't be used
GenericProperty -- this does not play nicely with strongly typed messages
ModelKey -- this is only intended for the key of the instance, and doesn't
make sense to send in messages
ComputedProperty -- a variant of this class is needed to determine the type
desired of the output. Such a variant is provided in
properties
"""
from .. import utils
from protorpc import messages
from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
__all__ = []
GeoPtMessage = utils.GeoPtMessage
RaiseNotImplementedMethod = utils.RaiseNotImplementedMethod
UserMessage = utils.UserMessage
MODEL_KEY_EXPLANATION = (
'A model key property can\'t be used to define an EndpointsModel. These '
'are intended to be used as the lone key of an entity and all ModelKey '
'properties on an entity will have the same value.')
COMPUTED_PROPERTY_EXPLANATION = (
'A computed property can\'t be used to define an EndpointsModel. The type '
'of the message field must be explicitly named; this can be done by using '
'the property EndpointsComputedProperty.')
NDB_PROPERTY_TO_PROTO = {
ndb.BlobKeyProperty: messages.StringField,
ndb.BlobProperty: messages.BytesField, # No concept of compressed here
ndb.BooleanProperty: messages.BooleanField,
ndb.ComputedProperty: RaiseNotImplementedMethod(
ndb.ComputedProperty,
explanation=COMPUTED_PROPERTY_EXPLANATION),
ndb.DateProperty: messages.StringField,
ndb.DateTimeProperty: messages.StringField,
ndb.FloatProperty: messages.FloatField,
ndb.GenericProperty: RaiseNotImplementedMethod(ndb.GenericProperty),
ndb.IntegerProperty: messages.IntegerField,
ndb.JsonProperty: messages.BytesField,
ndb.KeyProperty: messages.StringField,
ndb.ModelKey: RaiseNotImplementedMethod(
ndb.ModelKey,
explanation=MODEL_KEY_EXPLANATION),
ndb.PickleProperty: messages.StringField,
ndb.Property: RaiseNotImplementedMethod(ndb.Property),
ndb.StringProperty: messages.StringField,
ndb.TextProperty: messages.StringField, # No concept of compressed here
ndb.TimeProperty: messages.StringField,
}
def GetKeywordArgs(prop, include_default=True):
"""Captures attributes from an NDB property to be passed to a ProtoRPC field.
Args:
prop: The NDB property which will have its attributes captured.
include_default: An optional boolean indicating whether or not the default
value of the property should be included. Defaults to True, and is
intended to be turned off for special ProtoRPC fields which don't take
a default.
Returns:
A dictionary of attributes, intended to be passed to the constructor of a
ProtoRPC field as keyword arguments.
"""
kwargs = {
'required': prop._required,
'repeated': prop._repeated,
}
if include_default and hasattr(prop, '_default'):
kwargs['default'] = prop._default
if hasattr(prop, '_variant'):
kwargs['variant'] = prop._variant
return kwargs
def MessageFromSimpleField(field, prop, index):
"""Converts a property to the corresponding field of specified type.
Assumes index is the only positional argument needed to create an instance
of {field}, hence only simple fields will work and an EnumField or
MessageField will fail.
Args:
field: A ProtoRPC field type.
prop: The NDB property to be converted.
index: The index of the property within the message.
Returns:
An instance of field with attributes corresponding to those in prop and
index corresponding to that which was passed in.
"""
return field(index, **GetKeywordArgs(prop))
def StructuredPropertyToProto(prop, index):
"""Converts a structured property to the corresponding message field.
Args:
prop: The NDB property to be converted.
index: The index of the property within the message.
Returns:
A message field with attributes corresponding to those in prop, index
corresponding to that which was passed in and with underlying message
class equal to the message class produced by the model class, which
should be a subclass of EndpointsModel.
Raises:
TypeError if the model class of the property does not have a callable
ProtoModel method. This is because we expected a subclass of
EndpointsModel set on the structured property.
"""
modelclass = prop._modelclass
try:
property_proto_method = modelclass.ProtoModel
property_proto = property_proto_method()
except (AttributeError, TypeError):
error_msg = ('Structured properties must receive a model class with a '
'callable ProtoModel attribute. The class %s has no such '
'attribute.' % (modelclass.__name__,))
raise TypeError(error_msg)
# No default for {MessageField}s
kwargs = GetKeywordArgs(prop, include_default=False)
return messages.MessageField(property_proto, index, **kwargs)
NDB_PROPERTY_TO_PROTO[ndb.StructuredProperty] = StructuredPropertyToProto
# Ignore fact that LocalStructuredProperty is just a blob in the datastore
NDB_PROPERTY_TO_PROTO[ndb.LocalStructuredProperty] = StructuredPropertyToProto
def EnumPropertyToProto(prop, index):
"""Converts an enum property from a model to a message field.
Args:
prop: The NDB enum property to be converted.
index: The index of the property within the message.
Returns:
An enum field with attributes corresponding to those in prop, index
corresponding to that which was passed in and with underlying enum type
equal to the enum type set in the enum property.
"""
enum = prop._enum_type
kwargs = GetKeywordArgs(prop)
return messages.EnumField(enum, index, **kwargs)
NDB_PROPERTY_TO_PROTO[msgprop.EnumProperty] = EnumPropertyToProto
def MessagePropertyToProto(prop, index):
"""Converts a message property from a model to a message field.
Args:
prop: The NDB message property to be converted.
index: The index of the property within the message.
Returns:
A message field with attributes corresponding to those in prop, index
corresponding to that which was passed in and with underlying message
class equal to the message type set in the message property.
"""
message_type = prop._message_type
# No default for {MessageField}s
kwargs = GetKeywordArgs(prop, include_default=False)
return messages.MessageField(message_type, index, **kwargs)
NDB_PROPERTY_TO_PROTO[msgprop.MessageProperty] = MessagePropertyToProto
def GeoPtPropertyToProto(prop, index):
"""Converts a model property to a Geo Point message field.
Args:
prop: The NDB property to be converted.
index: The index of the property within the message.
Returns:
A message field with attributes corresponding to those in prop, index
corresponding to that which was passed in and with underlying message
class equal to GeoPtMessage.
"""
# No default for {MessageField}s
kwargs = GetKeywordArgs(prop, include_default=False)
return messages.MessageField(GeoPtMessage, index, **kwargs)
NDB_PROPERTY_TO_PROTO[ndb.GeoPtProperty] = GeoPtPropertyToProto
def UserPropertyToProto(prop, index):
"""Converts a model property to a user message field.
Args:
prop: The NDB property to be converted.
index: The index of the property within the message.
Returns:
A message field with attributes corresponding to those in prop, index
corresponding to that which was passed in and with underlying message
class equal to UserMessage.
"""
# No default for {MessageField}s
kwargs = GetKeywordArgs(prop, include_default=False)
return messages.MessageField(UserMessage, index, **kwargs)
NDB_PROPERTY_TO_PROTO[ndb.UserProperty] = UserPropertyToProto