Skip to main content

No project description provided

Project description

Basilisk

PyPI Test

Write Python canisters for the Internet Computer. Forked from Kybra.

Features

  • Write IC canisters in pure Python using @query and @update decorators
  • Two backends: CPython 3.13 (default, fast builds) and RustPython
  • Fast template builds: CPython canisters build in seconds, not minutes
  • IC system APIs: ic.caller(), ic.time(), ic.print(), ic.canister_balance(), etc.
  • In-memory filesystem: os.mkdir, os.path.exists, os.rename, os.makedirs, open() for file I/O
  • Chunked code upload for canisters larger than 10MB
  • StableBTreeMap for persistent key-value storage across upgrades
  • Principal, Opt, Vec, Record, Variant type support

Getting Started

Prerequisites

  • dfx (IC SDK)
  • Python 3.10+
  • WASI SDK (for CPython backend)

Install

pip install ic-basilisk

Create a new project

basilisk new my_project
cd my_project

This creates a ready-to-deploy project:

my_project/
  src/main.py    -- your canister code
  dfx.json       -- IC project config

The generated canister code

from basilisk import query, update, text, nat64, ic

# A simple counter stored in a global variable.
# State persists across calls but resets on canister upgrade.
counter = 0

@query
def greet(name: text) -> text:
    """Return a greeting message."""
    return f"Hello, {name}! The counter is at {counter}."

@query
def get_counter() -> nat64:
    """Read the current counter value."""
    return counter

@update
def increment() -> nat64:
    """Increment the counter and return the new value."""
    global counter
    counter += 1
    return counter

@query
def get_time() -> nat64:
    """Return the current IC timestamp in nanoseconds."""
    return ic.time()

@query
def whoami() -> text:
    """Return the caller's principal ID."""
    return str(ic.caller())

Deploy and call

dfx start --background
dfx deploy

dfx canister call my_project greet '("World")'
# ("Hello, World! The counter is at 0.")

dfx canister call my_project increment
# (1 : nat64)

dfx canister call my_project whoami
# ("2vxsx-fae")

Filesystem

Basilisk provides an in-memory filesystem via the WASI polyfill. You can use standard Python os operations and open() for file I/O — no special imports needed.

Directory operations

import os

@update
def create_workspace() -> text:
    os.makedirs("/data/reports", exist_ok=True)
    os.mkdir("/data/logs")
    return f"exists={os.path.exists('/data/reports')} is_dir={os.path.isdir('/data/logs')}"

@update
def cleanup(path: text) -> text:
    os.rename("/data/logs", "/data/archive")
    os.rmdir("/data/archive")
    return f"renamed and removed, gone={not os.path.exists('/data/archive')}"

Supported: os.mkdir, os.makedirs, os.rmdir, os.rename, os.path.exists, os.path.isdir, os.path.isfile, os.stat.

File I/O

@update
def save_config(data: text) -> text:
    with open("/data/config.json", "w") as f:
        f.write(data)
    return "saved"

@query
def load_config() -> text:
    with open("/data/config.json", "r") as f:
        return f.read()

Note: The filesystem is in-memory (heap). Data persists across calls but resets on canister upgrade. For persistent storage, use StableBTreeMap.

StableBTreeMap

StableBTreeMap provides key-value storage that survives canister upgrades using IC stable memory.

from basilisk import query, update, text, nat64, Opt, StableBTreeMap

db = StableBTreeMap[str, str](memory_id=0, max_key_size=100, max_value_size=100)

@update
def db_set(key: text, value: text) -> text:
    old = db.insert(key, value)
    return f"set {key}={value} (old={old})"

@query
def db_get(key: text) -> Opt[text]:
    return db.get(key)

@query
def db_len() -> nat64:
    return db.len()
dfx canister call my_project db_set '("name", "Alice")'
# ("set name=Alice (old=None)")

dfx canister call my_project db_get '("name")'
# (opt "Alice")

# Data survives upgrades:
dfx deploy my_project --upgrade-unchanged
dfx canister call my_project db_get '("name")'
# (opt "Alice")  ← still there!

Python Backends

Basilisk supports two Python backends:

# CPython 3.13 (default) -- fast template builds
basilisk new my_project

# RustPython -- legacy, full Rust build
basilisk new --backend rustpython my_project

CPython vs RustPython

CPython 3.13 RustPython
Build time ~seconds (template) ~60-120s (Cargo build)
Wasm size ~5.3 MB ~26 MB
Python compatibility Full (reference implementation) Partial (~3.10)

Benchmark Results

Wasm instruction counts measured on a PocketIC replica via GitHub Actions CI. Lower is better — fewer instructions means lower cycle cost on the IC.

Benchmark CPython (instructions) RustPython (instructions) RustPython / CPython
noop (call overhead) 15,914 88,918 5.6x
increment (state mutation) 16,050 92,485 5.8x
fibonacci(25) (iterative) 37,269 294,649 7.9x
fibonacci_recursive(20) 29,617,903 337,795,318 11.4x
string_ops (100 concatenations) 275,375 2,135,202 7.8x
list_ops (500 append + sort) 602,711 5,819,267 9.7x
dict_ops (500 inserts + lookups) 3,407,101 23,087,720 6.8x
method_overhead (total prelude) 11,122 42,216 3.8x

CPython is 6–11x faster than RustPython for compute-heavy workloads due to its optimized C interpreter. The gap is largest for recursive function calls (11.4x) and list operations (9.7x). Even the minimum overhead per call is lower: 11K vs 42K instructions.

Full CI logs: CPython run · RustPython run

Run it yourself: trigger the Benchmark workflow from the Actions tab — select cpython, rustpython, or both as the backend, and local or ic as the network.

The benchmark source is in benchmarks/counter/.

Disclaimer

Basilisk may have unknown security vulnerabilities due to the following:

  • Limited or no production deployments on the IC
  • No extensive automated property tests
  • No independent security reviews/audits

Documentation

For detailed architecture notes, see CPYTHON_MIGRATION_NOTES.md.

Discussion

Feel free to open issues.

License

See LICENSE.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ic_basilisk-0.8.46.tar.gz (445.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ic_basilisk-0.8.46-py3-none-any.whl (582.7 kB view details)

Uploaded Python 3

File details

Details for the file ic_basilisk-0.8.46.tar.gz.

File metadata

  • Download URL: ic_basilisk-0.8.46.tar.gz
  • Upload date:
  • Size: 445.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.19

File hashes

Hashes for ic_basilisk-0.8.46.tar.gz
Algorithm Hash digest
SHA256 0d559e63bb10c22c1cf3f32ba642441c254c4b257fa9c6644c21de7838e70bcc
MD5 9aba9d9d297b12f7f5b2cbb718b849e6
BLAKE2b-256 f4b8cade775fcb325ae07dc8f184a6fa9724cfc0efbf08523631d8eeafc0661c

See more details on using hashes here.

File details

Details for the file ic_basilisk-0.8.46-py3-none-any.whl.

File metadata

  • Download URL: ic_basilisk-0.8.46-py3-none-any.whl
  • Upload date:
  • Size: 582.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.19

File hashes

Hashes for ic_basilisk-0.8.46-py3-none-any.whl
Algorithm Hash digest
SHA256 ded760533f7d26b0227f42f6efe7bc876339d7c379254335018c582e81eb2869
MD5 e764f827a8631c0a2d1615541c9230ad
BLAKE2b-256 a62727150b34818ed0e0fd1ab16f9cd060fa91c50e1fa8d74901244cff554a73

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page