memydev-base-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
memybase/_types.py ADDED
@@ -0,0 +1,91 @@
1
+ """
2
+ @fileoverview Structured wire-response shapes hand-mirrored from the TypeScript SDK (sdk/js/src/types.ts).
3
+ @module memybase._types
4
+ @description Pure/shared module (like _errors.py/_mapping.py/_filter.py — NOT in gen_sync.py's
5
+ ASYNC_MODULES, so it has no _sync twin and both _self.py and its generated twin import it by the
6
+ same absolute path). Collection.list/get/... deliberately keep returning `dict[str, Any]` (this SDK
7
+ does not transform data-plane response payloads), so these TypedDicts are not new return-type
8
+ signatures — they are IMPORTABLE SHAPES a caller can use to type-annotate or `cast()` the raw dict
9
+ they already receive, the same role `RetryConfig` (_transport.py) plays for request options. Field
10
+ names mirror the wire JSON verbatim (camelCase), not Python's snake_case convention, because they
11
+ describe payloads parsed directly from server JSON rather than SDK-constructed request bodies.
12
+ @dependencies none
13
+ @relatedFiles ../../js/src/types.ts (ExpandRelationMeta, SchemaActivationJobSummary — the TS twins of
14
+ these shapes), ./_transport.py (RetryConfig — same TypedDict idiom), ./_self.py (SchemaActivationJobSummary
15
+ consumer), ./_errors.py (ValidationError.issues / RateLimitedError.retry_after — the SDK's existing
16
+ idiom for exposing structured detail out of an otherwise-untyped payload)
17
+ @created 2026-08-01
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import Literal, Optional, TypedDict
22
+
23
+ __all__ = [
24
+ "ExpandRelationMeta",
25
+ "ExpandMeta",
26
+ "SchemaActivationJobState",
27
+ "SchemaActivationJobSummary",
28
+ ]
29
+
30
+
31
+ class ExpandRelationMeta(TypedDict, total=False):
32
+ """One relation's read caveats under a record's `expandMeta` envelope — the Python mirror of the JS
33
+ SDK's `ExpandRelationMeta` (sdk/js/src/types.ts) and the server's `ExpandRelationMeta` /
34
+ `EXPAND_META_KEY` (src/data/data-access.ts:196,214-220). Written ONLY when a relation named in
35
+ `expand=` was degraded for THIS record; a nominal read carries no `expandMeta` key at all, so its
36
+ absence is the promise that every expanded relation came back complete.
37
+
38
+ Closes a real consumer hazard: a REST `expand=` request that used to reject with `400 unsafe_query`
39
+ on an oversized expansion now returns `200` carrying this envelope instead. Code that used to branch
40
+ on the 400 must check ``record.get("expandMeta")`` to detect truncated/failed relations in an
41
+ otherwise-200 response::
42
+
43
+ meta: ExpandMeta = record.get("expandMeta") or {}
44
+ if meta.get("tags", {}).get("truncated"):
45
+ ... # this record's "tags" relation was rationed, not necessarily short
46
+ """
47
+
48
+ truncated: bool
49
+ """The relation list was RATIONED: more links exist than this response carries. The list under this
50
+ flag MAY BE EMPTY — that does not mean the record has no links. Re-request with a narrower
51
+ `expand=` or a smaller page to see them."""
52
+
53
+ failed: bool
54
+ """The link read for THIS record failed; the relation is empty because of a read error, not because
55
+ the record has no links. Sibling records in the same page are unaffected — safe to retry."""
56
+
57
+
58
+ ExpandMeta = dict[str, ExpandRelationMeta]
59
+ """A record's full `expandMeta` envelope, keyed by relation field. Present only when `expand=` was
60
+ requested AND at least one named relation was degraded for that record."""
61
+
62
+
63
+ SchemaActivationJobState = Literal[
64
+ "planned", "preparing", "verifying", "activating", "completed", "failed", "rolled-back"
65
+ ]
66
+ """Mirrors the server's `SchemaActivationJobState` (@memydev/base-contract)."""
67
+
68
+
69
+ class SchemaActivationJobSummary(TypedDict):
70
+ """Customer-visible summary of a schema activation job — the response shape of
71
+ ``SelfService.get_schema_activation`` / ``.rollback_schema_activation``
72
+ (``GET/POST /api/self/.../schema/activations/{job_id}[/rollback]``).
73
+
74
+ Hand-typed to match EXACTLY what the self-service endpoint sends: `CustomerSchemaActivationJob` in
75
+ src/api/self/self-service.ts:55-57 strips the job's `compensation` block before a customer ever sees
76
+ it, so the vendored `SchemaActivationJob.compensation` — and with it `deferredIndexBuilds` /
77
+ `retiredSupersededIndexes` (@memydev/base-contract's `SchemaActivationCompensation`) — is NEVER present
78
+ on the wire here. Claiming those fields exist would silently mistype every caller; this type models
79
+ the SERVER'S ACTUAL RESPONSE, not the internal job record it was narrowed from. If a future change
80
+ surfaces activation-index caveats to customers, widen this type alongside that change.
81
+ """
82
+
83
+ _id: str
84
+ schemaVersionId: str
85
+ state: SchemaActivationJobState
86
+ stage: str
87
+ error: Optional[str]
88
+ retryCount: int
89
+ createdAt: str
90
+ updatedAt: str
91
+ completedAt: Optional[str]
memybase/py.typed ADDED
File without changes
@@ -0,0 +1,41 @@
1
+ """
2
+ @fileoverview Synchronous MemyBase client — a thin re-export of the unasync-GENERATED sync twins.
3
+ @module memybase.sync
4
+ @description The async modules (memybase/_*.py) are the single source of truth. scripts/gen_sync.py
5
+ mechanically derives the blocking twins in memybase/_sync/ (async def→def, await-strip,
6
+ httpx.AsyncClient→Client, asyncio→time, …); this facade only re-exports them so
7
+ ``from memybase.sync import MemyBase`` keeps working. Because the sync surface is generated,
8
+ it CANNOT drift from the async one — a new async method appears in the sync client on the next
9
+ gen_sync run, and tests/test_sync_parity.py + a CI ``gen_sync.py --check`` gate enforce it.
10
+ Realtime (Collection.subscribe / MemyBase.stream) is async-only (SSE is inherently async; this
11
+ sync client targets scripts / provisioning tooling with no event loop) and is deliberately
12
+ absent here — see tests/test_sync_parity.py's documented exclusion.
13
+
14
+ Usage::
15
+
16
+ with MemyBase("https://memybase.example.com", api_key="pbk_...") as mb:
17
+ customers = mb.collection("customers", project="crm", database="analytics")
18
+ page = customers.list(page_size=10)
19
+ @relatedFiles ../scripts/gen_sync.py, .._sync/, ../tests/test_sync_parity.py
20
+ @created 2026-07-04
21
+ @updated 2026-07-05 hand-written asyncio.run/ThreadPoolExecutor mirror → unasync single-source generation.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ from .._filter import FilterBuilder # shared/pure module (no async → no twin), same object as async side
26
+ from .._sync._client import MemyBase
27
+ from .._sync._collection import Collection
28
+ from .._sync._graphql import GraphQLClient
29
+ from .._sync._helpers import with_conflict_retry
30
+ from .._sync._self import SelfService
31
+ from .._sync._transport import RetryConfig
32
+
33
+ __all__ = [
34
+ "MemyBase",
35
+ "Collection",
36
+ "SelfService",
37
+ "GraphQLClient",
38
+ "FilterBuilder",
39
+ "RetryConfig",
40
+ "with_conflict_retry",
41
+ ]
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: memydev-base-sdk
3
+ Version: 0.1.0
4
+ Summary: Official MemyBase client SDK for Python
5
+ License: Proprietary and unlicensed pending approved service terms.
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: httpx<1,>=0.27
8
+ Provides-Extra: dev
9
+ Requires-Dist: editables==0.5; extra == 'dev'
10
+ Requires-Dist: hatchling==1.27.0; extra == 'dev'
11
+ Requires-Dist: pytest-asyncio==0.24.0; extra == 'dev'
12
+ Requires-Dist: pytest==8.3.5; extra == 'dev'
13
+ Requires-Dist: respx==0.22.0; extra == 'dev'
14
+ Requires-Dist: unasync==0.6.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # memydev-base-sdk — Official MemyBase Python SDK
18
+
19
+ Async-first (with sync wrapper) client for the [MemyBase](https://base.memy.dev) hosted data service.
20
+ Mirrors the JS `@memydev/base-sdk` selector→handle API: `mb.collection(slug, project=…, database=…)`.
21
+
22
+ ## Install
23
+
24
+ Install a released package from the customer-accessible Python registry configured for the hosted
25
+ MemyBase subscription. Keep the release version explicit in application configuration:
26
+
27
+ ```bash
28
+ MEMYBASE_SDK_VERSION="0.1.0"
29
+ MEMYBASE_PYTHON_INDEX_URL="<customer Python index URL>"
30
+ python -m pip install --index-url "$MEMYBASE_PYTHON_INDEX_URL" "memydev-base-sdk==$MEMYBASE_SDK_VERSION"
31
+ ```
32
+
33
+ Do not install this SDK from a workspace path, local wheel, or `file://` dependency in subscriber
34
+ applications. A hosted subscription provides the MemyBase service and SDK integration surface; it does
35
+ not deliver runtime/source/Devtron/MongoDB components.
36
+
37
+ Release validation produces both the wheel and source distribution through Hatchling, verifies both with
38
+ `twine check`, and installs the wheel into a clean venv from the configured subscriber Python index. The
39
+ package includes `py.typed` and both async `memybase.MemyBase` and sync `memybase._sync.MemyBase` surfaces.
40
+
41
+ ## Usage
42
+
43
+ ```python
44
+ from memybase import MemyBase
45
+
46
+ mb = MemyBase("https://base.memy.dev", api_key="…") # or token=… / user_token=…
47
+ users = mb.collection("users", project="acme", database="prod")
48
+
49
+ rec = await users.create({"name": "Ada"}) # omit None keys (reserved-field safe)
50
+ page = await users.list(filter={"active": True}, page=1, page_size=50)
51
+ one = await users.get(rec["_id"])
52
+ await users.update(rec["_id"], {"name": "Ada L."})
53
+ await users.soft_delete(rec["_id"], reason="gdpr")
54
+ ```
55
+
56
+ Sync facade: `from memybase.sync import MemyBase` (mechanically generated from the async core via
57
+ `scripts/gen_sync.py` — single source, no drift; CRUD + customer management only).
58
+
59
+ Retry policy mirrors the JS SDK: `retry={"max_retries": 2, "base_delay": 0.5,
60
+ "retryable_statuses": []}` by default. Extra retryable statuses apply only to reads; writes stay limited
61
+ to 429/503. The legacy constructor shortcut `max_retries=` is still accepted as an alias for
62
+ `retry["max_retries"]`.
63
+
64
+ ### The `MemyBaseClient` facade convention (recommended)
65
+
66
+ Do **not** spray `MemyBase(...)` construction and `except NotFoundError/…` handling across your codebase.
67
+ The memy convention — shared by **every** MemyBase consumer — is a **single-seam `MemyBaseClient` facade**:
68
+ ONE module wraps ONE `MemyBase` instance, and the rest of the app depends on that facade (never on
69
+ `@memydev/base-sdk` / `memybase` directly). This keeps the SDK swappable, centralizes error translation, and
70
+ makes call sites read **identically across languages**:
71
+
72
+ ```python
73
+ # your_app/data/memybase/client.py — the ONLY module that imports `memybase`
74
+ from memybase import MemyBase
75
+
76
+ class MemyBaseClient: # ← the canonical name (same in JS + memybase itself)
77
+ def __init__(self, base_url: str, api_key: str, project: str, db: str) -> None:
78
+ self._mb = MemyBase(base_url, api_key=api_key)
79
+ self._project, self._db = project, db
80
+
81
+ def collection(self, slug: str):
82
+ return self._mb.collection(slug, project=self._project, database=self._db)
83
+ # + None-on-404 reads, bool-returning deletes, a `normalize_filter` chokepoint, SDK→app error
84
+ # translation — whatever adaptations the SDK does not provide.
85
+
86
+ client = MemyBaseClient(base_url, api_key, project="acme", db="prod") # usage reads the same everywhere
87
+ ```
88
+
89
+ The **same decision** is implemented in every project: `class MemyBaseClient` in memyswarm
90
+ (`data/memybase/client.py`) and this SDK's Python peer; `class MemyBaseClient` in memyui
91
+ (`apps/server/src/memybase/client.ts`); and `class MemyBaseClient` in memybase's own stdio-MCP consumer
92
+ (`src/mcp/memybase-client.ts`). Reference implementation: memyswarm's `MemyBaseClient`.
93
+
94
+ > A **producer/engine** (the tier holding the DB connection) is the ONE exception: in-process code binds
95
+ > the data services directly and must NOT loop back through the SDK. `MemyBaseClient` is for **consumers**
96
+ > — anything reaching the engine over the wire (apps, workers, MCP-over-stdio, a separate frontend).
97
+
98
+ ### Realtime (SSE)
99
+
100
+ Subscribe to live change events over Server-Sent Events (async only — SSE is inherently async; the sync
101
+ facade does not expose it). The callback fires for `ready` / `change` / `heartbeat` / `resync` events;
102
+ the client auto-reconnects with backoff and resumes via `Last-Event-ID`.
103
+
104
+ ```python
105
+ async with MemyBase("https://base.memy.dev", api_key="…") as mb:
106
+ def on_event(e): # e: StreamEvent(event, data, id)
107
+ if e.event == "change":
108
+ print(e.data["type"], e.data["entity"], e.data["id"])
109
+
110
+ sub = await mb.collection("users", project="acme", database="prod").subscribe(on_event)
111
+ # … or all entities: sub = await mb.stream("acme", "prod", on_event)
112
+ await sub.wait() # run until closed / terminal
113
+ await sub.close() # stop + drain
114
+ ```
115
+
116
+ ## Surface
117
+
118
+ - **Data plane** — `collection(slug, project=, database=)` → list/get/create/update/soft_delete/restore/versions/audit/hard_delete
119
+ - **Realtime** — `collection(…).subscribe(on_event)` / `mb.stream(project, database, on_event)` → SSE (async only): `RealtimeClient`, `StreamEvent`, `SubscribeOptions`, `Subscription`
120
+ - **Customer management** — `SelfService` for projects, databases, schema, credentials, settings, plans, usage, and lifecycle
121
+ - **GraphQL** — `mb.graphql(project=, database=)`
122
+ - **Filters** — `FilterBuilder`, `OPERATORS`, `serialize_filter`
123
+ - **Errors** — typed hierarchy (`MemyBaseError` + `Conflict`/`NotFound`/`Validation`/`RateLimited`/… `decode_error`)
124
+ - **Helpers** — `with_conflict_retry`, `guard_reserved_fields`, `omit_none`, `RESERVED_FIELDS`
125
+
126
+ Reserved fields (`_id`, `createdAt`, `updatedAt`, …) are never sent on write; string fields are non-nullable — omit a key rather than sending `null`.
127
+
128
+ ## Versioning
129
+
130
+ `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).
@@ -0,0 +1,24 @@
1
+ memybase/__init__.py,sha256=B8VNKi6tpH94GZZkQy_GYjS5HxOlj-gWtAFhvjUuRIA,2148
2
+ memybase/_client.py,sha256=vxhOkN973l4cC4uDrVVuXRz8Ab0n6uM6C77h-DOZidM,9100
3
+ memybase/_collection.py,sha256=Tspa3sChfla5E6UUbSYecHKXCf4m16LWZXRz-IpiC3I,16821
4
+ memybase/_errors.py,sha256=vyGEZJ9T7MSmN-YCIlol_j9ltG4-PwG1o37F5yE_wkM,7415
5
+ memybase/_filter.py,sha256=wWLoQwK2HwEGEAIvdw4YREubMugste8SphU6ApreSBE,3499
6
+ memybase/_graphql.py,sha256=Otjs-nIn_AcXxEbJNHSLFeUrVnZs2RpGl0WqUzWOwv8,1728
7
+ memybase/_helpers.py,sha256=qkaycuwkUEDI-Kc_pTQ02r-HkIfpYOsmeBJWR7ZcYpI,1118
8
+ memybase/_mapping.py,sha256=aMTeLCkNUw-ZqDis-6wQYL1Ubled3TtfUe0XjjZ8Qr8,1602
9
+ memybase/_realtime.py,sha256=5OwseUNnCeb55d9NjoAy2ByJTLwXb4NN9XUcpdDlcTg,12505
10
+ memybase/_self.py,sha256=E7n89Fk_hbBN6-_Gm2sHiYDBxd4au2gWLJM6grgADLc,7748
11
+ memybase/_transport.py,sha256=1JuBxYNayxTdEUPbFTQAnNSTAWXBqIJWje9OHaojt8g,10037
12
+ memybase/_types.py,sha256=ThiIdz6q3klJRqHSVSYcrRu2oB4kDo-nf78J9ljCYcE,4796
13
+ memybase/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ memybase/_sync/__init__.py,sha256=H-jWQCc2cF5HStW7tfhwUVb9J8QWlEnwSP6uNwwwxYc,253
15
+ memybase/_sync/_client.py,sha256=p4Gc5SmF8Og_VtWGBTOSAhRLdSy0svS16dQlB_ErvQI,8322
16
+ memybase/_sync/_collection.py,sha256=08gCgp0d_xNSeEjQuOFgg6hydpvRR81F-nnJG1cFnO0,15513
17
+ memybase/_sync/_graphql.py,sha256=dWATdGQ9zjxKWbluSbnaggLpA5imVvA9fbxPCV-dHJA,1864
18
+ memybase/_sync/_helpers.py,sha256=fH44PcpVmXfUsPbLlkqupCJitSW0msbg0axsHiY2yCY,1243
19
+ memybase/_sync/_self.py,sha256=XCw5IXioAR3FowgfveaPjmG1nu53lWG6qV0r0RiLxQ8,7569
20
+ memybase/_sync/_transport.py,sha256=LzRN-NmICgiLXHO7pOqRQv7ym1uEXBguBST5s_MNS7g,8378
21
+ memybase/sync/__init__.py,sha256=x2LyVA_Rv3vOl4EF9kYWHZTGjfHJMZwXK4bAAT0rZRg,2074
22
+ memydev_base_sdk-0.1.0.dist-info/METADATA,sha256=toRsxB7a2lUI3tANHcjaTcyvcIhy8CYFEr4olK8gMnc,6830
23
+ memydev_base_sdk-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ memydev_base_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any