<filename>couchbase_v2/bucket.py
#
# Copyright 2013, Couchbase, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import couchbase_core._libcouchbase as _LCB
from couchbase_core._libcouchbase import Bucket as _Base

import couchbase_v2
from couchbase_core.client import Client as CoreClient
from couchbase_core import _depr
from couchbase_core.result import *
from couchbase_core.bucketmanager import BucketManager

from typing import *


class Pipeline(object):
    def __init__(self, parent):
        """
        .. versionadded:: 1.2.0

        Creates a new pipeline context. See :meth:`~Bucket.pipeline`
        for more details
        """
        self._parent = parent
        self._results = None

    def __enter__(self):
        self._parent._pipeline_begin()

    def __exit__(self, *args):
        self._results = self._parent._pipeline_end()
        return False

    @property
    def results(self):
        """
        Contains a list of results for each pipelined operation
        executed within the context. The list remains until this
        context is reused.

        The elements in the list are either :class:`.Result`
        objects (for single operations) or :class:`.MultiResult`
        objects (for multi operations)
        """
        return self._results


class DurabilityContext(object):
    def __init__(self, parent, persist_to=-1, replicate_to=-1, timeout=0.0):
        self._parent = parent
        self._new = {
            '_dur_persist_to': persist_to,
            '_dur_replicate_to': replicate_to,
            '_dur_timeout': int(timeout * 1000000)
        }
        self._old = {}

    def __enter__(self):
        for k, v in self._new.items():
            self._old[k] = getattr(self._parent, k)
            setattr(self._parent, k, v)

    def __exit__(self, *args):
        for k, v in self._old.items():
            setattr(self._parent, k, v)
        return False


