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/__init__.py +88 -0
- memybase/_client.py +233 -0
- memybase/_collection.py +411 -0
- memybase/_errors.py +232 -0
- memybase/_filter.py +86 -0
- memybase/_graphql.py +49 -0
- memybase/_helpers.py +35 -0
- memybase/_mapping.py +55 -0
- memybase/_realtime.py +295 -0
- memybase/_self.py +137 -0
- memybase/_sync/__init__.py +3 -0
- memybase/_sync/_client.py +219 -0
- memybase/_sync/_collection.py +383 -0
- memybase/_sync/_graphql.py +51 -0
- memybase/_sync/_helpers.py +37 -0
- memybase/_sync/_self.py +139 -0
- memybase/_sync/_transport.py +223 -0
- memybase/_transport.py +257 -0
- memybase/_types.py +91 -0
- memybase/py.typed +0 -0
- memybase/sync/__init__.py +41 -0
- memydev_base_sdk-0.1.0.dist-info/METADATA +130 -0
- memydev_base_sdk-0.1.0.dist-info/RECORD +24 -0
- memydev_base_sdk-0.1.0.dist-info/WHEEL +4 -0
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,,
|