Skip to main content

Python client library for the Xapiand search engine

Project description

Xapiand Python Client

Async Python client library for Xapiand, a RESTful search engine built on top of Xapian.

Features

  • Fully async — built on httpx.AsyncClient for native asyncio support.
  • Full coverage of Xapiand REST operations: search, get, post, put, patch, merge, delete, store, stats, and head.
  • Automatic serialization/deserialization with JSON and msgpack (preferred when available).
  • Attribute-style access on response objects (result.hits instead of result['hits']).
  • Custom HTTP methods (MERGE, STORE) supported natively via httpx.

Requirements

Installation

pip install pyxapiand

With optional msgpack support (recommended for better performance):

pip install pyxapiand[msgpack]

For development (editable install):

git clone https://github.com/Dubalu-Development-Team/pyxapiand.git
cd pyxapiand
pip install -e ".[msgpack,test]"

Python version with pyenv

This project includes a .python-version file that pins Python 3.12. If you use pyenv, make sure you have a 3.12.x version installed:

pyenv install 3.12

This installs the latest available 3.12.x release. pyenv will then automatically select it when you enter the project directory.

Quick Start

import asyncio
from xapiand import Xapiand

async def main():
    client = Xapiand(host="localhost", port=8880)

    # Index a document
    await client.put("books", body={"title": "The Art of Search", "year": 2024}, id="book1")

    # Retrieve a document
    doc = await client.get("books", id="book1")
    print(doc.title)  # "The Art of Search"

    # Search
    results = await client.search("books", query="search")
    for hit in results.hits:
        print(hit.title)

    # Delete a document
    await client.delete("books", id="book1")

asyncio.run(main())

A pre-configured module-level client singleton is also available:

from xapiand import client

results = await client.search("myindex", query="hello world")
print(results.count)  # total count
print(results.total)  # estimated matches

Configuration

The client reads configuration from environment variables:

Environment Variable Default Description
XAPIAND_HOST 127.0.0.1 Server hostname
XAPIAND_PORT 8880 Server port
XAPIAND_COMMIT False Auto-commit write operations
XAPIAND_PREFIX default URL prefix prepended to index paths

Client initialization

You can also pass configuration directly when creating a client:

client = Xapiand(
    host="192.168.1.100",
    port=8880,
    commit=True,          # auto-commit writes
    prefix="production",  # URL prefix for index paths
)

The host parameter accepts a host:port format ("192.168.1.100:9000"), in which case the port part overrides the port parameter.

API Reference

All API methods are async and return DictObject instances, which are dictionaries with attribute-style access.

Search

results = await client.search(
    "myindex",
    query="hello world",    # query string
    partial="hel",          # partial query for autocomplete
    terms="tag:python",     # term filters
    offset=0,               # starting offset
    limit=10,               # max results
    sort="date",            # sort field
    language="en",          # query language
    check_at_least=100,     # minimum documents to check
)

results.hits    # list of matching documents
results.count   # total count
results.total   # estimated matches

Search with a request body (uses POST internally):

results = await client.search("myindex", body={
    "_query": {"title": "search engine"},
})

Count

results = await client.count("myindex", query="hello")
print(results.count)

Get

doc = await client.get("myindex", id="doc1")

# With a default value (returns it instead of raising on 404)
doc = await client.get("myindex", id="doc1", default=None)

Create (POST)

# Server-assigned ID
result = await client.post("myindex", body={"title": "New Document"})

Create or Replace (PUT)

result = await client.put("myindex", body={"title": "My Document"}, id="doc1")

# Alias
result = await client.index("myindex", body={"title": "My Document"}, id="doc1")

Partial Update (PATCH)

result = await client.patch("myindex", id="doc1", body={"title": "Updated Title"})

Deep Merge (MERGE)

Uses Xapiand's custom MERGE HTTP method for deep-merging fields:

result = await client.merge("myindex", id="doc1", body={"metadata": {"tags": ["new"]}})

Update

Chooses strategy based on content type:

# Partial merge (default)
await client.update("myindex", id="doc1", body={"title": "Updated"})

