Exemplo n.º 1
0
    def __init__(self, protocol='pbc', transport_options={}, nodes=None,
                 credentials=None, multiget_pool_size=None, **unused_args):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'pbc'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        :param credentials: optional object of security info
        :type credentials: :class:`~riak.security.SecurityCreds` or dict
        :param multiget_pool_size: the number of threads to use in
           :meth:`multiget` operations. Defaults to a factor of the number of
           CPUs in the system
        :type multiget_pool_size: int
        """
        unused_args = unused_args.copy()

        if nodes is None:
            self.nodes = [self._create_node(unused_args), ]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self._multiget_pool_size = multiget_pool_size
        self.protocol = protocol or 'pbc'
        self._resolver = None
        self._credentials = self._create_credentials(credentials)
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        if PY2:
            self._encoders = {'application/json': default_encoder,
                              'text/json': default_encoder,
                              'text/plain': str}
            self._decoders = {'application/json': json.loads,
                              'text/json': json.loads,
                              'text/plain': str}
        else:
            self._encoders = {'application/json': binary_json_encoder,
                              'text/json': binary_json_encoder,
                              'text/plain': str_to_bytes,
                              'binary/octet-stream': binary_encoder_decoder}
            self._decoders = {'application/json': binary_json_decoder,
                              'text/json': binary_json_decoder,
                              'text/plain': bytes_to_str,
                              'binary/octet-stream': binary_encoder_decoder}
        self._buckets = WeakValueDictionary()
        self._bucket_types = WeakValueDictionary()
Exemplo n.º 2
0
    def __init__(self,
                 protocol='http',
                 transport_options={},
                 nodes=None,
                 **unused_args):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'http'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        """
        unused_args = unused_args.copy()

        if 'port' in unused_args:
            deprecated("port option is deprecated, use http_port or pb_port,"
                       " or the nodes option. Your given port of %r will be "
                       "used as the %s port unless already set" %
                       (unused_args['port'], protocol))
            unused_args['already_warned_port'] = True
            if (protocol in ['http', 'https']
                    and 'http_port' not in unused_args):
                unused_args['http_port'] = unused_args['port']
            elif protocol == 'pbc' and 'pb_port' not in unused_args:
                unused_args['pb_port'] = unused_args['port']

        if 'transport_class' in unused_args:
            deprecated(
                "transport_class is deprecated, use the protocol option")

        if nodes is None:
            self.nodes = [
                self._create_node(unused_args),
            ]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self.protocol = protocol or 'http'
        self.resolver = default_resolver
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        self._encoders = {
            'application/json': default_encoder,
            'text/json': default_encoder,
            'text/plain': str
        }
        self._decoders = {
            'application/json': default_decoder,
            'text/json': default_decoder,
            'text/plain': str
        }
        self._buckets = WeakValueDictionary()
    def __init__(
        self, protocol="pbc", transport_options={}, nodes=None, credentials=None, multiget_pool_size=None, **unused_args
    ):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'pbc'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        :param credentials: optional object of security info
        :type credentials: :class:`~riak.security.SecurityCreds` or dict
        :param multiget_pool_size: the number of threads to use in
           :meth:`multiget` operations. Defaults to a factor of the number of
           CPUs in the system
        :type multiget_pool_size: int
        """
        unused_args = unused_args.copy()

        if nodes is None:
            self.nodes = [self._create_node(unused_args)]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self._multiget_pool_size = multiget_pool_size
        self.protocol = protocol or "pbc"
        self._resolver = None
        self._credentials = self._create_credentials(credentials)
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        if PY2:
            self._encoders = {"application/json": default_encoder, "text/json": default_encoder, "text/plain": str}
            self._decoders = {"application/json": json.loads, "text/json": json.loads, "text/plain": str}
        else:
            self._encoders = {
                "application/json": binary_json_encoder,
                "text/json": binary_json_encoder,
                "text/plain": str_to_bytes,
                "binary/octet-stream": binary_encoder_decoder,
            }
            self._decoders = {
                "application/json": binary_json_decoder,
                "text/json": binary_json_decoder,
                "text/plain": bytes_to_str,
                "binary/octet-stream": binary_encoder_decoder,
            }
        self._buckets = WeakValueDictionary()
        self._bucket_types = WeakValueDictionary()
Exemplo n.º 4
0
    def __init__(self, protocol='http', transport_options={}, nodes=None,
                 credentials=None, **unused_args):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'http'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        :param credentials: optional object of security info
        :type credentials: SecurityCreds or dict
        """
        unused_args = unused_args.copy()

        if 'port' in unused_args:
            deprecated("port option is deprecated, use http_port or pb_port,"
                       " or the nodes option. Your given port of %r will be "
                       "used as the %s port unless already set" %
                       (unused_args['port'], protocol))
            unused_args['already_warned_port'] = True
            if (protocol == 'http' and
                    'http_port' not in unused_args):
                unused_args['http_port'] = unused_args['port']
            elif protocol == 'pbc' and 'pb_port' not in unused_args:
                unused_args['pb_port'] = unused_args['port']

        if 'transport_class' in unused_args:
            deprecated(
                "transport_class is deprecated, use the protocol option")

        if nodes is None:
            self.nodes = [self._create_node(unused_args), ]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self.protocol = protocol or 'http'
        self.resolver = default_resolver
        self._credentials = self._create_credentials(credentials)
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        self._encoders = {'application/json': default_encoder,
                          'text/json': default_encoder,
                          'text/plain': str}
        self._decoders = {'application/json': json.loads,
                          'text/json': json.loads,
                          'text/plain': str}
        self._buckets = WeakValueDictionary()
        self._bucket_types = WeakValueDictionary()
