=========
Documents
=========

A key role in *aiocouch* takes the :class:`~aiocouch.document.Document` class. Every data send and
retrieved from the server is represented by an instance of that class. There are no other ways in
*aiocouch* to interact with documents.


Getting a Document instance
===========================

While the constructor can be used to get an instance representing a specific document, the canonical
way is the usage of member functions of instances of the :class:`~aiocouch.database.Database` class.

.. code-block :: python

    butterfly_doc = await database["butterfly"]
    wolpertinger = await database.get("wolpertinger")

These methods create a :class:`~aiocouch.document.Document` and fetch the data from the server. For
some cases, though, a precise control other the performed requests are required. The above code
snippet is equivalent to this:

.. code-block :: python

    butterfly_doc = Document(database, "butterly")
    await butterfly_doc.fetch()


Creating new Documents
======================

The creation of a new document on the server consists of three steps. First, you need a local
document handle, i.e., an :class:`~aiocouch.document.Document` instance. Then you set the contents
of the document. And finally, the local document is saved to the server.

.. code-block :: python

    # get an Document instance
    doc = await database.create("new_doc")

    # set the document content
    doc["name"] = "The new document"

    # actually perform the request to save the document on the server
    await doc.save()


Modify existing documents
=========================

The modification of an existing document works very similarly to the creation. Retrieving the
document, updating its contents, and finally saving the modified data to the server.

.. code-block :: python

    # get an Document instance
    doc = await database["existing_doc"]

    # update the document content
    doc["name"] = "The modified document"

    # actually perform the request to save the modification to the server
    await doc.save()


Using Async Context Managers
============================

To simplify the process of retrieving a document from remote server (or creating 
a new one if it didn't exist before), modifying it, and saving changes on remote 
server, you can also use asynchronous context managers.

Using context managers saves you from having to manually perform a lot of 
these operations as the context managers handle these operations for you automatically.

**aiocouch** provides async context managers for both :class:`~aiocouch.document.Document`
and :class:`~aiocouch.document.SecurityDocument`.


Document Context Manager Example
--------------------------------

.. code-block :: python

    from aiocouch import CouchDB
    from aiocouch.document import Document

    async with CouchDB(SERVER_URL, USER, PASSWORD) as client:
        # Create database on remote server (fetching it if it already exists)
        my_database = await client.create("my_database", exists_ok=True)

        # If document exists, it's fetched from the remote server
        async with Document(my_database, "secret_agents") as document:
            # Changes are made locally
            document["name"] = "James Bond"
            document["code"] = "007"
        # Upon exit from above context manager, document is saved remotely

        # Display the newly created document after fetching from remote server
        document = await my_database["secret_agents"]
        print(document)

.. warning:: Uncaught exceptions inside the ``async with`` block will prevent your
             document changes from being saved to the remote server.


Security Document Context Manager Example
-----------------------------------------

Similarly, you can also use Security Document context manager to add or remove admins 
or members from a CouchDB database

.. code-block :: python

    from aiocouch import CouchDB
    from aiocouch.document import Document

    async with CouchDB(SERVER_URL, USER, PASSWORD) as client:
        # Create database on remote server (fetching it if it already exists)
        my_database = await client.create("my_database", exists_ok=True)

        async with SecurityDocument(my_database) as security_doc:
            # Give user 'bond' member access to 'my_database' database
            security_doc.add_member("bond")
            # Give user 'fleming' admin access to 'my_database' database
            security_doc.add_admin("fleming")
        # Upon exit from above context manager, document is saved remotely

        # Display the recent changes made to security document        
        security_doc = await my_database.security()
        print(security_doc)

.. warning:: Uncaught exceptions inside the ``async with`` block will prevent your
             security document changes from being saved to the remote server.

Conflict handling
=================

Whenever, two or more different :class:`~aiocouch.document.Document` instances want to save the same
document on the server, a :class:`~aiocouch.ConflictError` can occur. To cope with conflicts, there
are a set of different strategies, which can be used.

One trivial solution is to simply ignore conflicts.This is a viable strategy if only the existance
of the document matters.

.. code-block :: python

    with contextlib.suppress(aiocouch.ConflictError):
        await doc.save()

Another straight-forward solution is to override the contents of the existing document. Though, 
this example code isn't a complete solution either, as the second call to
:meth:`~aiocouch.document.Document.save()` might raise a :class:`~aiocouch.ConflictError` again.

.. code-block :: python

    try:
        await doc.save()
    except aiocouch.ConflictError:
        info = await doc.info()
        doc.rev = info["rev"]
        await doc.save()

Other use cases may require a more sophisticated merging of documents. However, there isn't a
generic solution to such an approach. Thus, we forego to show example code here.

Reference
=========

.. autoclass:: aiocouch.document.Document
    :members:

.. autoclass:: aiocouch.remote.HTTPResponse
    :members:
