#!/usr/bin/env python
#
# Copyright 2007 Google LLC
#
# 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.
#




"""Utilities for converting between v3 and v1 datastore protocol buffers.

This module is internal and should not be used by client applications.
"""

import six














from google.appengine.datastore import datastore_v4_pb2
from google.appengine.datastore import entity_v4_pb2
from google.appengine.datastore import entity_bytes_pb2 as entity_pb2





_MIN_CLOUD_DATASTORE_VERSION = (6, 0, 0)
_CLOUD_DATASTORE_ENABLED = False

try:
  
  
  import googledatastore

  if googledatastore.VERSION >= _MIN_CLOUD_DATASTORE_VERSION:
    _CLOUD_DATASTORE_ENABLED = True
except ImportError:
  pass
except AttributeError:

  pass

MISSING_CLOUD_DATASTORE_MESSAGE = (
    'Could not import googledatastore. This library must be installed with '
    'version >= %s to use the Cloud Datastore API.' %
    '.'.join([str(v) for v in _MIN_CLOUD_DATASTORE_VERSION]))


MEANING_ATOM_CATEGORY = 1
MEANING_URL = 2
MEANING_ATOM_TITLE = 3
MEANING_ATOM_CONTENT = 4
MEANING_ATOM_SUMMARY = 5
MEANING_ATOM_AUTHOR = 6
MEANING_NON_RFC_3339_TIMESTAMP = 7
MEANING_GD_EMAIL = 8
MEANING_GEORSS_POINT = 9
MEANING_GD_IM = 10
MEANING_GD_PHONENUMBER = 11
MEANING_GD_POSTALADDRESS = 12
MEANING_PERCENT = 13
MEANING_TEXT = 15
MEANING_BYTESTRING = 16
MEANING_BLOBKEY = 17
MEANING_INDEX_ONLY = 18
MEANING_PREDEFINED_ENTITY_USER = 20
MEANING_PREDEFINED_ENTITY_POINT = 21
MEANING_ZLIB = 22
MEANING_POINT_WITHOUT_V3_MEANING = 23
MEANING_EMPTY_LIST = 24


URI_MEANING_ZLIB = 'ZLIB'



MAX_URL_CHARS = 2083
MAX_INDEXED_STRING_CHARS = 500
MAX_INDEXED_BLOB_BYTES = 500
MAX_PARTITION_ID_LENGTH = 100
MAX_DATASET_ID_SECTION_LENGTH = 100



MAX_DATASET_ID_LENGTH = MAX_DATASET_ID_SECTION_LENGTH * 3 + 2
MAX_KEY_PATH_LENGTH = 100


PROPERTY_NAME_X = 'x'
PROPERTY_NAME_Y = 'y'


PROPERTY_NAME_EMAIL = 'email'
PROPERTY_NAME_AUTH_DOMAIN = 'auth_domain'
PROPERTY_NAME_USER_ID = 'user_id'
PROPERTY_NAME_INTERNAL_ID = 'internal_id'
PROPERTY_NAME_FEDERATED_IDENTITY = 'federated_identity'
PROPERTY_NAME_FEDERATED_PROVIDER = 'federated_provider'


PROPERTY_NAME_KEY = '__key__'

DEFAULT_GAIA_ID = 0


RFC_3339_MIN_MICROSECONDS_INCLUSIVE = -62135596800 * 1000 * 1000
RFC_3339_MAX_MICROSECONDS_INCLUSIVE = 253402300799 * 1000 * 1000 + 999999


def v4_key_to_string(v4_key):
  """Generates a string representing a key's path.

  The output makes no effort to qualify special characters in strings.

  The key need not be valid, but if any of the key path elements have
  both a name and an ID the name is ignored.

  Args:
    v4_key: An `entity_v4_pb2.Key`.

  Returns:
    A string representing the key's path.
  """
  path_element_strings = []
  for path_element in v4_key.path_element:
    if path_element.HasField('id'):
      id_or_name = str(path_element.id)
    elif path_element.HasField('name'):
      id_or_name = path_element.name
    else:
      id_or_name = ''
    path_element_strings.append('%s: %s' % (path_element.kind, id_or_name))
  return '[%s]' % ', '.join(path_element_strings)


def is_complete_v4_key(v4_key):
  """Returns True if a key specifies an ID or name, False otherwise.

  Args:
    v4_key: An `entity_v4_pb2.Key`.

  Returns:
    `True` if the key specifies an ID or name, `False` otherwise.
  """
  assert len(v4_key.path_element) >= 1
  last_element = v4_key.path_element(len(v4_key.path_element) - 1)
  return last_element.HasField('id') or last_element.HasField('name')


def v1_key_to_string(v1_key):
  """Generates a string representing a key's path.

  The output makes no effort to qualify special characters in strings.

  The key need not be valid, but if any of the key path elements have
  both a name and an ID the name is ignored.

  Args:
    v1_key: An `googledatastore.Key`.

  Returns:
    A string representing the key's path.
  """
  path_element_strings = []
  for path_element in v1_key.path:
    field = path_element.WhichOneof('id_type')
    if field == 'id':
      id_or_name = str(path_element.id)
    elif field == 'name':
      id_or_name = path_element.name
    else:
      id_or_name = ''
    path_element_strings.append('%s: %s' % (path_element.kind, id_or_name))
  return '[%s]' % ', '.join(path_element_strings)


def is_complete_v1_key(v1_key):
  """Returns `True` if a key specifies an ID or name, `False` otherwise.

  Args:
    v1_key: An `googledatastore.Key`.

  Returns:
    `True` if the key specifies an ID or name, `False` otherwise.
  """
  assert len(v1_key.path) >= 1
  last_element = v1_key.path[len(v1_key.path) - 1]
  return last_element.WhichOneof('id_type') is not None


def is_complete_v3_key(v3_key):
  """Returns `True` if a key specifies an ID or name, `False` otherwise.

  Args:
    v3_key: A `datastore_pb.Reference`.

  Returns:
    `True` if the key specifies an ID or name, `False` otherwise.
  """
  assert len(v3_key.path.element) >= 1
  last_element = v3_key.path.element[-1]
  return ((last_element.HasField('id') and last_element.id) or
          (last_element.HasField('name') and last_element.name))


def get_v1_mutation_key_and_entity(v1_mutation):
  """Returns the v1 key and entity for a v1 mutation proto, if applicable.

  Args:
    v1_mutation: A `googledatastore.Mutation`.

  Returns:
    A tuple `(googledatastore.Key for this mutation,
             googledatastore.Entity or None if the mutation is a deletion)`.
  """
  if v1_mutation.HasField('delete'):
    return v1_mutation.delete, None
  else:
    v1_entity = getattr(v1_mutation, v1_mutation.WhichOneof('operation'))
    return v1_entity.key, v1_entity


def is_valid_utf8(s):
  if isinstance(s, six.text_type):
    return True
  try:
    s.decode('utf-8')
    return True
  except UnicodeDecodeError:
    return False


def check_conversion(condition, message):
  """Asserts a conversion condition and raises an error if it's not met.

  Args:
    condition: A boolean condition to enforce.
    message: Error message.

  Raises:
    InvalidConversionError: if condition is not met
  """
  if not condition:
    raise InvalidConversionError(message)


def is_in_rfc_3339_bounds(microseconds):
  return (RFC_3339_MIN_MICROSECONDS_INCLUSIVE <= microseconds
          <= RFC_3339_MAX_MICROSECONDS_INCLUSIVE)



class InvalidConversionError(Exception):
  """Raised when conversion fails."""
  pass


