Skip to main content

CouchDB3

CouchDB3 is a sync and async Python wrapper around the CouchDB API. For more detailed information, please refer to the documentation.

Contents

Disclaimer

Big parts of the documentation (and thus docstrings) have been copied from CouchDB's API's great official documentation.

Requirements

  • Python version >= 3.11
  • CouchDB version 3.x

Installation

Installing via PyPi

pip install couchdb3

Installing via Github

python -m pip install git+https://github.com/n-Vlahovic/couchdb3.git

Installing from source

git clone https://github.com/n-Vlahovic/couchdb3
python -m pip install -e couchdb3

Import styles

All public classes are available flat from couchdb3 for backward compatibility. Explicit subpackage imports are also supported for clarity:

# Flat (backward-compatible)
from couchdb3 import Server, AsyncServer, Document

# Explicit sync subpackage
from couchdb3.sync import Server, Database, Partition

# Explicit async subpackage
from couchdb3.aio import AsyncServer, AsyncDatabase, AsyncPartition

# Shared types always from couchdb3 directly
from couchdb3 import Document, ViewResult, ViewRow, exceptions

Quickstart

Connecting to a database server

import couchdb3

client = couchdb3.Server(
    "http://user:password@127.0.0.1:5984"
)

# Checking if the server is up
print(client.up())
# True

user and password can also be passed into the Server constructor as keyword parameters, e.g.

client = couchdb3.Server(
    "127.0.0.1:5984",  # Scheme omitted - will assume http protocol
    user="user",
    password="password"
)

Both approaches are equivalent, i.e. in both cases the instance's scheme,host,port,user,password will be identical.

Further, clients can be used with context managers:

with couchdb3.Server("http://user:password@127.0.0.1:5984") as client:
    # Do stuff
    ...

Getting or creating a database

dbname = "mydb"
db = client.get(dbname) if dbname in client else client.create(dbname)
print(db)
# Database: mydb

Creating a document

mydoc = {
    "_id": "mydoc-id",
    "name": "Hello",
    "type": "World"
}
print(db.save(mydoc))
# ('mydoc-id', True, '1-24fa3b3fd2691da9649dd6abe3cafc7e')

Note: Database.save requires the document to have an id (i.e. a key _id), Database.create does not.

Updating a document

To update an existing document, retrieving the revision is paramount. In the example below, dbdoc contains the key _rev and the builtin dict.update function is used to update the document before saving it.

mydoc = {
    "_id": "mydoc-id",
    "name": "Hello World",
    "type": "Hello World"
}
dbdoc = db.get(mydoc["_id"])
dbdoc.update(mydoc)
print(db.save(dbdoc))
# ('mydoc-id', True, '2-374aa8f0236b9120242ca64935e2e8f1')

Alternatively, one can use Database.rev to fetch the latest revision and overwrite the document

mydoc = {
    "_id": "mydoc-id",
    "_rev": db.rev("mydoc-id"),
    "name": "Hello World",
    "type": "Hello World"
}
print(db.save(mydoc))
# ('mydoc-id', True, '3-d56b14b7ffb87960b51d03269990a30d')

Deleting a document

To delete a document, the docid and rev are needed

docid = "mydoc-id"
print(db.delete(docid=docid, rev=db.rev(docid)))  # Fetch the revision on the go
# True

Fetching documents

# Fetch a single document (returns None if not found)
doc = db.get("mydoc-id")

# Subscript shorthand (raises KeyError if not found)
doc = db["mydoc-id"]

# Fetch all documents (metadata only)
result = db.all_docs()  # ViewResult

# Fetch all documents with full bodies
result = db.all_docs(include_docs=True)
for row in result.rows:
    print(row.id, row.doc)

# Fetch specific documents by ID
result = db.all_docs(keys=["id-1", "id-2"], include_docs=True)

# Batch fetch by ID
result = db.bulk_get(docs=[{"id": "id-1"}, {"id": "id-2"}])
for item in result:
    print(item["id"], item["docs"][0]["ok"])

Views

# 1. Create a design document with a map function
db.put_design("my-ddoc", views={
    "my-view": {
        "map": "function(doc) { if (doc.type === 'post') emit(doc._id, null); }"
    }
})

# 2. Query the view
result = db.view("my-ddoc", "my-view")                        # ViewResult
result = db.view("my-ddoc", "my-view", include_docs=True, limit=10)

# 3. Iterate results
for row in result.rows:
    print(row.id, row.key, row.value)

Mango queries

# 1. Create an index
db.save_index({"fields": ["type", "name"]}, ddoc="my-ddoc", name="type-name-idx")

# 2. Query with a selector
result = db.find({"type": {"$eq": "post"}}, fields=["_id", "name"], limit=10)
for doc in result["docs"]:
    print(doc)

# 3. Inspect the query plan
plan = db.explain({"type": {"$eq": "post"}}, limit=10)

Working with partitions

For a partitioned database, the couchdb3.sync.Partition class offers a wrapper around partitions (acting similarly to collections in Mongo).

from couchdb3.sync import Server, Database, Partition


client: Server = Server(...)
db: Database = client["some-db"]
partition: Partition = db.get_partition("partition_id")

Partition instances append the partition's ID to the document IDs (partition-id:doc-id) for a simpler user interaction, e.g.

doc_id = "test-id"
print(doc_id in partition)  # no need to append the partition's ID
rev = partition.rev(doc_id)
partition.save({
    "_id": doc_id,  # no need to append the partition's ID
    "_rev": rev,
    ...
})

The partition ID will only be appended provided document IDs do not start with partition-id, e.g. the following will work and be equivalent to the previous example

doc_id = "partition_id:test-id"
print(doc_id in partition)
rev = partition.rev(doc_id)
partition.save({
    "_id": doc_id,
    "_rev": rev,
    ...
})