class Bucket(CoreClient):
    _MEMCACHED_OPERATIONS = CoreClient._MEMCACHED_OPERATIONS+('endure',
                                                   'observe', 'rget', 'set', 'add', 'delete', 'counter','lock', 'append', 'prepend')

    def pipeline(self):
        """
        Returns a new :class:`Pipeline` context manager. When the
        context manager is active, operations performed will return
        ``None``, and will be sent on the network when the context
        leaves (in its ``__exit__`` method). To get the results of the
        pipelined operations, inspect the :attr:`Pipeline.results`
        property.

        Operational errors (i.e. negative replies from the server, or
        network errors) are delivered when the pipeline exits, but
        argument errors are thrown immediately.

        :return: a :class:`Pipeline` object
        :raise: :exc:`.PipelineException` if a pipeline is already created
        :raise: Other operation-specific errors.

        Scheduling multiple operations, without checking results::

          with cb.pipeline():
            cb.upsert("key1", "value1")
            cb.counter("counter")
            cb.upsert_multi({
              "new_key1" : "new_value_1",
              "new_key2" : "new_value_2"
            })

        Retrieve the results for several operations::

          pipeline = cb.pipeline()
          with pipeline:
            cb.upsert("foo", "bar")
            cb.replace("something", "value")

          for result in pipeline.results:
            print("Pipeline result: CAS {0}".format(result.cas))

        .. note::

          When in pipeline mode, you cannot execute view queries.
          Additionally, pipeline mode is not supported on async handles

        .. warning::

          Pipeline mode should not be used if you are using the same
          object concurrently from multiple threads. This only refers
          to the internal lock within the object itself. It is safe
          to use if you employ your own locking mechanism (for example
          a connection pool)

        .. versionadded:: 1.2.0

        """
        return Pipeline(self)

    # We have these wrappers so that IDEs can do param tooltips and the
    # like. we might move this directly into C some day

    def upsert(self, key, value, cas=0, ttl=0, format=None,
               persist_to=0, replicate_to=0):
        # type: (Union[str, bytes], Any, int, int, int, int, int, int) -> Result
        """Unconditionally store the object in Couchbase.

        :param key:
            The key to set the value with. By default, the key must be
            either a :class:`bytes` or :class:`str` object encodable as
            UTF-8. If a custom `transcoder` class is used (see
            :meth:`~__init__`), then the key object is passed directly
            to the transcoder, which may serialize it how it wishes.
        :type key: string or bytes

        :param value: The value to set for the key.
            This should be a native Python value which will be transparently
            serialized to JSON by the library. Do not pass already-serialized
            JSON as the value or it will be serialized again.

            If you are using a different `format` setting (see `format`
            parameter), and/or a custom transcoder then value for this
            argument may need to conform to different criteria.

        :param int cas: The _CAS_ value to use. If supplied, the value
            will only be stored if it already exists with the supplied
            CAS

        :param int ttl: If specified, the key will expire after this
            many seconds

        :param int format: If specified, indicates the `format` to use
            when encoding the value. If none is specified, it will use
            the `default_format` For more info see
            :attr:`~.default_format`

        :param int persist_to:
            Perform durability checking on this many nodes nodes for
            persistence to disk. See :meth:`endure` for more information

        :param int replicate_to: Perform durability checking on this
            many replicas for presence in memory. See :meth:`endure` for
            more information.

        :raise: :exc:`.InvalidArgumentException` if an argument is supplied that is
            not applicable in this context. For example setting the CAS
            as a string.
        :raise: :exc`.CouchbaseNetworkException`
        :raise: :exc:`.DocumentExistsException` if the key already exists on the
            server with a different CAS value.
        :raise: :exc:`.ValueFormatException` if the value cannot be
            serialized with chosen encoder, e.g. if you try to store a
            dictionary in plain mode.
        :return: :class:`~.Result`.

        Simple set::

            cb.upsert('key', 'value')

        Force JSON document format for value::

            cb.upsert('foo', {'bar': 'baz'}, format=couchbase_v2.FMT_JSON)

        Insert JSON from a string::

            JSONstr = '{"key1": "value1", "key2": 123}'
            JSONobj = json.loads(JSONstr)
            cb.upsert("documentID", JSONobj, format=couchbase_v2.FMT_JSON)

        Force UTF8 document format for value::

            cb.upsert('foo', "<xml></xml>", format=couchbase_v2.FMT_UTF8)

        Perform optimistic locking by specifying last known CAS version::

            cb.upsert('foo', 'bar', cas=8835713818674332672)

        Several sets at the same time (mutli-set)::

            cb.upsert_multi({'foo': 'bar', 'baz': 'value'})

        .. seealso:: :meth:`upsert_multi`
        """
        pass

    def upsert(self, key, value, *args, **kwargs):
        return _Base.upsert(self, key, value, *args, **kwargs)

    @overload
    def insert(self, key, value, ttl=0, format=None, persist_to=0, replicate_to=0):
        """Store an object in Couchbase unless it already exists.

        Follows the same conventions as :meth:`upsert` but the value is
        stored only if it does not exist already. Conversely, the value
        is not stored if the key already exists.

        Notably missing from this method is the `cas` parameter, this is
        because `insert` will only succeed if a key does not already
        exist on the server (and thus can have no CAS)

        :raise: :exc:`.DocumentExistsException` if the key already exists

        .. seealso:: :meth:`upsert`, :meth:`insert_multi`
        """
        pass

    def insert(self, key, value, *args, **kwargs):
        return _Base.insert(self, key, value, *args, **kwargs)

    @overload
    def replace(self, key, value, cas=0, ttl=0, format=None,
                persist_to=0, replicate_to=0):
        """Store an object in Couchbase only if it already exists.

        Follows the same conventions as :meth:`upsert`, but the value is
        stored only if a previous value already exists.

        :raise: :exc:`.DocumentNotFoundException` if the key does not exist

        .. seealso:: :meth:`upsert`, :meth:`replace_multi`
        """
        pass

    def replace(self, key, value, *args, **kwargs):
        return _Base.replace(self, key, value, *args, **kwargs)

    def append(self,
               key,  # type: str
               value,  # type: couchbase_v2.JSON
               cas=0,  # type: long
               format=None,  # type: long
               persist_to=0,  # type: int
               replicate_to=0  # type: int
               ):
        # type: (...) -> couchbase_v2.result.Result
        """Append a string to an existing value in Couchbase.

        :param string value: The data to append to the existing value.

        Other parameters follow the same conventions as :meth:`upsert`.

        The `format` argument must be one of
        :const:`~couchbase_v2.FMT_UTF8` or :const:`~couchbase_v2.FMT_BYTES`.
        If not specified, it will be :const:`~.FMT_UTF8` (overriding the
        :attr:`default_format` attribute). This is because JSON or
        Pickle formats will be nonsensical when random data is appended
        to them. If you wish to modify a JSON or Pickle encoded object,
        you will need to retrieve it (via :meth:`get`), modify it, and
        then store it again (using :meth:`upsert`).

        Additionally, you must ensure the value (and flags) for the
        current value is compatible with the data to be appended. For an
        example, you may append a :const:`~.FMT_BYTES` value to an
        existing :const:`~couchbase_v2.FMT_JSON` value, but an error will
        be thrown when retrieving the value using :meth:`get` (you may
        still use the :attr:`data_passthrough` to overcome this).

        :raise: :exc:`.NotStoredException` if the key does not exist

        .. seealso:: :meth:`upsert`, :meth:`append_multi`
        """
        pass

    def append(self, key, value, *args, **kwargs):
        return _Base.append(self, key, value, *args, **kwargs)

    def prepend(self, key, value, cas=0, format=None,
                persist_to=0, replicate_to=0):
        # type: (...) -> Result
        """Prepend a string to an existing value in Couchbase.

        .. seealso:: :meth:`append`, :meth:`prepend_multi`
        """

    def prepend(self, key, value, *args, **kwargs):
        return _Base.prepend(self, key, value, *args, **kwargs)

    def get(self,  # type: Bucket
            key,  # type: str
            ttl=0,  # type: int
            quiet=None,  # type: bool
            replica=False,  # type: bool
            no_format=False  # type: bool
            ):
        # type: (...) -> couchbase_v2.result.Result
        """Obtain an object stored in Couchbase by given key.

        :param string key: The key to fetch. The type of key is the same
            as mentioned in :meth:`upsert`

        :param int ttl: If specified, indicates that the key's expiry
            time should be *modified* when retrieving the value.

        :param boolean quiet: causes `get` to return None instead of
            raising an exception when the key is not found. It defaults
            to the value set by :attr:`~quiet` on the instance. In
            `quiet` mode, the error may still be obtained by inspecting
            the :attr:`~.Result.rc` attribute of the :class:`.Result`
            object, or checking :attr:`.Result.success`.

            Note that the default value is `None`, which means to use
            the :attr:`quiet`. If it is a boolean (i.e. `True` or
            `False`) it will override the `couchbase_v2.bucket.Bucket`-level
            :attr:`quiet` attribute.

        :param bool replica: Whether to fetch this key from a replica
            rather than querying the master server. This is primarily
            useful when operations with the master fail (possibly due to
            a configuration change). It should normally be used in an
            exception handler like so

            Using the ``replica`` option::

                try:
                    res = c.get("key", quiet=True) # suppress not-found errors
                catch CouchbaseException:
                    res = c.get("key", replica=True, quiet=True)

        :param bool no_format: If set to ``True``, then the value will
            always be delivered in the :class:`~couchbase_v2.result.Result`
            object as being of :data:`~couchbase_v2.FMT_BYTES`. This is a
            item-local equivalent of using the :attr:`data_passthrough`
            option

        :raise: :exc:`.DocumentNotFoundException` if the key does not exist
        :raise: :exc:`.CouchbaseNetworkException`
        :raise: :exc:`.ValueFormatException` if the value cannot be
            deserialized with chosen decoder, e.g. if you try to
            retreive an object stored with an unrecognized format
        :return: A :class:`~.Result` object

        Simple get::

            value = cb.get('key').value

        Get multiple values::

            cb.get_multi(['foo', 'bar'])
            # { 'foo' : <Result(...)>, 'bar' : <Result(...)> }

        Inspect the flags::

            rv = cb.get("key")
            value, flags, cas = rv.value, rv.flags, rv.cas

        Update the expiry time::

            rv = cb.get("key", ttl=10)
            # Expires in ten seconds

        .. seealso:: :meth:`get_multi`
        """

        return _Base.get(self, key, ttl=ttl, quiet=quiet,
                         replica=replica, no_format=no_format)

    def touch(self, key, ttl=0):
        """Update a key's expiry time

        :param string key: The key whose expiry time should be
            modified
        :param int ttl: The new expiry time. If the expiry time
            is `0` then the key never expires (and any existing
            expiry is removed)
        :return: :class:`.OperationResult`

        Update the expiry time of a key ::

            cb.upsert("key", ttl=100)
            # expires in 100 seconds
            cb.touch("key", ttl=0)
            # key should never expire now

        :raise: The same things that :meth:`get` does

        .. seealso:: :meth:`get` - which can be used to get *and* update the
            expiry, :meth:`touch_multi`
        """
        return _Base.touch(self, key, ttl=ttl)

    def lock(self, key, ttl=0):
        """Lock and retrieve a key-value entry in Couchbase.

        :param key: A string which is the key to lock.

        :param ttl: a TTL for which the lock should be valid.
            While the lock is active, attempts to access the key (via
            other :meth:`lock`, :meth:`upsert` or other mutation calls)
            will fail with an :exc:`.DocumentExistsException`. Note that the
            value for this option is limited by the maximum allowable
            lock time determined by the server (currently, this is 30
            seconds). If passed a higher value, the server will silently
            lower this to its maximum limit.


        This function otherwise functions similarly to :meth:`get`;
        specifically, it will return the value upon success. Note the
        :attr:`~.Result.cas` value from the :class:`.Result` object.
        This will be needed to :meth:`unlock` the key.

        Note the lock will also be implicitly released if modified by
        one of the :meth:`upsert` family of functions when the valid CAS
        is supplied

        :raise: :exc:`.TemporaryFailException` if the key is already locked.
        :raise: See :meth:`get` for possible exceptions

        Lock a key ::

            rv = cb.lock("locked_key", ttl=5)
            # This key is now locked for the next 5 seconds.
            # attempts to access this key will fail until the lock
            # is released.

            # do important stuff...

            cb.unlock("locked_key", rv.cas)

        Lock a key, implicitly unlocking with :meth:`upsert` with CAS ::

            rv = self.cb.lock("locked_key", ttl=5)
            new_value = rv.value.upper()
            cb.upsert("locked_key", new_value, rv.cas)


        Poll and Lock ::

            rv = None
            begin_time = time.time()
            while time.time() - begin_time < 15:
                try:
                    rv = cb.lock("key", ttl=10)
                    break
                except TemporaryFailException:
                    print("Key is currently locked.. waiting")
                    time.sleep(1)

            if not rv:
                raise Exception("Waited too long..")

            # Do stuff..

            cb.unlock("key", rv.cas)


        .. seealso:: :meth:`get`, :meth:`lock_multi`, :meth:`unlock`
        """
        return _Base.lock(self, key, ttl=ttl)

    def unlock(self, key, cas):
        """Unlock a Locked Key in Couchbase.

        This unlocks an item previously locked by :meth:`lock`

        :param key: The key to unlock
        :param cas: The cas returned from :meth:`lock`'s
            :class:`.Result` object.

        See :meth:`lock` for an example.

        :raise: :exc:`.TemporaryFailException` if the CAS supplied does not
            match the CAS on the server (possibly because it was
            unlocked by previous call).

        .. seealso:: :meth:`lock` :meth:`unlock_multi`
        """
        return _Base.unlock(self, key, cas=cas)

    def remove(self, key, cas=0, quiet=None, persist_to=0, replicate_to=0):
        """Remove the key-value entry for a given key in Couchbase.

        :param key: A string which is the key to remove. The format and
            type of the key follows the same conventions as in
            :meth:`upsert`
        :type key: string, dict, or tuple/list

        :param int cas: The CAS to use for the removal operation.
            If specified, the key will only be removed from the server
            if it has the same CAS as specified. This is useful to
            remove a key only if its value has not been changed from the
            version currently visible to the client. If the CAS on the
            server does not match the one specified, an exception is
            thrown.
        :param boolean quiet:
            Follows the same semantics as `quiet` in :meth:`get`
        :param int persist_to: If set, wait for the item to be removed
            from the storage of at least these many nodes
        :param int replicate_to: If set, wait for the item to be removed
            from the cache of at least these many nodes
            (excluding the master)
        :raise: :exc:`.DocumentNotFoundException` if the key does not exist.
        :raise: :exc:`.DocumentExistsException` if a CAS was specified, but
            the CAS on the server had changed
        :return: A :class:`~.Result` object.

        Simple remove::

            ok = cb.remove("key").success

        Don't complain if key does not exist::

            ok = cb.remove("key", quiet=True)

        Only remove if CAS matches our version::

            rv = cb.get("key")
            cb.remove("key", cas=rv.cas)

        Remove multiple keys::

            oks = cb.remove_multi(["key1", "key2", "key3"])

        Remove multiple keys with CAS::

            oks = cb.remove({
                "key1" : cas1,
                "key2" : cas2,
                "key3" : cas3
            })

        .. seealso:: :meth:`remove_multi`, :meth:`endure`
            for more information on the ``persist_to`` and
            ``replicate_to`` options.
        """
        return _Base.remove(self, key, cas=cas, quiet=quiet,
                            persist_to=persist_to, replicate_to=replicate_to)

    def counter(self, key, delta=1, initial=None, ttl=0):
        """Increment or decrement the numeric value of an item.

        This method instructs the server to treat the item stored under
        the given key as a numeric counter.

        Counter operations require that the stored value
        exists as a string representation of a number (e.g. ``123``). If
        storing items using the :meth:`upsert` family of methods, and
        using the default :const:`couchbase_v2.FMT_JSON` then the value
        will conform to this constraint.

        :param string key: A key whose counter value is to be modified
        :param int delta: an amount by which the key should be modified.
            If the number is negative then this number will be
            *subtracted* from the current value.
        :param initial: The initial value for the key, if it does not
            exist. If the key does not exist, this value is used, and
            `delta` is ignored. If this parameter is `None` then no
            initial value is used
        :type initial: int or `None`
        :param int ttl: The lifetime for the key, after which it will
            expire
        :raise: :exc:`.DocumentNotFoundException` if the key does not exist on the
            bucket (and `initial` was `None`)
        :raise: :exc:`.DeltaBadvalException` if the key exists, but the
            existing value is not numeric
        :return: A :class:`.Result` object. The current value of the
            counter may be obtained by inspecting the return value's
            `value` attribute.

        Simple increment::

            rv = cb.counter("key")
            rv.value
            # 42

        Increment by 10::

            rv = cb.counter("key", delta=10)

        Decrement by 5::

            rv = cb.counter("key", delta=-5)

        Increment by 20, set initial value to 5 if it does not exist::

            rv = cb.counter("key", delta=20, initial=5)

        Increment three keys::

            kv = cb.counter_multi(["foo", "bar", "baz"])
            for key, result in kv.items():
                print "Key %s has value %d now" % (key, result.value)

        .. seealso:: :meth:`counter_multi`
        """
        return _Base.counter(self, key, delta=delta, initial=initial, ttl=ttl)

    def lookup_in(self, key, *specs, **kwargs):
        """Atomically retrieve one or more paths from a document.

        :param key: The key of the document to lookup
        :param spec: A list of specs (see :mod:`.couchbase_core.subdocument`)
        :return: A :class:`.couchbase_core.result.SubdocResult` object.
            This object contains the results and any errors of the
            operation.

        Example::

            import couchbase_core.subdocument as SD
            rv = cb.lookup_in('user',
                              SD.get('email'),
                              SD.get('name'),
                              SD.exists('friends.therock'))

            email = rv[0]
            name = rv[1]
            friend_exists = rv.exists(2)

        .. seealso:: :meth:`retrieve_in` which acts as a convenience wrapper
        """
        return super(Bucket, self).lookup_in(key,specs,**kwargs)

    def mutate_in(self, key, *specs, **kwargs):
        """Perform multiple atomic modifications within a document.

        :param key: The key of the document to modify
        :param specs: A list of specs (See :mod:`.couchbase_core.subdocument`)
        :param bool create_doc:
            Whether the document should be create if it doesn't exist
        :param bool insert_doc: If the document should be created anew, and the
            operations performed *only* if it does not exist.
        :param bool upsert_doc: If the document should be created anew if it
            does not exist. If it does exist the commands are still executed.
        :param kwargs: CAS, etc.
        :return: A :class:`~.couchbase_core.result.SubdocResult` object.

        Here's an example of adding a new tag to a "user" document
        and incrementing a modification counter::

            import couchbase_core.subdocument as SD
            # ....
            cb.mutate_in('user',
                         SD.array_addunique('tags', 'dog'),
                         SD.counter('updates', 1))

        .. note::

            The `insert_doc` and `upsert_doc` options are mutually exclusive.
            Use `insert_doc` when you wish to create a new document with
            extended attributes (xattrs).

        .. seealso:: :mod:`.couchbase_core.subdocument`
        """
        return super(Bucket, self).mutate_in(key, specs, **kwargs)

    def retrieve_in(self, key, *paths, **kwargs):
        """Atomically fetch one or more paths from a document.

        Convenience method for retrieval operations. This functions
        identically to :meth:`lookup_in`. As such, the following two
        forms are equivalent:

        .. code-block:: python

            import couchbase_v2.subdocument as SD
            rv = cb.lookup_in(key,
                              SD.get('email'),
                              SD.get('name'),
                              SD.get('friends.therock')

            email, name, friend = rv

        .. code-block:: python

            rv = cb.retrieve_in(key, 'email', 'name', 'friends.therock')
            email, name, friend = rv

        .. seealso:: :meth:`lookup_in`
        """
        import couchbase_core.subdocument as SD
        return self.lookup_in(key, *tuple(SD.get(x) for x in paths), **kwargs)

    def incr(self, key, amount=1, **kwargs):
        _depr('incr', 'counter')
        return self.counter(key, delta=amount, **kwargs)

    def incr_multi(self, keys, amount=1, **kwargs):
        _depr('incr_multi', 'counter_multi')
        return self.counter_multi(keys, delta=amount, **kwargs)

    def decr(self, key, amount=1, **kwargs):
        _depr('decr', 'counter')
        return self.counter(key, delta=-amount, **kwargs)

    def decr_multi(self, keys, amount=1, **kwargs):
        _depr('decr_multi', 'counter_multi')
        return self.counter_multi(keys, delta=-amount, **kwargs)

    def stats(self, keys=None, keystats=False):
        """Request server statistics.

        Fetches stats from each node in the cluster. Without a key
        specified the server will respond with a default set of
        statistical information. It returns the a `dict` with stats keys
        and node-value pairs as a value.

        :param keys: One or several stats to query
        :type keys: string or list of string
        :raise: :exc:`.CouchbaseNetworkException`
        :return: `dict` where keys are stat keys and values are
            host-value pairs

        Find out how many items are in the bucket::

            total = 0
            for key, value in cb.stats()['total_items'].items():
                total += value

        Get memory stats (works on couchbase buckets)::

            cb.stats('memory')
            # {'mem_used': {...}, ...}
        """
        if keys and not isinstance(keys, (tuple, list)):
            keys = (keys,)
        return self._stats(keys, keystats=keystats)

    def observe(self, key, master_only=False):
        """Return storage information for a key.

        It returns a :class:`.ValueResult` object with the ``value``
        field set to a list of :class:`~.ObserveInfo` objects. Each
        element in the list responds to the storage status for the key
        on the given node. The length of the list (and thus the number
        of :class:`~.ObserveInfo` objects) are equal to the number of
        online replicas plus the master for the given key.

        :param string key: The key to inspect
        :param bool master_only: Whether to only retrieve information
            from the master node.

        .. seealso:: :ref:`observe_info`
        """
        return _Base.observe(self, key, master_only=master_only)

    def endure(self, key, persist_to=-1, replicate_to=-1, cas=0,
               check_removed=False, timeout=5.0, interval=0.010):
        """Wait until a key has been distributed to one or more nodes

        By default, when items are stored to Couchbase, the operation is
        considered successful if the vBucket master (i.e. the "primary"
        node) for the key has successfully stored the item in its
        memory.

        In most situations, this is sufficient to assume that the item
        has successfully been stored. However the possibility remains
        that the "master" server will go offline as soon as it sends
        back the successful response and the data is lost.

        The ``endure`` function allows you to provide stricter criteria
        for success. The criteria may be expressed in terms of number of
        nodes for which the item must exist in that node's RAM and/or on
        that node's disk. Ensuring that an item exists in more than one
        place is a safer way to guarantee against possible data loss.

        We call these requirements `Durability Constraints`, and thus
        the method is called `endure`.

        :param string key: The key to endure.
        :param int persist_to: The minimum number of nodes which must
            contain this item on their disk before this function
            returns. Ensure that you do not specify too many nodes;
            otherwise this function will fail. Use the
            :attr:`server_nodes` to determine how many nodes exist in
            the cluster.

            The maximum number of nodes an item can reside on is
            currently fixed to 4 (i.e. the "master" node, and up to
            three "replica" nodes). This limitation is current as of
            Couchbase Server version 2.1.0.

            If this parameter is set to a negative value, the maximum
            number of possible nodes the key can reside on will be used.

        :param int replicate_to: The minimum number of replicas which
            must contain this item in their memory for this method to
            succeed. As with ``persist_to``, you may specify a negative
            value in which case the requirement will be set to the
            maximum number possible.

        :param float timeout: A timeout value in seconds before this
            function fails with an exception. Typically it should take
            no longer than several milliseconds on a functioning cluster
            for durability requirements to be satisfied (unless
            something has gone wrong).

        :param float interval: The polling interval in seconds to use
            for checking the key status on the respective nodes.
            Internally, ``endure`` is implemented by polling each server
            individually to see if the key exists on that server's disk
            and memory. Once the status request is sent to all servers,
            the client will check if their replies are satisfactory; if
            they are then this function succeeds, otherwise the client
            will wait a short amount of time and try again. This
            parameter sets this "wait time".

        :param bool check_removed: This flag inverts the check. Instead
            of checking that a given key *exists* on the nodes, this
            changes the behavior to check that the key is *removed* from
            the nodes.

        :param long cas: The CAS value to check against. It is possible
            for an item to exist on a node but have a CAS value from a
            prior operation. Passing the CAS ensures that only replies
            from servers with a CAS matching this parameter are accepted.

        :return: A :class:`~.OperationResult`
        :raise: see :meth:`upsert` and :meth:`get` for possible errors

        .. seealso:: :meth:`upsert`, :meth:`endure_multi`
        """
        # We really just wrap 'endure_multi'
        kv = {key: cas}
        rvs = self.endure_multi(keys=kv, persist_to=persist_to,
                                replicate_to=replicate_to,
                                check_removed=check_removed, timeout=timeout,
                                interval=interval)
        return rvs[key]

    def durability(self, persist_to=-1, replicate_to=-1, timeout=0.0):
        """Returns a context manager which will apply the given
        persistence/replication settings to all mutation operations when
        active

        :param int persist_to:
        :param int replicate_to:

        See :meth:`endure` for the meaning of these two values

        Thus, something like::

          with cb.durability(persist_to=3):
            cb.upsert("foo", "foo_value")
            cb.upsert("bar", "bar_value")
            cb.upsert("baz", "baz_value")

        is equivalent to::

            cb.upsert("foo", "foo_value", persist_to=3)
            cb.upsert("bar", "bar_value", persist_to=3)
            cb.upsert("baz", "baz_value", persist_to=3)


        .. versionadded:: 1.2.0

        .. seealso:: :meth:`endure`
        """
        return DurabilityContext(self, persist_to, replicate_to, timeout)

    def bucket_manager(self):
        """
        Returns a :class:`~.BucketManager` object which may be used to
        perform management operations on the current bucket. These
        operations may create/modify design documents and flush the
        bucket
        """
        return BucketManager(self)

    n1ql_query = CoreClient.query
    query = CoreClient.view_query

    def _analytics_query(self, query, *args, **kwargs):
        # we used to take the ANALYTICS host as the second parameter, but
        # LCB now retrieves the hostname from the cluster
        return super(Bucket,self).analytics_query(query, *args, **kwargs)

    analytics_query = _analytics_query

    # "items" interface
    def append_items(self, items, **kwargs):
        """
        Method to append data to multiple :class:`~.Item` objects.

        This method differs from the normal :meth:`append_multi` in that
        each `Item`'s `value` field is updated with the appended data
        upon successful completion of the operation.


        :param items: The item dictionary. The value for each key should
            contain a ``fragment`` field containing the object to append
            to the value on the server.
        :type items: :class:`~couchbase_v2.items.ItemOptionDict`.

        The rest of the options are passed verbatim to
        :meth:`append_multi`

        .. seealso:: :meth:`append_multi`, :meth:`append`
        """
        rv = self.append_multi(items, **kwargs)
        # Assume this is an 'ItemOptionDict'
        for k, v in items.dict.items():
            if k.success:
                k.value += v['fragment']

        return rv

    def prepend_items(self, items, **kwargs):
        """Method to prepend data to multiple :class:`~.Item` objects.
        .. seealso:: :meth:`append_items`
        """
        rv = self.prepend_multi(items, **kwargs)
        for k, v in items.dict.items():
            if k.success:
                k.value = v['fragment'] + k.value

        return rv

    _OLDOPS = { 'set': 'upsert', 'add': 'insert', 'delete': 'remove'}
    for o, n in _OLDOPS.items():
        for variant in ('', '_multi'):
            oldname = o + variant
            newname = n + variant

            try:
                dst = locals()[n + variant]
            except KeyError:
                dst = getattr(_Base, n + variant)

            def mkmeth(oldname, newname, _dst):
                def _tmpmeth(self, *args, **kwargs):
                    _depr(oldname, newname)
                    return _dst(self, *args, **kwargs)
                return _tmpmeth

            locals().update({oldname: mkmeth(oldname, newname, dst)})

    """
    Lists the names of all the memcached operations. This is useful
    for classes which want to wrap all the methods
    """

    def design_get(self, *args, **kwargs):
        _depr('design_get', 'bucket_manager().design_get')
        return self.bucket_manager().design_get(*args, **kwargs)

    def design_create(self, *args, **kwargs):
        _depr('design_create', 'bucket_manager().design_create')
        return self.bucket_manager().design_create(*args, **kwargs)

    def design_publish(self, *args, **kwargs):
        _depr('design_publish', 'bucket_manager().design_publish')
        return self.bucket_manager().design_publish(*args, **kwargs)

    def design_delete(self, *args, **kwargs):
        _depr('design_delete', 'bucket_manager().design_delete')
        return self.bucket_manager().design_delete(*args, **kwargs)

