Skip to content

Commit

Permalink
allow installing jiter, and thereby openai and anthropic (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Feb 7, 2025
1 parent 185ed81 commit b839ccc
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 10 deletions.
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,50 @@ If you choose to save code, it's stored in CloudFlare's R2 object storage, and s

Dependencies are installed when code is run.

Dependencies can be either:
Dependencies can be defined in one of two ways:

- 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)
- or, inferred from imports in the code — e.g. `import pydantic` will install the `pydantic` package
### Inferred from imports

If there's no metadata, dependencies are inferred from imports in the code.

```py
import pydantic

class Model(pydantic.BaseModel):
x: int

print(Model(x='42'))
```

### Inline script metadata

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.

This allows use of dependencies that aren't imported in the code, and is more explicit.

```py
# /// script
# dependencies = ["pydantic", "email-validator"]
# ///
import pydantic

class Model(pydantic.BaseModel):
email: pydantic.EmailStr

print(Model(email='hello@pydantic.dev'))
```

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`).

```py
# /// script
# dependencies = ["rich<13"]
# ///
import rich
from importlib.metadata import version

rich.print(f'[red]Rich version:[/red] [blue]{version('rich')}[/blue]')
```

### Sandbox via link

Expand Down
3 changes: 1 addition & 2 deletions src/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export default function () {
if (data.kind == 'print') {
newTerminalOutput = true
for (const chunk of data.data) {
const arr = new Uint8Array(chunk)
terminalOutput += decoder.decode(arr)
terminalOutput += decoder.decode(chunk)
}
} else if (data.kind == 'status') {
setStatus(data.message)
Expand Down
36 changes: 35 additions & 1 deletion src/frontend/src/install_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@
from pathlib import Path
from typing import Any, TypedDict, Iterable, Literal
import importlib.util
from urllib.parse import urlparse

import tomllib
from packaging.tags import parse_tag # noqa
from packaging.version import Version # noqa

import micropip # noqa
from micropip import transaction # noqa
from micropip.wheelinfo import WheelInfo # noqa

from pyodide.code import find_imports # noqa
import pyodide_js # noqa

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


# This is a temporary hack to install jiter from a URL until
# https://github.com/pyodide/pyodide/pull/5388 is released.
real_find_wheel = transaction.find_wheel


def custom_find_wheel(metadata: Any, req: Any) -> Any:
if metadata.name == 'jiter':
known_version = Version('0.8.2')
if known_version in metadata.releases:
tag = 'cp312-cp312-emscripten_3_1_58_wasm32'
filename = f'{metadata.name}-{known_version}-{tag}.whl'
url = f'https://files.pydantic.run/{filename}'
return WheelInfo(
name=metadata.name,
version=known_version,
filename=filename,
build=(),
tags=frozenset({parse_tag(tag)}),
url=url,
parsed_url=urlparse(url),
)
return real_find_wheel(metadata, req)


transaction.find_wheel = custom_find_wheel


async def install_deps(files: list[File]) -> Success | Error:
sys.setrecursionlimit(400)
cwd = Path.cwd()
Expand Down Expand Up @@ -84,7 +119,6 @@ async def install_deps(files: list[File]) -> Success | Error:
if install_ssl:
install_dependencies.append('ssl')

import micropip # noqa
with _micropip_logging() as logs_filename:
try:
await micropip.install(install_dependencies, keep_going=True)
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface RunCode {

export interface Print {
kind: 'print'
data: ArrayBuffer[]
data: Uint8Array[]
}
export interface Message {
kind: 'status' | 'error' | 'installed'
Expand Down
8 changes: 5 additions & 3 deletions src/frontend/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ self.onmessage = async ({ data }: { data: RunCode }) => {
postPrint()
post({ kind: 'status', message: `${msg}ran code in ${asMs(execTime)}` })
} catch (err) {
postPrint()
console.warn(err)
post({ kind: 'status', message: `${msg}Error occurred` })
post({ kind: 'error', message: formatError(err) })
Expand Down Expand Up @@ -156,12 +157,13 @@ function makeTtyOps() {
}
}

let chunks: ArrayBuffer[] = []
let chunks: Uint8Array[] = []
let last_post = 0

function print(tty: any) {
if (tty.output && tty.output.length > 0) {
chunks.push(tty.output)
const output: number[] | null = tty.output
if (output && output.length > 0) {
chunks.push(new Uint8Array(output))
tty.output = []
const now = performance.now()
if (now - last_post > 100) {
Expand Down

0 comments on commit b839ccc

Please sign in to comment.