# Full replacement when content_type is specified
await client.update("myindex", id="doc1", body=data, content_type="application/json")

Delete

await client.delete("myindex", id="doc1")

Store

Store binary content using Xapiand's custom STORE HTTP method:

await client.store("myindex", id="doc1", body="/path/to/file.bin")

Head

Check if a document exists:

await client.head("myindex", id="doc1")

Stats

stats = await client.stats("myindex")

Common Parameters

Most methods accept these optional parameters:

Parameter Type Description
commit bool Commit changes immediately (write ops)
pretty bool Request pretty-printed response
volatile bool Bypass caches, read from disk
kwargs dict Additional arguments passed to httpx

Error Handling

import asyncio
from xapiand import Xapiand, TransportError

async def main():
    client = Xapiand()

    # NotFoundError on missing documents
    try:
        doc = await client.get("myindex", id="nonexistent")
    except client.NotFoundError:
        print("Document not found")

    # TransportError (httpx.HTTPStatusError) on other HTTP errors
    try:
        await client.search("myindex", query="test")
    except TransportError as e:
        print(f"HTTP error: {e}")

asyncio.run(main())

Utilities

xapiand.collections

Dict subclasses with attribute-style access:

from xapiand.collections import DictObject, OrderedDictObject

obj = DictObject(name="test", value=42)
obj.name      # "test"
obj["value"]  # 42

xapiand.constants

Predefined Xapian term constants for configuring index schema accuracy:

from xapiand.constants import (
    # Date accuracy
    HOUR_TERM, DAY_TERM, MONTH_TERM, YEAR_TERM,
    DAY_TO_YEAR_ACCURACY,     # [day, month, year]
    HOUR_TO_YEAR_ACCURACY,    # [hour, day, month, year]

    # Geospatial accuracy (HTM levels)
    LEVEL_0_TERM,             # ~10,000 km
    LEVEL_10_TERM,            # ~10 km
    LEVEL_20_TERM,            # ~10 m
    STATE_TO_BLOCK_ACCURACY,  # [level_5, level_10, level_15]

    # Numeric accuracy
    TENS_TO_TEN_THOUSANDS_ACCURACY,
    HUDREDS_TO_MILLIONS_ACCURACY,
)

xapiand.utils

Xapian-compatible binary serialization for lengths, strings, and characters:

from xapiand.utils import serialise_length, unserialise_length

encoded = serialise_length(42)
length, remaining = unserialise_length(encoded)
assert length == 42

Migrating from v1.x

v2.0 replaces requests with httpx and makes all API methods async. Key changes:

  • All client methods require awaitclient.search(...) becomes await client.search(...).
  • Dependency changedrequests replaced by httpx.
  • Session class removedhttpx.AsyncClient handles custom HTTP methods natively.
  • TransportError — now aliases httpx.HTTPStatusError instead of requests.HTTPError.
  • No allow_redirects kwarg — redirects are disabled at the client level.

License

MIT License - Copyright (c) 2015-2026 Dubalu LLC

See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyxapiand-2.1.0.tar.gz (27.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyxapiand-2.1.0-py3-none-any.whl (19.1 kB view details)

Uploaded Python 3

File details

Details for the file pyxapiand-2.1.0.tar.gz.

File metadata

  • Download URL: pyxapiand-2.1.0.tar.gz
  • Upload date:
  • Size: 27.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pyxapiand-2.1.0.tar.gz
Algorithm Hash digest
SHA256 6c92e38d32abfaa6133080c02e7d8c7b54dff8cea5883236e85428255506096e
MD5 02f6064c9ece225e7c53c014e4e9f041
BLAKE2b-256 07141ac995077b3c076aa00c441c7f186219ea056ab36c5b7a701f0f526396ec

See more details on using hashes here.

File details

Details for the file pyxapiand-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyxapiand-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pyxapiand-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7b05f3e9418cdbc857647818ecacb97c339a4263524b14cd01def2121dd132ba
MD5 7603195e41581854fbf7fe326501cc2f
BLAKE2b-256 00132abb26ab8d4bc2aeac6b37ccba9604533b4e4c955da91181e1062e570f8e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page