class RiakClient(RiakMapReduceChain, RiakClientOperations):
    """
    The ``RiakClient`` object holds information necessary to connect
    to Riak. Requests can be made to Riak directly through the client
    or by using the methods on related objects.
    """

    #: The supported protocols
    PROTOCOLS = ["http", "pbc"]

    def __init__(
        self, protocol="pbc", transport_options={}, nodes=None, credentials=None, multiget_pool_size=None, **unused_args
    ):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'pbc'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        :param credentials: optional object of security info
        :type credentials: :class:`~riak.security.SecurityCreds` or dict
        :param multiget_pool_size: the number of threads to use in
           :meth:`multiget` operations. Defaults to a factor of the number of
           CPUs in the system
        :type multiget_pool_size: int
        """
        unused_args = unused_args.copy()

        if nodes is None:
            self.nodes = [self._create_node(unused_args)]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self._multiget_pool_size = multiget_pool_size
        self.protocol = protocol or "pbc"
        self._resolver = None
        self._credentials = self._create_credentials(credentials)
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        if PY2:
            self._encoders = {"application/json": default_encoder, "text/json": default_encoder, "text/plain": str}
            self._decoders = {"application/json": json.loads, "text/json": json.loads, "text/plain": str}
        else:
            self._encoders = {
                "application/json": binary_json_encoder,
                "text/json": binary_json_encoder,
                "text/plain": str_to_bytes,
                "binary/octet-stream": binary_encoder_decoder,
            }
            self._decoders = {
                "application/json": binary_json_decoder,
                "text/json": binary_json_decoder,
                "text/plain": bytes_to_str,
                "binary/octet-stream": binary_encoder_decoder,
            }
        self._buckets = WeakValueDictionary()
        self._bucket_types = WeakValueDictionary()

    def _get_protocol(self):
        return self._protocol

    def _set_protocol(self, value):
        if value not in self.PROTOCOLS:
            raise ValueError("protocol option is invalid, must be one of %s" % repr(self.PROTOCOLS))
        self._protocol = value

    protocol = property(
        _get_protocol,
        _set_protocol,
        doc="""
                        Which protocol to prefer, one of
                        :attr:`PROTOCOLS
                        <riak.client.RiakClient.PROTOCOLS>`. Please
                        note that when one protocol is selected, the
                        other protocols MAY NOT attempt to connect.
                        Changing to another protocol will cause a
                        connection on the next request.

                        Some requests are only valid over ``'http'``,
                        and will always be sent via
                        those transports, regardless of which protocol
                        is preferred.
                         """,
    )

    def _get_resolver(self):
        return self._resolver or default_resolver

    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 client.
                        Defaults to :func:`riak.resolver.default_resolver`.""",
    )

    def _get_client_id(self):
        with self._transport() as transport:
            return transport.client_id

    def _set_client_id(self, client_id):
        for http in self._http_pool:
            http.client_id = client_id
        for pb in self._pb_pool:
            pb.client_id = client_id

    client_id = property(_get_client_id, _set_client_id, doc="""The client ID for this client instance""")

    def get_encoder(self, content_type):
        """
        Get the encoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :rtype: function
        """
        return self._encoders.get(content_type)

    def set_encoder(self, content_type, encoder):
        """
        Set the encoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :param encoder: an encoding function, takes a single object
            argument and returns encoded data
        :type encoder: function
        """
        self._encoders[content_type] = encoder

    def get_decoder(self, content_type):
        """
        Get the decoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :rtype: function
        """
        return self._decoders.get(content_type)

    def set_decoder(self, content_type, decoder):
        """
        Set the decoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :param decoder: a decoding function, takes encoded data and
            returns a Python type
        :type decoder: function
        """
        self._decoders[content_type] = decoder

    def bucket(self, name, bucket_type="default"):
        """
        Get the bucket by the specified name. Since buckets always exist,
        this will always return a
        :class:`RiakBucket <riak.bucket.RiakBucket>`.

        If you are using a bucket that is contained in a bucket type, it is
        preferable to access it from the bucket type object::

            # Preferred:
            client.bucket_type("foo").bucket("bar")

            # Equivalent, but not preferred:
            client.bucket("bar", bucket_type="foo")

        :param name: the bucket name
        :type name: str
        :param bucket_type: the parent bucket-type
        :type bucket_type: :class:`BucketType <riak.bucket.BucketType>`
              or str
        :rtype: :class:`RiakBucket <riak.bucket.RiakBucket>`

        """
        if not isinstance(name, string_types):
            raise TypeError("Bucket name must be a string")

        if isinstance(bucket_type, string_types):
            bucket_type = self.bucket_type(bucket_type)
        elif not isinstance(bucket_type, BucketType):
            raise TypeError("bucket_type must be a string " "or riak.bucket.BucketType")

        return self._buckets.setdefault((bucket_type, name), RiakBucket(self, name, bucket_type))

    def bucket_type(self, name):
        """
        Gets the bucket-type by the specified name. Bucket-types do
        not always exist (unlike buckets), but this will always return
        a :class:`BucketType <riak.bucket.BucketType>` object.

        :param name: the bucket name
        :type name: str
        :rtype: :class:`BucketType <riak.bucket.BucketType>`
        """
        if not isinstance(name, string_types):
            raise TypeError("Bucket name must be a string")

        if name in self._bucket_types:
            return self._bucket_types[name]
        else:
            btype = BucketType(self, name)
            self._bucket_types[name] = btype
            return btype

    def close(self):
        """
        Iterate through all of the connections and close each one.
        """
        if self._http_pool is not None:
            self._http_pool.clear()
        if self._pb_pool is not None:
            self._pb_pool.clear()

    def _create_node(self, n):
        if isinstance(n, RiakNode):
            return n
        elif isinstance(n, tuple) and len(n) is 3:
            host, http_port, pb_port = n
            return RiakNode(host=host, http_port=http_port, pb_port=pb_port)
        elif isinstance(n, dict):
            return RiakNode(**n)
        else:
            raise TypeError("%s is not a valid node configuration" % repr(n))

    def _create_credentials(self, n):
        """
        Create security credentials, if necessary.
        """
        if not n:
            return n
        elif isinstance(n, SecurityCreds):
            return n
        elif isinstance(n, dict):
            return SecurityCreds(**n)
        else:
            raise TypeError("%s is not a valid security configuration" % repr(n))

    def _choose_node(self, nodes=None):
        """
        Chooses a random node from the list of nodes in the client,
        taking into account each node's recent error rate.
        :rtype RiakNode
        """
        if not nodes:
            nodes = self.nodes

        # Prefer nodes which have gone a reasonable time without
        # errors
        def _error_rate(node):
            return node.error_rate.value()

        good = [n for n in nodes if _error_rate(n) < 0.1]

        if len(good) is 0:
            # Fall back to a minimally broken node
            return min(nodes, key=_error_rate)
        else:
            return random.choice(good)

    @lazy_property
    def _multiget_pool(self):
        if self._multiget_pool_size:
            return MultiGetPool(self._multiget_pool_size)
        else:
            return None

    def __hash__(self):
        return hash(frozenset([(n.host, n.http_port, n.pb_port) for n in self.nodes]))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return hash(self) == hash(other)
        else:
            return False

    def __ne__(self, other):
        if isinstance(other, self.__class__):
            return hash(self) != hash(other)
        else:
            return True
