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).
  • Django settings integration for seamless configuration in Django projects.
  • 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, with optional overrides from Django settings:

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

Django settings take precedence over environment variables when Django is available.

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())

When Django is installed, NotFoundError is a subclass of django.core.exceptions.ObjectDoesNotExist, making it compatible with Django's error handling patterns.

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.0.0.tar.gz (25.4 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.0.0-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for pyxapiand-2.0.0.tar.gz
Algorithm Hash digest
SHA256 ec7a339fff3938b98e053d2165a68da35c8216341fdea5699012e78796651e9e
MD5 c9ce2136326d9a1f5e68e1070afcc027
BLAKE2b-256 b3f3c9ba64ee8c2616505873ee90a2273ba7b43425b8b9fdef062c7a9521ccd8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyxapiand-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 18.7 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.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d2c86af68018946d2ffe5caf7522c1632c7a69b440c36e35005b32262770f72b
MD5 19bd1dabdb142296b2ed262670134141
BLAKE2b-256 520d1089475f3ee0caba963a8ab4fadcf8a5332f6fd4dc9fbd6eea2d2496510f

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