<filename>couchbase_v2/bucket.py
#
# Copyright 2013, Couchbase, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import couchbase_core._libcouchbase as _LCB
from couchbase_core._libcouchbase import Bucket as _Base

import couchbase_v2
from couchbase_core.client import Client as CoreClient
from couchbase_core import _depr
from couchbase_core.result import *
from couchbase_core.bucketmanager import BucketManager

from typing import *


class Pipeline(object):
    def __init__(self, parent):
        """
        .. versionadded:: 1.2.0

        Creates a new pipeline context. See :meth:`~Bucket.pipeline`
        for more details
        """
        self._parent = parent
        self._results = None

    def __enter__(self):
        self._parent._pipeline_begin()

    def __exit__(self, *args):
        self._results = self._parent._pipeline_end()
        return False

    @property
    def results(self):
        """
        Contains a list of results for each pipelined operation
        executed within the context. The list remains until this
        context is reused.

        The elements in the list are either :class:`.Result`
        objects (for single operations) or :class:`.MultiResult`
        objects (for multi operations)
        """
        return self._results


class DurabilityContext(object):
    def __init__(self, parent, persist_to=-1, replicate_to=-1, timeout=0.0):
        self._parent = parent
        self._new = {
            '_dur_persist_to': persist_to,
            '_dur_replicate_to': replicate_to,
            '_dur_timeout': int(timeout * 1000000)
        }
        self._old = {}

    def __enter__(self):
        for k, v in self._new.items():
            self._old[k] = getattr(self._parent, k)
            setattr(self._parent, k, v)

    def __exit__(self, *args):
        for k, v in self._old.items():
            setattr(self._parent, k, v)
        return False