Exemplo n.º 6
0
class RiakClient(RiakMapReduceChain, RiakClientOperations):
    """
    The ``RiakClient`` object holds information necessary to connect
    to Riak. Requests can be made to Riak directly through the client
    or by using the methods on related objects.
    """

    #: The supported protocols
    PROTOCOLS = ['http', 'pbc']

    def __init__(self,
                 protocol='pbc',
                 transport_options={},
                 nodes=None,
                 credentials=None,
                 multiget_pool_size=None,
                 **unused_args):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'pbc'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        :param credentials: optional object of security info
        :type credentials: :class:`~riak.security.SecurityCreds` or dict
        :param multiget_pool_size: the number of threads to use in
           :meth:`multiget` operations. Defaults to a factor of the number of
           CPUs in the system
        :type multiget_pool_size: int
        """
        unused_args = unused_args.copy()

        if nodes is None:
            self.nodes = [
                self._create_node(unused_args),
            ]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self._multiget_pool_size = multiget_pool_size
        self.protocol = protocol or 'pbc'
        self._resolver = None
        self._credentials = self._create_credentials(credentials)
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        if PY2:
            self._encoders = {
                'application/json': default_encoder,
                'text/json': default_encoder,
                'text/plain': str
            }
            self._decoders = {
                'application/json': json.loads,
                'text/json': json.loads,
                'text/plain': str
            }
        else:
            self._encoders = {
                'application/json': binary_json_encoder,
                'text/json': binary_json_encoder,
                'text/plain': str_to_bytes,
                'binary/octet-stream': binary_encoder_decoder
            }
            self._decoders = {
                'application/json': binary_json_decoder,
                'text/json': binary_json_decoder,
                'text/plain': bytes_to_str,
                'binary/octet-stream': binary_encoder_decoder
            }
        self._buckets = WeakValueDictionary()
        self._bucket_types = WeakValueDictionary()

    def _get_protocol(self):
        return self._protocol

    def _set_protocol(self, value):
        if value not in self.PROTOCOLS:
            raise ValueError("protocol option is invalid, must be one of %s" %
                             repr(self.PROTOCOLS))
        self._protocol = value

    protocol = property(_get_protocol,
                        _set_protocol,
                        doc="""
                        Which protocol to prefer, one of
                        :attr:`PROTOCOLS
                        <riak.client.RiakClient.PROTOCOLS>`. Please
                        note that when one protocol is selected, the
                        other protocols MAY NOT attempt to connect.
                        Changing to another protocol will cause a
                        connection on the next request.

                        Some requests are only valid over ``'http'``,
                        and will always be sent via
                        those transports, regardless of which protocol
                        is preferred.
                         """)

    def _get_resolver(self):
        return self._resolver or default_resolver

    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 client.
                        Defaults to :func:`riak.resolver.default_resolver`.""")

    def _get_client_id(self):
        with self._transport() as transport:
            return transport.client_id

    def _set_client_id(self, client_id):
        for http in self._http_pool:
            http.client_id = client_id
        for pb in self._pb_pool:
            pb.client_id = client_id

    client_id = property(_get_client_id,
                         _set_client_id,
                         doc="""The client ID for this client instance""")

    def get_encoder(self, content_type):
        """
        Get the encoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :rtype: function
        """
        return self._encoders.get(content_type)

    def set_encoder(self, content_type, encoder):
        """
        Set the encoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :param encoder: an encoding function, takes a single object
            argument and returns encoded data
        :type encoder: function
        """
        self._encoders[content_type] = encoder

    def get_decoder(self, content_type):
        """
        Get the decoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :rtype: function
        """
        return self._decoders.get(content_type)

    def set_decoder(self, content_type, decoder):
        """
        Set the decoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :param decoder: a decoding function, takes encoded data and
            returns a Python type
        :type decoder: function
        """
        self._decoders[content_type] = decoder

    def bucket(self, name, bucket_type='default'):
        """
        Get the bucket by the specified name. Since buckets always exist,
        this will always return a
        :class:`RiakBucket <riak.bucket.RiakBucket>`.

        If you are using a bucket that is contained in a bucket type, it is
        preferable to access it from the bucket type object::

            # Preferred:
            client.bucket_type("foo").bucket("bar")

            # Equivalent, but not preferred:
            client.bucket("bar", bucket_type="foo")

        :param name: the bucket name
        :type name: str
        :param bucket_type: the parent bucket-type
        :type bucket_type: :class:`BucketType <riak.bucket.BucketType>`
              or str
        :rtype: :class:`RiakBucket <riak.bucket.RiakBucket>`

        """
        if not isinstance(name, string_types):
            raise TypeError('Bucket name must be a string')

        if isinstance(bucket_type, string_types):
            bucket_type = self.bucket_type(bucket_type)
        elif not isinstance(bucket_type, BucketType):
            raise TypeError('bucket_type must be a string '
                            'or riak.bucket.BucketType')

        return self._buckets.setdefault((bucket_type, name),
                                        RiakBucket(self, name, bucket_type))

    def bucket_type(self, name):
        """
        Gets the bucket-type by the specified name. Bucket-types do
        not always exist (unlike buckets), but this will always return
        a :class:`BucketType <riak.bucket.BucketType>` object.

        :param name: the bucket name
        :type name: str
        :rtype: :class:`BucketType <riak.bucket.BucketType>`
        """
        if not isinstance(name, string_types):
            raise TypeError('Bucket name must be a string')

        if name in self._bucket_types:
            return self._bucket_types[name]
        else:
            btype = BucketType(self, name)
            self._bucket_types[name] = btype
            return btype

    def close(self):
        """
        Iterate through all of the connections and close each one.
        """
        if self._http_pool is not None:
            self._http_pool.clear()
        if self._pb_pool is not None:
            self._pb_pool.clear()

    def _create_node(self, n):
        if isinstance(n, RiakNode):
            return n
        elif isinstance(n, tuple) and len(n) is 3:
            host, http_port, pb_port = n
            return RiakNode(host=host, http_port=http_port, pb_port=pb_port)
        elif isinstance(n, dict):
            return RiakNode(**n)
        else:
            raise TypeError("%s is not a valid node configuration" % repr(n))

    def _create_credentials(self, n):
        """
        Create security credentials, if necessary.
        """
        if not n:
            return n
        elif isinstance(n, SecurityCreds):
            return n
        elif isinstance(n, dict):
            return SecurityCreds(**n)
        else:
            raise TypeError("%s is not a valid security configuration" %
                            repr(n))

    def _choose_node(self, nodes=None):
        """
        Chooses a random node from the list of nodes in the client,
        taking into account each node's recent error rate.
        :rtype RiakNode
        """
        if not nodes:
            nodes = self.nodes

        # Prefer nodes which have gone a reasonable time without
        # errors
        def _error_rate(node):
            return node.error_rate.value()

        good = [n for n in nodes if _error_rate(n) < 0.1]

        if len(good) is 0:
            # Fall back to a minimally broken node
            return min(nodes, key=_error_rate)
        else:
            return random.choice(good)

    @lazy_property
    def _multiget_pool(self):
        if self._multiget_pool_size:
            return MultiGetPool(self._multiget_pool_size)
        else:
            return None

    def __hash__(self):
        return hash(
            frozenset([(n.host, n.http_port, n.pb_port) for n in self.nodes]))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return hash(self) == hash(other)
        else:
            return False

    def __ne__(self, other):
        if isinstance(other, self.__class__):
            return hash(self) != hash(other)
        else:
            return True
