No project description provided
Project description
Basilisk
Write Python canisters for the Internet Computer. Forked from Kybra.
Features
- Write IC canisters in pure Python using
@queryand@updatedecorators - 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
StableBTreeMapfor persistent key-value storage across upgradesPrincipal,Opt,Vec,Record,Varianttype support
Getting Started
Prerequisites
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, orbothas the backend, andlocaloricas 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ic_basilisk-0.8.47.tar.gz.
File metadata
- Download URL: ic_basilisk-0.8.47.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc0c0dbd5ce32f0cbd9d950a59692ad5862880cfe7c0f238b1a8fcb07b1a9331
|
|
| MD5 |
acf64428e10db85e6a874f85be22be79
|
|
| BLAKE2b-256 |
f5be0ff2a59d9fb1ac0e87aa672295ac6963d49ec60698c5dd4c5abd46558d3b
|
File details
Details for the file ic_basilisk-0.8.47-py3-none-any.whl.
File metadata
- Download URL: ic_basilisk-0.8.47-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02d1a0bed418217052bfa43cff2862f6caa491410f6e99e52711d4613e51266b
|
|
| MD5 |
b7009f7ee4cfc4d44bb33343e704dd31
|
|
| BLAKE2b-256 |
c0019e1c23ef15bac2b836484006aa203b1648563234cbc7d7735645bee1234a
|