Metadata-Version: 2.5
Name: dittolive-ditto
Version: 5.2.0.dev0
Summary: Asyncio-native Python SDK for Ditto
Author: DittoLive Incorporated
License: Ditto Binary License
        
        Copyright © 2025 DittoLive Incorporated. All rights reserved.
        
        NOTICE: All information contained herein is, and remains the property of
        DittoLive. The intellectual and technical concepts contained herein are
        proprietary to DittoLive and may be covered by U.S. and Foreign Patents,
        patents in process, and are protected by trade secret and copyright law.
        
        Redistribution and use in binary form, with or without modification, is
        permitted provided that the following conditions are met:
        
        1. You agree not to attempt to decompile, disassemble, reverse engineer or
        otherwise discover the source code from which the binary code was derived.
        
        2. Redistributions in binary form must reproduce the above copyright notice,
        this list of conditions and the following disclaimer in the documentation
        and/or other materials provided with the distribution.
        
        3. Neither the name of the copyright holder nor the names of its
        contributors may be used to endorse or promote products derived from this
        software without specific prior written permission.
        
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
        ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
        LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
        CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
        SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
        INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
        CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
        ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        POSSIBILITY OF SUCH DAMAGE.
License-File: LICENSE.md
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cbor2<6,>=5.6
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == 'dev'
Requires-Dist: hatchling>=1.27; extra == 'dev'
Requires-Dist: mypy>=1.15; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.11; extra == 'dev'
Provides-Extra: docs
Requires-Dist: pdoc==16.0.0; extra == 'docs'
Description-Content-Type: text/markdown

# Ditto Python SDK