Exemplo n.º 7
0
class RiakClient(RiakMapReduceChain, RiakClientOperations):
    """
    The ``RiakClient`` object holds information necessary to connect
    to Riak. Requests can be made to Riak directly through the client
    or by using the methods on related objects.
    """

    #: The supported protocols
    PROTOCOLS = ['http', 'pbc']

    def __init__(self, protocol='http', transport_options={}, nodes=None,
                 credentials=None, **unused_args):
        """
        Construct a new ``RiakClient`` object.

        :param protocol: the preferred protocol, defaults to 'http'
        :type protocol: string
        :param nodes: a list of node configurations,
           where each configuration is a dict containing the keys
           'host', 'http_port', and 'pb_port'
        :type nodes: list
        :param transport_options: Optional key-value args to pass to
                                  the transport constructor
        :type transport_options: dict
        :param credentials: optional object of security info
        :type credentials: SecurityCreds or dict
        """
        unused_args = unused_args.copy()

        if 'port' in unused_args:
            deprecated("port option is deprecated, use http_port or pb_port,"
                       " or the nodes option. Your given port of %r will be "
                       "used as the %s port unless already set" %
                       (unused_args['port'], protocol))
            unused_args['already_warned_port'] = True
            if (protocol == 'http' and
                    'http_port' not in unused_args):
                unused_args['http_port'] = unused_args['port']
            elif protocol == 'pbc' and 'pb_port' not in unused_args:
                unused_args['pb_port'] = unused_args['port']

        if 'transport_class' in unused_args:
            deprecated(
                "transport_class is deprecated, use the protocol option")

        if nodes is None:
            self.nodes = [self._create_node(unused_args), ]
        else:
            self.nodes = [self._create_node(n) for n in nodes]

        self.protocol = protocol or 'http'
        self.resolver = default_resolver
        self._credentials = self._create_credentials(credentials)
        self._http_pool = RiakHttpPool(self, **transport_options)
        self._pb_pool = RiakPbcPool(self, **transport_options)

        self._encoders = {'application/json': default_encoder,
                          'text/json': default_encoder,
                          'text/plain': str}
        self._decoders = {'application/json': json.loads,
                          'text/json': json.loads,
                          'text/plain': str}
        self._buckets = WeakValueDictionary()
        self._bucket_types = WeakValueDictionary()

    def _get_protocol(self):
        return self._protocol

    def _set_protocol(self, value):
        if value not in self.PROTOCOLS:
            raise ValueError("protocol option is invalid, must be one of %s" %
                             repr(self.PROTOCOLS))
        self._protocol = value

    protocol = property(_get_protocol, _set_protocol,
                        doc="""
                        Which protocol to prefer, one of
                        :attr:`PROTOCOLS
                        <riak.client.RiakClient.PROTOCOLS>`. Please
                        note that when one protocol is selected, the
                        other protocols MAY NOT attempt to connect.
                        Changing to another protocol will cause a
                        connection on the next request.

                        Some requests are only valid over ``'http'``,
                        and will always be sent via
                        those transports, regardless of which protocol
                        is preferred.
                         """)

    def get_transport(self):
        """
        Get the transport instance the client is using for it's
        connection.

        .. deprecated:: 2.0.0
           There is no equivalent to this method, it will return ``None``.
        """
        deprecated("get_transport is deprecated, use client, " +
                   "bucket, or object methods instead")
        return None

    def get_client_id(self):
        """
        Get the client identifier.

        .. deprecated:: 2.0.0
           Use the :attr:`client_id` attribute instead.

        :rtype: string
        """
        deprecated(
            "``get_client_id`` is deprecated, use the ``client_id`` property")
        return self.client_id

    def set_client_id(self, client_id):
        """
        Set the client identifier.

        .. deprecated:: 2.0.0
           Use the :attr:`client_id` attribute instead.

        :param client_id: The new client_id.
        :type client_id: string
        """
        deprecated(
            "``set_client_id`` is deprecated, use the ``client_id`` property")
        self.client_id = client_id
        return self

    def _get_client_id(self):
        with self._transport() as transport:
            return transport.client_id

    def _set_client_id(self, client_id):
        for http in self._http_pool:
            http.client_id = client_id
        for pb in self._pb_pool:
            pb.client_id = client_id

    client_id = property(_get_client_id, _set_client_id,
                         doc="""The client ID for this client instance""")

    def get_encoder(self, content_type):
        """
        Get the encoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :rtype: function
        """
        return self._encoders.get(content_type)

    def set_encoder(self, content_type, encoder):
        """
        Set the encoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :param encoder: an encoding function, takes a single object
            argument and returns a string
        :type encoder: function
        """
        self._encoders[content_type] = encoder

    def get_decoder(self, content_type):
        """
        Get the decoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :rtype: function
        """
        return self._decoders.get(content_type)

    def set_decoder(self, content_type, decoder):
        """
        Set the decoding function for the provided content type.

        :param content_type: the requested media type
        :type content_type: str
        :param decoder: a decoding function, takes a string and
            returns a Python type
        :type decoder: function
        """
        self._decoders[content_type] = decoder

    def bucket(self, name, bucket_type='default'):
        """
        Get the bucket by the specified name. Since buckets always exist,
        this will always return a :class:`RiakBucket <riak.bucket.RiakBucket>`.

        :param name: the bucket name
        :type name: str
        :param bucket_type: the parent bucket-type
        :type bucket_type: :class:`BucketType <riak.bucket.BucketType>` or str
        :rtype: :class:`RiakBucket <riak.bucket.RiakBucket>`
        """
        if not isinstance(name, basestring):
            raise TypeError('Bucket name must be a string')

        if isinstance(bucket_type, basestring):
            bucket_type = self.bucket_type(bucket_type)
        elif not isinstance(bucket_type, BucketType):
            raise TypeError('bucket_type must be a string '
                            'or riak.bucket.BucketType')

        if (bucket_type, name) in self._buckets:
            return self._buckets[(bucket_type, name)]
        else:
            bucket = RiakBucket(self, name, bucket_type)
            self._buckets[(bucket_type, name)] = bucket
            return bucket

    def bucket_type(self, name):
        """
        Gets the bucket-type by the specified name. Bucket-types do
        not always exist (unlike buckets), but this will always return
        a :class:`BucketType <riak.bucket.BucketType>` object.

        :param name: the bucket name
        :type name: str
        :rtype: :class:`BucketType <riak.bucket.BucketType>`
        """
        if not isinstance(name, basestring):
            raise TypeError('Bucket name must be a string')

        if name in self._bucket_types:
            return self._bucket_types[name]
        else:
            btype = BucketType(self, name)
            self._bucket_types[name] = btype
            return btype

    @lazy_property
    def solr(self):
        """
        Returns a RiakSearch object which can access search indexes.

        .. deprecated:: 2.0.0
           Use the ``fulltext_*`` methods instead.
        """
        deprecated("``solr`` is deprecated, use ``fulltext_search``,"
                   " ``fulltext_add`` and ``fulltext_delete`` directly")
        return RiakSearch(self)

    def close(self):
        """
        Iterate through all of the connections and close each one.
        """
        if self._http_pool is not None:
            self._http_pool.clear()
        if self._pb_pool is not None:
            self._pb_pool.clear()

    def _create_node(self, n):
        if isinstance(n, RiakNode):
            return n
        elif isinstance(n, tuple) and len(n) is 3:
            host, http_port, pb_port = n
            return RiakNode(host=host,
                            http_port=http_port,
                            pb_port=pb_port)
        elif isinstance(n, dict):
            return RiakNode(**n)
        else:
            raise TypeError("%s is not a valid node configuration"
                            % repr(n))

    def _create_credentials(self, n):
        """
        Create security credentials, if necessary.
        """
        if not n:
            return n
        elif isinstance(n, SecurityCreds):
            return n
        elif isinstance(n, dict):
            return SecurityCreds(**n)
        else:
            raise TypeError("%s is not a valid security configuration"
                            % repr(n))

    def _choose_node(self, nodes=None):
        """
        Chooses a random node from the list of nodes in the client,
        taking into account each node's recent error rate.
        :rtype RiakNode
        """
        if not nodes:
            nodes = self.nodes

        # Prefer nodes which have gone a reasonable time without
        # errors
        def _error_rate(node):
            return node.error_rate.value()

        good = [n for n in nodes if _error_rate(n) < 0.1]

        if len(good) is 0:
            # Fall back to a minimally broken node
            return min(nodes, key=_error_rate)
        else:
            return random.choice(good)

    def __hash__(self):
        return hash(frozenset([(n.host, n.http_port, n.pb_port)
                               for n in self.nodes]))

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return hash(self) == hash(other)
        else:
            return False

    def __ne__(self, other):
        if isinstance(other, self.__class__):
            return hash(self) != hash(other)
        else:
            return True