class IdResolver(object):
  """A class that can handle project id <--> application id transformations."""

  def __init__(self, app_ids=()):
    """Create a new `IdResolver`.

    Args:
     app_ids: A list of application ids with application id shard set. i.e.
       s~my_app or e~my_app.
    """
    resolver_map = {}
    for app_id in app_ids:
      resolver_map[self.resolve_project_id(app_id)] = app_id
    self._resolver_map = resolver_map

  def resolve_project_id(self, app_id):
    """Converts an application id to a project id.

    Args:
      app_id: The application id.
    Returns:
      The project id.
    """
    return app_id.rsplit('~')[-1]

  def resolve_app_id(self, project_id):
    """Converts a project id to an application id.

    Args:
      project_id: The project id.
    Returns:
      The application id.
    Raises:
      InvalidConversionError: if the application is unknown for the project id.
    """
    check_conversion(project_id in self._resolver_map,
                     'Cannot determine application id for provided project id: '
                     '"%s".'
                     % project_id)
    return self._resolver_map[project_id]


class _IdentityIdResolver(IdResolver):
  """An `IdResolver` that resolve app_id == project_id."""

  def resolve_project_id(self, app_id):
    return app_id

  def resolve_app_id(self, project_id):
    return project_id


class _EntityConverter(object):
  """Converter for entities and keys."""

  def __init__(self, id_resolver):
    """Creates a new `EntityConverter`.

    Args:
      id_resolver: An `IdResolver` object for converting.
        `project_id <--> application_id`.
    """
    self._id_resolver = id_resolver

  def v4_to_v3_reference(self, v4_key, v3_ref):
    """Converts a v4 Key to a v3 Reference.

    Args:
      v4_key: An `entity_v4_pb2.Key`.
      v3_ref: An `entity_pb2.Reference` to populate.
    """
    v3_ref.Clear()
    if v4_key.HasField('partition_id'):
      if v4_key.partition_id.HasField('dataset_id'):
        v3_ref.app = v4_key.partition_id.dataset_id
      if v4_key.partition_id.HasField('namespace'):
        v3_ref.name_space = v4_key.partition_id.namespace
    for v4_element in v4_key.path_element:
      v3_element = v3_ref.path.element.add()
      v3_element.type = v4_element.kind
      if v4_element.HasField('id'):
        v3_element.id = v4_element.id
      if v4_element.HasField('name'):
        v3_element.name = v4_element.name

  def v4_to_v3_references(self, v4_keys):
    """Converts a list of v4 Keys to a list of v3 References.

    Args:
      v4_keys: A list of `entity_v4_pb2.Key` objects.

    Returns:
      A list of `entity_pb2.Reference` objects.
    """
    v3_refs = []
    for v4_key in v4_keys:
      v3_ref = entity_pb2.Reference()
      self.v4_to_v3_reference(v4_key, v3_ref)
      v3_refs.append(v3_ref)
    return v3_refs

  def v3_to_v4_key(self, v3_ref, v4_key):
    """Converts a v3 Reference to a v4 Key.

    Args:
      v3_ref: An `entity_p2b.Reference`.
      v4_key: An `entity_v4_pb2.Key` to populate.
    """
    v4_key.Clear()
    if not v3_ref.app:
      return
    v4_key.partition_id.dataset_id = v3_ref.app
    if v3_ref.name_space:
      v4_key.partition_id.namespace = v3_ref.name_space
    for v3_element in v3_ref.path.element:
      v4_element = v4_key.path_element.add()
      v4_element.kind = v3_element.type
      if v3_element.HasField('id'):
        v4_element.id = v3_element.id
      if v3_element.HasField('name'):
        v4_element.name = v3_element.name

  def v3_to_v4_keys(self, v3_refs):
    """Converts a list of v3 References to a list of v4 Keys.

    Args:
      v3_refs: A list of `entity_pb2.Reference` objects.

    Returns:
      A list of `entity_v4_pb2.Key` objects.
    """
    v4_keys = []
    for v3_ref in v3_refs:
      v4_key = entity_v4_pb2.Key()
      self.v3_to_v4_key(v3_ref, v4_key)
      v4_keys.append(v4_key)
    return v4_keys

  def v4_to_v3_entity(self, v4_entity, v3_entity, is_projection=False):
    """Converts a v4 Entity to a v3 `EntityProto`.

    Args:
      v4_entity: An `entity_v4_pb2.Entity`.
      v3_entity: An `entity_pb2.EntityProto` to populate.
      is_projection: `True` if the `v4_entity` is from a projection query.
    """
    v3_entity.Clear()
    for v4_property in v4_entity.property:
      property_name = v4_property.name
      v4_value = v4_property.value
      if v4_value.list_value:
        for v4_sub_value in v4_value.list_value:
          self.__add_v3_property_from_v4(
              property_name, True, is_projection, v4_sub_value, v3_entity)
      else:
        self.__add_v3_property_from_v4(
            property_name, False, is_projection, v4_value, v3_entity)
    if v4_entity.HasField('key'):
      v4_key = v4_entity.key
      self.v4_to_v3_reference(v4_key, v3_entity.key)
      v3_ref = v3_entity.key
      self.v3_reference_to_group(v3_ref, v3_entity.entity_group)
    else:


      pass

  def v3_to_v4_entity(self, v3_entity, v4_entity):
    """Converts a v3 `EntityProto` to a v4 Entity.

    Args:
      v3_entity: An `entity_pb2.EntityProto`.
      v4_entity: An `entity_v4_pb2.Proto` to populate.
    """
    v4_entity.Clear()
    self.v3_to_v4_key(v3_entity.key, v4_entity.key)
    if not v3_entity.key.HasField('app'):

      v4_entity.ClearField('key')




    v4_properties = {}
    for v3_property in v3_entity.property:
      self.__add_v4_property_to_entity(v4_entity, v4_properties, v3_property,
                                       True)
    for v3_property in v3_entity.raw_property:
      self.__add_v4_property_to_entity(v4_entity, v4_properties, v3_property,
                                       False)

  def v4_value_to_v3_property_value(self, v4_value, v3_value):
    """Converts a v4 Value to a v3 `PropertyValue`.

    Args:
      v4_value: An `entity_v4_pb2.Value`.
      v3_value: An `entity_pb2.PropertyValue` to populate.
    """
    v3_value.Clear()
    if v4_value.HasField('boolean_value'):
      v3_value.booleanValue = v4_value.boolean_value
    elif v4_value.HasField('integer_value'):
      v3_value.int64Value = v4_value.integer_value
    elif v4_value.HasField('double_value'):
      v3_value.doubleValue = v4_value.double_value
    elif v4_value.HasField('timestamp_microseconds_value'):
      v3_value.int64Value = v4_value.timestamp_microseconds_value
    elif v4_value.HasField('key_value'):
      v3_ref = entity_pb2.Reference()
      self.v4_to_v3_reference(v4_value.key_value, v3_ref)
      self.v3_reference_to_v3_property_value(v3_ref, v3_value)
    elif v4_value.HasField('blob_key_value'):
      v3_value.stringValue = v4_value.blob_key_value
    elif v4_value.HasField('string_value'):
      v3_value.stringValue = v4_value.string_value.encode('utf-8')
    elif v4_value.HasField('blob_value'):
      v3_value.stringValue = v4_value.blob_value
    elif v4_value.HasField('entity_value'):
      v4_entity_value = v4_value.entity_value
      v4_meaning = v4_value.meaning
      if (v4_meaning == MEANING_GEORSS_POINT
          or v4_meaning == MEANING_PREDEFINED_ENTITY_POINT):
        self.__v4_to_v3_point_value(v4_entity_value, v3_value.pointvalue)
      elif v4_meaning == MEANING_PREDEFINED_ENTITY_USER:
        self.v4_entity_to_v3_user_value(v4_entity_value, v3_value.uservalue)
      else:
        v3_entity_value = entity_pb2.EntityProto()
        self.v4_to_v3_entity(v4_entity_value, v3_entity_value)
        v3_value.stringValue = v3_entity_value.SerializePartialToString()
    elif v4_value.HasField('geo_point_value'):
      point_value = v3_value.pointvalue
      point_value.x = v4_value.geo_point_value.latitude
      point_value.y = v4_value.geo_point_value.longitude
    else:

      pass

  def v3_property_to_v4_value(self, v3_property, indexed, v4_value):
    """Converts a v3 Property to a v4 Value.

    Args:
      v3_property: An `entity_pb2.Property`.
      indexed: Whether the v3 property is indexed.
      v4_value: An `entity_v4_pb2.Value` to populate.
    """
    v4_value.Clear()
    v3_property_value = v3_property.value
    v3_meaning = v3_property.meaning
    v3_uri_meaning = None
    if v3_property.meaning_uri:
      v3_uri_meaning = v3_property.meaning_uri

    if not self.__is_v3_property_value_union_valid(v3_property_value):


      v3_meaning = None
      v3_uri_meaning = None
    elif v3_meaning == entity_pb2.Property.NO_MEANING:
      v3_meaning = None
    elif not self.__is_v3_property_value_meaning_valid(v3_property_value,
                                                       v3_meaning):

      v3_meaning = None

    is_zlib_value = False
    if v3_uri_meaning:
      if v3_uri_meaning == URI_MEANING_ZLIB:
        if v3_property_value.HasField('stringValue'):
          is_zlib_value = True
          if v3_meaning != entity_pb2.Property.BLOB:

            v3_meaning = entity_pb2.Property.BLOB
        else:
          pass
      else:
        pass


    if v3_property_value.HasField('booleanValue'):
      v4_value.boolean_value = v3_property_value.booleanValue
    elif v3_property_value.HasField('int64Value'):
      if v3_meaning == entity_pb2.Property.GD_WHEN:
        v4_value.timestamp_microseconds_value = v3_property_value.int64Value
        v3_meaning = None
      else:
        v4_value.integer_value = v3_property_value.int64Value
    elif v3_property_value.HasField('doubleValue'):
      v4_value.double_value = v3_property_value.doubleValue
    elif v3_property_value.HasField('referencevalue'):
      v3_ref = entity_pb2.Reference()
      self.__v3_reference_value_to_v3_reference(
          v3_property_value.referencevalue, v3_ref)
      self.v3_to_v4_key(v3_ref, v4_value.key_value)
    elif v3_property_value.HasField('stringValue'):
      if v3_meaning == entity_pb2.Property.ENTITY_PROTO:
        serialized_entity_v3 = v3_property_value.stringValue
        v3_entity = entity_pb2.EntityProto()


        v3_entity.ParsePartialFromString(serialized_entity_v3)
        self.v3_to_v4_entity(v3_entity, v4_value.entity_value)
        v3_meaning = None
      elif (v3_meaning == entity_pb2.Property.BLOB or
            v3_meaning == entity_pb2.Property.BYTESTRING):
        v4_value.blob_value = v3_property_value.stringValue

        if indexed or v3_meaning == entity_pb2.Property.BLOB:
          v3_meaning = None
      else:
        string_value = v3_property_value.stringValue
        if is_valid_utf8(string_value):
          if v3_meaning == entity_pb2.Property.BLOBKEY:
            v4_value.blob_key_value = string_value
            v3_meaning = None
          else:
            v4_value.string_value = string_value
        else:

          v4_value.blob_value = string_value

          if v3_meaning != entity_pb2.Property.INDEX_VALUE:
            v3_meaning = None


    elif v3_property_value.HasField('pointvalue'):
      if v3_meaning == MEANING_GEORSS_POINT:
        point_value = v3_property_value.pointvalue
        v4_value.geo_point_value.latitude = point_value.x
        v4_value.geo_point_value.longitude = point_value.y
      else:
        self.__v3_to_v4_point_entity(v3_property_value.pointvalue,
                                     v4_value.entity_value)
        v4_value.meaning = MEANING_PREDEFINED_ENTITY_POINT

      v3_meaning = None
    elif v3_property_value.HasField('uservalue'):
      self.v3_user_value_to_v4_entity(v3_property_value.uservalue,
                                      v4_value.entity_value)
      v4_value.meaning = MEANING_PREDEFINED_ENTITY_USER
      v3_meaning = None
    else:
      pass

    if is_zlib_value:
      v4_value.meaning = MEANING_ZLIB
    elif v3_meaning:
      v4_value.meaning = v3_meaning


    if indexed != v4_value.indexed:
      v4_value.indexed = indexed

  def v4_to_v3_property(self, property_name, is_multi, is_projection,
                        v4_value, v3_property):
    """Converts info from a v4 Property to a v3 Property.

    `v4_value` must not have a `list_value`.

    Args:
      property_name: The name of the property.
      is_multi: Whether the property contains multiple values.
      is_projection: Whether the property is projected.
      v4_value: An `entity_v4_pb2.Value`.
      v3_property: An `entity_pb2.Property` to populate.
    """
    assert not v4_value.list_value, 'v4 list_value not convertable to v3'
    v3_property.Clear()
    v3_property.name = property_name

    if v4_value.HasField('meaning') and v4_value.meaning == MEANING_EMPTY_LIST:
      v3_property.meaning = MEANING_EMPTY_LIST
      v3_property.multiple = False
      v3_property.value.Clear()
      return

    v3_property.multiple = is_multi
    self.v4_value_to_v3_property_value(v4_value, v3_property.value)

    v4_meaning = None
    if v4_value.HasField('meaning'):
      v4_meaning = v4_value.meaning
    if v4_value.HasField('timestamp_microseconds_value'):
      v3_property.meaning = entity_pb2.Property.GD_WHEN
    elif v4_value.HasField('blob_key_value'):
      v3_property.meaning = entity_pb2.Property.BLOBKEY
    elif v4_value.HasField('blob_value'):
      if v4_meaning == MEANING_ZLIB:
        v3_property.meaning_uri = URI_MEANING_ZLIB
      if v4_meaning == entity_pb2.Property.BYTESTRING:
        if v4_value.indexed:
          pass


      else:
        if v4_value.indexed:
          v3_property.meaning = entity_pb2.Property.BYTESTRING
        else:
          v3_property.meaning = entity_pb2.Property.BLOB
        v4_meaning = None
    elif v4_value.HasField('entity_value'):
      if v4_meaning != MEANING_GEORSS_POINT:
        if (v4_meaning != MEANING_PREDEFINED_ENTITY_POINT
            and v4_meaning != MEANING_PREDEFINED_ENTITY_USER):
          v3_property.meaning = entity_pb2.Property.ENTITY_PROTO
        v4_meaning = None
    elif v4_value.HasField('geo_point_value'):
      v3_property.meaning = MEANING_GEORSS_POINT
    else:

      pass
    if v4_meaning is not None:
      v3_property.meaning = v4_meaning

    if is_projection:
      v3_property.meaning = entity_pb2.Property.INDEX_VALUE

  def __add_v3_property_from_v4(self, property_name, is_multi, is_projection,
                                v4_value, v3_entity):
    """Adds a v3 Property to an Entity based on information from a v4 Property.

    Args:
      property_name: The name of the property.
      is_multi: Whether the property contains multiple values.
      is_projection: Whether the property is a projection.
      v4_value: An `entity_v4_pb2.Value`.
      v3_entity: An `entity_pb2.EntityProto`.
    """
    if v4_value.indexed:
      prop = v3_entity.property.add()
      self.v4_to_v3_property(property_name, is_multi, is_projection, v4_value,
                             prop)
    else:
      prop = v3_entity.raw_property.add()
      self.v4_to_v3_property(property_name, is_multi, is_projection, v4_value,
                             prop)

  def __build_name_to_v4_property_map(self, v4_entity):
    property_map = {}
    for prop in v4_entity.property:
      property_map[prop.name] = prop
    return property_map

  def __add_v4_property_to_entity(self, v4_entity, property_map, v3_property,
                                  indexed):
    """Adds a v4 Property to an entity or modifies an existing one.

    `property_map` is used to track of properties that have already been added.
    The same dict should be used for all of an entity's properties.

    Args:
      v4_entity: An `entity_v4_pb.Entity`.
      property_map: A dict of name -> v4_property.
      v3_property: An `entity_pb2.Property` to convert to v4 and add to the
        dict.
      indexed: Whether the property is indexed.
    """
    property_name = v3_property.name
    if property_name in property_map:
      v4_property = property_map[property_name]
    else:
      v4_property = v4_entity.property.add()
      v4_property.name = property_name
      property_map[property_name] = v4_property
    if v3_property.multiple:
      self.v3_property_to_v4_value(v3_property, indexed,
                                   v4_property.value.list_value.add())
    else:
      self.v3_property_to_v4_value(v3_property, indexed,
                                   v4_property.value)

  def __get_v4_integer_value(self, v4_property):
    """Returns an integer value from a v4 Property.

    Args:
      v4_property: An `entity_v4_pb2.Property`.

    Returns:
      An integer.

    Raises:
      InvalidConversionError: if the property doesn't contain an integer value
    """
    check_conversion(v4_property.value.HasField('integer_value'),
                     'Property does not contain an integer value.')
    return v4_property.value.integer_value

  def __get_v4_double_value(self, v4_property):
    """Returns a double value from a v4 Property.

    Args:
      v4_property: An `entity_v4_pb2.Property`.

    Returns:
      A double.

    Raises:
      InvalidConversionError: if the property doesn't contain a double value
    """
    check_conversion(v4_property.value.HasField('double_value'),
                     'Property does not contain a double value.')
    return v4_property.value.double_value

  def __get_v4_string_value(self, v4_property):
    """Returns a string value from a v4 Property.

    Args:
      v4_property: An `entity_v4_pb2.Property`.

    Returns:
      A string.

    Throws:
      InvalidConversionError: if the property doesn't contain a string value
    """
    check_conversion(v4_property.value.HasField('string_value'),
                     'Property does not contain a string value.')
    return v4_property.value.string_value

  def __v4_integer_property(self, name, value, indexed):
    """Creates a single-integer-valued v4 Property.

    Args:
      name: The property name.
      value: The integer value of the property.
      indexed: Whether the value should be indexed.

    Returns:
      An `entity_v4_pb2.Property`.
    """
    v4_property = entity_v4_pb2.Property()
    v4_property.name = name
    v4_value = v4_property.value
    v4_value.indexed = indexed
    v4_value.integer_value = value
    return v4_property

  def __v4_double_property(self, name, value, indexed):
    """Creates a single-double-valued v4 Property.

    Args:
      name: The property name.
      value: The double value of the property.
      indexed: Whether the value should be indexed.

    Returns:
      An `entity_v4_pb2.Property`.
    """
    v4_property = entity_v4_pb2.Property()
    v4_property.name = name
    v4_value = v4_property.value
    v4_value.indexed = indexed
    v4_value.double_value = value
    return v4_property

  def __v4_string_property(self, name, value, indexed):
    """Creates a single-string-valued v4 Property.

    Args:
      name: The property name.
      value: The string value of the property.
      indexed: Whether the value should be indexed.

    Returns:
      An `entity_v4_pb2.Property`.
    """
    v4_property = entity_v4_pb2.Property()
    v4_property.name = name
    v4_value = v4_property.value
    v4_value.indexed = indexed
    v4_value.string_value = value
    return v4_property

  def __v4_to_v3_point_value(self, v4_point_entity, v3_point_value):
    """Converts a v4 point Entity to a v3 `PointValue`.

    Args:
      v4_point_entity: An `entity_v4_pb2.Entity` representing a point.
      v3_point_value: An `entity_pb2.PropertyValue.PointValue` to populate.
    """
    v3_point_value.Clear()
    name_to_v4_property = self.__build_name_to_v4_property_map(v4_point_entity)
    v3_point_value.x = self.__get_v4_double_value(name_to_v4_property['x'])
    v3_point_value.y = self.__get_v4_double_value(name_to_v4_property['y'])

  def __v3_to_v4_point_entity(self, v3_point_value, v4_entity):
    """Converts a v3 `UserValue` to a v4 user Entity.

    Args:
      v3_point_value: An `entity_pb2.PropertyValue.PointValue`.
      v4_entity: An `entity_v4_pb2.Entity` to populate.
    """
    v4_entity.Clear()
    v4_entity.property.append(
        self.__v4_double_property(PROPERTY_NAME_X, v3_point_value.x, False))
    v4_entity.property.append(
        self.__v4_double_property(PROPERTY_NAME_Y, v3_point_value.y, False))

  def v4_entity_to_v3_user_value(self, v4_user_entity, v3_user_value):
    """Converts a v4 user Entity to a v3 `UserValue`.

    Args:
      v4_user_entity: An `entity_v4_pb2.Entity` representing a user.
      v3_user_value: An `entity_pb2.PropertyValue.UserValue` to populate.
    """
    v3_user_value.Clear()
    name_to_v4_property = self.__build_name_to_v4_property_map(v4_user_entity)

    v3_user_value.email = self.__get_v4_string_value(
        name_to_v4_property[PROPERTY_NAME_EMAIL])
    v3_user_value.auth_domain = self.__get_v4_string_value(
        name_to_v4_property[PROPERTY_NAME_AUTH_DOMAIN])
    if PROPERTY_NAME_USER_ID in name_to_v4_property:
      v3_user_value.obfuscated_gaiaid = self.__get_v4_string_value(
          name_to_v4_property[PROPERTY_NAME_USER_ID])
    if PROPERTY_NAME_INTERNAL_ID in name_to_v4_property:
      v3_user_value.gaiaid = self.__get_v4_integer_value(
          name_to_v4_property[PROPERTY_NAME_INTERNAL_ID])
    else:

      v3_user_value.gaiaid = 0
    if PROPERTY_NAME_FEDERATED_IDENTITY in name_to_v4_property:
      v3_user_value.federated_identity = self.__get_v4_string_value(
          name_to_v4_property[PROPERTY_NAME_FEDERATED_IDENTITY])
    if PROPERTY_NAME_FEDERATED_PROVIDER in name_to_v4_property:
      v3_user_value.federated_provider = self.__get_v4_string_value(
          name_to_v4_property[PROPERTY_NAME_FEDERATED_PROVIDER])

  def v3_user_value_to_v4_entity(self, v3_user_value, v4_entity):
    """Converts a v3 `UserValue` to a v4 user Entity.

    Args:
      v3_user_value: An `entity_pb2.PropertyValue.UserValue`.
      v4_entity: An `entity_v4_pb2.Entity` to populate.
    """
    v4_entity.Clear()
    v4_entity.property.append(
        self.__v4_string_property(PROPERTY_NAME_EMAIL, v3_user_value.email,
                                  False))
    v4_entity.property.append(
        self.__v4_string_property(PROPERTY_NAME_AUTH_DOMAIN,
                                  v3_user_value.auth_domain, False))

    if v3_user_value.gaiaid != 0:
      v4_entity.property.append(
          self.__v4_integer_property(PROPERTY_NAME_INTERNAL_ID,
                                     v3_user_value.gaiaid, False))
    if v3_user_value.HasField('obfuscated_gaiaid'):
      v4_entity.property.append(
          self.__v4_string_property(PROPERTY_NAME_USER_ID,
                                    v3_user_value.obfuscated_gaiaid, False))
    if v3_user_value.HasField('federated_identity'):
      v4_entity.property.append(
          self.__v4_string_property(PROPERTY_NAME_FEDERATED_IDENTITY,
                                    v3_user_value.federated_identity, False))
    if v3_user_value.HasField('federated_provider'):
      v4_entity.property.append(
          self.__v4_string_property(PROPERTY_NAME_FEDERATED_PROVIDER,
                                    v3_user_value.federated_provider, False))

  def v1_to_v3_reference(self, v1_key, v3_ref):
    """Converts a v1 Key to a v3 Reference.

    Args:
      v1_key: An `googledatastore.Key`.
      v3_ref: An `entity_pb2.Reference` to populate.
    """
    v3_ref.Clear()
    if v1_key.HasField('partition_id'):
      project_id = v1_key.partition_id.project_id
      if project_id:
        app_id = self._id_resolver.resolve_app_id(project_id)
        v3_ref.app = app_id
      if v1_key.partition_id.namespace_id:
        v3_ref.name_space = v1_key.partition_id.namespace_id
    for v1_element in v1_key.path:
      v3_element = v3_ref.path.element.add()
      v3_element.type = v1_element.kind.encode('utf-8')
      id_type = v1_element.WhichOneof('id_type')
      if id_type == 'id':
        v3_element.id = v1_element.id
      elif id_type == 'name':
        v3_element.name = v1_element.name.encode('utf-8')

  def v1_to_v3_references(self, v1_keys):
    """Converts a list of v1 Keys to a list of v3 References.

    Args:
      v1_keys: A list of `googledatastore.Key` objects.

    Returns:
      A list of `entity_pb2.Reference` objects.
    """
    v3_refs = []
    for v1_key in v1_keys:
      v3_ref = entity_pb2.Reference()
      self.v1_to_v3_reference(v1_key, v3_ref)
      v3_refs.append(v3_ref)
    return v3_refs

  def v3_to_v1_key(self, v3_ref, v1_key):
    """Converts a v3 Reference to a v1 Key.

    Args:
      v3_ref: An `entity_pb2.Reference`.
      v1_key: An `googledatastore.Key` to populate.
    """
    v1_key.Clear()
    if not v3_ref.app:
      return
    project_id = self._id_resolver.resolve_project_id(v3_ref.app)
    v1_key.partition_id.project_id = project_id
    if v3_ref.name_space:
      v1_key.partition_id.namespace_id = v3_ref.name_space
    for v3_element in v3_ref.path.element:
      v1_element = v1_key.path.add()
      v1_element.kind = v3_element.type
      if v3_element.HasField('id'):
        v1_element.id = v3_element.id
      if v3_element.HasField('name'):
        v1_element.name = v3_element.name

  def v3_to_v1_keys(self, v3_refs):
    """Converts a list of v3 References to a list of v1 Keys.

    Args:
      v3_refs: A list of `entity_pb2.Reference` objects.

    Returns:
      A list of `googledatastore.Key` objects.
    """
    v1_keys = []
    for v3_ref in v3_refs:
      v1_key = googledatastore.Key()
      self.v3_to_v1_key(v3_ref, v1_key)
      v1_keys.append(v1_key)
    return v1_keys

  def project_to_app_id(self, project_id):
    """Converts a string project id to a string app id."""
    return self._id_resolver.resolve_app_id(project_id)

  def app_to_project_id(self, app_id):
    """Converts a string app id to a string project id."""
    return self._id_resolver.resolve_project_id(app_id)

  def __new_v3_property(self, v3_entity, is_indexed):
    if is_indexed:
      return v3_entity.property.add()
    else:
      return v3_entity.raw_property.add()

  def v1_to_v3_entity(self, v1_entity, v3_entity, is_projection=False):
    """Converts a v1 Entity to a v3 `EntityProto`.

    Args:
      v1_entity: An `googledatastore.Entity`.
      v3_entity: An `entity_pb2.EntityProto` to populate.
      is_projection: `True` if the `v1_entity` is from a projection query.
    """
    v3_entity.Clear()
    for property_name, v1_value in six.iteritems(v1_entity.properties):

      if v1_value.HasField('array_value'):
        if len(v1_value.array_value.values) == 0:
          empty_list = self.__new_v3_property(v3_entity,
                                              not v1_value.exclude_from_indexes)
          empty_list.name = property_name
          empty_list.multiple = False
          empty_list.meaning = MEANING_EMPTY_LIST
          empty_list.value.Clear()
        else:
          for v1_sub_value in v1_value.array_value.values:
            list_element = self.__new_v3_property(
                v3_entity, not v1_sub_value.exclude_from_indexes)
            self.v1_to_v3_property(
                property_name, True, is_projection, v1_sub_value, list_element)
      else:
        value_property = self.__new_v3_property(
            v3_entity, not v1_value.exclude_from_indexes)
        self.v1_to_v3_property(
            property_name, False, is_projection, v1_value, value_property)

    if v1_entity.HasField('key'):
      v1_key = v1_entity.key
      self.v1_to_v3_reference(v1_key, v3_entity.key)
      v3_ref = v3_entity.key
      self.v3_reference_to_group(v3_ref, v3_entity.entity_group)
    else:


      pass

  def v3_to_v1_entity(self, v3_entity, v1_entity):
    """Converts a v3 `EntityProto` to a v1 Entity.

    Args:
      v3_entity: An `entity_pb2.EntityProto`.
      v1_entity: An `googledatastore.Proto` to populate.
    """
    v1_entity.Clear()
    self.v3_to_v1_key(v3_entity.key, v1_entity.key)
    if not v3_entity.key.HasField('app'):

      v1_entity.ClearField('key')




    for v3_property in v3_entity.property:
      self.__add_v1_property_to_entity(v1_entity, v3_property, True)
    for v3_property in v3_entity.raw_property:
      self.__add_v1_property_to_entity(v1_entity, v3_property, False)

  def v1_value_to_v3_property_value(self, v1_value, v3_value):
    """Converts a v1 Value to a v3 `PropertyValue`.

    Args:
      v1_value: An `googledatastore.Value`.
      v3_value: An `entity_pb2.PropertyValue` to populate.
    """
    v3_value.Clear()
    field = v1_value.WhichOneof('value_type')
    if field == 'boolean_value':
      v3_value.booleanValue = v1_value.boolean_value
    elif field == 'integer_value':
      v3_value.int64Value = v1_value.integer_value
    elif field == 'double_value':
      v3_value.doubleValue = v1_value.double_value
    elif field == 'timestamp_value':
      v3_value.int64Value = googledatastore.helper.micros_from_timestamp(
          v1_value.timestamp_value)
    elif field == 'key_value':
      v3_ref = entity_pb2.Reference()
      self.v1_to_v3_reference(v1_value.key_value, v3_ref)
      self.v3_reference_to_v3_property_value(v3_ref, v3_value)
    elif field == 'string_value':
      v3_value.stringValue = v1_value.string_value.encode('utf-8')
    elif field == 'blob_value':
      v3_value.stringValue = v1_value.blob_value
    elif field == 'entity_value':
      v1_entity_value = v1_value.entity_value
      v1_meaning = v1_value.meaning
      if v1_meaning == MEANING_PREDEFINED_ENTITY_USER:
        self.v1_entity_to_v3_user_value(v1_entity_value, v3_value.uservalue)
      else:
        v3_entity_value = entity_pb2.EntityProto()
        self.v1_to_v3_entity(v1_entity_value, v3_entity_value)
        v3_value.stringValue = v3_entity_value.SerializePartialToString()
    elif field == 'geo_point_value':
      point_value = v3_value.pointvalue
      point_value.x = v1_value.geo_point_value.latitude
      point_value.y = v1_value.geo_point_value.longitude
    elif field == 'null_value':
      v3_value.Clear()
    else:
      v3_value.Clear()

  def v3_property_to_v1_value(self, v3_property, indexed, v1_value):
    """Converts a v3 Property to a v1 Value.

    Args:
      v3_property: An `entity_pb2.Property`.
      indexed: Whether the v3 property is indexed.
      v1_value: An `googledatastore.Value` to populate.
    """
    v1_value.Clear()
    v3_property_value = v3_property.value
    v3_meaning = v3_property.meaning
    v3_uri_meaning = None
    if v3_property.meaning_uri:
      v3_uri_meaning = v3_property.meaning_uri

    if not self.__is_v3_property_value_union_valid(v3_property_value):


      v3_meaning = None
      v3_uri_meaning = None
    elif v3_meaning == entity_pb2.Property.NO_MEANING:
      v3_meaning = None
    elif not self.__is_v3_property_value_meaning_valid(v3_property_value,
                                                       v3_meaning):

      v3_meaning = None

    is_zlib_value = False
    if v3_uri_meaning:
      if v3_uri_meaning == URI_MEANING_ZLIB:
        if v3_property_value.HasField('stringValue'):
          is_zlib_value = True
          if v3_meaning != entity_pb2.Property.BLOB:

            v3_meaning = entity_pb2.Property.BLOB
        else:
          pass
      else:
        pass


    if v3_property.meaning == entity_pb2.Property.EMPTY_LIST:
      v1_value.array_value.values.extend([])
      v3_meaning = None
    elif v3_property_value.HasField('booleanValue'):
      v1_value.boolean_value = v3_property_value.booleanValue
    elif v3_property_value.HasField('int64Value'):
      if (v3_meaning == entity_pb2.Property.GD_WHEN and
          is_in_rfc_3339_bounds(v3_property_value.int64Value)):
        googledatastore.helper.micros_to_timestamp(v3_property_value.int64Value,
                                                   v1_value.timestamp_value)
        v3_meaning = None
      else:
        v1_value.integer_value = v3_property_value.int64Value
    elif v3_property_value.HasField('doubleValue'):
      v1_value.double_value = v3_property_value.doubleValue
    elif v3_property_value.HasField('referencevalue'):
      v3_ref = entity_pb2.Reference()
      self.__v3_reference_value_to_v3_reference(
          v3_property_value.referencevalue, v3_ref)
      self.v3_to_v1_key(v3_ref, v1_value.key_value)
    elif v3_property_value.HasField('stringValue'):
      if v3_meaning == entity_pb2.Property.ENTITY_PROTO:
        serialized_entity_v3 = v3_property_value.stringValue
        v3_entity = entity_pb2.EntityProto()
        v3_entity.ParseFromString(serialized_entity_v3)
        self.v3_to_v1_entity(v3_entity, v1_value.entity_value)
        v3_meaning = None
      elif (v3_meaning == entity_pb2.Property.BLOB or
            v3_meaning == entity_pb2.Property.BYTESTRING):
        v1_value.blob_value = v3_property_value.stringValue

        if indexed or v3_meaning == entity_pb2.Property.BLOB:
          v3_meaning = None
      else:
        string_value = v3_property_value.stringValue
        if is_valid_utf8(string_value):
          v1_value.string_value = string_value
        else:

          v1_value.blob_value = string_value

          if v3_meaning != entity_pb2.Property.INDEX_VALUE:
            v3_meaning = None


    elif v3_property_value.HasField('pointvalue'):
      if v3_meaning != MEANING_GEORSS_POINT:
        v1_value.meaning = MEANING_POINT_WITHOUT_V3_MEANING

      point_value = v3_property_value.pointvalue
      v1_value.geo_point_value.latitude = point_value.x
      v1_value.geo_point_value.longitude = point_value.y
      v3_meaning = None
    elif v3_property_value.HasField('uservalue'):
      self.v3_user_value_to_v1_entity(v3_property_value.uservalue,
                                      v1_value.entity_value)
      v1_value.meaning = MEANING_PREDEFINED_ENTITY_USER
      v3_meaning = None
    else:

      v1_value.null_value = googledatastore.NULL_VALUE

    if is_zlib_value:
      v1_value.meaning = MEANING_ZLIB
    elif v3_meaning:
      v1_value.meaning = v3_meaning


    if indexed == v1_value.exclude_from_indexes:
      v1_value.exclude_from_indexes = not indexed

  def v1_to_v3_property(self, property_name, is_multi, is_projection,
                        v1_value, v3_property):
    """Converts info from a v1 Property to a v3 Property.

    v1_value must not have an array_value.

    Args:
      property_name: The name of the property, unicode.
      is_multi: whether The property contains multiple values.
      is_projection: Whether the property is projected.
      v1_value: An `googledatastore.Value`.
      v3_property: An `entity_pb2.Property` to populate.
    """
    v1_value_type = v1_value.WhichOneof('value_type')
    if v1_value_type == 'array_value':
      assert False, 'v1 array_value not convertable to v3'
    v3_property.Clear()
    v3_property.name = property_name.encode('utf-8')
    v3_property.multiple = is_multi
    self.v1_value_to_v3_property_value(v1_value, v3_property.value)

    v1_meaning = None
    if v1_value.meaning:
      v1_meaning = v1_value.meaning
    if v1_value_type == 'timestamp_value':
      v3_property.meaning = entity_pb2.Property.GD_WHEN
    elif v1_value_type == 'blob_value':
      if v1_meaning == MEANING_ZLIB:
        v3_property.meaning_uri = URI_MEANING_ZLIB
      if v1_meaning == entity_pb2.Property.BYTESTRING:
        if not v1_value.exclude_from_indexes:
          pass


      else:
        if not v1_value.exclude_from_indexes:
          v3_property.meaning = entity_pb2.Property.BYTESTRING
        else:
          v3_property.meaning = entity_pb2.Property.BLOB
        v1_meaning = None
    elif v1_value_type == 'entity_value':
      if v1_meaning != MEANING_PREDEFINED_ENTITY_USER:
        v3_property.meaning = entity_pb2.Property.ENTITY_PROTO
      v1_meaning = None
    elif v1_value_type == 'geo_point_value':
      if v1_meaning != MEANING_POINT_WITHOUT_V3_MEANING:
        v3_property.meaning = MEANING_GEORSS_POINT
      v1_meaning = None
    elif v1_value_type == 'integer_value':
      if v1_meaning == MEANING_NON_RFC_3339_TIMESTAMP:
        v3_property.meaning = entity_pb2.Property.GD_WHEN
        v1_meaning = None
    else:

      pass
    if v1_meaning is not None:
      v3_property.meaning = v1_meaning

    if is_projection:
      v3_property.meaning = entity_pb2.Property.INDEX_VALUE

  def __add_v1_property_to_entity(self, v1_entity, v3_property, indexed):
    """Adds a v1 Property to an entity or modifies an existing one.

    Args:
      v1_entity: An `googledatastore.Entity`.
      v3_property: An `entity_pb2.Property` to convert to v1 and add to the
        dict.
      indexed: Whether the property is indexed.
    """
    property_name = v3_property.name
    v1_value = v1_entity.properties[property_name]
    if v3_property.multiple:
      self.v3_property_to_v1_value(v3_property, indexed,
                                   v1_value.array_value.values.add())
    else:
      self.v3_property_to_v1_value(v3_property, indexed, v1_value)

  def __get_v1_integer_value(self, v1_value):
    """Returns an integer value from a v1 Value.

    Args:
      v1_value: A `googledatastore.Value`.

    Returns:
      An integer.

    Raises:
      InvalidConversionError: if the value doesn't contain an integer value
    """
    check_conversion(v1_value.HasField('integer_value'),
                     'Value does not contain an integer value.')
    return v1_value.integer_value

  def __get_v1_double_value(self, v1_value):
    """Returns a double value from a v1 Value.

    Args:
      v1_value: An `googledatastore.Value`.

    Returns:
      A double.

    Raises:
      InvalidConversionError: if the value doesn't contain a double value
    """
    check_conversion(v1_value.HasField('double_value'),
                     'Value does not contain a double value.')
    return v1_value.double_value

  def __get_v1_string_value(self, v1_value):
    """Returns an string value from a v1 Value.

    Args:
      v1_value: An `googledatastore.Value`.

    Returns:
      A string.

    Throws:
      InvalidConversionError: if the value doesn't contain a string value
    """
    check_conversion(v1_value.HasField('string_value'),
                     'Value does not contain a string value.')
    return v1_value.string_value.encode('utf-8')

  def __v1_integer_property(self, entity, name, value, indexed):
    """Populates a single-integer-valued v1 Property.

    Args:
      entity: The entity to populate.
      name: The name of the property to populate.
      value: The integer value of the property.
      indexed: Whether the value should be indexed.
    """
    v1_value = entity.properties[name]
    v1_value.exclude_from_indexes = not indexed
    v1_value.integer_value = value

  def __v1_double_property(self, entity, name, value, indexed):
    """Populates a single-double-valued v1 Property.

    Args:
      entity: The entity to populate.
      name: The name of the property to populate.
      value: The double value of the property.
      indexed: Whether the value should be indexed.
    """
    v1_value = entity.properties[name]
    v1_value.exclude_from_indexes = not indexed
    v1_value.double_value = value

  def __v1_string_property(self, entity, name, value, indexed):
    """Populates a single-string-valued v1 Property.

    Args:
      entity: The entity to populate.
      name: The name of the property to populate.
      value: The string value of the property.
      indexed: Whether the value should be indexed.
    """
    v1_value = entity.properties[name]
    v1_value.exclude_from_indexes = not indexed
    v1_value.string_value = value

  def v1_entity_to_v3_user_value(self, v1_user_entity, v3_user_value):
    """Converts a v1 user Entity to a v3 `UserValue`.

    Args:
      v1_user_entity: An `googledatastore.Entity` representing a user.
      v3_user_value: An `entity_pb2.Property.UserValue` to populate.
    """
    v3_user_value.Clear()
    properties = v1_user_entity.properties

    v3_user_value.email = self.__get_v1_string_value(
        properties[PROPERTY_NAME_EMAIL])
    v3_user_value.auth_domain = self.__get_v1_string_value(
        properties[PROPERTY_NAME_AUTH_DOMAIN])
    if PROPERTY_NAME_USER_ID in properties:
      v3_user_value.obfuscated_gaiaid = self.__get_v1_string_value(
          properties[PROPERTY_NAME_USER_ID])
    if PROPERTY_NAME_INTERNAL_ID in properties:
      v3_user_value.gaiaid = self.__get_v1_integer_value(
          properties[PROPERTY_NAME_INTERNAL_ID])
    else:

      v3_user_value.gaiaid = 0
    if PROPERTY_NAME_FEDERATED_IDENTITY in properties:
      v3_user_value.federated_identity = self.__get_v1_string_value(
          properties[PROPERTY_NAME_FEDERATED_IDENTITY])
    if PROPERTY_NAME_FEDERATED_PROVIDER in properties:
      v3_user_value.federated_provider = self.__get_v1_string_value(
          properties[PROPERTY_NAME_FEDERATED_PROVIDER])

  def v3_user_value_to_v1_entity(self, v3_user_value, v1_entity):
    """Converts a v3 `UserValue` to a v1 user Entity.

    Args:
      v3_user_value: An `entity_pb2.Property_UserValue`.
      v1_entity: An `googledatastore.Entity` to populate.
    """
    v1_entity.Clear()
    self.__v1_string_property(v1_entity, PROPERTY_NAME_EMAIL,
                              v3_user_value.email, False)
    self.__v1_string_property(v1_entity, PROPERTY_NAME_AUTH_DOMAIN,
                              v3_user_value.auth_domain, False)

    if v3_user_value.gaiaid != 0:
      self.__v1_integer_property(v1_entity, PROPERTY_NAME_INTERNAL_ID,
                                 v3_user_value.gaiaid, False)
    if v3_user_value.HasField('obfuscated_gaiaid'):
      self.__v1_string_property(v1_entity, PROPERTY_NAME_USER_ID,
                                v3_user_value.obfuscated_gaiaid, False)
    if v3_user_value.HasField('federated_identity'):
      self.__v1_string_property(v1_entity, PROPERTY_NAME_FEDERATED_IDENTITY,
                                v3_user_value.federated_identity, False)
    if v3_user_value.HasField('federated_provider'):
      self.__v1_string_property(v1_entity, PROPERTY_NAME_FEDERATED_PROVIDER,
                                v3_user_value.federated_provider, False)

  def __is_v3_property_value_union_valid(self, v3_property_value):
    """Returns True if the v3 PropertyValue's union is valid."""
    num_sub_values = (
        v3_property_value.HasField('booleanValue') +
        v3_property_value.HasField('int64Value') +
        v3_property_value.HasField('doubleValue') +
        v3_property_value.HasField('referencevalue') +
        v3_property_value.HasField('stringValue') +
        v3_property_value.HasField('pointvalue') +
        v3_property_value.HasField('uservalue'))
    return num_sub_values <= 1

  def __is_v3_property_value_meaning_valid(self, v3_property_value, v3_meaning):
    """Returns True if the v3 PropertyValue's type value matches its meaning."""
    def ReturnTrue():
      return True
    def HasStringValue():
      return v3_property_value.HasField('stringValue')

    def HasInt64Value():
      return v3_property_value.HasField('int64Value')

    def HasPointValue():
      return v3_property_value.HasField('pointvalue')
    def ReturnFalse():
      return False
    value_checkers = {
        entity_pb2.Property.NO_MEANING: ReturnTrue,
        entity_pb2.Property.INDEX_VALUE: ReturnTrue,
        entity_pb2.Property.BLOB: HasStringValue,
        entity_pb2.Property.TEXT: HasStringValue,
        entity_pb2.Property.BYTESTRING: HasStringValue,
        entity_pb2.Property.ATOM_CATEGORY: HasStringValue,
        entity_pb2.Property.ATOM_LINK: HasStringValue,
        entity_pb2.Property.ATOM_TITLE: HasStringValue,
        entity_pb2.Property.ATOM_CONTENT: HasStringValue,
        entity_pb2.Property.ATOM_SUMMARY: HasStringValue,
        entity_pb2.Property.ATOM_AUTHOR: HasStringValue,
        entity_pb2.Property.GD_EMAIL: HasStringValue,
        entity_pb2.Property.GD_IM: HasStringValue,
        entity_pb2.Property.GD_PHONENUMBER: HasStringValue,
        entity_pb2.Property.GD_POSTALADDRESS: HasStringValue,
        entity_pb2.Property.BLOBKEY: HasStringValue,
        entity_pb2.Property.ENTITY_PROTO: HasStringValue,
        entity_pb2.Property.GD_WHEN: HasInt64Value,
        entity_pb2.Property.GD_RATING: HasInt64Value,
        entity_pb2.Property.GEORSS_POINT: HasPointValue,
        entity_pb2.Property.EMPTY_LIST: ReturnTrue,
    }
    default = ReturnFalse
    return value_checkers.get(v3_meaning, default)()

  def __v3_reference_has_id_or_name(self, v3_ref):
    """Determines if a v3 Reference specifies an ID or name.

    Args:
      v3_ref: An `entity_pb2.Reference`.

    Returns:
      Boolean. `True` if the last path element specifies an ID or name.
    """
    path = v3_ref.path
    assert len(path.element) >= 1
    last_element = path.element[len(path.element) - 1]
    return last_element.HasField('id') or last_element.HasField('name')

  def v3_reference_to_group(self, v3_ref, group):
    """Converts a v3 Reference to a v3 Path representing the entity group.

    The entity group is represented as an `entity_pb2.Path` containing only the
    first element in the provided Reference.

    Args:
      v3_ref: An `entity_pb2.Reference`.
      group: An `entity_pb2.Path` to populate.
    """
    group.Clear()
    path = v3_ref.path
    assert len(path.element) >= 1
    element = entity_pb2.Path.Element()
    element.CopyFrom(path.element[0])
    group.element.append(element)

  def v3_reference_to_v3_property_value(self, v3_ref, v3_property_value):
    """Converts a v3 Reference to a v3 `PropertyValue`.

    Args:
      v3_ref: An `entity_pb2.Reference`.
      v3_property_value: An `entity_pb2.PropertyValue` to populate.
    """
    v3_property_value.Clear()
    reference_value = v3_property_value.referencevalue
    if v3_ref.HasField('app'):
      reference_value.app = v3_ref.app
    if v3_ref.HasField('name_space'):
      reference_value.name_space = v3_ref.name_space
    for v3_path_element in v3_ref.path.element:
      v3_ref_value_path_element = reference_value.pathelement.add()

      copy_path_element(v3_path_element, v3_ref_value_path_element)

  def __v3_reference_value_to_v3_reference(self, v3_ref_value, v3_ref):
    """Converts a v3 `ReferenceValue` to a v3 Reference.

    Args:
      v3_ref_value: An `entity_pb2.PropertyValue.ReferenceValue`.
      v3_ref: An `entity_pb2.Reference` to populate.
    """
    v3_ref.Clear()
    if v3_ref_value.HasField('app'):
      v3_ref.app = v3_ref_value.app
    if v3_ref_value.HasField('name_space'):
      v3_ref.name_space = v3_ref_value.name_space
    for v3_ref_value_path_element in v3_ref_value.pathelement:
      v3_path_element = v3_ref.path.element.add()
      copy_path_element(v3_ref_value_path_element, v3_path_element)