class Bucket(CoreClient):
    _MEMCACHED_OPERATIONS = CoreClient._MEMCACHED_OPERATIONS+('endure',
                                                   'observe', 'rget', 'set', 'add', 'delete', 'counter','lock', 'append', 'prepend')

    def pipeline(self):
        """
        Returns a new :class:`Pipeline` context manager. When the
        context manager is active, operations performed will return
        ``None``, and will be sent on the network when the context
        leaves (in its ``__exit__`` method). To get the results of the
        pipelined operations, inspect the :attr:`Pipeline.results`
        property.

        Operational errors (i.e. negative replies from the server, or
        network errors) are delivered when the pipeline exits, but
        argument errors are thrown immediately.

        :return: a :class:`Pipeline` object
        :raise: :exc:`.PipelineException` if a pipeline is already created
        :raise: Other operation-specific errors.

        Scheduling multiple operations, without checking results::

          with cb.pipeline():
            cb.upsert("key1", "value1")
            cb.counter("counter")
            cb.upsert_multi({
              "new_key1" : "new_value_1",
              "new_key2" : "new_value_2"
            })

        Retrieve the results for several operations::

          pipeline = cb.pipeline()
          with pipeline:
            cb.upsert("foo", "bar")
            cb.replace("something", "value")

          for result in pipeline.results:
            print("Pipeline result: CAS {0}".format(result.cas))

        .. note::

          When in pipeline mode, you cannot execute view queries.
          Additionally, pipeline mode is not supported on async handles

        .. warning::

          Pipeline mode should not be used if you are using the same
          object concurrently from multiple threads. This only refers
          to the internal lock within the object itself. It is safe
          to use if you employ your own locking mechanism (for example
          a connection pool)

        .. versionadded:: 1.2.0

        """
        return Pipeline(self)

    # We have these wrappers so that IDEs can do param tooltips and the
    # like. we might move this directly into C some day

    def upsert(self, key, value, cas=0, ttl=0, format=None,
               persist_to=0, replicate_to=0):
        # type: (Union[str, bytes], Any, int, int, int, int, int, int) -> Result
        """Unconditionally store the object in Couchbase.

        :param key:
            The key to set the value with. By default, the key must be
            either a :class:`bytes` or :class:`str` object encodable as
            UTF-8. If a custom `transcoder` class is used (see
            :meth:`~__init__`), then the key object is passed directly
            to the transcoder, which may serialize it how it wishes.
        :type key: string or bytes

        :param value: The value to set for the key.
            This should be a native Python value which will be transparently
            serialized to JSON by the library. Do not pass already-serialized
            JSON as the value or it will be serialized again.

            If you are using a different `format` setting (see `format`
            parameter), and/or a custom transcoder then value for this
            argument may need to conform to different criteria.

        :param int cas: The _CAS_ value to use. If supplied, the value
            will only be stored if it already exists with the supplied
            CAS

        :param int ttl: If specified, the key will expire after this
            many seconds

        :param int format: If specified, indicates the `format` to use
            when encoding the value. If none is specified, it will use
            the `default_format` For more info see
            :attr:`~.default_format`

        :param int persist_to:
            Perform durability checking on this many nodes nodes for
            persistence to disk. See :meth:`endure` for more information

        :param int replicate_to: Perform durability checking on this
            many replicas for presence in memory. See :meth:`endure` for
            more information.

        :raise: :exc:`.InvalidArgumentException` if an argument is supplied that is
            not applicable in this context. For example setting the CAS
            as a string.
        :raise: :exc`.CouchbaseNetworkException`
        :raise: :exc:`.DocumentExistsException` if the key already exists on the
            server with a different CAS value.
        :raise: :exc:`.ValueFormatException` if the value cannot be
            serialized with chosen encoder, e.g. if you try to store a
            dictionary in plain mode.
        :return: :class:`~.Result`.

        Simple set::

            cb.upsert('key', 'value')

        Force JSON document format for value::

            cb.upsert('foo', {'bar': 'baz'}, format=couchbase_v2.FMT_JSON)

        Insert JSON from a string::

            JSONstr = '{"key1": "value1", "key2": 123}'
            JSONobj = json.loads(JSONstr)
            cb.upsert("documentID", JSONobj, format=couchbase_v2.FMT_JSON)

        Force UTF8 document format for value::

            cb.upsert('foo', "<xml></xml>", format=couchbase_v2.FMT_UTF8)

        Perform optimistic locking by specifying last known CAS version::

            cb.upsert('foo', 'bar', cas=8835713818674332672)

        Several sets at the same time (mutli-set)::

            cb.upsert_multi({'foo': 'bar', 'baz': 'value'})

        .. seealso:: :meth:`upsert_multi`
        """
        pass

    def upsert(self, key, value, *args, **kwargs):
        return _Base.upsert(self, key, value, *args, **kwargs)

    @overload
    def insert(self, key, value, ttl=0, format=None, persist_to=0, replicate_to=0):
        """Store an object in Couchbase unless it already exists.

        Follows the same conventions as :meth:`upsert` but the value is
        stored only if it does not exist already. Conversely, the value
        is not stored if the key already exists.

        Notably missing from this method is the `cas` parameter, this is
        because `insert` will only succeed if a key does not already
        exist on the server (and thus can have no CAS)

        :raise: :exc:`.DocumentExistsException` if the key already exists

        .. seealso:: :meth:`upsert`, :meth:`insert_multi`
        """
        pass

    def insert(self, key, value, *args, **kwargs):
        return _Base.insert(self, key, value, *args, **kwargs)

    @overload
    def replace(self, key, value, cas=0, ttl=0, format=None,
                persist_to=0, replicate_to=0):
        """Store an object in Couchbase only if it already exists.

        Follows the same conventions as :meth:`upsert`, but the value is
        stored only if a previous value already exists.

        :raise: :exc:`.DocumentNotFoundException` if the key does not exist

        .. seealso:: :meth:`upsert`, :meth:`replace_multi`
        """
        pass

    def replace(self, key, value, *args, **kwargs):
        return _Base.replace(self, key, value, *args, **kwargs)

    def append(self,
               key,  # type: str
               value,  # type: couchbase_v2.JSON
               cas=0,  # type: long
               format=None,  # type: long
               persist_to=0,  # type: int
               replicate_to=0  # type: int
               ):
        # type: (...) -> couchbase_v2.result.Result
        """Append a string to an existing value in Couchbase.

        :param string value: The data to append to the existing value.

        Other parameters follow the same conventions as :meth:`upsert`.

        The `format` argument must be one of
        :const:`~couchbase_v2.FMT_UTF8` or :const:`~couchbase_v2.FMT_BYTES`.
        If not specified, it will be :const:`~.FMT_UTF8` (overriding the
        :attr:`default_format` attribute). This is because JSON or
        Pickle formats will be nonsensical when random data is appended
        to them. If you wish to modify a JSON or Pickle encoded object,
        you will need to retrieve it (via :meth:`get`), modify it, and
        then store it again (using :meth:`upsert`).

        Additionally, you must ensure the value (and flags) for the
        current value is compatible with the data to be appended. For an
        example, you may append a :const:`~.FMT_BYTES` value to an
        existing :const:`~couchbase_v2.FMT_JSON` value, but an error will
        be thrown when retrieving the value using :meth:`get` (you may
        still use the :attr:`data_passthrough` to overcome this).

        :raise: :exc:`.NotStoredException` if the key does not exist

        .. seealso:: :meth:`upsert`, :meth:`append_multi`
        """
        pass

    def append(self, key, value, *args, **kwargs):
        return _Base.append(self, key, value, *args, **kwargs)

    def prepend(self, key, value, cas=0, format=None,
                persist_to=0, replicate_to=0):
        # type: (...) -> Result
        """Prepend a string to an existing value in Couchbase.

        .. seealso:: :meth:`append`, :meth:`prepend_multi`
        """

    def prepend(self, key, value, *args, **kwargs):
        return _Base.prepend(self, key, value, *args, **kwargs)

    def get(self,  # type: Bucket
            key,  # type: str
            ttl=0,  # type: int
            quiet=None,  # type: bool
            replica=False,  # type: bool
            no_format=False  # type: bool
            ):
        # type: (...) -> couchbase_v2.result.Result
        """Obtain an object stored in Couchbase by given key.

        :param string key: The key to fetch. The type of key is the same
            as mentioned in :meth:`upsert`

        :param int ttl: If specified, indicates that the key's expiry
            time should be *modified* when retrieving the value.

        :param boolean quiet: causes `get` to return None instead of
            raising an exception when the key is not found. It defaults
            to the value set by :attr:`~quiet` on the instance. In
            `quiet` mode, the error may still be obtained by inspecting
            the :attr:`~.Result.rc` attribute of the :class:`.Result`
            object, or checking :attr:`.Result.success`.

            Note that the default value is `None`, which means to use
            the :attr:`quiet`. If it is a boolean (i.e. `True` or
            `False`) it will override the `couchbase_v2.bucket.Bucket`-level
            :attr:`quiet` attribute.

        :param bool replica: Whether to fetch this key from a replica
            rather than querying the master server. This is primarily
            useful when operations with the master fail (possibly due to
            a configuration change). It should normally be used in an
            exception handler like so

            Using the ``replica`` option::

                try:
                    res = c.get("key", quiet=True) # suppress not-found errors
                catch CouchbaseException:
                    res = c.get("key", replica=True, quiet=True)

        :param bool no_format: If set to ``True``, then the value will
            always be delivered in the :class:`~couchbase_v2.result.Result`
            object as being of :data:`~couchbase_v2.FMT_BYTES`. This is a
            item-local equivalent of using the :attr:`data_passthrough`
            option

        :raise: :exc:`.DocumentNotFoundException` if the key does not exist
        :raise: :exc:`.CouchbaseNetworkException`
        :raise: :exc:`.ValueFormatException` if the value cannot be
            deserialized with chosen decoder, e.g. if you try to
            retreive an object stored with an unrecognized format
        :return: A :class:`~.Result` object

        Simple get::

            value = cb.get('key').value

        Get multiple values::

            cb.get_multi(['foo', 'bar'])
            # { 'foo' : <Result(...)>, 'bar' : <Result(...)> }

        Inspect the flags::

            rv = cb.get("key")
            value, flags, cas = rv.value, rv.flags, rv.cas

        Update the expiry time::

            rv = cb.get("key", ttl=10)
            # Expires in ten seconds

        .. seealso:: :meth:`get_multi`
        """

        return _Base.get(self, key, ttl=ttl, quiet=quiet,
                         replica=replica, no_format=no_format)

    def touch(self, key, ttl=0):
        """Update a key's expiry time

        :param string key: The key whose expiry time should be
            modified
        :param int ttl: The new expiry time. If the expiry time
            is `0` then the key never expires (and any existing
            expiry is removed)
        :return: :class:`.OperationResult`

        Update the expiry time of a key ::

            cb.upsert("key", ttl=100)
            # expires in 100 seconds
            cb.touch("key", ttl=0)
            # key should never expire now

        :raise: The same things that :meth:`get` does

        .. seealso:: :meth:`get` - which can be used to get *and* update the
            expiry, :meth:`touch_multi`
        """
        return _Base.touch(self, key, ttl=ttl)

    def lock(self, key, ttl=0):
        """Lock and retrieve a key-value entry in Couchbase.

        :param key: A string which is the key to lock.

        :param ttl: a TTL for which the lock should be valid.
            While the lock is active, attempts to access the key (via
            other :meth:`lock`, :meth:`upsert` or other mutation calls)
            will fail with an :exc:`.DocumentExistsException`. Note that the
            value for this option is limited by the maximum allowable
            lock time determined by the server (currently, this is 30
            seconds). If passed a higher value, the server will silently
            lower this to its maximum limit.


        This function otherwise functions similarly to :meth:`get`;
        specifically, it will return the value upon success. Note the
        :attr:`~.Result.cas` value from the :class:`.Result` object.
        This will be needed to :meth:`unlock` the key.

        Note the lock will also be implicitly released if modified by
        one of the :meth:`upsert` family of functions when the valid CAS
        is supplied

        :raise: :exc:`.TemporaryFailException` if the key is already locked.
        :raise: See :meth:`get` for possible exceptions

        Lock a key ::

            rv = cb.lock("locked_key", ttl=5)
            # This key is now locked for the next 5 seconds.
            # attempts to access this key will fail until the lock
            # is released.

            # do important stuff...

            cb.unlock("locked_key", rv.cas)

        Lock a key, implicitly unlocking with :meth:`upsert` with CAS ::

            rv = self.cb.lock("locked_key", ttl=5)
            new_value = rv.value.upper()
            cb.upsert("locked_key", new_value, rv.cas)


        Poll and Lock ::

            rv = None
            begin_time = time.time()
            while time.time() - begin_time < 15:
                try:
                    rv = cb.lock("key", ttl=10)
                    break
                except TemporaryFailException:
                    print("Key is currently locked.. waiting")
                    time.sleep(1)

            if not rv:
                raise Exception("Waited too long..")

            # Do stuff..

            cb.unlock("key", rv.cas)


        .. seealso:: :meth:`get`, :meth:`lock_multi`, :meth:`unlock`
        """
        return _Base.lock(self, key, ttl=ttl)

    def unlock(self, key, cas):
        """Unlock a Locked Key in Couchbase.

        This unlocks an item previously locked by :meth:`lock`

        :param key: The key to unlock
        :param cas: The cas returned from :meth:`lock`'s
            :class:`.Result` object.

        See :meth:`lock` for an example.

        :raise: :exc:`.TemporaryFailException` if the CAS supplied does not
            match the CAS on the server (possibly because it was
            unlocked by previous call).

        .. seealso:: :meth:`lock` :meth:`unlock_multi`
        """
        return _Base.unlock(self, key, cas=cas)

    def remove(self, key, cas=0, quiet=None, persist_to=0, replicate_to=0):
        """Remove the key-value entry for a given key in Couchbase.

        :param key: A string which is the key to remove. The format and
            type of the key follows the same conventions as in
            :meth:`upsert`
        :type key: string, dict, or tuple/list

        :param int cas: The CAS to use for the removal operation.
            If specified, the key will only be removed from the server
            if it has the same CAS as specified. This is useful to
            remove a key only if its value has not been changed from the
            version currently visible to the client. If the CAS on the
            server does not match the one specified, an exception is
            thrown.
        :param boolean quiet:
            Follows the same semantics as `quiet` in :meth:`get`
        :param int persist_to: If set, wait for the item to be removed
            from the storage of at least these many nodes
        :param int replicate_to: If set, wait for the item to be removed
            from the cache of at least these many nodes
            (excluding the master)
        :raise: :exc:`.DocumentNotFoundException` if the key does not exist.
        :raise: :exc:`.DocumentExistsException` if a CAS was specified, but
            the CAS on the server had changed
        :return: A :class:`~.Result` object.

        Simple remove::

            ok = cb.remove("key").success

        Don't complain if key does not exist::

            ok = cb.remove("key", quiet=True)

        Only remove if CAS matches our version::

            rv = cb.get("key")
            cb.remove("key", cas=rv.cas)

        Remove multiple keys::

            oks = cb.remove_multi(["key1", "key2", "key3"])

        Remove multiple keys with CAS::

            oks = cb.remove({
                "key1" : cas1,
                "key2" : cas2,
                "key3" : cas3
            })

        .. seealso:: :meth:`remove_multi`, :meth:`endure`
            for more information on the ``persist_to`` and
            ``replicate_to`` options.
        """
        return _Base.remove(self, key, cas=cas, quiet=quiet,
                            persist_to=persist_to, replicate_to=replicate_to)

    def counter(self, key, delta=1, initial=None, ttl=0):
        """Increment or decrement the numeric value of an item.

        This method instructs the server to treat the item stored under
        the given key as a numeric counter.

        Counter operations require that the stored value
        exists as a string representation of a number (e.g. ``123``). If
        storing items using the :meth:`upsert` family of methods, and
        using the default :const:`couchbase_v2.FMT_JSON` then the value
        will conform to this constraint.

        :param string key: A key whose counter value is to be modified
        :param int delta: an amount by which the key should be modified.
            If the number is negative then this number will be
            *subtracted* from the current value.
        :param initial: The initial value for the key, if it does not
            exist. If the key does not exist, this value is used, and
            `delta` is ignored. If this parameter is `None` then no
            initial value is used
        :type initial: int or `None`
        :param int ttl: The lifetime for the key, after which it will
            expire
        :raise: :exc:`.DocumentNotFoundException` if the key does not exist on the
            bucket (and `initial` was `None`)
        :raise: :exc:`.DeltaBadvalException` if the key exists, but the
            existing value is not numeric
        :return: A :class:`.Result` object. The current value of the
            counter may be obtained by inspecting the return value's
            `value` attribute.

        Simple increment::

            rv = cb.counter("key")
            rv.value
            # 42

        Increment by 10::

            rv = cb.counter("key", delta=10)

        Decrement by 5::

            rv = cb.counter("key", delta=-5)

        Increment by 20, set initial value to 5 if it does not exist::

            rv = cb.counter("key", delta=20, initial=5)

        Increment three keys::

            kv = cb.counter_multi(["foo", "bar", "baz"])
            for key, result in kv.items():
                print "Key %s has value %d now" % (key, result.value)

        .. seealso:: :meth:`counter_multi`
        """
        return _Base.counter(self, key, delta=delta, initial=initial, ttl=ttl)

    def lookup_in(self, key, *specs, **kwargs):
        """Atomically retrieve one or more paths from a document.

        :param key: The key of the document to lookup
        :param spec: A list of specs (see :mod:`.couchbase_core.subdocument`)
        :return: A :class:`.couchbase_core.result.SubdocResult` object.
            This object contains the results and any errors of the
            operation.

        Example::

            import couchbase_core.subdocument as SD
            rv = cb.lookup_in('user',
                              SD.get('email'),
                              SD.get('name'),
                              SD.exists('friends.therock'))

            email = rv[0]
            name = rv[1]
            friend_exists = rv.exists(2)

        .. seealso:: :meth:`retrieve_in` which acts as a convenience wrapper
        """
        return super(Bucket, self).lookup_in(key,specs,**kwargs)

    def mutate_in(self, key, *specs, **kwargs):
        """Perform multiple atomic modifications within a document.

        :param key: The key of the document to modify
        :param specs: A list of specs (See :mod:`.couchbase_core.subdocument`)
        :param bool create_doc:
            Whether the document should be create if it doesn't exist
        :param bool insert_doc: If the document should be created anew, and the
            operations performed *only* if it does not exist.
        :param bool upsert_doc: If the document should be created anew if it
            does not exist. If it does exist the commands are still executed.
        :param kwargs: CAS, etc.
        :return: A :class:`~.couchbase_core.result.SubdocResult` object.

        Here's an example of adding a new tag to a "user" document
        and incrementing a modification counter::

            import couchbase_core.subdocument as SD
            # ....
            cb.mutate_in('user',
                         SD.array_addunique('tags', 'dog'),
                         SD.counter('updates', 1))

        .. note::

            The `insert_doc` and `upsert_doc` options are mutually exclusive.
            Use `insert_doc` when you wish to create a new document with
            extended attributes (xattrs).

        .. seealso:: :mod:`.couchbase_core.subdocument`
        """
        return super(Bucket, self).mutate_in(key, specs, **kwargs)

    def retrieve_in(self, key, *paths, **kwargs):
        """Atomically fetch one or more paths from a document.

        Convenience method for retrieval operations. This functions
        identically to :meth:`lookup_in`. As such, the following two
        forms are equivalent:

        .. code-block:: python

            import couchbase_v2.subdocument as SD
            rv = cb.lookup_in(key,
                              SD.get('email'),
                              SD.get('name'),
                              SD.get('friends.therock')

            email, name, friend = rv

        .. code-block:: python

            rv = cb.retrieve_in(key, 'email', 'name', 'friends.therock')
            email, name, friend = rv

        .. seealso:: :meth:`lookup_in`
        """
        import couchbase_core.subdocument as SD
        return self.lookup_in(key, *tuple(SD.get(x) for x in paths), **kwargs)

    def incr(self, key, amount=1, **kwargs):
        _depr('incr', 'counter')
        return self.counter(key, delta=amount, **kwargs)

    def incr_multi(self, keys, amount=1, **kwargs):
        _depr('incr_multi', 'counter_multi')
        return self.counter_multi(keys, delta=amount, **kwargs)

    def decr(self, key, amount=1, **kwargs):
        _depr('decr', 'counter')
        return self.counter(key, delta=-amount, **kwargs)

    def decr_multi(self, keys, amount=1, **kwargs):
        _depr('decr_multi', 'counter_multi')
        return self.counter_multi(keys, delta=-amount, **kwargs)

    def stats(self, keys=None, keystats=False):
        """Request server statistics.

        Fetches stats from each node in the cluster. Without a key
        specified the server will respond with a default set of
        statistical information. It returns the a `dict` with stats keys
        and node-value pairs as a value.

        :param keys: One or several stats to query
        :type keys: string or list of string
        :raise: :exc:`.CouchbaseNetworkException`
        :return: `dict` where keys are stat keys and values are
            host-value pairs

        Find out how many items are in the bucket::

            total = 0
            for key, value in cb.stats()['total_items'].items():
                total += value

        Get memory stats (works on couchbase buckets)::

            cb.stats('memory')
            # {'mem_used': {...}, ...}
        """
        if keys and not isinstance(keys, (tuple, list)):
            keys = (keys,)
        return self._stats(keys, keystats=keystats)

    def observe(self, key, master_only=False):
        """Return storage information for a key.

        It returns a :class:`.ValueResult` object with the ``value``
        field set to a list of :class:`~.ObserveInfo` objects. Each
        element in the list responds to the storage status for the key
        on the given node. The length of the list (and thus the number
        of :class:`~.ObserveInfo` objects) are equal to the number of
        online replicas plus the master for the given key.

        :param string key: The key to inspect
        :param bool master_only: Whether to only retrieve information
            from the master node.

        .. seealso:: :ref:`observe_info`
        """
        return _Base.observe(self, key, master_only=master_only)

    def endure(self, key, persist_to=-1, replicate_to=-1, cas=0,
               check_removed=False, timeout=5.0, interval=0.010):
        """Wait until a key has been distributed to one or more nodes

        By default, when items are stored to Couchbase, the operation is
        considered successful if the vBucket master (i.e. the "primary"
        node) for the key has successfully stored the item in its
        memory.

        In most situations, this is sufficient to assume that the item
        has successfully been stored. However the possibility remains
        that the "master" server will go offline as soon as it sends
        back the successful response and the data is lost.

        The ``endure`` function allows you to provide stricter criteria
        for success. The criteria may be expressed in terms of number of
        nodes for which the item must exist in that node's RAM and/or on
        that node's disk. Ensuring that an item exists in more than one
        place is a safer way to guarantee against possible data loss.

        We call these requirements `Durability Constraints`, and thus
        the method is called `endure`.

        :param string key: The key to endure.
        :param int persist_to: The minimum number of nodes which must
            contain this item on their disk before this function
            returns. Ensure that you do not specify too many nodes;
            otherwise this function will fail. Use the
            :attr:`server_nodes` to determine how many nodes exist in
            the cluster.

            The maximum number of nodes an item can reside on is
            currently fixed to 4 (i.e. the "master" node, and up to
            three "replica" nodes). This limitation is current as of
            Couchbase Server version 2.1.0.

            If this parameter is set to a negative value, the maximum
            number of possible nodes the key can reside on will be used.

        :param int replicate_to: The minimum number of replicas which
            must contain this item in their memory for this method to
            succeed. As with ``persist_to``, you may specify a negative
            value in which case the requirement will be set to the
            maximum number possible.

        :param float timeout: A timeout value in seconds before this
            function fails with an exception. Typically it should take
            no longer than several milliseconds on a functioning cluster
            for durability requirements to be satisfied (unless
            something has gone wrong).

        :param float interval: The polling interval in seconds to use
            for checking the key status on the respective nodes.
            Internally, ``endure`` is implemented by polling each server
            individually to see if the key exists on that server's disk
            and memory. Once the status request is sent to all servers,
            the client will check if their replies are satisfactory; if
            they are then this function succeeds, otherwise the client
            will wait a short amount of time and try again. This
            parameter sets this "wait time".

        :param bool check_removed: This flag inverts the check. Instead
            of checking that a given key *exists* on the nodes, this
            changes the behavior to check that the key is *removed* from
            the nodes.

        :param long cas: The CAS value to check against. It is possible
            for an item to exist on a node but have a CAS value from a
            prior operation. Passing the CAS ensures that only replies
            from servers with a CAS matching this parameter are accepted.

        :return: A :class:`~.OperationResult`
        :raise: see :meth:`upsert` and :meth:`get` for possible errors

        .. seealso:: :meth:`upsert`, :meth:`endure_multi`
        """
        # We really just wrap 'endure_multi'
        kv = {key: cas}
        rvs = self.endure_multi(keys=kv, persist_to=persist_to,
                                replicate_to=replicate_to,
                                check_removed=check_removed, timeout=timeout,
                                interval=interval)
        return rvs[key]

    def durability(self, persist_to=-1, replicate_to=-1, timeout=0.0):
        """Returns a context manager which will apply the given
        persistence/replication settings to all mutation operations when
        active

        :param int persist_to:
        :param int replicate_to:

        See :meth:`endure` for the meaning of these two values

        Thus, something like::

          with cb.durability(persist_to=3):
            cb.upsert("foo", "foo_value")
            cb.upsert("bar", "bar_value")
            cb.upsert("baz", "baz_value")

        is equivalent to::

            cb.upsert("foo", "foo_value", persist_to=3)
            cb.upsert("bar", "bar_value", persist_to=3)
            cb.upsert("baz", "baz_value", persist_to=3)


        .. versionadded:: 1.2.0

        .. seealso:: :meth:`endure`
        """
        return DurabilityContext(self, persist_to, replicate_to, timeout)

    def bucket_manager(self):
        """
        Returns a :class:`~.BucketManager` object which may be used to
        perform management operations on the current bucket. These
        operations may create/modify design documents and flush the
        bucket
        """
        return BucketManager(self)

    n1ql_query = CoreClient.query
    query = CoreClient.view_query

    def _analytics_query(self, query, *args, **kwargs):
        # we used to take the ANALYTICS host as the second parameter, but
        # LCB now retrieves the hostname from the cluster
        return super(Bucket,self).analytics_query(query, *args, **kwargs)

    analytics_query = _analytics_query

    # "items" interface
    def append_items(self, items, **kwargs):
        """
        Method to append data to multiple :class:`~.Item` objects.

        This method differs from the normal :meth:`append_multi` in that
        each `Item`'s `value` field is updated with the appended data
        upon successful completion of the operation.


        :param items: The item dictionary. The value for each key should
            contain a ``fragment`` field containing the object to append
            to the value on the server.
        :type items: :class:`~couchbase_v2.items.ItemOptionDict`.

        The rest of the options are passed verbatim to
        :meth:`append_multi`

        .. seealso:: :meth:`append_multi`, :meth:`append`
        """
        rv = self.append_multi(items, **kwargs)
        # Assume this is an 'ItemOptionDict'
        for k, v in items.dict.items():
            if k.success:
                k.value += v['fragment']

        return rv

    def prepend_items(self, items, **kwargs):
        """Method to prepend data to multiple :class:`~.Item` objects.
        .. seealso:: :meth:`append_items`
        """
        rv = self.prepend_multi(items, **kwargs)
        for k, v in items.dict.items():
            if k.success:
                k.value = v['fragment'] + k.value

        return rv

    _OLDOPS = { 'set': 'upsert', 'add': 'insert', 'delete': 'remove'}
    for o, n in _OLDOPS.items():
        for variant in ('', '_multi'):
            oldname = o + variant
            newname = n + variant

            try:
                dst = locals()[n + variant]
            except KeyError:
                dst = getattr(_Base, n + variant)

            def mkmeth(oldname, newname, _dst):
                def _tmpmeth(self, *args, **kwargs):
                    _depr(oldname, newname)
                    return _dst(self, *args, **kwargs)
                return _tmpmeth

            locals().update({oldname: mkmeth(oldname, newname, dst)})

    """
    Lists the names of all the memcached operations. This is useful
    for classes which want to wrap all the methods
    """

    def design_get(self, *args, **kwargs):
        _depr('design_get', 'bucket_manager().design_get')
        return self.bucket_manager().design_get(*args, **kwargs)

    def design_create(self, *args, **kwargs):
        _depr('design_create', 'bucket_manager().design_create')
        return self.bucket_manager().design_create(*args, **kwargs)

    def design_publish(self, *args, **kwargs):
        _depr('design_publish', 'bucket_manager().design_publish')
        return self.bucket_manager().design_publish(*args, **kwargs)

    def design_delete(self, *args, **kwargs):
        _depr('design_delete', 'bucket_manager().design_delete')
        return self.bucket_manager().design_delete(*args, **kwargs)

