Skip to content

Commit b839ccc

Browse files
authored
allow installing jiter, and thereby openai and anthropic (#43)
1 parent 185ed81 commit b839ccc

File tree

5 files changed

+85
-10
lines changed

5 files changed

+85
-10
lines changed

README.md

+43-3
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,50 @@ If you choose to save code, it's stored in CloudFlare's R2 object storage, and s
1212

1313
Dependencies are installed when code is run.
1414

15-
Dependencies can be either:
15+
Dependencies can be defined in one of two ways:
1616

17-
- defined via [inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/#inline-script-metadata) — e.g. a comment at the top of the file, as used by [uv](https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies)
18-
- or, inferred from imports in the code — e.g. `import pydantic` will install the `pydantic` package
17+
### Inferred from imports
18+
19+
If there's no metadata, dependencies are inferred from imports in the code.
20+
21+
```py
22+
import pydantic
23+
24+
class Model(pydantic.BaseModel):
25+
x: int
26+
27+
print(Model(x='42'))
28+
```
29+
30+
### Inline script metadata
31+
32+
As introduced in PEP 723, explained [here](https://packaging.python.org/en/latest/specifications/inline-script-metadata/#inline-script-metadata), and popularised by [uv](https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies) — dependencies can be defined in a comment at the top of the file.
33+
34+
This allows use of dependencies that aren't imported in the code, and is more explicit.
35+
36+
```py
37+
# /// script
38+
# dependencies = ["pydantic", "email-validator"]
39+
# ///
40+
import pydantic
41+
42+
class Model(pydantic.BaseModel):
43+
email: pydantic.EmailStr
44+
45+
print(Model(email='[email protected]'))
46+
```
47+
48+
It also allows version to be pinned for non-binary packages (Pyodide only supports a single version for the binary packages it supports, like `pydantic` and `numpy`).
49+
50+
```py
51+
# /// script
52+
# dependencies = ["rich<13"]
53+
# ///
54+
import rich
55+
from importlib.metadata import version
56+
57+
rich.print(f'[red]Rich version:[/red] [blue]{version('rich')}[/blue]')
58+
```
1959

2060
### Sandbox via link
2161

src/frontend/src/app.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ export default function () {
2424
if (data.kind == 'print') {
2525
newTerminalOutput = true
2626
for (const chunk of data.data) {
27-
const arr = new Uint8Array(chunk)
28-
terminalOutput += decoder.decode(arr)
27+
terminalOutput += decoder.decode(chunk)
2928
}
3029
} else if (data.kind == 'status') {
3130
setStatus(data.message)

src/frontend/src/install_dependencies.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@
1515
from pathlib import Path
1616
from typing import Any, TypedDict, Iterable, Literal
1717
import importlib.util
18+
from urllib.parse import urlparse
1819

1920
import tomllib
21+
from packaging.tags import parse_tag # noqa
22+
from packaging.version import Version # noqa
23+
24+
import micropip # noqa
25+
from micropip import transaction # noqa
26+
from micropip.wheelinfo import WheelInfo # noqa
27+
2028
from pyodide.code import find_imports # noqa
2129
import pyodide_js # noqa
2230

@@ -41,6 +49,33 @@ class Error:
4149
kind: Literal['error'] = 'error'
4250

4351

52+
# This is a temporary hack to install jiter from a URL until
53+
# https://github.com/pyodide/pyodide/pull/5388 is released.
54+
real_find_wheel = transaction.find_wheel
55+
56+
57+
def custom_find_wheel(metadata: Any, req: Any) -> Any:
58+
if metadata.name == 'jiter':
59+
known_version = Version('0.8.2')
60+
if known_version in metadata.releases:
61+
tag = 'cp312-cp312-emscripten_3_1_58_wasm32'
62+
filename = f'{metadata.name}-{known_version}-{tag}.whl'
63+
url = f'https://files.pydantic.run/{filename}'
64+
return WheelInfo(
65+
name=metadata.name,
66+
version=known_version,
67+
filename=filename,
68+
build=(),
69+
tags=frozenset({parse_tag(tag)}),
70+
url=url,
71+
parsed_url=urlparse(url),
72+
)
73+
return real_find_wheel(metadata, req)
74+
75+
76+
transaction.find_wheel = custom_find_wheel
77+
78+
4479
async def install_deps(files: list[File]) -> Success | Error:
4580
sys.setrecursionlimit(400)
4681
cwd = Path.cwd()
@@ -84,7 +119,6 @@ async def install_deps(files: list[File]) -> Success | Error:
84119
if install_ssl:
85120
install_dependencies.append('ssl')
86121

87-
import micropip # noqa
88122
with _micropip_logging() as logs_filename:
89123
try:
90124
await micropip.install(install_dependencies, keep_going=True)

src/frontend/src/types.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface RunCode {
1111

1212
export interface Print {
1313
kind: 'print'
14-
data: ArrayBuffer[]
14+
data: Uint8Array[]
1515
}
1616
export interface Message {
1717
kind: 'status' | 'error' | 'installed'

src/frontend/src/worker.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ self.onmessage = async ({ data }: { data: RunCode }) => {
5252
postPrint()
5353
post({ kind: 'status', message: `${msg}ran code in ${asMs(execTime)}` })
5454
} catch (err) {
55+
postPrint()
5556
console.warn(err)
5657
post({ kind: 'status', message: `${msg}Error occurred` })
5758
post({ kind: 'error', message: formatError(err) })
@@ -156,12 +157,13 @@ function makeTtyOps() {
156157
}
157158
}
158159

159-
let chunks: ArrayBuffer[] = []
160+
let chunks: Uint8Array[] = []
160161
let last_post = 0
161162

162163
function print(tty: any) {
163-
if (tty.output && tty.output.length > 0) {
164-
chunks.push(tty.output)
164+
const output: number[] | null = tty.output
165+
if (output && output.length > 0) {
166+
chunks.push(new Uint8Array(output))
165167
tty.output = []
166168
const now = performance.now()
167169
if (now - last_post > 100) {

0 commit comments

Comments
 (0)