Async client

couchdb3 ships an async client built on httpx.AsyncClient. It mirrors the sync API exactly, with async def methods and async with context manager support.

Connecting to a database server

import asyncio
from couchdb3.aio import AsyncServer

async def main():
    async with AsyncServer("http://user:password@127.0.0.1:5984") as client:
        print(await client.up())
        # True

asyncio.run(main())

user and password can also be passed as keyword parameters, and the manual lifecycle is also supported via await client.aclose():

client = AsyncServer(
    "127.0.0.1:5984",
    user="user",
    password="password"
)
# ... do stuff ...
await client.aclose()

Note: AsyncServer does not implement __getitem__ — Python does not allow __getitem__ to be a coroutine. Use await client.get(name) instead of client[name].

Getting or creating a database

async with AsyncServer("http://user:password@127.0.0.1:5984") as client:
    all_dbs = await client.all_dbs()
    dbname = "mydb"
    db = await client.get(dbname) if dbname in all_dbs else await client.create(dbname)
    print(db)
    # AsyncDatabase: mydb

Creating a document

async with AsyncServer("http://user:password@127.0.0.1:5984") as client:
    db = await client.get("mydb")
    mydoc = {
        "_id": "mydoc-id",
        "name": "Hello",
        "type": "World"
    }
    print(await db.save(mydoc))
    # ('mydoc-id', True, '1-24fa3b3fd2691da9649dd6abe3cafc7e')

Updating a document

mydoc = {
    "_id": "mydoc-id",
    "_rev": await db.rev("mydoc-id"),
    "name": "Hello World",
    "type": "Hello World"
}
print(await db.save(mydoc))
# ('mydoc-id', True, '2-374aa8f0236b9120242ca64935e2e8f1')

Deleting a document

docid = "mydoc-id"
print(await db.delete(docid=docid, rev=await db.rev(docid)))
# True

Fetching documents

# Fetch a single document (returns None if not found)
doc = await db.get("mydoc-id")

# Fetch all documents with full bodies
result = await db.all_docs(include_docs=True)
for row in result.rows:
    print(row.id, row.doc)

# Batch fetch by ID
result = await db.bulk_get(docs=[{"id": "id-1"}, {"id": "id-2"}])
for item in result:
    print(item["id"], item["docs"][0]["ok"])

Views

# 1. Create a design document with a map function
await db.put_design("my-ddoc", views={
    "my-view": {
        "map": "function(doc) { if (doc.type === 'post') emit(doc._id, null); }"
    }
})

# 2. Query the view
result = await db.view("my-ddoc", "my-view")
result = await db.view("my-ddoc", "my-view", include_docs=True, limit=10)

# 3. Iterate results
for row in result.rows:
    print(row.id, row.key, row.value)

Mango queries

# 1. Create an index
await db.save_index({"fields": ["type", "name"]}, ddoc="my-ddoc", name="type-name-idx")

# 2. Query with a selector
result = await db.find({"type": {"$eq": "post"}}, fields=["_id", "name"], limit=10)
for doc in result["docs"]:
    print(doc)

# 3. Inspect the query plan
plan = await db.explain({"type": {"$eq": "post"}}, limit=10)

Controlling concurrency

httpx.AsyncClient pools connections internally. For application-level concurrency control use asyncio.Semaphore, and optionally tune the connection pool via httpx.Limits:

import asyncio
import httpx
from couchdb3.aio import AsyncServer

# Limit to 10 concurrent CouchDB operations
sem = asyncio.Semaphore(10)

async def fetch(db, docid):
    async with sem:
        return await db.get(docid)

# Optionally tune the underlying connection pool
client = AsyncServer(
    "http://user:password@127.0.0.1:5984",
    session=httpx.AsyncClient(
        limits=httpx.Limits(max_connections=20, max_keepalive_connections=10)
    ),
)

Working with async partitions

from couchdb3.aio import AsyncServer, AsyncDatabase, AsyncPartition

async with AsyncServer("http://user:password@127.0.0.1:5984") as client:
    db: AsyncDatabase = await client.get("some-db")
    partition: AsyncPartition = await db.get_partition("partition_id")

    doc_id = "test-id"
    await partition.save({
        "_id": doc_id,  # no need to append the partition's ID
        "type": "example"
    })
    doc = await partition.get(doc_id)

Release files for couchdb3 3.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for couchdb3 3.2.1
File Size Uploaded
couchdb3-3.2.1.tar.gz 40.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for couchdb3 3.2.1
File Interpreter ABI Platform
couchdb3-3.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 91.5 kB

Release files / couchdb3-3.2.1.tar.gz

Download URL couchdb3-3.2.1.tar.gz
Size 40.4 kB
Tags Source
SHA-256 checksum
How to use checksums
438b100b8e2f95fdc909852643f434f5bd17b04dd709d03a65c7bb9641428240
BLAKE2b-256 checksum
How to use checksums
b9e2200dafa01058147161e09e1e379704b2ce319ddbc9734dd9efc488afbc87
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / couchdb3-3.2.1-py3-none-any.whl

Download URL couchdb3-3.2.1-py3-none-any.whl
Size 51.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
403d3da6abda1c7bb815ea9f2730b8c5e97512aa981500ada2cca0b0d6cc7eee
BLAKE2b-256 checksum
How to use checksums
6ec663266518680ffb3be2e98928407e4f5c84177b2a96827452cf14fa6d7c5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release history Release notifications | RSS feed

3.4.4

2 release files

3.4.3

2 release files

3.4.2

2 release files

3.4.1

2 release files

3.4.0

2 release files

3.3.1

2 release files

3.3.0

2 release files

This release

3.2.1 This release

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.4

2 release files

3.0.3

2 release files

3.0.2

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.3.0

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page