# # Copyright 2013, Couchbase, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License") # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #     http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # .. versionadded:: 1.2.0          Creates a new pipeline context. See :meth:`~Bucket.pipeline`         for more details Contains a list of results for each pipelined operation         executed within the context. The list remains until this         context is reused.          The elements in the list are either :class:`.Result`         objects (for single operations) or :class:`.MultiResult`         objects (for multi operations) Returns a new :class:`Pipeline` context manager. When the         context manager is active, operations performed will return         ``None``, and will be sent on the network when the context         leaves (in its ``__exit__`` method). To get the results of the         pipelined operations, inspect the :attr:`Pipeline.results`         property.          Operational errors (i.e. negative replies from the server, or         network errors) are delivered when the pipeline exits, but         argument errors are thrown immediately.          :return: a :class:`Pipeline` object         :raise: :exc:`.PipelineException` if a pipeline is already created         :raise: Other operation-specific errors.          Scheduling multiple operations, without checking results::            with cb.pipeline():             cb.upsert("key1", "value1")             cb.counter("counter")             cb.upsert_multi({               "new_key1" : "new_value_1",               "new_key2" : "new_value_2"             })          Retrieve the results for several operations::            pipeline = cb.pipeline()           with pipeline:             cb.upsert("foo", "bar")             cb.replace("something", "value")            for result in pipeline.results:             print("Pipeline result: CAS {0}".format(result.cas))          .. note::            When in pipeline mode, you cannot execute view queries.           Additionally, pipeline mode is not supported on async handles          .. warning::            Pipeline mode should not be used if you are using the same           object concurrently from multiple threads. This only refers           to the internal lock within the object itself. It is safe           to use if you employ your own locking mechanism (for example           a connection pool)          .. versionadded:: 1.2.0 # We have these wrappers so that IDEs can do param tooltips and the # like. we might move this directly into C some day # type: (Union[str, bytes], Any, int, int, int, int, int, int) -> Result Unconditionally store the object in Couchbase.          :param key:             The key to set the value with. By default, the key must be             either a :class:`bytes` or :class:`str` object encodable as             UTF-8. If a custom `transcoder` class is used (see             :meth:`~__init__`), then the key object is passed directly             to the transcoder, which may serialize it how it wishes.         :type key: string or bytes          :param value: The value to set for the key.             This should be a native Python value which will be transparently             serialized to JSON by the library. Do not pass already-serialized             JSON as the value or it will be serialized again.              If you are using a different `format` setting (see `format`             parameter), and/or a custom transcoder then value for this             argument may need to conform to different criteria.          :param int cas: The _CAS_ value to use. If supplied, the value             will only be stored if it already exists with the supplied             CAS          :param int ttl: If specified, the key will expire after this             many seconds          :param int format: If specified, indicates the `format` to use             when encoding the value. If none is specified, it will use             the `default_format` For more info see             :attr:`~.default_format`          :param int persist_to:             Perform durability checking on this many nodes nodes for             persistence to disk. See :meth:`endure` for more information          :param int replicate_to: Perform durability checking on this             many replicas for presence in memory. See :meth:`endure` for             more information.          :raise: :exc:`.InvalidArgumentException` if an argument is supplied that is             not applicable in this context. For example setting the CAS             as a string.         :raise: :exc`.CouchbaseNetworkException`         :raise: :exc:`.DocumentExistsException` if the key already exists on the             server with a different CAS value.         :raise: :exc:`.ValueFormatException` if the value cannot be             serialized with chosen encoder, e.g. if you try to store a             dictionary in plain mode.         :return: :class:`~.Result`.          Simple set::              cb.upsert('key', 'value')          Force JSON document format for value::              cb.upsert('foo', {'bar': 'baz'}, format=couchbase_v2.FMT_JSON)          Insert JSON from a string::              JSONstr = '{"key1": "value1", "key2": 123}'             JSONobj = json.loads(JSONstr)             cb.upsert("documentID", JSONobj, format=couchbase_v2.FMT_JSON)          Force UTF8 document format for value::              cb.upsert('foo', "<xml></xml>", format=couchbase_v2.FMT_UTF8)          Perform optimistic locking by specifying last known CAS version::              cb.upsert('foo', 'bar', cas=8835713818674332672)          Several sets at the same time (mutli-set)::              cb.upsert_multi({'foo': 'bar', 'baz': 'value'})          .. seealso:: :meth:`upsert_multi` Store an object in Couchbase unless it already exists.          Follows the same conventions as :meth:`upsert` but the value is         stored only if it does not exist already. Conversely, the value         is not stored if the key already exists.          Notably missing from this method is the `cas` parameter, this is         because `insert` will only succeed if a key does not already         exist on the server (and thus can have no CAS)          :raise: :exc:`.DocumentExistsException` if the key already exists          .. seealso:: :meth:`upsert`, :meth:`insert_multi` Store an object in Couchbase only if it already exists.          Follows the same conventions as :meth:`upsert`, but the value is         stored only if a previous value already exists.          :raise: :exc:`.DocumentNotFoundException` if the key does not exist          .. seealso:: :meth:`upsert`, :meth:`replace_multi` # type: str # type: couchbase_v2.JSON # type: long # type: long # type: int # type: int # type: (...) -> couchbase_v2.result.Result Append a string to an existing value in Couchbase.          :param string value: The data to append to the existing value.          Other parameters follow the same conventions as :meth:`upsert`.          The `format` argument must be one of         :const:`~couchbase_v2.FMT_UTF8` or :const:`~couchbase_v2.FMT_BYTES`.         If not specified, it will be :const:`~.FMT_UTF8` (overriding the         :attr:`default_format` attribute). This is because JSON or         Pickle formats will be nonsensical when random data is appended         to them. If you wish to modify a JSON or Pickle encoded object,         you will need to retrieve it (via :meth:`get`), modify it, and         then store it again (using :meth:`upsert`).          Additionally, you must ensure the value (and flags) for the         current value is compatible with the data to be appended. For an         example, you may append a :const:`~.FMT_BYTES` value to an         existing :const:`~couchbase_v2.FMT_JSON` value, but an error will         be thrown when retrieving the value using :meth:`get` (you may         still use the :attr:`data_passthrough` to overcome this).          :raise: :exc:`.NotStoredException` if the key does not exist          .. seealso:: :meth:`upsert`, :meth:`append_multi` # type: (...) -> Result Prepend a string to an existing value in Couchbase.          .. seealso:: :meth:`append`, :meth:`prepend_multi` # type: Bucket # type: str # type: int # type: bool # type: bool # type: bool # type: (...) -> couchbase_v2.result.Result Obtain an object stored in Couchbase by given key.          :param string key: The key to fetch. The type of key is the same             as mentioned in :meth:`upsert`          :param int ttl: If specified, indicates that the key's expiry             time should be *modified* when retrieving the value.          :param boolean quiet: causes `get` to return None instead of             raising an exception when the key is not found. It defaults             to the value set by :attr:`~quiet` on the instance. In             `quiet` mode, the error may still be obtained by inspecting             the :attr:`~.Result.rc` attribute of the :class:`.Result`             object, or checking :attr:`.Result.success`.              Note that the default value is `None`, which means to use             the :attr:`quiet`. If it is a boolean (i.e. `True` or             `False`) it will override the `couchbase_v2.bucket.Bucket`-level             :attr:`quiet` attribute.          :param bool replica: Whether to fetch this key from a replica             rather than querying the master server. This is primarily             useful when operations with the master fail (possibly due to             a configuration change). It should normally be used in an             exception handler like so              Using the ``replica`` option::                  try:                     res = c.get("key", quiet=True) # suppress not-found errors                 catch CouchbaseException:                     res = c.get("key", replica=True, quiet=True)          :param bool no_format: If set to ``True``, then the value will             always be delivered in the :class:`~couchbase_v2.result.Result`             object as being of :data:`~couchbase_v2.FMT_BYTES`. This is a             item-local equivalent of using the :attr:`data_passthrough`             option          :raise: :exc:`.DocumentNotFoundException` if the key does not exist         :raise: :exc:`.CouchbaseNetworkException`         :raise: :exc:`.ValueFormatException` if the value cannot be             deserialized with chosen decoder, e.g. if you try to             retreive an object stored with an unrecognized format         :return: A :class:`~.Result` object          Simple get::              value = cb.get('key').value          Get multiple values::              cb.get_multi(['foo', 'bar'])             # { 'foo' : <Result(...)>, 'bar' : <Result(...)> }          Inspect the flags::              rv = cb.get("key")             value, flags, cas = rv.value, rv.flags, rv.cas          Update the expiry time::              rv = cb.get("key", ttl=10)             # Expires in ten seconds          .. seealso:: :meth:`get_multi` Update a key's expiry time          :param string key: The key whose expiry time should be             modified         :param int ttl: The new expiry time. If the expiry time             is `0` then the key never expires (and any existing             expiry is removed)         :return: :class:`.OperationResult`          Update the expiry time of a key ::              cb.upsert("key", ttl=100)             # expires in 100 seconds             cb.touch("key", ttl=0)             # key should never expire now          :raise: The same things that :meth:`get` does          .. seealso:: :meth:`get` - which can be used to get *and* update the             expiry, :meth:`touch_multi` Lock and retrieve a key-value entry in Couchbase.          :param key: A string which is the key to lock.          :param ttl: a TTL for which the lock should be valid.             While the lock is active, attempts to access the key (via             other :meth:`lock`, :meth:`upsert` or other mutation calls)             will fail with an :exc:`.DocumentExistsException`. Note that the             value for this option is limited by the maximum allowable             lock time determined by the server (currently, this is 30             seconds). If passed a higher value, the server will silently             lower this to its maximum limit.           This function otherwise functions similarly to :meth:`get`;         specifically, it will return the value upon success. Note the         :attr:`~.Result.cas` value from the :class:`.Result` object.         This will be needed to :meth:`unlock` the key.          Note the lock will also be implicitly released if modified by         one of the :meth:`upsert` family of functions when the valid CAS         is supplied          :raise: :exc:`.TemporaryFailException` if the key is already locked.         :raise: See :meth:`get` for possible exceptions          Lock a key ::              rv = cb.lock("locked_key", ttl=5)             # This key is now locked for the next 5 seconds.             # attempts to access this key will fail until the lock             # is released.              # do important stuff...              cb.unlock("locked_key", rv.cas)          Lock a key, implicitly unlocking with :meth:`upsert` with CAS ::              rv = self.cb.lock("locked_key", ttl=5)             new_value = rv.value.upper()             cb.upsert("locked_key", new_value, rv.cas)           Poll and Lock ::              rv = None             begin_time = time.time()             while time.time() - begin_time < 15:                 try:                     rv = cb.lock("key", ttl=10)                     break                 except TemporaryFailException:                     print("Key is currently locked.. waiting")                     time.sleep(1)              if not rv:                 raise Exception("Waited too long..")              # Do stuff..              cb.unlock("key", rv.cas)           .. seealso:: :meth:`get`, :meth:`lock_multi`, :meth:`unlock` Unlock a Locked Key in Couchbase.          This unlocks an item previously locked by :meth:`lock`          :param key: The key to unlock         :param cas: The cas returned from :meth:`lock`'s             :class:`.Result` object.          See :meth:`lock` for an example.          :raise: :exc:`.TemporaryFailException` if the CAS supplied does not             match the CAS on the server (possibly because it was             unlocked by previous call).          .. seealso:: :meth:`lock` :meth:`unlock_multi` Remove the key-value entry for a given key in Couchbase.          :param key: A string which is the key to remove. The format and             type of the key follows the same conventions as in             :meth:`upsert`         :type key: string, dict, or tuple/list          :param int cas: The CAS to use for the removal operation.             If specified, the key will only be removed from the server             if it has the same CAS as specified. This is useful to             remove a key only if its value has not been changed from the             version currently visible to the client. If the CAS on the             server does not match the one specified, an exception is             thrown.         :param boolean quiet:             Follows the same semantics as `quiet` in :meth:`get`         :param int persist_to: If set, wait for the item to be removed             from the storage of at least these many nodes         :param int replicate_to: If set, wait for the item to be removed             from the cache of at least these many nodes             (excluding the master)         :raise: :exc:`.DocumentNotFoundException` if the key does not exist.         :raise: :exc:`.DocumentExistsException` if a CAS was specified, but             the CAS on the server had changed         :return: A :class:`~.Result` object.          Simple remove::              ok = cb.remove("key").success          Don't complain if key does not exist::              ok = cb.remove("key", quiet=True)          Only remove if CAS matches our version::              rv = cb.get("key")             cb.remove("key", cas=rv.cas)          Remove multiple keys::              oks = cb.remove_multi(["key1", "key2", "key3"])          Remove multiple keys with CAS::              oks = cb.remove({                 "key1" : cas1,                 "key2" : cas2,                 "key3" : cas3             })          .. seealso:: :meth:`remove_multi`, :meth:`endure`             for more information on the ``persist_to`` and             ``replicate_to`` options. Increment or decrement the numeric value of an item.          This method instructs the server to treat the item stored under         the given key as a numeric counter.          Counter operations require that the stored value         exists as a string representation of a number (e.g. ``123``). If         storing items using the :meth:`upsert` family of methods, and         using the default :const:`couchbase_v2.FMT_JSON` then the value         will conform to this constraint.          :param string key: A key whose counter value is to be modified         :param int delta: an amount by which the key should be modified.             If the number is negative then this number will be             *subtracted* from the current value.         :param initial: The initial value for the key, if it does not             exist. If the key does not exist, this value is used, and             `delta` is ignored. If this parameter is `None` then no             initial value is used         :type initial: int or `None`         :param int ttl: The lifetime for the key, after which it will             expire         :raise: :exc:`.DocumentNotFoundException` if the key does not exist on the             bucket (and `initial` was `None`)         :raise: :exc:`.DeltaBadvalException` if the key exists, but the             existing value is not numeric         :return: A :class:`.Result` object. The current value of the             counter may be obtained by inspecting the return value's             `value` attribute.          Simple increment::              rv = cb.counter("key")             rv.value             # 42          Increment by 10::              rv = cb.counter("key", delta=10)          Decrement by 5::              rv = cb.counter("key", delta=-5)          Increment by 20, set initial value to 5 if it does not exist::              rv = cb.counter("key", delta=20, initial=5)          Increment three keys::              kv = cb.counter_multi(["foo", "bar", "baz"])             for key, result in kv.items():                 print "Key %s has value %d now" % (key, result.value)          .. seealso:: :meth:`counter_multi` Atomically retrieve one or more paths from a document.          :param key: The key of the document to lookup         :param spec: A list of specs (see :mod:`.couchbase_core.subdocument`)         :return: A :class:`.couchbase_core.result.SubdocResult` object.             This object contains the results and any errors of the             operation.          Example::              import couchbase_core.subdocument as SD             rv = cb.lookup_in('user',                               SD.get('email'),                               SD.get('name'),                               SD.exists('friends.therock'))              email = rv[0]             name = rv[1]             friend_exists = rv.exists(2)          .. seealso:: :meth:`retrieve_in` which acts as a convenience wrapper Perform multiple atomic modifications within a document.          :param key: The key of the document to modify         :param specs: A list of specs (See :mod:`.couchbase_core.subdocument`)         :param bool create_doc:             Whether the document should be create if it doesn't exist         :param bool insert_doc: If the document should be created anew, and the             operations performed *only* if it does not exist.         :param bool upsert_doc: If the document should be created anew if it             does not exist. If it does exist the commands are still executed.         :param kwargs: CAS, etc.         :return: A :class:`~.couchbase_core.result.SubdocResult` object.          Here's an example of adding a new tag to a "user" document         and incrementing a modification counter::              import couchbase_core.subdocument as SD             # ....             cb.mutate_in('user',                          SD.array_addunique('tags', 'dog'),                          SD.counter('updates', 1))          .. note::              The `insert_doc` and `upsert_doc` options are mutually exclusive.             Use `insert_doc` when you wish to create a new document with             extended attributes (xattrs).          .. seealso:: :mod:`.couchbase_core.subdocument` Atomically fetch one or more paths from a document.          Convenience method for retrieval operations. This functions         identically to :meth:`lookup_in`. As such, the following two         forms are equivalent:          .. code-block:: python              import couchbase_v2.subdocument as SD             rv = cb.lookup_in(key,                               SD.get('email'),                               SD.get('name'),                               SD.get('friends.therock')              email, name, friend = rv          .. code-block:: python              rv = cb.retrieve_in(key, 'email', 'name', 'friends.therock')             email, name, friend = rv          .. seealso:: :meth:`lookup_in` Request server statistics.          Fetches stats from each node in the cluster. Without a key         specified the server will respond with a default set of         statistical information. It returns the a `dict` with stats keys         and node-value pairs as a value.          :param keys: One or several stats to query         :type keys: string or list of string         :raise: :exc:`.CouchbaseNetworkException`         :return: `dict` where keys are stat keys and values are             host-value pairs          Find out how many items are in the bucket::              total = 0             for key, value in cb.stats()['total_items'].items():                 total += value          Get memory stats (works on couchbase buckets)::              cb.stats('memory')             # {'mem_used': {...}, ...} Return storage information for a key.          It returns a :class:`.ValueResult` object with the ``value``         field set to a list of :class:`~.ObserveInfo` objects. Each         element in the list responds to the storage status for the key         on the given node. The length of the list (and thus the number         of :class:`~.ObserveInfo` objects) are equal to the number of         online replicas plus the master for the given key.          :param string key: The key to inspect         :param bool master_only: Whether to only retrieve information             from the master node.          .. seealso:: :ref:`observe_info` Wait until a key has been distributed to one or more nodes          By default, when items are stored to Couchbase, the operation is         considered successful if the vBucket master (i.e. the "primary"         node) for the key has successfully stored the item in its         memory.          In most situations, this is sufficient to assume that the item         has successfully been stored. However the possibility remains         that the "master" server will go offline as soon as it sends         back the successful response and the data is lost.          The ``endure`` function allows you to provide stricter criteria         for success. The criteria may be expressed in terms of number of         nodes for which the item must exist in that node's RAM and/or on         that node's disk. Ensuring that an item exists in more than one         place is a safer way to guarantee against possible data loss.          We call these requirements `Durability Constraints`, and thus         the method is called `endure`.          :param string key: The key to endure.         :param int persist_to: The minimum number of nodes which must             contain this item on their disk before this function             returns. Ensure that you do not specify too many nodes;             otherwise this function will fail. Use the             :attr:`server_nodes` to determine how many nodes exist in             the cluster.              The maximum number of nodes an item can reside on is             currently fixed to 4 (i.e. the "master" node, and up to             three "replica" nodes). This limitation is current as of             Couchbase Server version 2.1.0.              If this parameter is set to a negative value, the maximum             number of possible nodes the key can reside on will be used.          :param int replicate_to: The minimum number of replicas which             must contain this item in their memory for this method to             succeed. As with ``persist_to``, you may specify a negative             value in which case the requirement will be set to the             maximum number possible.          :param float timeout: A timeout value in seconds before this             function fails with an exception. Typically it should take             no longer than several milliseconds on a functioning cluster             for durability requirements to be satisfied (unless             something has gone wrong).          :param float interval: The polling interval in seconds to use             for checking the key status on the respective nodes.             Internally, ``endure`` is implemented by polling each server             individually to see if the key exists on that server's disk             and memory. Once the status request is sent to all servers,             the client will check if their replies are satisfactory; if             they are then this function succeeds, otherwise the client             will wait a short amount of time and try again. This             parameter sets this "wait time".          :param bool check_removed: This flag inverts the check. Instead             of checking that a given key *exists* on the nodes, this             changes the behavior to check that the key is *removed* from             the nodes.          :param long cas: The CAS value to check against. It is possible             for an item to exist on a node but have a CAS value from a             prior operation. Passing the CAS ensures that only replies             from servers with a CAS matching this parameter are accepted.          :return: A :class:`~.OperationResult`         :raise: see :meth:`upsert` and :meth:`get` for possible errors          .. seealso:: :meth:`upsert`, :meth:`endure_multi` # We really just wrap 'endure_multi' Returns a context manager which will apply the given         persistence/replication settings to all mutation operations when         active          :param int persist_to:         :param int replicate_to:          See :meth:`endure` for the meaning of these two values          Thus, something like::            with cb.durability(persist_to=3):             cb.upsert("foo", "foo_value")             cb.upsert("bar", "bar_value")             cb.upsert("baz", "baz_value")          is equivalent to::              cb.upsert("foo", "foo_value", persist_to=3)             cb.upsert("bar", "bar_value", persist_to=3)             cb.upsert("baz", "baz_value", persist_to=3)           .. versionadded:: 1.2.0          .. seealso:: :meth:`endure` Returns a :class:`~.BucketManager` object which may be used to         perform management operations on the current bucket. These         operations may create/modify design documents and flush the         bucket # we used to take the ANALYTICS host as the second parameter, but # LCB now retrieves the hostname from the cluster # "items" interface Method to append data to multiple :class:`~.Item` objects.          This method differs from the normal :meth:`append_multi` in that         each `Item`'s `value` field is updated with the appended data         upon successful completion of the operation.           :param items: The item dictionary. The value for each key should             contain a ``fragment`` field containing the object to append             to the value on the server.         :type items: :class:`~couchbase_v2.items.ItemOptionDict`.          The rest of the options are passed verbatim to         :meth:`append_multi`          .. seealso:: :meth:`append_multi`, :meth:`append` # Assume this is an 'ItemOptionDict' Method to prepend data to multiple :class:`~.Item` objects.         .. seealso:: :meth:`append_items` Lists the names of all the memcached operations. This is useful     for classes which want to wrap all the methods