from aioriak.content import RiakContent
from aioriak.error import ConflictError
def content_property(name, doc=None):
'''
Delegates a property to the first sibling in a RiakObject, raising
an error when the object is in conflict.
'''
def _setter(self, value):
if len(self.siblings) == 0:
# In this case, assume that what the user wants is to
# create a new sibling inside an empty object.
self.siblings = [RiakContent(self)]
if len(self.siblings) != 1:
raise ConflictError()
setattr(self.siblings[0], name, value)
def _getter(self):
if len(self.siblings) == 0:
return
if len(self.siblings) != 1:
raise ConflictError()
return getattr(self.siblings[0], name)
return property(_getter, _setter, doc=doc)
[docs]class RiakObject:
'''
The RiakObject holds meta information about a Riak object, plus the
object's data.
'''
siblings = []
def __init__(self, client, bucket, key=None):
'''
Construct a new RiakObject.
:param client: A RiakClient object.
:type client: :class:`RiakClient <aioriak.client.RiakClient>`
:param bucket: A RiakBucket object.
:type bucket: :class:`RiakBucket <aioriak.bucket.RiakBucket>`
:param key: An optional key. If not specified, then the key
is generated by the server when :func:`store` is called.
:type key: string
'''
if key is not None and len(key) == 0:
raise ValueError('Key name must either be "None"'
' or a non-empty string.')
self.client = client
self.bucket = bucket
self.key = key
self.vclock = None
self.siblings = [RiakContent(self)]
self._resolver = None
def _exists(self):
if len(self.siblings) == 0:
return False
elif len(self.siblings) > 1:
# Even if all of the siblings are tombstones, the object
# essentially exists.
return True
else:
return self.siblings[0].exists
exists = property(_exists, None, doc='''
Whether the object exists. This is only ``False`` when there
are no siblings (the object was not found), or the solitary
sibling is a tombstone.
''')
[docs] async def reload(self):
'''
Reload the object from Riak. When this operation completes, the
object could contain new metadata and a new value, if the object
was updated in Riak since it was last retrieved.
.. note:: Even if the key is not found in Riak, this will
return a :class:`RiakObject`. Check the :attr:`exists`
property to see if the key was found.
:rtype: :class:`RiakObject`
'''
await self.client.get(self)
return self
[docs] async def store(self, w=None, dw=None, pw=None, return_body=True,
if_none_match=False, timeout=None):
'''
Store the object in Riak. When this operation completes, the
object could contain new metadata and possibly new data if Riak
contains a newer version of the object according to the object's
vector clock.
:param return_body: if the newly stored object should be retrieved
:type return_body: bool
:rtype: :class:`RiakObject` '''
if len(self.siblings) != 1:
raise ConflictError("Attempting to store an invalid object, "
"resolve the siblings first")
await self.client.put(self, w=w, dw=dw, pw=pw,
return_body=return_body,
if_none_match=if_none_match, timeout=timeout)
return self
[docs] async def delete(self):
'''
Delete this object from Riak.
:rtype: :class:`RiakObject`
'''
await self.client.delete(self)
self.clear()
return self
def clear(self):
'''
Reset this object.
:rtype: RiakObject
'''
self.siblings = []
return self
data = content_property('data', doc='''
The data stored in this object, as Python objects. For the raw
data, use the `encoded_data` property. If unset, accessing
this property will result in decoding the `encoded_data`
property into Python values. The decoding is dependent on the
`content_type` property and the bucket's registered decoders.
''')
encoded_data = content_property('encoded_data', doc='''
The raw data stored in this object, essentially the encoded
form of the `data` property. If unset, accessing this property
will result in encoding the `data` property into a string. The
encoding is dependent on the `content_type` property and the
bucket's registered encoders.
''')
charset = content_property('charset', doc='''
The character set of the encoded data as a string
''')
content_encoding = content_property('content_encoding', doc='''
The encoding (compression) of the encoded data. Valid values
are identity, deflate, gzip
''')
usermeta = content_property('usermeta', doc='''
Arbitrary user-defined metadata dict, mapping strings to strings.
''')
links = content_property('links', doc='''
A set of bucket/key/tag 3-tuples representing links to other
keys.
''')
content_type = content_property('content_type', doc='''
The MIME media type of the encoded data as a string
''')
indexes = content_property('indexes', doc='''
The set of secondary index entries, consisting of
index-name/value tuples
''')
def _get_resolver(self):
if callable(self._resolver):
return self._resolver
elif self._resolver is None:
return self.bucket.resolver
else:
raise TypeError("resolver is not a function")
def _set_resolver(self, value):
if value is None or callable(value):
self._resolver = value
else:
raise TypeError("resolver is not a function")
resolver = property(_get_resolver, _set_resolver,
doc='''The sibling-resolution function for this
object. If the resolver is not set, the
bucket's resolver will be used.''')