memydev-base-sdk 0.1.0__tar.gz

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.
Files changed (41) hide show
  1. memydev_base_sdk-0.1.0/.gitignore +5 -0
  2. memydev_base_sdk-0.1.0/PKG-INFO +130 -0
  3. memydev_base_sdk-0.1.0/README.md +114 -0
  4. memydev_base_sdk-0.1.0/pyproject.toml +33 -0
  5. memydev_base_sdk-0.1.0/requirements-release.in +12 -0
  6. memydev_base_sdk-0.1.0/requirements-release.lock +634 -0
  7. memydev_base_sdk-0.1.0/scripts/gen_sync.py +123 -0
  8. memydev_base_sdk-0.1.0/src/memybase/__init__.py +88 -0
  9. memydev_base_sdk-0.1.0/src/memybase/_client.py +233 -0
  10. memydev_base_sdk-0.1.0/src/memybase/_collection.py +411 -0
  11. memydev_base_sdk-0.1.0/src/memybase/_errors.py +232 -0
  12. memydev_base_sdk-0.1.0/src/memybase/_filter.py +86 -0
  13. memydev_base_sdk-0.1.0/src/memybase/_graphql.py +49 -0
  14. memydev_base_sdk-0.1.0/src/memybase/_helpers.py +35 -0
  15. memydev_base_sdk-0.1.0/src/memybase/_mapping.py +55 -0
  16. memydev_base_sdk-0.1.0/src/memybase/_realtime.py +295 -0
  17. memydev_base_sdk-0.1.0/src/memybase/_self.py +137 -0
  18. memydev_base_sdk-0.1.0/src/memybase/_sync/__init__.py +3 -0
  19. memydev_base_sdk-0.1.0/src/memybase/_sync/_client.py +219 -0
  20. memydev_base_sdk-0.1.0/src/memybase/_sync/_collection.py +383 -0
  21. memydev_base_sdk-0.1.0/src/memybase/_sync/_graphql.py +51 -0
  22. memydev_base_sdk-0.1.0/src/memybase/_sync/_helpers.py +37 -0
  23. memydev_base_sdk-0.1.0/src/memybase/_sync/_self.py +139 -0
  24. memydev_base_sdk-0.1.0/src/memybase/_sync/_transport.py +223 -0
  25. memydev_base_sdk-0.1.0/src/memybase/_transport.py +257 -0
  26. memydev_base_sdk-0.1.0/src/memybase/_types.py +91 -0
  27. memydev_base_sdk-0.1.0/src/memybase/py.typed +0 -0
  28. memydev_base_sdk-0.1.0/src/memybase/sync/__init__.py +41 -0
  29. memydev_base_sdk-0.1.0/tests/test_client.py +102 -0
  30. memydev_base_sdk-0.1.0/tests/test_collection.py +251 -0
  31. memydev_base_sdk-0.1.0/tests/test_contract_parity.py +101 -0
  32. memydev_base_sdk-0.1.0/tests/test_errors.py +77 -0
  33. memydev_base_sdk-0.1.0/tests/test_filter.py +64 -0
  34. memydev_base_sdk-0.1.0/tests/test_graphql.py +29 -0
  35. memydev_base_sdk-0.1.0/tests/test_helpers.py +47 -0
  36. memydev_base_sdk-0.1.0/tests/test_mapping.py +45 -0
  37. memydev_base_sdk-0.1.0/tests/test_realtime.py +386 -0
  38. memydev_base_sdk-0.1.0/tests/test_self.py +71 -0
  39. memydev_base_sdk-0.1.0/tests/test_sync_functional.py +44 -0
  40. memydev_base_sdk-0.1.0/tests/test_sync_parity.py +71 -0
  41. memydev_base_sdk-0.1.0/tests/test_transport.py +206 -0
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .pytest_cache/
5
+ __pycache__/
@@ -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,114 @@
1
+ # memydev-base-sdk — Official MemyBase Python SDK
2
+
3
+ Async-first (with sync wrapper) client for the [MemyBase](https://base.memy.dev) hosted data service.
4
+ Mirrors the JS `@memydev/base-sdk` selector→handle API: `mb.collection(slug, project=…, database=…)`.
5
+
6
+ ## Install
7
+
8
+ Install a released package from the customer-accessible Python registry configured for the hosted
9
+ MemyBase subscription. Keep the release version explicit in application configuration:
10
+
11
+ ```bash
12
+ MEMYBASE_SDK_VERSION="0.1.0"
13
+ MEMYBASE_PYTHON_INDEX_URL="<customer Python index URL>"
14
+ python -m pip install --index-url "$MEMYBASE_PYTHON_INDEX_URL" "memydev-base-sdk==$MEMYBASE_SDK_VERSION"
15
+ ```
16
+
17
+ Do not install this SDK from a workspace path, local wheel, or `file://` dependency in subscriber
18
+ applications. A hosted subscription provides the MemyBase service and SDK integration surface; it does
19
+ not deliver runtime/source/Devtron/MongoDB components.
20
+
21
+ Release validation produces both the wheel and source distribution through Hatchling, verifies both with
22
+ `twine check`, and installs the wheel into a clean venv from the configured subscriber Python index. The
23
+ package includes `py.typed` and both async `memybase.MemyBase` and sync `memybase._sync.MemyBase` surfaces.
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ from memybase import MemyBase
29
+
30
+ mb = MemyBase("https://base.memy.dev", api_key="…") # or token=… / user_token=…
31
+ users = mb.collection("users", project="acme", database="prod")
32
+
33
+ rec = await users.create({"name": "Ada"}) # omit None keys (reserved-field safe)
34
+ page = await users.list(filter={"active": True}, page=1, page_size=50)
35
+ one = await users.get(rec["_id"])
36
+ await users.update(rec["_id"], {"name": "Ada L."})
37
+ await users.soft_delete(rec["_id"], reason="gdpr")
38
+ ```
39
+
40
+ Sync facade: `from memybase.sync import MemyBase` (mechanically generated from the async core via
41
+ `scripts/gen_sync.py` — single source, no drift; CRUD + customer management only).
42
+
43
+ Retry policy mirrors the JS SDK: `retry={"max_retries": 2, "base_delay": 0.5,
44
+ "retryable_statuses": []}` by default. Extra retryable statuses apply only to reads; writes stay limited
45
+ to 429/503. The legacy constructor shortcut `max_retries=` is still accepted as an alias for
46
+ `retry["max_retries"]`.
47
+
48
+ ### The `MemyBaseClient` facade convention (recommended)
49
+
50
+ Do **not** spray `MemyBase(...)` construction and `except NotFoundError/…` handling across your codebase.
51
+ The memy convention — shared by **every** MemyBase consumer — is a **single-seam `MemyBaseClient` facade**:
52
+ ONE module wraps ONE `MemyBase` instance, and the rest of the app depends on that facade (never on
53
+ `@memydev/base-sdk` / `memybase` directly). This keeps the SDK swappable, centralizes error translation, and
54
+ makes call sites read **identically across languages**:
55
+
56
+ ```python
57
+ # your_app/data/memybase/client.py — the ONLY module that imports `memybase`
58
+ from memybase import MemyBase
59
+
60
+ class MemyBaseClient: # ← the canonical name (same in JS + memybase itself)
61
+ def __init__(self, base_url: str, api_key: str, project: str, db: str) -> None:
62
+ self._mb = MemyBase(base_url, api_key=api_key)
63
+ self._project, self._db = project, db
64
+
65
+ def collection(self, slug: str):
66
+ return self._mb.collection(slug, project=self._project, database=self._db)
67
+ # + None-on-404 reads, bool-returning deletes, a `normalize_filter` chokepoint, SDK→app error
68
+ # translation — whatever adaptations the SDK does not provide.
69
+
70
+ client = MemyBaseClient(base_url, api_key, project="acme", db="prod") # usage reads the same everywhere
71
+ ```
72
+
73
+ The **same decision** is implemented in every project: `class MemyBaseClient` in memyswarm
74
+ (`data/memybase/client.py`) and this SDK's Python peer; `class MemyBaseClient` in memyui
75
+ (`apps/server/src/memybase/client.ts`); and `class MemyBaseClient` in memybase's own stdio-MCP consumer
76
+ (`src/mcp/memybase-client.ts`). Reference implementation: memyswarm's `MemyBaseClient`.
77
+
78
+ > A **producer/engine** (the tier holding the DB connection) is the ONE exception: in-process code binds
79
+ > the data services directly and must NOT loop back through the SDK. `MemyBaseClient` is for **consumers**
80
+ > — anything reaching the engine over the wire (apps, workers, MCP-over-stdio, a separate frontend).
81
+
82
+ ### Realtime (SSE)
83
+
84
+ Subscribe to live change events over Server-Sent Events (async only — SSE is inherently async; the sync
85
+ facade does not expose it). The callback fires for `ready` / `change` / `heartbeat` / `resync` events;
86
+ the client auto-reconnects with backoff and resumes via `Last-Event-ID`.
87
+
88
+ ```python
89
+ async with MemyBase("https://base.memy.dev", api_key="…") as mb:
90
+ def on_event(e): # e: StreamEvent(event, data, id)
91
+ if e.event == "change":
92
+ print(e.data["type"], e.data["entity"], e.data["id"])
93
+
94
+ sub = await mb.collection("users", project="acme", database="prod").subscribe(on_event)
95
+ # … or all entities: sub = await mb.stream("acme", "prod", on_event)
96
+ await sub.wait() # run until closed / terminal
97
+ await sub.close() # stop + drain
98
+ ```
99
+
100
+ ## Surface
101
+
102
+ - **Data plane** — `collection(slug, project=, database=)` → list/get/create/update/soft_delete/restore/versions/audit/hard_delete
103
+ - **Realtime** — `collection(…).subscribe(on_event)` / `mb.stream(project, database, on_event)` → SSE (async only): `RealtimeClient`, `StreamEvent`, `SubscribeOptions`, `Subscription`
104
+ - **Customer management** — `SelfService` for projects, databases, schema, credentials, settings, plans, usage, and lifecycle
105
+ - **GraphQL** — `mb.graphql(project=, database=)`
106
+ - **Filters** — `FilterBuilder`, `OPERATORS`, `serialize_filter`
107
+ - **Errors** — typed hierarchy (`MemyBaseError` + `Conflict`/`NotFound`/`Validation`/`RateLimited`/… `decode_error`)
108
+ - **Helpers** — `with_conflict_retry`, `guard_reserved_fields`, `omit_none`, `RESERVED_FIELDS`
109
+
110
+ Reserved fields (`_id`, `createdAt`, `updatedAt`, …) are never sent on write; string fields are non-nullable — omit a key rather than sending `null`.
111
+
112
+ ## Versioning
113
+
114
+ `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,33 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "memydev-base-sdk"
7
+ version = "0.1.0"
8
+ description = "Official MemyBase client SDK for Python"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = {text = "Proprietary and unlicensed pending approved service terms."}
12
+ dependencies = [
13
+ "httpx>=0.27,<1",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = [
18
+ "editables==0.5",
19
+ "hatchling==1.27.0",
20
+ "pytest==8.3.5",
21
+ "pytest-asyncio==0.24.0",
22
+ "respx==0.22.0",
23
+ "unasync==0.6.0", # generates the sync twins in _sync/ (scripts/gen_sync.py); dev-only — the
24
+ # generated files are committed. Pulls tokenize-rt transitively.
25
+ ]
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/memybase"]
29
+
30
+ [tool.pytest.ini_options]
31
+ asyncio_mode = "auto"
32
+ asyncio_default_fixture_loop_scope = "function"
33
+ testpaths = ["tests"]
@@ -0,0 +1,12 @@
1
+ # Exact top-level inputs for the Python SDK release and subscriber-install gate.
2
+ # Compile with hashes into requirements-release.lock; the protected-tag workflow installs only that lock.
3
+ build==1.2.2.post1
4
+ editables==0.5
5
+ hatchling==1.27.0
6
+ httpx==0.27.2
7
+ pip-audit==2.8.0
8
+ pytest==8.3.5
9
+ pytest-asyncio==0.24.0
10
+ respx==0.22.0
11
+ twine==6.1.0
12
+ unasync==0.6.0