Metadata-Version: 2.4
Name: memydev-base-sdk
Version: 0.2.0
Summary: Official Python client for MemyBase, the hosted database for apps and AI agents.
Project-URL: Homepage, https://base.memy.dev
Project-URL: Documentation, https://base.memy.dev/docs/sdk-getting-started
Project-URL: Trust Center, https://base.memy.dev/trust
License: Proprietary. See LICENSE.
License-File: LICENSE
Keywords: ai-agents,audit,backend-as-a-service,database,dbaas,graphql,mcp,memybase,realtime,rest
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database :: Front-Ends
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Provides-Extra: dev
Requires-Dist: editables==0.5; extra == 'dev'
Requires-Dist: hatchling==1.27.0; extra == 'dev'
Requires-Dist: pytest-asyncio==0.24.0; extra == 'dev'
Requires-Dist: pytest==8.3.5; extra == 'dev'
Requires-Dist: respx==0.22.0; extra == 'dev'
Requires-Dist: unasync==0.6.0; extra == 'dev'
Description-Content-Type: text/markdown

# memydev-base-sdk — Official MemyBase Python SDK

Async-first (with sync wrapper) client for the [MemyBase](https://base.memy.dev) hosted data service.
Mirrors the JS `@memydev/base-sdk` selector→handle API: `mb.collection(slug, project=…, database=…)`.

- Getting started: https://base.memy.dev/docs/sdk-getting-started
- What the service enforces, and the status of every capability: https://base.memy.dev/docs/guarantees-and-status
- Controls and the assurance roadmap: https://base.memy.dev/trust

## Install

Published on PyPI as [`memydev-base-sdk`](https://pypi.org/project/memydev-base-sdk/). Pin the
version explicitly in application configuration:

```bash
python -m pip install "memydev-base-sdk==0.2.0"
```

The distribution name is `memydev-base-sdk`; the import name is `memybase`:

```python
from memybase import MemyBase
```

Do not install this SDK from a workspace path, local wheel, `file://`, or a vendored copy in
subscriber applications — the published package is the supported install. A hosted subscription
provides the MemyBase service and SDK integration surface; it does not deliver
runtime/source/MongoDB components.

Release validation produces both the wheel and source distribution through Hatchling, verifies both with
`twine check`, and installs the wheel into a clean venv from PyPI. The
package includes `py.typed` and both async `memybase.MemyBase` and sync `memybase._sync.MemyBase` surfaces.

## Usage

```python
from memybase import MemyBase

mb = MemyBase("https://base.memy.dev", api_key="…")           # or token=… / user_token=…
users = mb.collection("users", project="acme", database="prod")

rec  = await users.create({"name": "Ada"})                    # omit None keys (reserved-field safe)
page = await users.list(filter={"active": True}, page=1, page_size=50)
one  = await users.get(rec["_id"])
await users.update(rec["_id"], {"name": "Ada L."})
await users.soft_delete(rec["_id"], reason="gdpr")
```

Sync facade: `from memybase.sync import MemyBase` (mechanically generated from the async core via
`scripts/gen_sync.py` — single source, no drift; CRUD + customer management only).

Retry policy mirrors the JS SDK: `retry={"max_retries": 2, "base_delay": 0.5,
"retryable_statuses": []}` by default. Extra retryable statuses apply only to reads; writes stay limited
to 429/503. The legacy constructor shortcut `max_retries=` is still accepted as an alias for
`retry["max_retries"]`.

### The `MemyBaseClient` facade convention (recommended)

Do **not** spray `MemyBase(...)` construction and `except NotFoundError/…` handling across your codebase.
The memy convention — shared by **every** MemyBase consumer — is a **single-seam `MemyBaseClient` facade**:
ONE module wraps ONE `MemyBase` instance, and the rest of the app depends on that facade (never on
`@memydev/base-sdk` / `memybase` directly). This keeps the SDK swappable, centralizes error translation, and
makes call sites read **identically across languages**:

```python
# your_app/data/memybase/client.py — the ONLY module that imports `memybase`
from memybase import MemyBase

class MemyBaseClient:                       # ← the canonical name (same in JS + memybase itself)
    def __init__(self, base_url: str, api_key: str, project: str, db: str) -> None:
        self._mb = MemyBase(base_url, api_key=api_key)
        self._project, self._db = project, db

    def collection(self, slug: str):
        return self._mb.collection(slug, project=self._project, database=self._db)
    # + None-on-404 reads, bool-returning deletes, a `normalize_filter` chokepoint, SDK→app error
    #   translation — whatever adaptations the SDK does not provide.

client = MemyBaseClient(base_url, api_key, project="acme", db="prod")   # usage reads the same everywhere
```

The **same decision** is implemented in every project: `class MemyBaseClient` in memyswarm
(`data/memybase/client.py`) and this SDK's Python peer; `class MemyBaseClient` in memyui
(`apps/server/src/memybase/client.ts`); and `class MemyBaseClient` in memybase's own stdio-MCP consumer
(`src/mcp/memybase-client.ts`). Reference implementation: memyswarm's `MemyBaseClient`.

> A **producer/engine** (the tier holding the DB connection) is the ONE exception: in-process code binds
> the data services directly and must NOT loop back through the SDK. `MemyBaseClient` is for **consumers**
> — anything reaching the engine over the wire (apps, workers, MCP-over-stdio, a separate frontend).

### Realtime (SSE)

Subscribe to live change events over Server-Sent Events (async only — SSE is inherently async; the sync
facade does not expose it). The callback fires for `ready` / `change` / `heartbeat` / `resync` events;
the client auto-reconnects with backoff and resumes via `Last-Event-ID`.

```python
async with MemyBase("https://base.memy.dev", api_key="…") as mb:
    def on_event(e):        # e: StreamEvent(event, data, id)
        if e.event == "change":
            print(e.data["type"], e.data["entity"], e.data["id"])

    sub = await mb.collection("users", project="acme", database="prod").subscribe(on_event)
    # … or all entities: sub = await mb.stream("acme", "prod", on_event)
    await sub.wait()        # run until closed / terminal
    await sub.close()       # stop + drain
```

## Surface

- **Data plane** — `collection(slug, project=, database=)` → list/get/create/update/soft_delete/restore/versions/audit/hard_delete
- **Realtime** — `collection(…).subscribe(on_event)` / `mb.stream(project, database, on_event)` → SSE (async only): `RealtimeClient`, `StreamEvent`, `SubscribeOptions`, `Subscription`
- **Customer management** — `SelfService` for projects, databases, schema, credentials, settings, plans, usage, and lifecycle
- **GraphQL** — `mb.graphql(project=, database=)`
- **Filters** — `FilterBuilder`, `OPERATORS`, `serialize_filter`
- **Errors** — typed hierarchy (`MemyBaseError` + `Conflict`/`NotFound`/`Validation`/`RateLimited`/… `decode_error`)
- **Helpers** — `with_conflict_retry`, `guard_reserved_fields`, `omit_none`, `RESERVED_FIELDS`

Reserved fields (`_id`, `createdAt`, `updatedAt`, …) are never sent on write; string fields are non-nullable — omit a key rather than sending `null`.

## Versioning

`memybase.__version__` tracks `SDK_VERSION`. Vendored artifacts are regenerated by `memybase/scripts/sdk-pack.sh` (packs this wheel + the JS tgz into their consumer repos).

## Licence

Proprietary. See [LICENSE](LICENSE). This package is for use with a MemyBase account; the source is not published.