class _QueryConverter(object):
  """Base converter for v3 and v1 queries."""

  def __init__(self, entity_converter):
    self._entity_converter = entity_converter

  def get_entity_converter(self):
    return self._entity_converter

  def _v3_filter_to_v1_property_filter(self, v3_filter, v1_property_filter):
    """Converts a v3 Filter to a v1 `PropertyFilter`.

    Args:
      v3_filter: A `datastore_pb.Filter`.
      v1_property_filter: A `googledatastore.PropertyFilter` to populate.

    Raises:
      `InvalidConversionError` if the filter cannot be converted
    """
    check_conversion(len(v3_filter.property) == 1, 'invalid filter')
    check_conversion(v3_filter.op <= 5,
                     'unsupported filter op: %d' % v3_filter.op)
    v1_property_filter.Clear()
    v1_property_filter.op = v3_filter.op
    v1_property_filter.property.name = v3_filter.property[0].name
    self._entity_converter.v3_property_to_v1_value(v3_filter.property[0], True,
                                                   v1_property_filter.value)

  def _v3_query_to_v1_ancestor_filter(self, v3_query, v1_property_filter):
    """Converts a v3 Query to a v1 ancestor `PropertyFilter`.

    Args:
      v3_query: A `datastore_pb.Query`.
      v1_property_filter: A `googledatastore.PropertyFilter` to populate.
    """
    v1_property_filter.Clear()
    v1_property_filter.set_operator(
        v3_query.shallow() and
        googledatastore.PropertyFilter.HAS_PARENT or
        googledatastore.PropertyFilter.HAS_ANCESTOR)
    prop = v1_property_filter.property
    prop.set_name(PROPERTY_NAME_KEY)
    if v3_query.has_ancestor():
      self._entity_converter.v3_to_v1_key(
          v3_query.ancestor(),
          v1_property_filter.value.mutable_key_value)
    else:
      v1_property_filter.value.null_value = googledatastore.NULL_VALUE

  def v3_order_to_v1_order(self, v3_order, v1_order):
    """Converts a v3 Query order to a v1 `PropertyOrder`.

    Args:
      v3_order: A `datastore_pb.Query.Order`.
      v1_order: A `googledatastore.PropertyOrder` to populate.
    """
    v1_order.property.name = v3_order.property
    if v3_order.HasField('direction'):
      v1_order.direction = v3_order.direction

  def _v3_filter_to_v4_property_filter(self, v3_filter, v4_property_filter):
    """Converts a v3 Filter to a v4 `PropertyFilter`.

    Args:
      v3_filter: A `datastore_pb.Filter`.
      v4_property_filter: A `datastore_v4_pb2.PropertyFilter` to populate.

    Raises:
      `InvalidConversionError` if the filter cannot be converted
    """
    check_conversion(len(v3_filter.property) == 1, 'invalid filter')
    check_conversion(v3_filter.op <= 5,
                     'unsupported filter op: %d' % v3_filter.op)
    v4_property_filter.Clear()
    v4_property_filter.operator = v3_filter.op
    v4_property_filter.property.name = v3_filter.property[0].name
    self._entity_converter.v3_property_to_v4_value(v3_filter.property[0], True,
                                                   v4_property_filter.value)

  def _v3_query_to_v4_ancestor_filter(self, v3_query, v4_property_filter):
    """Converts a v3 Query to a v4 ancestor `PropertyFilter`.

    Args:
      v3_query: A `datastore_pb.Query`.
      v4_property_filter: A `datastore_v4_pb2.PropertyFilter` to populate.
    """
    v4_property_filter.Clear()
    v4_property_filter.operator = datastore_v4_pb2.PropertyFilter.HAS_ANCESTOR
    v4_property_filter.property.name = PROPERTY_NAME_KEY
    self._entity_converter.v3_to_v4_key(v3_query.ancestor,
                                        v4_property_filter.value.key_value)

  def v3_order_to_v4_order(self, v3_order, v4_order):
    """Converts a v3 Query order to a v4 `PropertyOrder`.

    Args:
      v3_order: A `datastore_pb.Query.Order`.
      v4_order: A `datastore_v4_pb2.PropertyOrder` to populate.
    """
    v4_order.property.name = v3_order.property
    if v3_order.HasField('direction'):
      v4_order.direction = v3_order.direction


def get_entity_converter(id_resolver=None):
  """Returns a converter for v3 and v1 entities and keys.

  Args:
    id_resolver: An `IdResolver` for project id resolution.
  """
  id_resolver = id_resolver or _IdentityIdResolver()
  return _EntityConverter(id_resolver)


def copy_path_element(source, destination):
  if source.HasField('type'):
    destination.type = source.type
  if source.HasField('id'):
    destination.id = source.id
  if source.HasField('name'):
    destination.name = source.name