An asyncio-native Python binding for [Ditto](https://ditto.com) — a cross-platform,
peer-to-peer database that syncs data **with and without** internet connectivity.
Install it, read and write with DQL, and Ditto automatically syncs changes to other
devices over Bluetooth LE, P2P Wi-Fi, LAN, and the cloud.

The distribution is named `dittolive-ditto`; you import it as `ditto`.

[![Docs](https://img.shields.io/badge/Docs-4285F4?logo=googledocs&logoColor=white)](https://ditto.com/link/docs)
[![DQL](https://img.shields.io/badge/DQL-000000?logo=sqlite&logoColor=white)](https://docs.ditto.live/dql/dql)
[![PyPI](https://img.shields.io/pypi/v/dittolive-ditto?logo=pypi&logoColor=white)](https://pypi.org/project/dittolive-ditto/)
[![Portal](https://img.shields.io/badge/Portal-5865F2?logo=cloudflare&logoColor=white)](https://portal.ditto.live)

> **Public Preview.** This is an early preview release. The API may change before
> a stable release, and it is published as a pre-release — see **Installation**.

## Installation

```bash
python3 -m pip install --pre dittolive-ditto
```

`--pre` is required while the SDK is in preview (the published versions are
pre-releases such as `5.2.0.dev0`). Requires **Python 3.10+**.

Published wheels are platform-specific and **bundle the matching native library
(`libdittoffi`)** — there is nothing else to install or build.
<!-- Maintainers: keep this platform list in sync with the wheel build matrix in
     .github/workflows/python-sdk-publish.yml. -->
Preview wheels are currently published for macOS on Apple Silicon and Linux on
x86_64 and arm64 (`manylinux_2_35` — glibc 2.35+); more platforms are on the way.

## Getting Started

`Ditto.open(...)` is both awaitable and an async context manager. The context
manager form closes the peer for you:

```python
import asyncio
from ditto import Ditto, DittoConfig, DittoConfigConnect


async def main() -> None:
    config = DittoConfig(
        database_id="your-database-id",
        connect=DittoConfigConnect.small_peers_only(),
        persistence_directory="./ditto-data",
    )

    async with Ditto.open(config) as peer:
        await peer.store.execute(
            "INSERT INTO cars DOCUMENTS (:car)",
            {"car": {"_id": "car1", "make": "Tesla", "color": "red"}},
        )
        with await peer.store.execute("SELECT * FROM cars") as result:
            for item in result:
                print(item.value)


asyncio.run(main())
```

The equivalent explicit form (`close()` is async because shutdown may wait for
native work):

```python
peer = await Ditto.open(config)
try:
    ...
finally:
    await peer.close()
```

`DittoConfigConnect.small_peers_only()` runs fully offline / local + peer-to-peer.
To sync through a Ditto server (Big Peer), use `DittoConfigConnect.server(...)` —
see [Sync](#sync) and [Connecting to a Ditto Server](#connecting-to-a-ditto-server).

## Store and Queries (DQL)

All reads and writes go through [DQL](https://docs.ditto.live/dql/dql), executed on
`peer.store`. `execute()` is `async` and returns a `QueryResult` you iterate for
document values. The snippets below run inside the `async with Ditto.open(config)
as peer:` block from [Getting Started](#getting-started); each result owns native
resources, so wrap it in `with await …:` (as the read below does) when you keep it:

```python
# Create / upsert
await peer.store.execute(
    "INSERT INTO cars DOCUMENTS (:car) ON ID CONFLICT DO UPDATE",
    {"car": {"_id": "car1", "make": "Tesla", "color": "red"}},
)

# Update
await peer.store.execute(
    "UPDATE cars SET color = :color WHERE _id = :id",
    {"color": "blue", "id": "car1"},
)

# Read
with await peer.store.execute("SELECT * FROM cars WHERE color = :color",
                              {"color": "blue"}) as result:
    cars = [item.value for item in result]

# Delete: writes a tombstone that propagates removal to peers
# (use EVICT instead to drop a document only from the local store)
await peer.store.execute("DELETE FROM cars WHERE _id = :id", {"id": "car1"})
```

### Reactive Observers

Register an observer to be called with the current result set whenever documents
matching a query change locally or arrive from a peer:

```python
def on_change(result):
    for item in result:
        print("cars changed:", item.value)


observer = peer.store.register_observer("SELECT * FROM cars", on_change)
# ... later, to stop receiving updates (close() alone does not stop delivery):
observer.cancel()
```

The handler may be a sync or async function, and is delivered on the event loop
that registered it.

## Sync

Sync is off until you start it. `peer.sync.start()` brings up the transports;
`register_subscription(...)` tells Ditto which documents to sync from other peers
(the argument must be a `SELECT` query):

```python
peer.sync.start()
subscription = peer.sync.register_subscription("SELECT * FROM cars")
# ... later:
subscription.cancel()
```

Syncing with other peers or a Ditto server requires a license. For offline /
small-peers-only use you can set an offline token obtained from the
[Ditto Portal](https://portal.ditto.live):

```python
peer.set_offline_only_license_token("your-offline-license-token")
```

### Connecting to a Ditto Server

Connecting through a server (`DittoConfigConnect.server(...)`) requires setting an
authentication expiration handler before starting sync — otherwise `sync.start()`
raises `DittoExpirationHandlerMissingError`:

```python
from datetime import timedelta
from ditto import Ditto, DittoConfig, DittoConfigConnect

config = DittoConfig(
    database_id="your-database-id",
    connect=DittoConfigConnect.server("https://your-app.cloud.ditto.live"),
)

async with Ditto.open(config) as peer:
    def on_expiring(ditto: Ditto, remaining: timedelta) -> None:
        # Obtain a fresh token and call `ditto.auth.login(...)`.
        ...

    peer.auth.expiration_handler = on_expiring
    peer.sync.start()
```

## Also Available

The SDK surfaces the rest of Ditto's v5 API through the same peer. All of these
are importable from the top-level `ditto` package:

- **Presence** — observe the live mesh with `peer.presence.register_observer(handler)`,
  where `handler(graph: PresenceGraph)` is called on each change.
- **Transactions** — group reads/writes with `peer.store.transaction(...)`.
- **Attachments** — store and fetch large binaries via `peer.store.new_attachment(...)`
  and `peer.store.fetch_attachment(...)`.
- **Transports** — configure Bluetooth LE, LAN, AWDL, Wi-Fi Aware, and WebSocket via
  `DittoConfig` / the `transport_config` types (`BluetoothLEConfig`, `DittoLanConfig`,
  `WifiAwareConfig`, …).
- **Disk usage, logging, and typed errors** — `DiskUsageObserver`, `DittoLogger` /
  `LogLevel`, and the `Ditto*Error` hierarchy.

See the [Ditto documentation](https://ditto.com/link/docs) for concepts (mesh
networking, data handling, sync) and the [DQL reference](https://docs.ditto.live/dql/dql).

## Native Library Discovery

Wheel installs need no configuration. If you are working from a **source checkout**
instead, point the loader at a compatible `libdittoffi` with `DITTOFFI_LIB_PATH` (the
file or a directory containing it) or `DITTOFFI_SEARCH_PATH` (platform-separated
directories). Importing `ditto` never loads the native library; it loads on first
use of a native API.

## Concurrency and Resource Ownership

- Native callbacks are delivered on the asyncio event loop that registered them;
  handlers may be sync or async.
- Result and observer objects own native resources — close them promptly or use
  their context-manager support. `Ditto.close()` is async.
- Ditto's native runtime and logger hold process-global state, so tests must run
  sequentially (`pytest-xdist` is intentionally unsupported).
- A Ditto handle must not cross a process fork. Use
  `multiprocessing.get_context("spawn")` and open a fresh `Ditto` in each worker.

## Resources

- 📖 [Documentation](https://ditto.com/link/docs)
- 🔎 [DQL Reference](https://docs.ditto.live/dql/dql)
- 🧭 [Ditto Portal](https://portal.ditto.live)
- 📦 [PyPI](https://pypi.org/project/dittolive-ditto/)

## License

Ditto is commercial software. See [ditto.com](https://ditto.com) for licensing.
