After a little testing, I discovered a flaw in my previously posted 
technique, namely that it was generating uint64 IDs, when the datastore will 
only accept non-zero uint63s.  I'd also forgotten to include my UUIDProperty 
class.  Here's the updated version.  The question remains whether there's 
likely to be any horrible loss of efficiency or problems by assigning IDs 
which will be essentially randomly distributed through the possible number 
space (the external DB mostly uses UUIDv4, so the distribution should be 
quite random).  As previously stated, auto-assigned IDs will not be used for 
these models, so collisions or disruption of auto-assignment are not a major 
concerns.  The code probably still needs the odd bit of polishing here and 
there.

MASK_64 = 2**64-1
MASK_63 = 2**63-1


class UUID(uuid.UUID):
    # Could use this, but Python doesn't guarantee future stability of
    # hash values
    # def get_id(self):
    #     return abs(hash(self.int))

    def get_id(self):
        """ Returns a positive, non-zero 63 bit integer from the
        UUID's int128 """
        x = ((self.int & MASK_64) ^ (self.int >> 64)) & MASK_63
        if (x == 0):
            x = 1
        return x

    id = property(get_id)


class UUIDProperty(Property):
    """A UUID property, stored as a 16 byte binary string."""

    data_type = UUID

    def get_value_for_datastore(self, model_instance):
        uuid = super(UUIDProperty,
                     self).get_value_for_datastore(model_instance)
        return ByteString(uuid.bytes)

    def make_value_from_datastore(self, value):
        if value is None:
            return None
        return UUID(bytes=value)

    def validate(self, value):
        if value is not None and not isinstance(value, self.data_type):
            try:
                value = self.data_type(value)
            except TypeError, err:
                raise BadValueError('Property %s must be convertible '
                                    'to a %s instance (%s)' %
                                    (self.name, self.data_type.__name__, 
err))
        value = super(UUIDProperty, self).validate(value)
        if value is not None and not isinstance(value, self.data_type):
            raise BadValueError('Property %s must be a %s instance' %
                                (self.name, self.data_type.__name__))
        return value


class UUIDModel(Model):
    @classmethod
    def get_by_uuid(cls, uuids, **kwds):
        uuids, multiple = datastore.NormalizeAndTypeCheck(uuids, (UUID, 
str))
        def normalize(uuid):
            if isinstance(uuid, str):
                return UUID(uuid)
            else:
                return uuid
        uuids = [normalize(uuid) for uuid in uuids]
        ids = [uuid.id for uuid in uuids]
        entities = cls.get_by_id(ids, **kwds)
        for index, entity in enumerate(entities):
            if entity is not None and entity.uuid != uuids[index]:
                raise BadKeyError('UUID hash collision detected (class %s): 
'
                                  '%s / %s'
                                  % (cls.kind(), entity.uuid, uuids[index]))
        if multiple:
            return entities
        else:
            return entities[0]

    @classmethod
    def get_or_insert_by_uuid(cls, uuid, **kwds):
        if isinstance(uuid, str):
            uuid = UUID(uuid)
        id = uuid.id
        def txn():
            entity = cls.get_by_id(id, parent=kwds.get('parent'))
            if entity is None:
                entity = cls(key=Key.from_path(cls.kind(), id,
                                               parent=kwds.get('parent')),
                             uuid=uuid,
                             **kwds)
                entity.put()
            elif entity.uuid != uuid:
                raise BadKeyError('UUID hash collision detected (class %s): 
'
                                  '%s / %s'
                                  % (cls.kind(), entity.uuid, uuid))
            return entity
        return db.run_in_transaction(txn)

    uuid = UUIDProperty('UUID')

-- 
You received this message because you are subscribed to the Google Groups 
"Google App Engine" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/google-appengine/-/BEkUAv6h6icJ.
To post to this group, send email to google-appengine@googlegroups.com.
To unsubscribe from this group, send email to 
google-appengine+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/google-appengine?hl=en.

Reply via email to