parse-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.
- parse_sdk/__init__.py +57 -0
- parse_sdk/_labels.py +51 -0
- parse_sdk/_oauth.py +301 -0
- parse_sdk/_project.py +184 -0
- parse_sdk/_runtime.py +2142 -0
- parse_sdk/_sanitize.py +15 -0
- parse_sdk/_sync.py +887 -0
- parse_sdk/checks.py +601 -0
- parse_sdk/cli.py +1667 -0
- parse_sdk/cli_help.py +110 -0
- parse_sdk/codegen_reconcile.py +157 -0
- parse_sdk/codegen_v2.py +3361 -0
- parse_sdk/config.py +349 -0
- parse_sdk/docgen.py +587 -0
- parse_sdk/doctor.py +348 -0
- parse_sdk/migrate.py +212 -0
- parse_sdk/preview.py +588 -0
- parse_sdk/py.typed +0 -0
- parse_sdk/resource_surface.py +196 -0
- parse_sdk/sample_gate.py +245 -0
- parse_sdk/scaffold.py +273 -0
- parse_sdk-0.1.0.dist-info/METADATA +177 -0
- parse_sdk-0.1.0.dist-info/RECORD +26 -0
- parse_sdk-0.1.0.dist-info/WHEEL +4 -0
- parse_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- parse_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
parse_sdk/scaffold.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""The committed `parse_apis` scaffold + reserved-name vocabulary.
|
|
2
|
+
|
|
3
|
+
`parse init` materializes a minimal, *committed* `parse_apis` distribution
|
|
4
|
+
(`pyproject.toml`, `src/parse_apis/__init__.py`, `src/parse_apis/py.typed`) and
|
|
5
|
+
`parse sync` fills the generated, gitignored payload around it. The scaffold
|
|
6
|
+
carries no API key, no user id, and no per-key inventory, so it is safe to
|
|
7
|
+
commit; only the generated slug packages / manifest / agent indexes are ignored.
|
|
8
|
+
|
|
9
|
+
This module owns the static scaffold text and the set of names a generated slug
|
|
10
|
+
directory may never take (because, as a directory under `src/parse_apis/`, the
|
|
11
|
+
name would shadow or be confused with a package-level scaffold/generated
|
|
12
|
+
member). Reservation is compared case-insensitively so it also holds on
|
|
13
|
+
case-insensitive filesystems (macOS/APFS, Windows).
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import keyword
|
|
18
|
+
import re
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import List
|
|
21
|
+
|
|
22
|
+
# Runtime errors re-exported by the committed scaffold. Key-independent and
|
|
23
|
+
# runtime-owned, so they carry no per-key metadata and are safe to commit
|
|
24
|
+
# (define `__all__` in the committed scaffold, not "post-sync").
|
|
25
|
+
_REEXPORTED = [
|
|
26
|
+
"ParseError",
|
|
27
|
+
"AuthError",
|
|
28
|
+
"ForbiddenError",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"BadRequestError",
|
|
31
|
+
"QuotaExceededError",
|
|
32
|
+
"RateLimitError",
|
|
33
|
+
"UpstreamError",
|
|
34
|
+
"PaginationError",
|
|
35
|
+
"PaginationLimitError",
|
|
36
|
+
"RequestMeta",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Case-folded names a generated slug directory may not take. Derived from the
|
|
40
|
+
# package-level scaffold members (`__init__.py`, `py.typed`) and the generated
|
|
41
|
+
# top-level artifacts (`_manifest.json`, `AGENTS.md`, `CLAUDE.md`) plus the
|
|
42
|
+
# runtime module names a reader would confuse with `parse_sdk._runtime` /
|
|
43
|
+
# `parse_sdk.config`. `__init__` is load-bearing — a `parse_apis/__init__/`
|
|
44
|
+
# directory is shadowed by the package initializer and becomes unimportable.
|
|
45
|
+
RESERVED_SLUGS = frozenset({
|
|
46
|
+
"__init__",
|
|
47
|
+
"__main__",
|
|
48
|
+
"py",
|
|
49
|
+
"py.typed",
|
|
50
|
+
"pyproject",
|
|
51
|
+
"_manifest",
|
|
52
|
+
"_runtime",
|
|
53
|
+
"_config",
|
|
54
|
+
"agents",
|
|
55
|
+
"claude",
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_reserved_slug(name: str) -> bool:
|
|
60
|
+
"""True if ``name`` collides (case-insensitively) with a package-level
|
|
61
|
+
scaffold/generated member and must be suffixed before use as a slug dir."""
|
|
62
|
+
return name.casefold() in RESERVED_SLUGS
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
_SLUG_UNSAFE = re.compile(r"[^0-9A-Za-z_]")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def safe_slug(name: str) -> str:
|
|
69
|
+
"""A filesystem- and import-safe slug: a valid Python identifier.
|
|
70
|
+
|
|
71
|
+
A server slug is an arbitrary value used BOTH as a directory name under
|
|
72
|
+
``src/parse_apis/`` AND as an import path (``parse_apis.<slug>``). A hostile
|
|
73
|
+
or malformed slug (``../../etc``, ``a/b``, ``.``, ``""``) must never escape
|
|
74
|
+
the package directory (a path-traversal that `rmtree`/`copytree` would then
|
|
75
|
+
act on — audit SEC-1) or break imports. Any character outside
|
|
76
|
+
``[0-9A-Za-z_]`` is replaced with ``_``; a leading digit is prefixed; an
|
|
77
|
+
empty result falls back to ``"api"``. Reserved/collision handling is the
|
|
78
|
+
caller's job (see ``cli._assign_slugs``).
|
|
79
|
+
"""
|
|
80
|
+
cleaned = _SLUG_UNSAFE.sub("_", name)
|
|
81
|
+
if not cleaned:
|
|
82
|
+
return "api"
|
|
83
|
+
if cleaned[0].isdigit():
|
|
84
|
+
cleaned = "_" + cleaned
|
|
85
|
+
# A slug that is a (hard) Python keyword (``class``, ``for``, ``from``, ...)
|
|
86
|
+
# is a valid identifier shape but ``from parse_apis.<slug> import`` is a
|
|
87
|
+
# SyntaxError — suffix it just like a reserved name. Soft keywords
|
|
88
|
+
# (``match``/``case``/``type``/``_``) are NOT suffixed: they are not reserved,
|
|
89
|
+
# so they are valid module names and import fine.
|
|
90
|
+
if keyword.iskeyword(cleaned):
|
|
91
|
+
cleaned = cleaned + "_"
|
|
92
|
+
return cleaned
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def render_scaffold_init() -> str:
|
|
96
|
+
"""The committed ``src/parse_apis/__init__.py``.
|
|
97
|
+
|
|
98
|
+
Importable before any slug exists (so installers have a valid dependency
|
|
99
|
+
target), re-exports the runtime errors by object identity, exposes no
|
|
100
|
+
credential writer, and gives a clear "run parse sync" diagnostic for
|
|
101
|
+
top-level helper access to a not-yet-generated slug.
|
|
102
|
+
"""
|
|
103
|
+
names = ",\n ".join(_REEXPORTED)
|
|
104
|
+
all_list = ",\n ".join(f'"{n}"' for n in _REEXPORTED)
|
|
105
|
+
return f'''"""Parse typed APIs.
|
|
106
|
+
|
|
107
|
+
Generated, typed clients for your Parse APIs appear here as submodules after
|
|
108
|
+
`parse sync`:
|
|
109
|
+
|
|
110
|
+
from parse_apis.<slug> import <Root>
|
|
111
|
+
|
|
112
|
+
Runtime errors are re-exported from `parse_sdk` so callers can
|
|
113
|
+
`from parse_apis import RateLimitError` without importing the engine directly.
|
|
114
|
+
This file is the committed scaffold — `parse sync` only writes the generated
|
|
115
|
+
payload around it.
|
|
116
|
+
"""
|
|
117
|
+
from __future__ import annotations
|
|
118
|
+
|
|
119
|
+
from parse_sdk import (
|
|
120
|
+
{names},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
__all__ = [
|
|
124
|
+
{all_list},
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def __getattr__(name: str):
|
|
129
|
+
# Submodule imports (`from parse_apis.<slug> import ...`) are handled by the
|
|
130
|
+
# import system; this only fires for attribute access on the package itself.
|
|
131
|
+
raise AttributeError(
|
|
132
|
+
f"module 'parse_apis' has no attribute {{name!r}}. "
|
|
133
|
+
f"If {{name!r}} is one of your Parse APIs, run `uv run parse sync` to generate it."
|
|
134
|
+
)
|
|
135
|
+
'''
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def render_scaffold_pyproject(parse_sdk_version: str) -> str:
|
|
139
|
+
"""The committed ``parse_apis/pyproject.toml``.
|
|
140
|
+
|
|
141
|
+
Pins ``parse-sdk`` to the EXACT version the clients were generated against
|
|
142
|
+
because generated code subclasses the underscore-private
|
|
143
|
+
``parse_sdk._runtime`` bases by name, so a loose range would let a fresh
|
|
144
|
+
`uv sync` pull a runtime the on-disk code was never generated for. `parse
|
|
145
|
+
init`/`sync` restamp this as the engine moves; regeneration is the upgrade
|
|
146
|
+
path.
|
|
147
|
+
"""
|
|
148
|
+
return f'''[build-system]
|
|
149
|
+
requires = ["hatchling"]
|
|
150
|
+
build-backend = "hatchling.build"
|
|
151
|
+
|
|
152
|
+
[project]
|
|
153
|
+
name = "parse-apis"
|
|
154
|
+
version = "0.0.0"
|
|
155
|
+
requires-python = ">=3.10"
|
|
156
|
+
dependencies = [
|
|
157
|
+
"parse-sdk=={parse_sdk_version}",
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
[tool.hatch.build.targets.wheel]
|
|
161
|
+
packages = ["src/parse_apis"]
|
|
162
|
+
'''
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def render_gitignore_block() -> str:
|
|
166
|
+
"""Deny-by-default ignore block for the consumer's ``.gitignore``.
|
|
167
|
+
|
|
168
|
+
Ignore EVERYTHING under the generated package and re-include only
|
|
169
|
+
the committed scaffold members, so any future top-level generated file is
|
|
170
|
+
ignored by construction — not by a hand-maintained per-filename allowlist
|
|
171
|
+
that leaks per-key data the day codegen adds an artifact.
|
|
172
|
+
"""
|
|
173
|
+
return (
|
|
174
|
+
"# Parse SDK — generated payload is per-key (reveals your API inventory).\n"
|
|
175
|
+
"# Ignore everything under the generated package; keep only the committed scaffold.\n"
|
|
176
|
+
"parse_apis/src/parse_apis/*\n"
|
|
177
|
+
"!parse_apis/src/parse_apis/__init__.py\n"
|
|
178
|
+
"!parse_apis/src/parse_apis/py.typed\n"
|
|
179
|
+
".parse/\n"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def render_workflow_core() -> str:
|
|
184
|
+
"""Steady-state workflow block shared (SSOT) by the committed root pointer
|
|
185
|
+
and ``parse help``: the whoami/list/sync workflow, the import model, and the
|
|
186
|
+
``limit=`` cost rule. No leading/trailing blank lines so each caller controls
|
|
187
|
+
its own surrounding spacing."""
|
|
188
|
+
return (
|
|
189
|
+
"Workflow (run from the project root):\n"
|
|
190
|
+
"- Check auth / base URL / host-trust: uv run parse whoami --json\n"
|
|
191
|
+
"- See which APIs your key can call: uv run parse list --json\n"
|
|
192
|
+
"- Generate / refresh typed clients: uv run parse sync --json\n"
|
|
193
|
+
" (machine-readable status: written / skipped / quarantined / failures)\n"
|
|
194
|
+
"\n"
|
|
195
|
+
"Import a client: from parse_apis.<slug> import <Root>\n"
|
|
196
|
+
"The exact <slug> and <Root> come from the generated tree, not `parse list`\n"
|
|
197
|
+
"(sync may sanitize/pin slugs): read `src/parse_apis/<slug>/README.md`, or\n"
|
|
198
|
+
"`uv run parse sync --json` (its `written[]` lists each slug + root).\n"
|
|
199
|
+
"\n"
|
|
200
|
+
"Before list/search calls, pass `limit=` unless exhaustive traversal is\n"
|
|
201
|
+
"explicitly requested — each page is a live, billed request."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def render_root_pointer() -> str:
|
|
206
|
+
"""The committed top-level agent pointer (``parse_apis/AGENTS.md`` and the
|
|
207
|
+
byte-identical ``parse_apis/CLAUDE.md``).
|
|
208
|
+
|
|
209
|
+
The cold-start surface — the first file an agent reads when it opens
|
|
210
|
+
``parse_apis/``, and (unlike the generated index) it survives a fresh clone.
|
|
211
|
+
It names the workflow (via ``render_workflow_core``), one fresh-clone
|
|
212
|
+
recovery line (the generated tree is absent until ``parse init``/``sync``;
|
|
213
|
+
routes to ``parse help``/``parse doctor``), the ``limit=`` cost rule, and
|
|
214
|
+
where the on-demand reference lives. Kept tiny so it costs ~O(1) context;
|
|
215
|
+
refreshed content-compared on every sync. No HTML sentinel.
|
|
216
|
+
"""
|
|
217
|
+
return (
|
|
218
|
+
"# Parse APIs — start here\n"
|
|
219
|
+
"\n"
|
|
220
|
+
"This package holds typed clients for this project's Parse APIs, generated\n"
|
|
221
|
+
"by the `parse` CLI (installed editable — imports resolve from any cwd).\n"
|
|
222
|
+
"`parse sync` overwrites generated files; change an API at parse.bot, then\n"
|
|
223
|
+
"re-sync.\n"
|
|
224
|
+
"\n"
|
|
225
|
+
"Fresh checkout? The generated tree (clients, per-API READMEs/examples,\n"
|
|
226
|
+
"`src/parse_apis/CLAUDE.md`) does not exist until `uv run parse init` (or\n"
|
|
227
|
+
"`uv run parse login` + `uv run parse sync`); until then importing a generated\n"
|
|
228
|
+
"client raises ModuleNotFoundError. Guided setup: `uv run parse help`;\n"
|
|
229
|
+
"diagnose a broken install: `uv run parse doctor`.\n"
|
|
230
|
+
"\n"
|
|
231
|
+
f"{render_workflow_core()}\n"
|
|
232
|
+
"\n"
|
|
233
|
+
"Conventions + the API index (generated; appear after `parse sync`):\n"
|
|
234
|
+
"`src/parse_apis/CLAUDE.md`. Per-API (generated): `src/parse_apis/<slug>/README.md`\n"
|
|
235
|
+
"+ runnable `example.py`.\n"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def write_scaffold(project_dir: Path, parse_sdk_version: str) -> List[Path]:
|
|
240
|
+
"""Write the committed scaffold under ``project_dir/parse_apis``.
|
|
241
|
+
|
|
242
|
+
Idempotent: overwrites the scaffold files (they are static modulo the
|
|
243
|
+
version stamp) and never touches generated payload. Returns the paths
|
|
244
|
+
written, sorted.
|
|
245
|
+
"""
|
|
246
|
+
project_dir = Path(project_dir)
|
|
247
|
+
root = project_dir / "parse_apis"
|
|
248
|
+
pkg = root / "src" / "parse_apis"
|
|
249
|
+
pkg.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
|
|
251
|
+
written: List[Path] = []
|
|
252
|
+
|
|
253
|
+
pyproject = root / "pyproject.toml"
|
|
254
|
+
pyproject.write_text(render_scaffold_pyproject(parse_sdk_version))
|
|
255
|
+
written.append(pyproject)
|
|
256
|
+
|
|
257
|
+
init = pkg / "__init__.py"
|
|
258
|
+
init.write_text(render_scaffold_init())
|
|
259
|
+
written.append(init)
|
|
260
|
+
|
|
261
|
+
py_typed = pkg / "py.typed"
|
|
262
|
+
py_typed.write_text("")
|
|
263
|
+
written.append(py_typed)
|
|
264
|
+
|
|
265
|
+
# Committed cold-start pointer at the package top (sibling to pyproject,
|
|
266
|
+
# outside the src/parse_apis gitignore glob → tracked). Byte-identical pair.
|
|
267
|
+
pointer = render_root_pointer()
|
|
268
|
+
for name in ("AGENTS.md", "CLAUDE.md"):
|
|
269
|
+
ptr = root / name
|
|
270
|
+
ptr.write_text(pointer)
|
|
271
|
+
written.append(ptr)
|
|
272
|
+
|
|
273
|
+
return sorted(written)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parse-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dynamic-yet-typed Python SDK for Parse APIs
|
|
5
|
+
Project-URL: Homepage, https://parse.bot
|
|
6
|
+
Project-URL: Changelog, https://pypi.org/project/parse-sdk/#history
|
|
7
|
+
Author-email: Parse <team@parse.bot>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,api-client,codegen,parse,scraping,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: click>=8.1
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pyright>=1.1.408; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Requires-Dist: tomli>=2; (python_version < '3.11') and extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# parse-sdk
|
|
32
|
+
|
|
33
|
+
Typed Python SDK tooling for [Parse](https://parse.bot) APIs.
|
|
34
|
+
|
|
35
|
+
`parse-sdk` is the shared **runtime + CLI**. It generates a project-local
|
|
36
|
+
`parse_apis` package — a real, installed, editable dependency — that holds the
|
|
37
|
+
typed client for every API your key can call. Your application code imports from
|
|
38
|
+
`parse_apis`; `parse-sdk` provides the runtime bases and errors those clients use.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
In a uv-managed project (`uv init` first if you don't have a `pyproject.toml`):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv add parse-sdk # installs the runtime + `parse` CLI into your project
|
|
46
|
+
uv run parse init # prompts for your API key, then scaffolds + syncs parse_apis
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Non-interactive (CI, agents, scripts): authenticate first with
|
|
50
|
+
`uv run parse login --api-key YOUR_KEY` (add `--base-url …` for a non-default
|
|
51
|
+
host), or export `PARSE_API_KEY` — then `uv run parse init` runs without prompts.
|
|
52
|
+
|
|
53
|
+
`parse init` is the one-time setup. It:
|
|
54
|
+
|
|
55
|
+
1. ensures credentials (prompts and stores to `~/.config/parse/credentials`, or run `parse login` first);
|
|
56
|
+
2. creates the committed `parse_apis` scaffold;
|
|
57
|
+
3. records `parse_apis` as an **editable path dependency** (and a `[tool.uv.sources]` entry);
|
|
58
|
+
4. appends a deny-by-default `.gitignore` block (commits the scaffold, ignores generated payload);
|
|
59
|
+
5. migrates — or safely refuses — any legacy flat `parse_apis/` layout;
|
|
60
|
+
6. runs the first `parse sync`.
|
|
61
|
+
|
|
62
|
+
Then use it:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from parse_apis.reddit_com_api import Reddit # generated, typed client (slugs look like <domain>_<tld>_api)
|
|
66
|
+
from parse_apis import RateLimitError # runtime errors, re-exported
|
|
67
|
+
|
|
68
|
+
reddit = Reddit() # picks up `parse login` credentials
|
|
69
|
+
try:
|
|
70
|
+
# Navigate from a constructible resource, with a bounded fetch:
|
|
71
|
+
for post in reddit.subreddit("smallbusiness").search_posts(query="hiring", limit=10):
|
|
72
|
+
print(post.title)
|
|
73
|
+
except RateLimitError:
|
|
74
|
+
...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
(The exact methods are generated from *your* API's spec — `parse_apis/<slug>/README.md`
|
|
78
|
+
and `example.py` document each client's real surface.)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv run python app.py # run your app in the project environment
|
|
82
|
+
uv run parse sync # later: refresh after you change APIs in Parse
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Running commands (`uv run` vs. activation)
|
|
86
|
+
|
|
87
|
+
`uv add parse-sdk` installs the `parse` console script into your project's `.venv`,
|
|
88
|
+
which isn't on your shell `PATH` by default. Two equivalent ways to reach it:
|
|
89
|
+
|
|
90
|
+
- **`uv run parse <cmd>`** — runs the venv's `parse` with no activation, auto-syncing first. (Recommended.)
|
|
91
|
+
- **`source .venv/bin/activate`** then `parse <cmd>` directly.
|
|
92
|
+
|
|
93
|
+
Prefer one of these over a global `uv tool install parse-sdk`: codegen must run with
|
|
94
|
+
the **same** pinned `parse-sdk` your project runs against (see *Versioning* below), and a
|
|
95
|
+
global copy can drift out of sync. Your end users running their *app* never invoke `parse` —
|
|
96
|
+
they just `import parse_apis`.
|
|
97
|
+
|
|
98
|
+
## On-disk layout
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
parse_apis/
|
|
102
|
+
pyproject.toml # committed: name="parse-apis", pinned parse-sdk==X
|
|
103
|
+
src/parse_apis/
|
|
104
|
+
__init__.py # committed scaffold — re-exports runtime errors
|
|
105
|
+
py.typed # committed
|
|
106
|
+
reddit/__init__.py # generated (gitignored)
|
|
107
|
+
_manifest.json # generated (gitignored)
|
|
108
|
+
AGENTS.md CLAUDE.md # generated cross-API index (gitignored)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Commit the three scaffold files; the generated payload is per-key (it reveals your API
|
|
112
|
+
inventory) and stays gitignored. `parse sync` builds the next payload in a staging tree,
|
|
113
|
+
self-tests it (secret scan, import-model, no-stale-docs), and promotes it with an atomic
|
|
114
|
+
directory swap — a failed sync never leaves broken or secret-bearing files in your project.
|
|
115
|
+
|
|
116
|
+
## Import model
|
|
117
|
+
|
|
118
|
+
- Application code imports generated clients from `parse_apis.<slug>`.
|
|
119
|
+
- Runtime errors/base types are re-exported from `parse_apis` (sourced from `parse_sdk`).
|
|
120
|
+
- Because `parse_apis` is an **installed** editable package, imports resolve from any
|
|
121
|
+
directory — no `sys.path` or editor `extraPaths` tricks.
|
|
122
|
+
- Generated files are project-local and differ per Parse API key.
|
|
123
|
+
|
|
124
|
+
## Fresh clone & CI (post-publish)
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv sync # installs deps incl. the editable parse_apis scaffold
|
|
128
|
+
uv run parse sync # regenerate the per-key payload
|
|
129
|
+
uv run pytest && uv run pyright # pyright must run against the PROJECT env —
|
|
130
|
+
# add both as dev deps (`uv add --dev pytest pyright`);
|
|
131
|
+
# a bare/global `pyright` can't resolve parse_apis
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The committed scaffold is a valid dependency target before any slugs exist, so `uv sync`
|
|
135
|
+
succeeds on a clean checkout.
|
|
136
|
+
|
|
137
|
+
## CLI
|
|
138
|
+
|
|
139
|
+
| Command | What it does |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `parse login` | Save API key + base URL to local credentials. No project changes. |
|
|
142
|
+
| `parse init` | One-time: ensure creds, scaffold `parse_apis`, record deps + gitignore, first sync. |
|
|
143
|
+
| `parse sync` | Regenerate the typed payload into `parse_apis/src/parse_apis` (staged + atomic). |
|
|
144
|
+
| `parse clean` | Remove generated payload; keep the committed scaffold. |
|
|
145
|
+
| `parse list` | Print the APIs the current key can call (no generation). |
|
|
146
|
+
| `parse whoami` | Show the current key + base URL. |
|
|
147
|
+
|
|
148
|
+
Run `parse <cmd> --help` for full per-command help.
|
|
149
|
+
|
|
150
|
+
## Versioning
|
|
151
|
+
|
|
152
|
+
`parse_apis/pyproject.toml` pins the **exact** `parse-sdk` it was generated against — generated
|
|
153
|
+
code subclasses the runtime by name. After upgrading `parse-sdk`, **re-run `parse sync`** to
|
|
154
|
+
regenerate against the new runtime; `init`/`sync` restamp the pin.
|
|
155
|
+
|
|
156
|
+
## Env vars
|
|
157
|
+
|
|
158
|
+
| Var | Default | Notes |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| `PARSE_API_KEY` | – | Auth for real requests (or use `parse login`). |
|
|
161
|
+
| `PARSE_API_BASE_URL` | `https://api.parse.bot` | Locally: `http://localhost:8001`. |
|
|
162
|
+
|
|
163
|
+
## Changelog
|
|
164
|
+
|
|
165
|
+
Release history is on the PyPI project page:
|
|
166
|
+
<https://pypi.org/project/parse-sdk/#history>.
|
|
167
|
+
|
|
168
|
+
## Security
|
|
169
|
+
|
|
170
|
+
To report a security issue, email **security@parse.bot** — please do not open
|
|
171
|
+
a public issue.
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
[MIT](LICENSE) © 2026 Parse. The license covers the `parse-sdk` runtime,
|
|
176
|
+
codegen, and CLI; use of the Parse API/service is governed separately by
|
|
177
|
+
Parse's terms.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
parse_sdk/__init__.py,sha256=cUCmf5MtpnAf14LKuwZXCnADVYxg1QwH1FWz3EesWmg,1434
|
|
2
|
+
parse_sdk/_labels.py,sha256=pX9ZQ7ZyOo1f3ZIKOqrbH1_H54XO-0s-ZBsjJKw6cSU,1877
|
|
3
|
+
parse_sdk/_oauth.py,sha256=HifVNsECBu1ZLx6da6hHMz5odcOTsAIVynn5_g1M_U4,13427
|
|
4
|
+
parse_sdk/_project.py,sha256=ivMCJ857OMjjAsGXdkewa0cDfKzljlrrGikshTrlwjg,8160
|
|
5
|
+
parse_sdk/_runtime.py,sha256=6E5ekavCouRUFAs3fxaquIvvpjCOz9VflQ9wZsGmoLk,107463
|
|
6
|
+
parse_sdk/_sanitize.py,sha256=kp6v_QiE60KMZZEfzqOSZS6D3RrMzipa9RGps6ZXeS0,788
|
|
7
|
+
parse_sdk/_sync.py,sha256=-uT_iYxsstEcOudP9XLD5CtB_keCXWHqBSQ_QELJfp0,42928
|
|
8
|
+
parse_sdk/checks.py,sha256=Cqsv_cnf118YC5gHRVy_XE65q6MOPVNLpf_xhrsIflg,31375
|
|
9
|
+
parse_sdk/cli.py,sha256=iiJDfqjmgxCIbWwxZ-wwbi6ItDFCnNciaxxXuGwL1xg,79440
|
|
10
|
+
parse_sdk/cli_help.py,sha256=tWuzg49Yphu1qQo47y8UOixYoil_bFOYGMY174w5h5k,4274
|
|
11
|
+
parse_sdk/codegen_reconcile.py,sha256=6_vRNX-Q4WuuUd3dk4QUi5IgiGT9fxU9C3L9hHAjPQ4,7961
|
|
12
|
+
parse_sdk/codegen_v2.py,sha256=MPC_XAfQFK0b8RLBZL1oKx1vnUHhbGubykUegNMASjg,169864
|
|
13
|
+
parse_sdk/config.py,sha256=5R2h-WNYp5gJKZ1L_dXS7Ex_eo0FxHTTBz127o7MuoU,16064
|
|
14
|
+
parse_sdk/docgen.py,sha256=qD6Zqr5TsWNwIa19auO-7C04s2aPvVsFuJpsmHvvH-A,29702
|
|
15
|
+
parse_sdk/doctor.py,sha256=6J7lA2_S1IF1jYII_PI94PCPU386UfU6x1rBagYC3Ns,14509
|
|
16
|
+
parse_sdk/migrate.py,sha256=VHATy8TTYijVwpZdM4Gt7z-SBzVU7HTcIb5sALRQZ3M,8998
|
|
17
|
+
parse_sdk/preview.py,sha256=6v10VM-WYFZ-slWrFQ826zo_TcMy8Z1xk-1DLYYAW-8,26595
|
|
18
|
+
parse_sdk/resource_surface.py,sha256=zrGlNxxLXkGBLVopDxRipYpMVZO6ORSRCZ8lRiQOXqQ,9682
|
|
19
|
+
parse_sdk/sample_gate.py,sha256=cnVa70vvtzy-CHcmKJNdn76Ob2sPYW2NalXZbVdbCkI,10757
|
|
20
|
+
parse_sdk/scaffold.py,sha256=8kh9ROdcVmPA6rX2lyYP2f_xPOC4DHxWpDECESh_jIo,10728
|
|
21
|
+
parse_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
parse_sdk-0.1.0.dist-info/METADATA,sha256=sEwJ_1WpSVnxCNPCtK8H40lFkf7M-rXKIBdYqmo-XVY,7356
|
|
23
|
+
parse_sdk-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
24
|
+
parse_sdk-0.1.0.dist-info/entry_points.txt,sha256=NmdGaxEvLmPom9KsbpKXv5vZ3PrVeYLuT88Ka3YcI-s,44
|
|
25
|
+
parse_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=bw_tWB6oxPxx0sPmtPQocG-4JH-_f2jjI3GhmapEQ2o,1062
|
|
26
|
+
parse_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Parse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|