Skip to content

Commit a883276

Browse files
committed
Merge branch 'master' into fixes/lrb/timeout-gh-425
# Conflicts: # riak/transports/tcp/connection.py
2 parents 5016352 + 5e0a909 commit a883276

12 files changed

Lines changed: 345 additions & 63 deletions

File tree

RELNOTES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Riak Python Client Release Notes
22

3+
## [2.5.0 Release](https://github.com/basho/riak-python-client/issues?q=milestone%3Ariak-python-client-2.5.0)
4+
5+
* [Socket Enhancements](https://github.com/basho/riak-python-client/pull/453) - Resolves [#399](https://github.com/basho/riak-python-client/issues/399)
6+
* [Add multi-put](https://github.com/basho/riak-python-client/pull/452)
7+
* [Add support for term-to-binary encoding](https://github.com/basho/riak-python-client/pull/448)
8+
39
## 2.4.2 Patch Release - 2016-02-20
410

511
* [Fix SSL host name](https://github.com/basho/riak-python-client/pull/436)

riak/client/__init__.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from riak.security import SecurityCreds
1717
from riak.util import lazy_property, bytes_to_str, str_to_bytes
1818
from six import string_types, PY2
19-
from riak.client.multiget import MultiGetPool
19+
from riak.client.multi import MultiGetPool, MultiPutPool
2020

2121

2222
def default_encoder(obj):
@@ -67,8 +67,10 @@ class RiakClient(RiakMapReduceChain, RiakClientOperations):
6767
#: The supported protocols
6868
PROTOCOLS = ['http', 'pbc']
6969

70-
def __init__(self, protocol='pbc', transport_options={}, nodes=None,
71-
credentials=None, multiget_pool_size=None, **kwargs):
70+
def __init__(self, protocol='pbc', transport_options={},
71+
nodes=None, credentials=None,
72+
multiget_pool_size=None, multiput_pool_size=None,
73+
**kwargs):
7274
"""
7375
Construct a new ``RiakClient`` object.
7476
@@ -87,6 +89,10 @@ def __init__(self, protocol='pbc', transport_options={}, nodes=None,
8789
:meth:`multiget` operations. Defaults to a factor of the number of
8890
CPUs in the system
8991
:type multiget_pool_size: int
92+
:param multiput_pool_size: the number of threads to use in
93+
:meth:`multiput` operations. Defaults to a factor of the number of
94+
CPUs in the system
95+
:type multiput_pool_size: int
9096
"""
9197
kwargs = kwargs.copy()
9298

@@ -96,6 +102,7 @@ def __init__(self, protocol='pbc', transport_options={}, nodes=None,
96102
self.nodes = [self._create_node(n) for n in nodes]
97103

98104
self._multiget_pool_size = multiget_pool_size
105+
self._multiput_pool_size = multiput_pool_size
99106
self.protocol = protocol or 'pbc'
100107
self._resolver = None
101108
self._credentials = self._create_credentials(credentials)
@@ -358,6 +365,13 @@ def _multiget_pool(self):
358365
else:
359366
return None
360367

368+
@lazy_property
369+
def _multiput_pool(self):
370+
if self._multiput_pool_size:
371+
return MultiPutPool(self._multiput_pool_size)
372+
else:
373+
return None
374+
361375
def __hash__(self):
362376
return hash(frozenset([(n.host, n.http_port, n.pb_port)
363377
for n in self.nodes]))
Lines changed: 111 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
from threading import Thread, Lock, Event
44
from multiprocessing import cpu_count
55
from six import PY2
6-
76
if PY2:
87
from Queue import Queue
98
else:
109
from queue import Queue
1110

12-
__all__ = ['multiget', 'MultiGetPool']
11+
__all__ = ['multiget', 'multiput', 'MultiGetPool', 'MultiPutPool']
1312

1413

1514
try:
@@ -21,25 +20,29 @@
2120
POOL_SIZE = 6
2221

2322
#: A :class:`namedtuple` for tasks that are fed to workers in the
24-
#: multiget pool.
25-
Task = namedtuple('Task', ['client', 'outq', 'bucket_type', 'bucket', 'key',
26-
'options'])
23+
#: multi pool.
24+
Task = namedtuple(
25+
'Task',
26+
['client', 'outq',
27+
'bucket_type', 'bucket', 'key',
28+
'object', 'options'])
2729

2830

29-
class MultiGetPool(object):
31+
class MultiPool(object):
3032
"""
31-
Encapsulates a pool of fetcher threads. These threads can be used
32-
across many multi-get requests.
33+
Encapsulates a pool of threads. These threads can be used
34+
across many multi requests.
3335
"""
3436

35-
def __init__(self, size=POOL_SIZE):
37+
def __init__(self, size=POOL_SIZE, name='unknown'):
3638
"""
3739
:param size: the desired size of the worker pool
3840
:type size: int
3941
"""
4042

4143
self._inq = Queue()
4244
self._size = size
45+
self._name = name
4346
self._started = Event()
4447
self._stop = Event()
4548
self._lock = Lock()
@@ -57,14 +60,14 @@ def enq(self, task):
5760
if not self._stop.is_set():
5861
self._inq.put(task)
5962
else:
60-
raise RuntimeError("Attempted to enqueue a fetch operation while "
61-
"multi-get pool was shutdown!")
63+
raise RuntimeError("Attempted to enqueue an operation while "
64+
"multi pool was shutdown!")
6265

6366
def start(self):
6467
"""
6568
Starts the worker threads if they are not already started.
6669
This method is thread-safe and will be called automatically
67-
when executing a MultiGet operation.
70+
when executing an operation.
6871
"""
6972
# Check whether we are already started, skip if we are.
7073
if not self._started.is_set():
@@ -73,8 +76,9 @@ def start(self):
7376
# If we got the lock, go ahead and start the worker
7477
# threads, set the started flag, and release the lock.
7578
for i in range(self._size):
76-
name = "riak.client.multiget-worker-{0}".format(i)
77-
worker = Thread(target=self._fetcher, name=name)
79+
name = "riak.client.multi-worker-{0}-{1}".format(
80+
self._name, i)
81+
worker = Thread(target=self._worker_method, name=name)
7882
worker.daemon = True
7983
worker.start()
8084
self._workers.append(worker)
@@ -105,7 +109,26 @@ def __del__(self):
105109
# shutting down.
106110
self.stop()
107111

108-
def _fetcher(self):
112+
def _worker_method(self):
113+
raise NotImplementedError
114+
115+
def _should_quit(self):
116+
"""
117+
Worker threads should exit when the stop flag is set and the
118+
input queue is empty. Once the stop flag is set, new enqueues
119+
are disallowed, meaning that the workers can safely drain the
120+
queue before exiting.
121+
122+
:rtype: bool
123+
"""
124+
return self.stopped() and self._inq.empty()
125+
126+
127+
class MultiGetPool(MultiPool):
128+
def __init__(self, size=POOL_SIZE):
129+
super(MultiGetPool, self).__init__(size=size, name='get')
130+
131+
def _worker_method(self):
109132
"""
110133
The body of the multi-get worker. Loops until
111134
:meth:`_should_quit` returns ``True``, taking tasks off the
@@ -121,24 +144,40 @@ def _fetcher(self):
121144
except KeyboardInterrupt:
122145
raise
123146
except Exception as err:
124-
task.outq.put((task.bucket_type, task.bucket, task.key, err), )
147+
errdata = (task.bucket_type, task.bucket, task.key, err)
148+
task.outq.put(errdata)
125149
finally:
126150
self._inq.task_done()
127151

128-
def _should_quit(self):
129-
"""
130-
Worker threads should exit when the stop flag is set and the
131-
input queue is empty. Once the stop flag is set, new enqueues
132-
are disallowed, meaning that the workers can safely drain the
133-
queue before exiting.
134152

135-
:rtype: bool
153+
class MultiPutPool(MultiPool):
154+
def __init__(self, size=POOL_SIZE):
155+
super(MultiPutPool, self).__init__(size=size, name='put')
156+
157+
def _worker_method(self):
136158
"""
137-
return self.stopped() and self._inq.empty()
159+
The body of the multi-put worker. Loops until
160+
:meth:`_should_quit` returns ``True``, taking tasks off the
161+
input queue, storing the object, and putting the result on
162+
the output queue.
163+
"""
164+
while not self._should_quit():
165+
task = self._inq.get()
166+
try:
167+
robj = task.object
168+
rv = task.client.put(robj, **task.options)
169+
task.outq.put(rv)
170+
except KeyboardInterrupt:
171+
raise
172+
except Exception as err:
173+
errdata = (task.object, err)
174+
task.outq.put(errdata)
175+
finally:
176+
self._inq.task_done()
138177

139178

140-
#: The default pool is automatically created and stored in this constant.
141179
RIAK_MULTIGET_POOL = MultiGetPool()
180+
RIAK_MULTIPUT_POOL = MultiPutPool()
142181

143182

144183
def multiget(client, keys, **options):
@@ -160,8 +199,8 @@ def multiget(client, keys, **options):
160199
:meth:`RiakBucket.get <riak.bucket.RiakBucket.get>`
161200
:type options: dict
162201
:rtype: list
163-
"""
164202
203+
"""
165204
outq = Queue()
166205

167206
if 'pool' in options:
@@ -172,7 +211,7 @@ def multiget(client, keys, **options):
172211

173212
pool.start()
174213
for bucket_type, bucket, key in keys:
175-
task = Task(client, outq, bucket_type, bucket, key, options)
214+
task = Task(client, outq, bucket_type, bucket, key, None, options)
176215
pool.enq(task)
177216

178217
results = []
@@ -184,3 +223,48 @@ def multiget(client, keys, **options):
184223
outq.task_done()
185224

186225
return results
226+
227+
228+
def multiput(client, objs, **options):
229+
"""Executes a parallel-store across multiple threads. Returns a list
230+
containing booleans or :class:`~riak.riak_object.RiakObject`
231+
232+
If a ``pool`` option is included, the request will use the given worker
233+
pool and not the default :data:`RIAK_MULTIPUT_POOL`. This option will
234+
be passed by the client if the ``multiput_pool_size`` option was set on
235+
client initialization.
236+
237+
:param client: the client to use
238+
:type client: :class:`RiakClient <riak.client.RiakClient>`
239+
:param objs: the Riak Objects to store in parallel
240+
:type keys: list of `RiakObject <riak.riak_object.RiakObject>`
241+
:param options: request options to
242+
:meth:`RiakClient.put <riak.client.RiakClient.put>`
243+
:type options: dict
244+
:rtype: list
245+
"""
246+
outq = Queue()
247+
248+
if 'pool' in options:
249+
pool = options['pool']
250+
del options['pool']
251+
else:
252+
pool = RIAK_MULTIPUT_POOL
253+
254+
pool.start()
255+
for robj in objs:
256+
bucket_type = robj.bucket.bucket_type
257+
bucket = robj.bucket.name
258+
key = robj.key
259+
task = Task(client, outq, bucket_type, bucket, key, robj, options)
260+
pool.enq(task)
261+
262+
results = []
263+
for _ in range(len(objs)):
264+
if pool.stopped():
265+
raise RuntimeError("Multi-put operation interrupted by pool "
266+
"stopping!")
267+
results.append(outq.get())
268+
outq.task_done()
269+
270+
return results

riak/client/operations.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import riak.client.multi
2+
13
from riak.client.transport import RiakClientTransport, \
24
retryable, retryableHttpOnly
3-
from riak.client.multiget import multiget
45
from riak.client.index_page import IndexPage
56
from riak.datatypes import TYPES
67
from riak.table import Table
@@ -976,7 +977,22 @@ def multiget(self, pairs, **params):
976977
"""
977978
if self._multiget_pool:
978979
params['pool'] = self._multiget_pool
979-
return multiget(self, pairs, **params)
980+
return riak.client.multi.multiget(self, pairs, **params)
981+
982+
def multiput(self, objs, **params):
983+
"""
984+
Stores objects in parallel via threads.
985+
986+
:param objs: the objects to store
987+
:type objs: list of `RiakObject <riak.riak_object.RiakObject>`
988+
:param params: additional request flags, e.g. w, dw, pw
989+
:type params: dict
990+
:rtype: list of boolean or
991+
:class:`RiakObjects <riak.riak_object.RiakObject>`,
992+
"""
993+
if self._multiput_pool:
994+
params['pool'] = self._multiput_pool
995+
return riak.client.multi.multiput(self, objs, **params)
980996

981997
@retryable
982998
def get_counter(self, transport, bucket, key, r=None, pr=None,

riak/client/transport.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from contextlib import contextmanager
22
from riak.transports.pool import BadResource
3-
from riak.transports.tcp import is_retryable as is_pbc_retryable
3+
from riak.transports.tcp import is_retryable as is_tcp_retryable
44
from riak.transports.http import is_retryable as is_http_retryable
55
import threading
66
from six import PY2
@@ -162,7 +162,7 @@ def _is_retryable(error):
162162
:type error: Exception
163163
:rtype: boolean
164164
"""
165-
return is_pbc_retryable(error) or is_http_retryable(error)
165+
return is_tcp_retryable(error) or is_http_retryable(error)
166166

167167

168168
def retryable(fn, protocol=None):

riak/riak_error.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ class RiakError(Exception):
2121
"""
2222
Base class for exceptions generated in the Riak API.
2323
"""
24-
def __init__(self, value):
25-
self.value = value
24+
def __init__(self, *args, **kwargs):
25+
super(RiakError, self).__init__(*args, **kwargs)
26+
if len(args) > 0:
27+
self.value = args[0]
28+
else:
29+
self.value = 'unknown'
2630

2731
def __str__(self):
2832
return repr(self.value)
@@ -34,5 +38,5 @@ class ConflictError(RiakError):
3438
:class:`~riak.riak_object.RiakObject` that has more than one
3539
sibling.
3640
"""
37-
def __init__(self, message="Object in conflict"):
41+
def __init__(self, message='Object in conflict'):
3842
super(ConflictError, self).__init__(message)

riak/tests/test_btypes.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,18 @@ def test_multiget_bucket_types(self):
151151
self.assertEqual(btype, mobj.bucket.bucket_type)
152152

153153
def test_write_once_bucket_type(self):
154-
btype = self.client.bucket_type('write_once')
155-
bucket = btype.bucket(self.bucket_name)
156-
157-
for i in range(100):
158-
obj = bucket.new(self.key_name + str(i))
159-
obj.data = {'id': i}
160-
obj.store()
154+
bt = 'write_once'
155+
skey = 'write_once-init'
156+
btype = self.client.bucket_type(bt)
157+
bucket = btype.bucket(bt)
158+
sobj = bucket.get(skey)
159+
if not sobj.exists:
160+
for i in range(100):
161+
o = bucket.new(self.key_name + str(i))
162+
o.data = {'id': i}
163+
o.store()
164+
o = bucket.new(skey, data={'id': skey})
165+
o.store()
161166

162167
mget = bucket.multiget([self.key_name + str(i) for i in range(100)])
163168
for mobj in mget:

0 commit comments

Comments
 (0)