diff --git a/README.md b/README.md index bd57bc81..9a15864d 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Contributors * Soren Hansen * Sreejith Kesavan * Timothée Peignier +* [`tobixx`](https://github.com/tobixx) * [Tin Tvrtković](https://github.com/Tinche) * [Vitaly Shestovskiy](https://github.com/lamp0chka) * William Kral diff --git a/RELNOTES.md b/RELNOTES.md index 9d21f8dd..bd567fde 100644 --- a/RELNOTES.md +++ b/RELNOTES.md @@ -1,5 +1,9 @@ # Riak Python Client Release Notes +## [`2.7.0` Release](https://github.com/basho/riak-python-client/issues?q=milestone%3Ariak-python-client-2.7.0) + * Riak TS 1.5 support + * Support for `head` parameter + ## [`2.6.1` Release](https://github.com/basho/riak-python-client/issues?q=milestone%3Ariak-python-client-2.6.0) * NOTE: Due to pypi upload errors, `2.6.1` takes the place of `2.6.0`. diff --git a/riak/bucket.py b/riak/bucket.py index f6dd3863..70340f08 100644 --- a/riak/bucket.py +++ b/riak/bucket.py @@ -194,7 +194,7 @@ def new(self, key=None, data=None, content_type='application/json', return obj def get(self, key, r=None, pr=None, timeout=None, include_context=None, - basic_quorum=None, notfound_ok=None): + basic_quorum=None, notfound_ok=None, head_only=False): """ Retrieve a :class:`~riak.riak_object.RiakObject` or :class:`~riak.datatypes.Datatype`, based on the presence and value @@ -216,6 +216,9 @@ def get(self, key, r=None, pr=None, timeout=None, include_context=None, :type basic_quorum: bool :param notfound_ok: whether to treat not-found responses as successful :type notfound_ok: bool + :param head_only: whether to fetch without value, so only metadata + (only available on PB transport) + :type head_only: bool :rtype: :class:`RiakObject ` or :class:`~riak.datatypes.Datatype` @@ -231,10 +234,12 @@ def get(self, key, r=None, pr=None, timeout=None, include_context=None, obj = RiakObject(self._client, self, key) return obj.reload(r=r, pr=pr, timeout=timeout, basic_quorum=basic_quorum, - notfound_ok=notfound_ok) + notfound_ok=notfound_ok, + head_only=head_only) def multiget(self, keys, r=None, pr=None, timeout=None, - basic_quorum=None, notfound_ok=None): + basic_quorum=None, notfound_ok=None, + head_only=False): """ Retrieves a list of keys belonging to this bucket in parallel. @@ -251,6 +256,9 @@ def multiget(self, keys, r=None, pr=None, timeout=None, :type basic_quorum: bool :param notfound_ok: whether to treat not-found responses as successful :type notfound_ok: bool + :param head_only: whether to fetch without value, so only metadata + (only available on PB transport) + :type head_only: bool :rtype: list of :class:`RiakObjects `, :class:`Datatypes `, or tuples of bucket_type, bucket, key, and the exception raised on fetch @@ -258,7 +266,8 @@ def multiget(self, keys, r=None, pr=None, timeout=None, bkeys = [(self.bucket_type.name, self.name, key) for key in keys] return self._client.multiget(bkeys, r=r, pr=pr, timeout=timeout, basic_quorum=basic_quorum, - notfound_ok=notfound_ok) + notfound_ok=notfound_ok, + head_only=head_only) def _get_resolver(self): if callable(self._resolver): diff --git a/riak/client/operations.py b/riak/client/operations.py index 87c7f0b9..bc6d4568 100644 --- a/riak/client/operations.py +++ b/riak/client/operations.py @@ -678,7 +678,7 @@ def ts_stream_keys(self, table, timeout=None): @retryable def get(self, transport, robj, r=None, pr=None, timeout=None, - basic_quorum=None, notfound_ok=None): + basic_quorum=None, notfound_ok=None, head_only=False): """ get(robj, r=None, pr=None, timeout=None) @@ -700,6 +700,9 @@ def get(self, transport, robj, r=None, pr=None, timeout=None, :type basic_quorum: bool :param notfound_ok: whether to treat not-found responses as successful :type notfound_ok: bool + :param head_only: whether to fetch without value, so only metadata + (only available on PB transport) + :type head_only: bool """ _validate_timeout(timeout) if not isinstance(robj.key, six.string_types): @@ -708,7 +711,8 @@ def get(self, transport, robj, r=None, pr=None, timeout=None, return transport.get(robj, r=r, pr=pr, timeout=timeout, basic_quorum=basic_quorum, - notfound_ok=notfound_ok) + notfound_ok=notfound_ok, + head_only=head_only) @retryable def delete(self, transport, robj, rw=None, r=None, w=None, dw=None, diff --git a/riak/codecs/pbuf.py b/riak/codecs/pbuf.py index 04515f9f..9a21aaa5 100644 --- a/riak/codecs/pbuf.py +++ b/riak/codecs/pbuf.py @@ -895,7 +895,8 @@ def decode_preflist(self, item): return result def encode_get(self, robj, r=None, pr=None, timeout=None, - basic_quorum=None, notfound_ok=None): + basic_quorum=None, notfound_ok=None, + head_only=False): bucket = robj.bucket req = riak.pb.riak_kv_pb2.RpbGetReq() if r: @@ -914,6 +915,7 @@ def encode_get(self, robj, r=None, pr=None, timeout=None, req.bucket = str_to_bytes(bucket.name) self._add_bucket_type(req, bucket.bucket_type) req.key = str_to_bytes(robj.key) + req.head = head_only mc = riak.pb.messages.MSG_CODE_GET_REQ rc = riak.pb.messages.MSG_CODE_GET_RESP return Msg(mc, req.SerializeToString(), rc) diff --git a/riak/content.py b/riak/content.py index d885827b..6b3f080d 100644 --- a/riak/content.py +++ b/riak/content.py @@ -90,6 +90,8 @@ def _serialize(self, value): format(self.content_type)) def _deserialize(self, value): + if not value: + return value decoder = self._robject.bucket.get_decoder(self.content_type) if decoder: return decoder(value) diff --git a/riak/riak_object.py b/riak/riak_object.py index ab7dd375..7657e2a4 100644 --- a/riak/riak_object.py +++ b/riak/riak_object.py @@ -269,7 +269,7 @@ def store(self, w=None, dw=None, pw=None, return_body=True, return self def reload(self, r=None, pr=None, timeout=None, basic_quorum=None, - notfound_ok=None): + notfound_ok=None, head_only=False): """ Reload the object from Riak. When this operation completes, the object could contain new metadata and a new value, if the object @@ -293,10 +293,13 @@ def reload(self, r=None, pr=None, timeout=None, basic_quorum=None, :type basic_quorum: bool :param notfound_ok: whether to treat not-found responses as successful :type notfound_ok: bool + :param head_only: whether to fetch without value, so only metadata + (only available on PB transport) + :type head_only: bool :rtype: :class:`RiakObject` """ - self.client.get(self, r=r, pr=pr, timeout=timeout) + self.client.get(self, r=r, pr=pr, timeout=timeout, head_only=head_only) return self def delete(self, r=None, w=None, dw=None, pr=None, pw=None, diff --git a/riak/tests/test_kv.py b/riak/tests/test_kv.py index 67dd901f..7db30a08 100644 --- a/riak/tests/test_kv.py +++ b/riak/tests/test_kv.py @@ -8,7 +8,7 @@ from time import sleep from riak import ConflictError, RiakBucket, RiakError from riak.resolver import default_resolver, last_written_resolver -from riak.tests import RUN_KV, RUN_RESOLVE +from riak.tests import RUN_KV, RUN_RESOLVE, PROTOCOL from riak.tests.base import IntegrationTestBase from riak.tests.comparison import Comparison @@ -79,6 +79,19 @@ def test_no_returnbody(self): o = bucket.new(self.key_name, "bar").store(return_body=False) self.assertEqual(o.vclock, None) + @unittest.skipUnless(PROTOCOL == 'pbc', 'Only available on pbc') + def test_get_no_returnbody(self): + bucket = self.client.bucket(self.bucket_name) + o = bucket.new(self.key_name, "Ain't no body") + o.store() + + stored_object = bucket.get(self.key_name, head_only=True) + self.assertFalse(stored_object.data) + + list_of_objects = bucket.multiget([self.key_name], head_only=True) + for stored_object in list_of_objects: + self.assertFalse(stored_object.data) + def test_many_link_headers_should_work_fine(self): bucket = self.client.bucket(self.bucket_name) o = bucket.new("lots_of_links", "My god, it's full of links!") diff --git a/riak/transports/http/transport.py b/riak/transports/http/transport.py index 1599d368..c2d7c6dc 100644 --- a/riak/transports/http/transport.py +++ b/riak/transports/http/transport.py @@ -107,7 +107,7 @@ def get_resources(self): return {} def get(self, robj, r=None, pr=None, timeout=None, basic_quorum=None, - notfound_ok=None): + notfound_ok=None, head_only=False): """ Get a bucket/key from the server """ diff --git a/riak/transports/tcp/transport.py b/riak/transports/tcp/transport.py index d8173402..39c0b456 100644 --- a/riak/transports/tcp/transport.py +++ b/riak/transports/tcp/transport.py @@ -133,7 +133,7 @@ def _set_client_id(self, client_id): doc="""the client ID for this connection""") def get(self, robj, r=None, pr=None, timeout=None, basic_quorum=None, - notfound_ok=None): + notfound_ok=None, head_only=False): """ Serialize get request and deserialize response """ @@ -141,7 +141,7 @@ def get(self, robj, r=None, pr=None, timeout=None, basic_quorum=None, codec = self._get_codec(msg_code) msg = codec.encode_get(robj, r, pr, timeout, basic_quorum, - notfound_ok) + notfound_ok, head_only) resp_code, resp = self._request(msg, codec) return codec.decode_get(robj, resp) diff --git a/riak/transports/transport.py b/riak/transports/transport.py index bda18e35..ba413865 100644 --- a/riak/transports/transport.py +++ b/riak/transports/transport.py @@ -54,7 +54,7 @@ def ping(self): raise NotImplementedError def get(self, robj, r=None, pr=None, timeout=None, basic_quorum=None, - notfound_ok=None): + notfound_ok=None, head_only=False): """ Fetches an object. """