ScriptsGuide

Dependencies

There is no requirements file to keep. Waiso reads the imports of your code, finds the package each one needs and installs it on the worker before main runs. For Python you can pin a version, add a package the code loads by name or swap the package behind an import in the Dependencies panel — and publishing freezes the exact versions, so a new upstream release never changes a published script.

9 min readBefore this: Writing codeOpen in Waiso
What you will learn
  • See how a Python import becomes a PyPI package (yaml → PyYAML)
  • Pin, add and override packages in the Dependencies panel
  • Know what publishing locks and how to update it
  • Read the four install errors and fix them

Imports are the dependency list

Each language is read its own way. Python imports are read with Python’s own parser at any depth — inside functions, try blocks and conditionals too — and the standard library and your own project scripts are left out. TypeScript and JavaScript imports (import … from, import(), require()) are installed with Bun. C and C++ install nothing: they use the system headers that ship with the worker.

report.py
1import yaml2import requests3 4def main(title: str = "Daily report"):5    rows = requests.get(API_URL).json()6    return {"title": title, "yaml": yaml.safe_dump(rows)}
Python 3.15LSP
3Dependencies2
yamlPyYAML
detectedlocked6.0.3line 1
requestsrequests
declaredlocked2.32.3line 2
Add packageUpdate dependencies
Versions are frozen when you publish. Update dependencies resolves them again for the next publish.
Each import with its package and where it came from: detected, declared, hint or locked.
read_config.py
import yaml  # installs the PyPI package PyYAML


def main(config: str = "name: nightly\nretries: 3\n"):
    data = yaml.safe_load(config)
    return {"name": data["name"], "retries": data["retries"]}
Expected outputSuccessThe same result in all three; the first run installs the package, later runs reuse the cache
{
  "name": "nightly",
  "retries": 3
}

The first run of a new set of packages shows the install in Logs; after that the worker reuses its cache and the run starts at once. An import that is not in the code is not installed — there is nothing to declare for packages your code already imports.

Python: from import to package

Many packages are imported by a name that is not the one you install. Waiso keeps a map of those — about 150 entries, the longest matching module path wins — and anything not in the map is tried by its own name.

You writeWaiso installs
import yamlPyYAML
from PIL import ImagePillow
from bs4 import BeautifulSoupbeautifulsoup4
import cv2opencv-python-headless
from sklearn import …scikit-learn
from dateutil import parserpython-dateutil
import jwtPyJWT
from google.cloud import storagegoogle-cloud-storage (by rule)
thumbnail.py
def main(width: int = 64, height: int = 64):
    # An import inside a function is found too: Pillow is installed.
    from PIL import Image

    img = Image.new("RGB", (width, height), "white")
    return {"size": list(img.size), "mode": img.mode}

The Dependencies panel

A Python script has a Dependencies tab in the editor’s side panel, with a count of its packages. Every row is one import and the package it installs, wearing a chip: detected (found in the code), declared (saved in the panel), hint (a legacy comment) and, once published, locked. An import the map does not know shows No package with this name and a Declare package button; a run still tries it under its own name.

  1. Pin a version

    Click Change the package for this import on a detected row, type a specifier in Version — ==2.31.0 or >=2,<3; empty means the latest — and Save. The row turns declared.

  2. Add a package the code does not import

    Add package declares one by name — for a plugin a library loads by itself, or for Extras such as requests[socks].

  3. Swap the package behind an import

    On a detected row, the same button lets you name another package for that import — for example the full opencv-python instead of the headless build.

Old comment hints

Older scripts may carry whole-line comments such as # @requests@2.31.0. They still work, with their version — but only that exact form: a comment on its own line, #, @, the package and an optional @version. When the panel finds them it says Packages are declared in code comments and offers Move to the panel, so they are saved with the script instead.

Publishing locks the versions

A draft run resolves the packages — the newest versions your declarations allow — and records what it installed. Publish freezes that resolution as the script’s lock: every package, the ones pulled in by others included, with its exact version. The published version then always installs exactly ==version of each, without resolving again, so schedules, the API and flows keep running what you tested.

To take newer versions, press Update dependencies at the foot of the panel: the next draft run resolves fresh, and the next publish freezes that. Until then the published version keeps its lock.

TypeScript and JavaScript

Every bare package an import names is installed with bun install when the script runs — its latest version, the first time a given code runs on a worker; the same code reuses that install afterwards. Scoped packages (@scope/pkg) work; a subpath (lodash/fp) installs its base package. Relative paths are your own project scripts, and node: and bun: modules are built in.

In the editor, each import line wears a pill with its state — installing, installed or failed — and clicking it opens a version picker. The version you pick is written as a trailing comment, import _ from 'lodash' // @4.17.21, which sets the version the editor installs for types and completions.

When an install fails

A Python run whose packages cannot be installed fails before main starts, with one of four codes. The error names the package — and the import and line that asked for it — and pip’s own lines are in Logs. When the editor’s own install of a package fails, the Problems tab shows it on the import line too.

CodeMeaningFix
dependency_not_foundNo package by that name on the index — usually an import whose install name differsName the right package for the import in the panel
dependency_version_not_foundThe package exists, but no version matches the specifier, or two requirements conflictLoosen or fix the version
dependency_index_unreachableThe worker could not reach the package indexRun again; if it repeats, ask your administrator
dependency_build_failedpip found the package but could not build or install itPick a version that ships a wheel
Expected outputSuccessA typo in an import: <code>dependency_not_found</code>, before any of your code runs
Could not install the package for `import sklern` (tried `sklern`). Declare the package explicitly: open Dependencies and add the package that provides `sklern`

Limits

  • Scripts have no network access; only the worker’s package step reaches the index, and an administrator may block or allow-list package names.
  • A TypeScript side-effect import with no binding (import 'pkg') is not installed — import something from it.
  • The Dependencies panel and the lock are Python only; C and C++ cannot install packages.
  • At most 100 declarations per script.