pyre-icp 1.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.
- pyre_icp-1.1.0/LICENSE +21 -0
- pyre_icp-1.1.0/PKG-INFO +235 -0
- pyre_icp-1.1.0/README.md +193 -0
- pyre_icp-1.1.0/pyproject.toml +35 -0
- pyre_icp-1.1.0/pyre/__init__.py +86 -0
- pyre_icp-1.1.0/pyre/_runtime.py +28 -0
- pyre_icp-1.1.0/pyre/adapters/__init__.py +17 -0
- pyre_icp-1.1.0/pyre/adapters/supabase.py +301 -0
- pyre_icp-1.1.0/pyre/adapters/upstash.py +148 -0
- pyre_icp-1.1.0/pyre/application.py +317 -0
- pyre_icp-1.1.0/pyre/auth.py +176 -0
- pyre_icp-1.1.0/pyre/certification.py +384 -0
- pyre_icp-1.1.0/pyre/cli.py +222 -0
- pyre_icp-1.1.0/pyre/compat/__init__.py +1 -0
- pyre_icp-1.1.0/pyre/compat/_stubs.py +91 -0
- pyre_icp-1.1.0/pyre/compat/urllib_request.py +61 -0
- pyre_icp-1.1.0/pyre/cors.py +68 -0
- pyre_icp-1.1.0/pyre/crypto.py +365 -0
- pyre_icp-1.1.0/pyre/data.py +158 -0
- pyre_icp-1.1.0/pyre/dev.py +119 -0
- pyre_icp-1.1.0/pyre/errors.py +53 -0
- pyre_icp-1.1.0/pyre/gateway.py +100 -0
- pyre_icp-1.1.0/pyre/http_types.py +129 -0
- pyre_icp-1.1.0/pyre/kv.py +127 -0
- pyre_icp-1.1.0/pyre/log.py +84 -0
- pyre_icp-1.1.0/pyre/outcall.py +233 -0
- pyre_icp-1.1.0/pyre/prandom.py +291 -0
- pyre_icp-1.1.0/pyre/ptime.py +78 -0
- pyre_icp-1.1.0/pyre/puuid.py +18 -0
- pyre_icp-1.1.0/pyre/routing.py +128 -0
- pyre_icp-1.1.0/pyre/sign.py +270 -0
- pyre_icp-1.1.0/pyre/templates/bare-api/.gitignore +4 -0
- pyre_icp-1.1.0/pyre/templates/bare-api/README.md +36 -0
- pyre_icp-1.1.0/pyre/templates/bare-api/dfx.json +8 -0
- pyre_icp-1.1.0/pyre/templates/bare-api/src/app.py +23 -0
- pyre_icp-1.1.0/pyre/templates/bare-api/src/main.py +126 -0
- pyre_icp-1.1.0/pyre/templates/crud-kv/.gitignore +4 -0
- pyre_icp-1.1.0/pyre/templates/crud-kv/README.md +36 -0
- pyre_icp-1.1.0/pyre/templates/crud-kv/dfx.json +8 -0
- pyre_icp-1.1.0/pyre/templates/crud-kv/src/app.py +61 -0
- pyre_icp-1.1.0/pyre/templates/crud-kv/src/main.py +126 -0
- pyre_icp-1.1.0/pyre/templates/outbound-proxy/.gitignore +4 -0
- pyre_icp-1.1.0/pyre/templates/outbound-proxy/README.md +36 -0
- pyre_icp-1.1.0/pyre/templates/outbound-proxy/dfx.json +8 -0
- pyre_icp-1.1.0/pyre/templates/outbound-proxy/src/app.py +35 -0
- pyre_icp-1.1.0/pyre/templates/outbound-proxy/src/main.py +126 -0
- pyre_icp-1.1.0/pyre/transform.py +54 -0
- pyre_icp-1.1.0/pyre/validation.py +94 -0
- pyre_icp-1.1.0/pyre_icp.egg-info/PKG-INFO +235 -0
- pyre_icp-1.1.0/pyre_icp.egg-info/SOURCES.txt +52 -0
- pyre_icp-1.1.0/pyre_icp.egg-info/dependency_links.txt +1 -0
- pyre_icp-1.1.0/pyre_icp.egg-info/entry_points.txt +2 -0
- pyre_icp-1.1.0/pyre_icp.egg-info/top_level.txt +2 -0
- pyre_icp-1.1.0/setup.cfg +4 -0
pyre_icp-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PYRE contributors
|
|
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.
|
pyre_icp-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyre-icp
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: PYRE — Flask-flavored Python on the Internet Computer: certified reads, urllib-shaped outcalls, stable-memory collections
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 PYRE contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/Sweet-Papa-Technologies/PYRE
|
|
28
|
+
Project-URL: Documentation, https://github.com/Sweet-Papa-Technologies/PYRE/tree/main/docs
|
|
29
|
+
Project-URL: Repository, https://github.com/Sweet-Papa-Technologies/PYRE
|
|
30
|
+
Project-URL: Issues, https://github.com/Sweet-Papa-Technologies/PYRE/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/DECISIONS.md
|
|
32
|
+
Keywords: internet-computer,icp,canister,web-framework,kybra
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<img src="https://raw.githubusercontent.com/Sweet-Papa-Technologies/PYRE/main/img/pyre-larger-banner.jpg" alt="PYRE — Python on the Internet Computer" width="720">
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
<a href="https://pypi.org/project/pyre-icp/"><img src="https://img.shields.io/pypi/v/pyre-icp?color=e05d44&label=pypi%20%7C%20pyre-icp" alt="PyPI"></a>
|
|
49
|
+
<a href="https://github.com/Sweet-Papa-Technologies/PYRE/actions/workflows/ci.yml"><img src="https://github.com/Sweet-Papa-Technologies/PYRE/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
50
|
+
<a href="https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT"></a>
|
|
51
|
+
<img src="https://img.shields.io/badge/python-3.10-blue" alt="Python 3.10">
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
Write recognizable Python — Flask-style routes, a data layer, an outbound
|
|
55
|
+
HTTP call — and run it on the [Internet Computer](https://internetcomputer.org)
|
|
56
|
+
(ICP), a decentralized WASM host. No Candid, no Rust, no Motoko.
|
|
57
|
+
|
|
58
|
+
What that buys you over Flask-on-a-VPS:
|
|
59
|
+
|
|
60
|
+
- **Certified responses** — clients cryptographically verify your API's
|
|
61
|
+
answers against the network's root of trust, not "trust the server."
|
|
62
|
+
- **Threshold-signed JWTs** — the subnet signs cooperatively; there is no
|
|
63
|
+
private key anywhere to steal.
|
|
64
|
+
- **Consensus-safe randomness & audited encryption** — the platform
|
|
65
|
+
footguns are defused; the safe paths look like ordinary Python.
|
|
66
|
+
- **~$0.40/month** for a light backend, measured on mainnet.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from pyre import App, Request, Response, data
|
|
70
|
+
|
|
71
|
+
app = App()
|
|
72
|
+
app.enable_cors(origins="*")
|
|
73
|
+
|
|
74
|
+
items = data.collection("items", schema={"name": str, "qty": (int, 1)})
|
|
75
|
+
|
|
76
|
+
@app.get("/health", certified=True) # served with a verifiable certificate
|
|
77
|
+
def health(req: Request) -> Response:
|
|
78
|
+
return Response.json({"status": "ok"})
|
|
79
|
+
|
|
80
|
+
@app.post("/items") # runs as an update: writes persist
|
|
81
|
+
def create_item(req: Request) -> Response:
|
|
82
|
+
return Response.json(items.insert(req.json()), status=201)
|
|
83
|
+
|
|
84
|
+
@app.get("/items")
|
|
85
|
+
def list_items(req: Request) -> Response:
|
|
86
|
+
return Response.json(items.list(limit=20, after=req.query.get("after")))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Outbound HTTPS looks like urllib, but async — because on ICP it is:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from pyre.compat import urllib_request as urllib
|
|
93
|
+
|
|
94
|
+
@app.get("/quote", update=True)
|
|
95
|
+
async def quote(req):
|
|
96
|
+
resp = await urllib.urlopen("https://api.example.com/quote",
|
|
97
|
+
max_response_bytes=8_192)
|
|
98
|
+
return Response.json({"upstream_status": resp.status, "data": resp.json()})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
And ICP's genuinely differentiated capabilities read like ordinary Python
|
|
102
|
+
(every one opt-in — a plain CRUD app never meets them):
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from pyre import random as prandom, time as ptime, sign
|
|
106
|
+
from pyre.adapters import supabase
|
|
107
|
+
|
|
108
|
+
@app.get("/id")
|
|
109
|
+
def new_id(req):
|
|
110
|
+
return Response.json({"id": prandom.uuid4()}) # consensus-safe; naive uuid4 fails loudly
|
|
111
|
+
|
|
112
|
+
@app.get("/attest", update=True)
|
|
113
|
+
async def attest(req):
|
|
114
|
+
token = await sign.jwt({"sub": req.caller, "iat": ptime.now()})
|
|
115
|
+
return Response.json({"jwt": token}) # threshold-signed: no key to steal
|
|
116
|
+
|
|
117
|
+
@app.get("/external", update=True)
|
|
118
|
+
async def external(req):
|
|
119
|
+
db = supabase.Client(url=SUPA_URL, anon_key=SUPA_KEY)
|
|
120
|
+
return Response.json(await db.table("items").select().limit(10))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Install
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install pyre-icp
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The distribution is `pyre-icp`; the import package and the CLI are both
|
|
130
|
+
`pyre`. To deploy canisters you also need, one time:
|
|
131
|
+
|
|
132
|
+
- **Python 3.10.x** (Kybra's RustPython targets 3.10 — [pyenv](https://github.com/pyenv/pyenv) recommended)
|
|
133
|
+
- **dfx** (the ICP SDK): `DFXVM_INIT_YES=true sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"`
|
|
134
|
+
- **Kybra** in your project's deploy venv: `pip install kybra==0.7.1` +
|
|
135
|
+
`python -m kybra install-dfx-extension`
|
|
136
|
+
|
|
137
|
+
Then:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pyre new myapp --template crud-kv # bare-api | crud-kv | outbound-proxy
|
|
141
|
+
cd myapp
|
|
142
|
+
pyre dev src/app.py # instant local server, no replica needed
|
|
143
|
+
dfx start --background && dfx deploy # real local canister
|
|
144
|
+
dfx deploy --network ic # mainnet
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The [quickstart](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/quickstart.md)
|
|
148
|
+
walks the whole path in ~15 minutes.
|
|
149
|
+
|
|
150
|
+
## The API surface
|
|
151
|
+
|
|
152
|
+
| Module | What it gives you | Docs |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `pyre.App` / `Request` / `Response` | Flask-style routing, path params, hooks, error handlers, CORS, certified routes | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md) |
|
|
155
|
+
| `pyre.data` / `pyre.kv` | Collections + KV over stable memory — survives upgrades; schemas, pagination, lazy migration | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyredata--collections-over-kv) |
|
|
156
|
+
| `pyre.validate` | Dict-schema request validation → clean per-field 400s | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyrevalidate) |
|
|
157
|
+
| `pyre.auth` | Bearer / API-key / HTTP Basic middleware, constant-time, hash-stored creds | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyreauth) |
|
|
158
|
+
| `pyre.compat.urllib_request` | urllib-shaped async HTTPS outcalls with determinism transforms | [concepts.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/concepts.md) |
|
|
159
|
+
| `pyre.random` / `pyre.uuid` / `pyre.time` | Consensus-safe RNG, UUIDs, timestamps (naive stdlib entropy **fails loudly** in-canister — by design) | [random-uuid-time.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/random-uuid-time.md) |
|
|
160
|
+
| `pyre.crypto` | AES-GCM, ChaCha20-Poly1305, sha2/sha3/blake2/blake3, HMAC — audited RustCrypto under the hood | [crypto.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/crypto.md) |
|
|
161
|
+
| `pyre.sign` | Threshold tECDSA signatures + ES256K JWTs — no private key exists | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyresign--threshold-signing-tecdsa) |
|
|
162
|
+
| `pyre.adapters` | Supabase (PostgREST) + Upstash Redis over outcalls, amplification-safe writes | [adapters.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/adapters.md) |
|
|
163
|
+
| `pyre.log` | Structured logging retrievable via `dfx canister logs` | [observability.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/observability.md) |
|
|
164
|
+
| `pyre` CLI | `pyre new` (templates), `pyre dev` (local server + footgun warnings) | [quickstart.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/quickstart.md) |
|
|
165
|
+
|
|
166
|
+
**All docs:**
|
|
167
|
+
[quickstart](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/quickstart.md) ·
|
|
168
|
+
[concepts](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/concepts.md) ·
|
|
169
|
+
[API reference](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md) ·
|
|
170
|
+
[troubleshooting](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/troubleshooting.md) ·
|
|
171
|
+
[stdlib support matrix](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/stdlib-matrix.md) ·
|
|
172
|
+
[secrets & outcalls](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/secrets-and-outcalls.md) ·
|
|
173
|
+
[extending with Rust](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/extending-with-rust.md) ·
|
|
174
|
+
[observability](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/observability.md) ·
|
|
175
|
+
[LLM/agent skill file](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/SKILL.md) ·
|
|
176
|
+
reference app: [examples/food_tracker](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/examples/food_tracker/src/app.py)
|
|
177
|
+
|
|
178
|
+
## The four ICP concepts PYRE teaches (and hides everything else)
|
|
179
|
+
|
|
180
|
+
1. **Query vs. update calls.** Queries are fast, read-only, uncertified;
|
|
181
|
+
updates go through consensus (~1–2 s) and can write. PYRE maps GET →
|
|
182
|
+
query, writes/async → update; honesty guards raise if you write state
|
|
183
|
+
or make outcalls from a query.
|
|
184
|
+
2. **Outbound HTTP is async and consensus-gated.** Every replica performs
|
|
185
|
+
your outcall independently and must agree byte-for-byte — hence
|
|
186
|
+
`await`, and hence transforms.
|
|
187
|
+
3. **The determinism transform.** Upstream responses differ per replica
|
|
188
|
+
(Date headers, request ids). Outcalls run through a transform that
|
|
189
|
+
canonicalizes the response before consensus; `pyre dev` shows you what
|
|
190
|
+
gets stripped before you ever deploy.
|
|
191
|
+
4. **Canisters are long-lived actors.** The interpreter boots once at
|
|
192
|
+
install and stays warm — no cold starts, but funding (cycles) and
|
|
193
|
+
instruction budgets are real. `make budgets` measures; DECISIONS.md
|
|
194
|
+
records.
|
|
195
|
+
|
|
196
|
+
Full explanations with failure symptoms in
|
|
197
|
+
[concepts.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/concepts.md).
|
|
198
|
+
|
|
199
|
+
## Mainnet-proven, not aspirational
|
|
200
|
+
|
|
201
|
+
Every load-bearing claim was tested against ICP mainnet (13-node subnet)
|
|
202
|
+
and recorded in
|
|
203
|
+
[DECISIONS.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/DECISIONS.md):
|
|
204
|
+
response certification verified by the official DFINITY verifier (BLS to
|
|
205
|
+
the NNS root key, tamper/stale rejected), outcall determinism proven
|
|
206
|
+
across real replicas, threshold signatures externally verified
|
|
207
|
+
(26.19B cycles ≈ 3.5¢ each), 13× write-amplification converging to single
|
|
208
|
+
rows through the adapters, and a light backend costing ≈ $0.40/month.
|
|
209
|
+
|
|
210
|
+
## Working from a clone
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
make setup # venvs (Python 3.10.7 via pyenv), kybra, dfx extension
|
|
214
|
+
make test # ~240 unit tests, no replica needed
|
|
215
|
+
make dev # instant local server for examples/rest_api
|
|
216
|
+
make start deploy # local replica + all example canisters
|
|
217
|
+
make e2e # 20-check acceptance suite
|
|
218
|
+
make pocketic # canister-level integration tests
|
|
219
|
+
make budget-gate # instruction + wasm-size/idle-burn regression gates
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
CI runs the same gates on every push. See
|
|
223
|
+
[CONTRIBUTING](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/CONTRIBUTING.md).
|
|
224
|
+
|
|
225
|
+
## Scope fences
|
|
226
|
+
|
|
227
|
+
Pure Python only — no C extensions, no Pydantic. No sockets/threads
|
|
228
|
+
(stubbed with guidance), no websockets/streaming. Secret-bearing outcalls
|
|
229
|
+
(calling Stripe/OpenAI with a private key) are a
|
|
230
|
+
[documented limitation](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/secrets-and-outcalls.md)
|
|
231
|
+
until v1.2's signed proxy.
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT © Sweet Papa Technologies
|
pyre_icp-1.1.0/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Sweet-Papa-Technologies/PYRE/main/img/pyre-larger-banner.jpg" alt="PYRE — Python on the Internet Computer" width="720">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://pypi.org/project/pyre-icp/"><img src="https://img.shields.io/pypi/v/pyre-icp?color=e05d44&label=pypi%20%7C%20pyre-icp" alt="PyPI"></a>
|
|
7
|
+
<a href="https://github.com/Sweet-Papa-Technologies/PYRE/actions/workflows/ci.yml"><img src="https://github.com/Sweet-Papa-Technologies/PYRE/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
8
|
+
<a href="https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT"></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/python-3.10-blue" alt="Python 3.10">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
Write recognizable Python — Flask-style routes, a data layer, an outbound
|
|
13
|
+
HTTP call — and run it on the [Internet Computer](https://internetcomputer.org)
|
|
14
|
+
(ICP), a decentralized WASM host. No Candid, no Rust, no Motoko.
|
|
15
|
+
|
|
16
|
+
What that buys you over Flask-on-a-VPS:
|
|
17
|
+
|
|
18
|
+
- **Certified responses** — clients cryptographically verify your API's
|
|
19
|
+
answers against the network's root of trust, not "trust the server."
|
|
20
|
+
- **Threshold-signed JWTs** — the subnet signs cooperatively; there is no
|
|
21
|
+
private key anywhere to steal.
|
|
22
|
+
- **Consensus-safe randomness & audited encryption** — the platform
|
|
23
|
+
footguns are defused; the safe paths look like ordinary Python.
|
|
24
|
+
- **~$0.40/month** for a light backend, measured on mainnet.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from pyre import App, Request, Response, data
|
|
28
|
+
|
|
29
|
+
app = App()
|
|
30
|
+
app.enable_cors(origins="*")
|
|
31
|
+
|
|
32
|
+
items = data.collection("items", schema={"name": str, "qty": (int, 1)})
|
|
33
|
+
|
|
34
|
+
@app.get("/health", certified=True) # served with a verifiable certificate
|
|
35
|
+
def health(req: Request) -> Response:
|
|
36
|
+
return Response.json({"status": "ok"})
|
|
37
|
+
|
|
38
|
+
@app.post("/items") # runs as an update: writes persist
|
|
39
|
+
def create_item(req: Request) -> Response:
|
|
40
|
+
return Response.json(items.insert(req.json()), status=201)
|
|
41
|
+
|
|
42
|
+
@app.get("/items")
|
|
43
|
+
def list_items(req: Request) -> Response:
|
|
44
|
+
return Response.json(items.list(limit=20, after=req.query.get("after")))
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Outbound HTTPS looks like urllib, but async — because on ICP it is:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from pyre.compat import urllib_request as urllib
|
|
51
|
+
|
|
52
|
+
@app.get("/quote", update=True)
|
|
53
|
+
async def quote(req):
|
|
54
|
+
resp = await urllib.urlopen("https://api.example.com/quote",
|
|
55
|
+
max_response_bytes=8_192)
|
|
56
|
+
return Response.json({"upstream_status": resp.status, "data": resp.json()})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
And ICP's genuinely differentiated capabilities read like ordinary Python
|
|
60
|
+
(every one opt-in — a plain CRUD app never meets them):
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pyre import random as prandom, time as ptime, sign
|
|
64
|
+
from pyre.adapters import supabase
|
|
65
|
+
|
|
66
|
+
@app.get("/id")
|
|
67
|
+
def new_id(req):
|
|
68
|
+
return Response.json({"id": prandom.uuid4()}) # consensus-safe; naive uuid4 fails loudly
|
|
69
|
+
|
|
70
|
+
@app.get("/attest", update=True)
|
|
71
|
+
async def attest(req):
|
|
72
|
+
token = await sign.jwt({"sub": req.caller, "iat": ptime.now()})
|
|
73
|
+
return Response.json({"jwt": token}) # threshold-signed: no key to steal
|
|
74
|
+
|
|
75
|
+
@app.get("/external", update=True)
|
|
76
|
+
async def external(req):
|
|
77
|
+
db = supabase.Client(url=SUPA_URL, anon_key=SUPA_KEY)
|
|
78
|
+
return Response.json(await db.table("items").select().limit(10))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install pyre-icp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The distribution is `pyre-icp`; the import package and the CLI are both
|
|
88
|
+
`pyre`. To deploy canisters you also need, one time:
|
|
89
|
+
|
|
90
|
+
- **Python 3.10.x** (Kybra's RustPython targets 3.10 — [pyenv](https://github.com/pyenv/pyenv) recommended)
|
|
91
|
+
- **dfx** (the ICP SDK): `DFXVM_INIT_YES=true sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"`
|
|
92
|
+
- **Kybra** in your project's deploy venv: `pip install kybra==0.7.1` +
|
|
93
|
+
`python -m kybra install-dfx-extension`
|
|
94
|
+
|
|
95
|
+
Then:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pyre new myapp --template crud-kv # bare-api | crud-kv | outbound-proxy
|
|
99
|
+
cd myapp
|
|
100
|
+
pyre dev src/app.py # instant local server, no replica needed
|
|
101
|
+
dfx start --background && dfx deploy # real local canister
|
|
102
|
+
dfx deploy --network ic # mainnet
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The [quickstart](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/quickstart.md)
|
|
106
|
+
walks the whole path in ~15 minutes.
|
|
107
|
+
|
|
108
|
+
## The API surface
|
|
109
|
+
|
|
110
|
+
| Module | What it gives you | Docs |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `pyre.App` / `Request` / `Response` | Flask-style routing, path params, hooks, error handlers, CORS, certified routes | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md) |
|
|
113
|
+
| `pyre.data` / `pyre.kv` | Collections + KV over stable memory — survives upgrades; schemas, pagination, lazy migration | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyredata--collections-over-kv) |
|
|
114
|
+
| `pyre.validate` | Dict-schema request validation → clean per-field 400s | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyrevalidate) |
|
|
115
|
+
| `pyre.auth` | Bearer / API-key / HTTP Basic middleware, constant-time, hash-stored creds | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyreauth) |
|
|
116
|
+
| `pyre.compat.urllib_request` | urllib-shaped async HTTPS outcalls with determinism transforms | [concepts.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/concepts.md) |
|
|
117
|
+
| `pyre.random` / `pyre.uuid` / `pyre.time` | Consensus-safe RNG, UUIDs, timestamps (naive stdlib entropy **fails loudly** in-canister — by design) | [random-uuid-time.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/random-uuid-time.md) |
|
|
118
|
+
| `pyre.crypto` | AES-GCM, ChaCha20-Poly1305, sha2/sha3/blake2/blake3, HMAC — audited RustCrypto under the hood | [crypto.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/crypto.md) |
|
|
119
|
+
| `pyre.sign` | Threshold tECDSA signatures + ES256K JWTs — no private key exists | [api.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md#pyresign--threshold-signing-tecdsa) |
|
|
120
|
+
| `pyre.adapters` | Supabase (PostgREST) + Upstash Redis over outcalls, amplification-safe writes | [adapters.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/adapters.md) |
|
|
121
|
+
| `pyre.log` | Structured logging retrievable via `dfx canister logs` | [observability.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/observability.md) |
|
|
122
|
+
| `pyre` CLI | `pyre new` (templates), `pyre dev` (local server + footgun warnings) | [quickstart.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/quickstart.md) |
|
|
123
|
+
|
|
124
|
+
**All docs:**
|
|
125
|
+
[quickstart](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/quickstart.md) ·
|
|
126
|
+
[concepts](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/concepts.md) ·
|
|
127
|
+
[API reference](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/api.md) ·
|
|
128
|
+
[troubleshooting](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/troubleshooting.md) ·
|
|
129
|
+
[stdlib support matrix](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/stdlib-matrix.md) ·
|
|
130
|
+
[secrets & outcalls](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/secrets-and-outcalls.md) ·
|
|
131
|
+
[extending with Rust](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/extending-with-rust.md) ·
|
|
132
|
+
[observability](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/observability.md) ·
|
|
133
|
+
[LLM/agent skill file](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/SKILL.md) ·
|
|
134
|
+
reference app: [examples/food_tracker](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/examples/food_tracker/src/app.py)
|
|
135
|
+
|
|
136
|
+
## The four ICP concepts PYRE teaches (and hides everything else)
|
|
137
|
+
|
|
138
|
+
1. **Query vs. update calls.** Queries are fast, read-only, uncertified;
|
|
139
|
+
updates go through consensus (~1–2 s) and can write. PYRE maps GET →
|
|
140
|
+
query, writes/async → update; honesty guards raise if you write state
|
|
141
|
+
or make outcalls from a query.
|
|
142
|
+
2. **Outbound HTTP is async and consensus-gated.** Every replica performs
|
|
143
|
+
your outcall independently and must agree byte-for-byte — hence
|
|
144
|
+
`await`, and hence transforms.
|
|
145
|
+
3. **The determinism transform.** Upstream responses differ per replica
|
|
146
|
+
(Date headers, request ids). Outcalls run through a transform that
|
|
147
|
+
canonicalizes the response before consensus; `pyre dev` shows you what
|
|
148
|
+
gets stripped before you ever deploy.
|
|
149
|
+
4. **Canisters are long-lived actors.** The interpreter boots once at
|
|
150
|
+
install and stays warm — no cold starts, but funding (cycles) and
|
|
151
|
+
instruction budgets are real. `make budgets` measures; DECISIONS.md
|
|
152
|
+
records.
|
|
153
|
+
|
|
154
|
+
Full explanations with failure symptoms in
|
|
155
|
+
[concepts.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/concepts.md).
|
|
156
|
+
|
|
157
|
+
## Mainnet-proven, not aspirational
|
|
158
|
+
|
|
159
|
+
Every load-bearing claim was tested against ICP mainnet (13-node subnet)
|
|
160
|
+
and recorded in
|
|
161
|
+
[DECISIONS.md](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/DECISIONS.md):
|
|
162
|
+
response certification verified by the official DFINITY verifier (BLS to
|
|
163
|
+
the NNS root key, tamper/stale rejected), outcall determinism proven
|
|
164
|
+
across real replicas, threshold signatures externally verified
|
|
165
|
+
(26.19B cycles ≈ 3.5¢ each), 13× write-amplification converging to single
|
|
166
|
+
rows through the adapters, and a light backend costing ≈ $0.40/month.
|
|
167
|
+
|
|
168
|
+
## Working from a clone
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
make setup # venvs (Python 3.10.7 via pyenv), kybra, dfx extension
|
|
172
|
+
make test # ~240 unit tests, no replica needed
|
|
173
|
+
make dev # instant local server for examples/rest_api
|
|
174
|
+
make start deploy # local replica + all example canisters
|
|
175
|
+
make e2e # 20-check acceptance suite
|
|
176
|
+
make pocketic # canister-level integration tests
|
|
177
|
+
make budget-gate # instruction + wasm-size/idle-burn regression gates
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
CI runs the same gates on every push. See
|
|
181
|
+
[CONTRIBUTING](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/CONTRIBUTING.md).
|
|
182
|
+
|
|
183
|
+
## Scope fences
|
|
184
|
+
|
|
185
|
+
Pure Python only — no C extensions, no Pydantic. No sockets/threads
|
|
186
|
+
(stubbed with guidance), no websockets/streaming. Secret-bearing outcalls
|
|
187
|
+
(calling Stripe/OpenAI with a private key) are a
|
|
188
|
+
[documented limitation](https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/docs/secrets-and-outcalls.md)
|
|
189
|
+
until v1.2's signed proxy.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT © Sweet Papa Technologies
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyre-icp"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "PYRE — Flask-flavored Python on the Internet Computer: certified reads, urllib-shaped outcalls, stable-memory collections"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
keywords = ["internet-computer", "icp", "canister", "web-framework", "kybra"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/Sweet-Papa-Technologies/PYRE"
|
|
23
|
+
Documentation = "https://github.com/Sweet-Papa-Technologies/PYRE/tree/main/docs"
|
|
24
|
+
Repository = "https://github.com/Sweet-Papa-Technologies/PYRE"
|
|
25
|
+
Issues = "https://github.com/Sweet-Papa-Technologies/PYRE/issues"
|
|
26
|
+
Changelog = "https://github.com/Sweet-Papa-Technologies/PYRE/blob/main/DECISIONS.md"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
pyre = "pyre.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
include = ["pyre*"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
pyre = ["templates/*/**/*", "templates/*/**/.*"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""PYRE — Python Runtime for the Edge.
|
|
2
|
+
|
|
3
|
+
Flask-flavored Python on the Internet Computer.
|
|
4
|
+
|
|
5
|
+
from pyre import App, Request, Response, kv
|
|
6
|
+
|
|
7
|
+
app = App()
|
|
8
|
+
|
|
9
|
+
@app.get("/health")
|
|
10
|
+
def health(req: Request) -> Response:
|
|
11
|
+
return Response.json({"status": "ok"})
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pyre._runtime import in_canister
|
|
15
|
+
from pyre.application import App
|
|
16
|
+
from pyre.errors import (
|
|
17
|
+
BadRequest,
|
|
18
|
+
KvWriteInQueryContext,
|
|
19
|
+
OutcallFailed,
|
|
20
|
+
OutcallInQueryContext,
|
|
21
|
+
PyreError,
|
|
22
|
+
ResponseTooLarge,
|
|
23
|
+
UpstreamHTTPError,
|
|
24
|
+
)
|
|
25
|
+
from pyre.http_types import Request, Response
|
|
26
|
+
from pyre.validation import ValidationError, validate
|
|
27
|
+
from pyre import auth
|
|
28
|
+
from pyre import crypto
|
|
29
|
+
from pyre import data
|
|
30
|
+
from pyre import kv
|
|
31
|
+
from pyre import log
|
|
32
|
+
from pyre import sign
|
|
33
|
+
from pyre import ptime
|
|
34
|
+
from pyre import prandom # imports ptime; keep after it
|
|
35
|
+
from pyre import puuid
|
|
36
|
+
|
|
37
|
+
# DX aliases (§v1.1 randomness/time). The real module files are
|
|
38
|
+
# prandom/ptime/puuid — files named random.py/uuid.py/time.py would shadow
|
|
39
|
+
# the stdlib inside the Kybra bundle. These attribute aliases make the
|
|
40
|
+
# documented spelling work:
|
|
41
|
+
#
|
|
42
|
+
# from pyre import random as prandom
|
|
43
|
+
# from pyre import time as ptime
|
|
44
|
+
# from pyre import uuid as puuid
|
|
45
|
+
#
|
|
46
|
+
# Note: only the `from pyre import ...` form works; the statement form
|
|
47
|
+
# `import pyre.random` will raise ModuleNotFoundError because there is no
|
|
48
|
+
# pyre/random.py file — that is deliberate.
|
|
49
|
+
random = prandom
|
|
50
|
+
time = ptime
|
|
51
|
+
uuid = puuid
|
|
52
|
+
|
|
53
|
+
__version__ = "1.1.0"
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"App",
|
|
57
|
+
"Request",
|
|
58
|
+
"Response",
|
|
59
|
+
"kv",
|
|
60
|
+
"auth",
|
|
61
|
+
"crypto",
|
|
62
|
+
"data",
|
|
63
|
+
"log",
|
|
64
|
+
"sign",
|
|
65
|
+
"random",
|
|
66
|
+
"time",
|
|
67
|
+
"uuid",
|
|
68
|
+
"prandom",
|
|
69
|
+
"ptime",
|
|
70
|
+
"puuid",
|
|
71
|
+
"validate",
|
|
72
|
+
"ValidationError",
|
|
73
|
+
"in_canister",
|
|
74
|
+
"PyreError",
|
|
75
|
+
"BadRequest",
|
|
76
|
+
"KvWriteInQueryContext",
|
|
77
|
+
"OutcallInQueryContext",
|
|
78
|
+
"OutcallFailed",
|
|
79
|
+
"ResponseTooLarge",
|
|
80
|
+
"UpstreamHTTPError",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
if in_canister():
|
|
84
|
+
from pyre.compat._stubs import install_stubs
|
|
85
|
+
|
|
86
|
+
install_stubs()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Runtime environment detection and per-dispatch context.
|
|
2
|
+
|
|
3
|
+
PYRE runs in two environments:
|
|
4
|
+
- "canister": inside a Kybra canister (RustPython compiled to WASM on ICP)
|
|
5
|
+
- "dev": on the host CPython (unit tests, `pyre dev` local runner)
|
|
6
|
+
|
|
7
|
+
Keep this module dependency-free; everything else in pyre imports it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def in_canister() -> bool:
|
|
14
|
+
# Kybra executes canister code under RustPython; host tooling is CPython.
|
|
15
|
+
return sys.implementation.name == "rustpython"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _Context:
|
|
19
|
+
"""Per-dispatch flags set by the gateway/dev-server around handler calls."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
# True while a handler is executing in query context (no state
|
|
23
|
+
# writes, no outcalls). The dev server also sets this for routes
|
|
24
|
+
# that are not marked update, so ICP restrictions surface locally.
|
|
25
|
+
self.in_query = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
ctx = _Context()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""pyre.adapters — clients for external HTTPS APIs, outcall-hazard-aware.
|
|
2
|
+
|
|
3
|
+
These wrap pyre's HTTPS outcalls with each service's auth and REST
|
|
4
|
+
conventions, and are designed around the platform facts:
|
|
5
|
+
|
|
6
|
+
- one canister outcall fans out to ~node-count upstream requests
|
|
7
|
+
(measured 13x on a 13-node subnet), so WRITES MUST BE IDEMPOTENT;
|
|
8
|
+
- only GET/HEAD/POST reach upstream;
|
|
9
|
+
- responses ride a determinism transform;
|
|
10
|
+
- ~2s consensus latency per call.
|
|
11
|
+
|
|
12
|
+
Standing rule: integration, not hot path. Your real datastore is
|
|
13
|
+
pyre.data over stable memory; adapters are for syncing with systems
|
|
14
|
+
that live outside the IC.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from pyre.adapters import supabase, upstash # noqa: F401
|