Candid
- text
- blob
- nat
- nat64
- nat32
- nat16
- nat8
- int
- int64
- int32
- int16
- int8
- float64
- float32
- bool
- null
- vec
- opt
- record
- variant
- func
- service
- principal
- reserved
- empty
Candid is an interface description language created by DFINITY. It can be used to define interfaces between services (canisters), allowing canisters and clients written in various languages to easily interact with each other.
Kybra allows you to express Candid types through a combination of native and Kybra-provided Python types. These types will be necessary in various places as you define your canister. For example, Candid types must be used when defining the parameters and return types of your query and update methods.
It's important to note that the Candid types are represented at runtime using specific Python data structures that may differ in behavior from the description of the actual Candid type. For example, a float32
Candid type is a Python float, a nat64
is a Python int, and an int
is also a Python int.
Keep this in mind as it may result in unexpected behavior. Each Candid type and its equivalent Python runtime value is explained in more detail in this chapter.
A reference of all Candid types available on the Internet Computer (IC) can be found here.
The following is a simple example showing how to import and use most of the Candid types available in Kybra:
from kybra import (
blob,
float64,
float32,
Func,
int8,
int16,
int32,
int64,
nat,
nat8,
nat16,
nat32,
nat64,
null,
Opt,
Principal,
query,
Query,
Record,
Service,
service_query,
service_update,
Variant,
Vec,
)
class Candid(Record):
text: str
blob: blob
nat: nat
nat64: nat64
nat32: nat32
nat16: nat16
nat8: nat8
int: int
int64: int64
int32: int32
int16: int16
int8: int8
float64: float64
float32: float32
bool: bool
null: null
vec: Vec[str]
opt: Opt["nat"]
record: "CandidRecord"
variant: "CandidVariant"
func: "CandidFunc"
service: "MyService"
principal: Principal
class CandidRecord(Record):
first_name: str
last_name: str
age: nat8
class CandidVariant(Variant, total=False):
Tag1: null
Tag2: null
Tag3: int
class MyService(Service):
@service_query
def query1(self) -> bool:
...
@service_update
def update1(self) -> str:
...
CandidFunc = Func(Query[[], Candid])
@query
def candid_types() -> Candid:
return {
"text": "text",
"blob": bytes(),
"nat": 340_282_366_920_938_463_463_374_607_431_768_211_455,
"nat64": 18_446_744_073_709_551_615,
"nat32": 4_294_967_295,
"nat16": 65_535,
"nat8": 255,
"int": 170_141_183_460_469_231_731_687_303_715_884_105_727,
"int64": 9_223_372_036_854_775_807,
"int32": 2_147_483_647,
"int16": 32_767,
"int8": 127,
"float64": 0.0,
"float32": 0.0,
"bool": True,
"null": None,
"vec": ["has one element"],
"opt": None,
"record": {"first_name": "John", "last_name": "Doe", "age": 35},
"variant": {"Tag1": None},
"func": (Principal.from_str("rrkah-fqaaa-aaaaa-aaaaq-cai"), "candid_types"),
"service": MyService(Principal.from_str("aaaaa-aa")),
"principal": Principal.from_str("ryjl3-tyaaa-aaaaa-aaaba-cai"),
}
Calling candid_types
with dfx
will return:
(
record {
"int" = 170_141_183_460_469_231_731_687_303_715_884_105_727 : int;
"nat" = 340_282_366_920_938_463_463_374_607_431_768_211_455 : nat;
"opt" = null;
"vec" = vec { "has one element" };
"service" = service "aaaaa-aa";
"principal" = principal "ryjl3-tyaaa-aaaaa-aaaba-cai";
"blob" = vec {};
"bool" = true;
"func" = func "rrkah-fqaaa-aaaaa-aaaaq-cai".candid_types;
"int8" = 127 : int8;
"nat8" = 255 : nat8;
"null" = null : null;
"text" = "text";
"nat16" = 65_535 : nat16;
"nat32" = 4_294_967_295 : nat32;
"nat64" = 18_446_744_073_709_551_615 : nat64;
"int16" = 32_767 : int16;
"int32" = 2_147_483_647 : int32;
"int64" = 9_223_372_036_854_775_807 : int64;
"variant" = variant { Tag1 };
"float32" = 0 : float32;
"float64" = 0 : float64;
"record" = record {
age = 35 : nat8;
first_name = "John";
last_name = "Doe";
};
},
)
text
The Python type str
and the Kybra type text
both correspond to the Candid type text and will become a Python str at runtime.
Python:
from kybra import ic, query
@query
def get_string() -> str:
return "Hello world!"
@query
def print_string(string: str) -> str:
ic.print(type(string))
return string
Candid:
service: {
"get_string": () -> (text) query;
"print_string": (text) -> (text) query;
}
blob
The Kybra type blob
corresponds to the Candid type blob and will become a Python bytes at runtime.
Python:
from kybra import blob, ic, query
@query
def get_blob() -> blob:
return bytes([68, 73, 68, 76, 0, 0])
@query
def print_blob(blob: blob) -> blob:
ic.print(type(blob))
return blob
Candid:
service: {
"get_blob": () -> (blob) query;
"print_blob": (blob) -> (blob) query;
}
nat
The Kybra type nat
corresponds to the Candid type nat and will become a Python int at runtime.
Python:
from kybra import ic, nat, query
@query
def get_nat() -> nat:
return 340_282_366_920_938_463_463_374_607_431_768_211_455
@query
def print_nat(nat: nat) -> nat:
ic.print(type(nat))
return nat
Candid:
service: {
"get_nat": () -> (nat) query;
"print_nat": (nat) -> (nat) query;
}
nat64
The Kybra type nat64
corresponds to the Candid type nat64 and will become a Python int at runtime.
Python:
from kybra import ic, nat64, query
@query
def get_nat64() -> nat64:
return 18_446_744_073_709_551_615
@query
def print_nat64(nat64: nat64) -> nat64:
ic.print(type(nat64))
return nat64
Candid:
service: {
"get_nat64": () -> (nat64) query;
"print_nat64": (nat64) -> (nat64) query;
}
nat32
The Kybra type nat32
corresponds to the Candid type nat32 and will become a Python int at runtime.
Python:
from kybra import ic, nat32, query
@query
def get_nat32() -> nat32:
return 4_294_967_295
@query
def print_nat32(nat32: nat32) -> nat32:
ic.print(type(nat32))
return nat32
Candid:
service: {
"get_nat32": () -> (nat32) query;
"print_nat32": (nat32) -> (nat32) query;
}
nat16
The Kybra type nat16
corresponds to the Candid type nat16 and will become a Python int at runtime.
Python:
from kybra import ic, nat16, query
@query
def get_nat16() -> nat16:
return 65_535
@query
def print_nat16(nat16: nat16) -> nat16:
ic.print(type(nat16))
return nat16
Candid:
service: {
"get_nat16": () -> (nat16) query;
"print_nat16": (nat16) -> (nat16) query;
}
nat8
The Kybra type nat8
corresponds to the Candid type nat8 and will become a Python int at runtime.
Python:
from kybra import ic, nat8, query
@query
def get_nat8() -> nat8:
return 255
@query
def print_nat8(nat8: nat8) -> nat8:
ic.print(type(nat8))
return nat8
Candid:
service: {
"get_nat8": () -> (nat8) query;
"print_nat8": (nat8) -> (nat8) query;
}
int
The Kybra type int
corresponds to the Candid type int and will become a Python int at runtime.
Python:
from kybra import ic, query
@query
def get_int() -> int:
return 170_141_183_460_469_231_731_687_303_715_884_105_727
@query
def print_int(int: int) -> int:
ic.print(type(int))
return int
Candid:
service: {
"get_int": () -> (int) query;
"print_int": (int) -> (int) query;
}
int64
The Kybra type int64
corresponds to the Candid type int64 and will become a Python int at runtime.
Python:
from kybra import ic, int64, query
@query
def get_int64() -> int64:
return 9_223_372_036_854_775_807
@query
def print_int64(int64: int64) -> int64:
ic.print(type(int64))
return int64
Candid:
service: {
"get_int64": () -> (int64) query;
"print_int64": (int64) -> (int64) query;
}
int32
The Kybra type int32
corresponds to the Candid type int32 and will become a Python int at runtime.
Python:
from kybra import ic, int32, query
@query
def get_int32() -> int32:
return 2_147_483_647
@query
def print_int32(int32: int32) -> int32:
ic.print(type(int32))
return int32
Candid:
service: {
"get_int32": () -> (int32) query;
"print_int32": (int32) -> (int32) query;
}
int16
The Kybra type int16
corresponds to the Candid type int16 and will become a Python int at runtime.
Python:
from kybra import ic, int16, query
@query
def get_int16() -> int16:
return 32_767
@query
def print_int16(int16: int16) -> int16:
ic.print(type(int16))
return int16
Candid:
service: {
"get_int16": () -> (int16) query;
"print_int16": (int16) -> (int16) query;
}
int8
The Kybra type int8
corresponds to the Candid type int8 and will become a Python int at runtime.
Python:
from kybra import ic, int8, query
@query
def get_int8() -> int8:
return 127
@query
def print_int8(int8: int8) -> int8:
ic.print(type(int8))
return int8
Candid:
service: {
"get_int8": () -> (int8) query;
"print_int8": (int8) -> (int8) query;
}
float64
The Kybra type float64
corresponds to the Candid type float64 and will become a Python float at runtime.
Python:
import math
from kybra import float64, ic, query
@query
def get_float64() -> float64:
return math.e
@query
def print_float64(float64: float64) -> float64:
ic.print(type(float64))
return float64
Candid:
service: {
"get_float64": () -> (float64) query;
"print_float64": (float64) -> (float64) query;
}
float32
The Kybra type float32
corresponds to the Candid type float32 and will become a Python float at runtime.
Python:
import math
from kybra import float32, ic, query
@query
def get_float32() -> float32:
return math.pi
@query
def print_float32(float32: float32) -> float32:
ic.print(type(float32))
return float32
Candid:
service: {
"get_float32": () -> (float32) query;
"print_float32": (float32) -> (float32) query;
}
bool
The Python type bool
corresponds to the Candid type bool and will become a Python Boolean Value at runtime.
Python:
from kybra import ic, query
@query
def get_bool() -> bool:
return True
@query
def print_bool(bool: bool) -> bool:
ic.print(type(bool))
return bool
Candid:
service: {
"get_bool": () -> (bool) query;
"print_bool": (bool) -> (bool) query;
}
null
The Kybra type null
corresponds to the Candid type null and will become the Python Null Object at runtime.
Python:
from kybra import ic, null, query
@query
def get_null() -> null:
return None
@query
def print_null(none: null) -> null:
ic.print(type(none))
return none
Candid:
service: {
"get_null": () -> (null) query;
"print_null": (null) -> (null) query;
}
vec
The Kybra type Vec
corresponds to the Candid type vec and will become an array of the specified type at runtime.
Python:
from kybra import int32, query, Vec
@query
def get_numbers() -> Vec[int32]:
return [0, 1, 2, 3]
Candid:
service: {
"get_numbers": () -> (vec int32) query;
}
Opt
The Kybra type Opt
corresponds to the Candid type opt and will become the enclosed Python type or None at runtime.
Python:
from kybra import Opt, query
@query
def get_opt_some() -> Opt[bool]:
return True
@query
def get_opt_none() -> Opt[bool]:
return None
Candid:
service: {
"get_opt_some": () -> (opt bool) query;
"get_opt_none": () -> (opt bool) query;
}
record
Python classes that inherit from the Kybra type Record
correspond to the Candid record type and will become Python TypedDicts at runtime.
Python:
from kybra import Record, Vec
class Post(Record):
id: str
author: "User"
text: str
thread: "Thread"
class Thread(Record):
id: str
author: "User"
posts: Vec[Post]
title: str
class User(Record):
id: str
posts: Vec[Post]
thread: Vec[Thread]
username: str
Candid:
type Post = record {
"id": text;
"author": User;
"text": text;
"thread": Thread;
};
type Thread = record {
"id": text;
"author": User;
"posts": vec Post;
"title": text;
};
type User = record {
"id": text;
"posts": vec Post;
"threads": vec Thread;
"username": text;
};
variant
Python classes that inherit from the Kybra type Variant
correspond to the Candid variant type and will become Python TypedDicts at runtime.
Python:
from kybra import nat32, null, Variant
class ReactionType(Variant, total=False):
Fire: null
ThumbsUp: null
ThumbsDown: null
Emotion: "Emotion"
Firework: "Firework"
class Emotion(Variant, total=False):
Happy: null
Sad: null
class Firework(Variant, total=False):
Color: str
NumStreaks: nat32
Candid:
type ReactionType = variant {
"Fire": null;
"ThumbsUp": null;
"ThumbsDown": null;
"Emotion": Emotion;
"Firework": Firework
};
type Emotion = variant {
"Happy": null;
"Sad": null
};
type Firework = record {
"Color": text;
"NumStreaks": nat32;
};
func
The Kybra type Func
corresponds to the Candid type func and at runtime will become a Python tuple with two elements, the first being an ic-py Principal and the second being a Python str. The ic-py Principal
represents the principal
of the canister/service where the function exists, and the str
represents the function's name.
Python:
from kybra import Func, nat64, null, Principal, query, Query, Record, Update, Variant
class User(Record):
id: str
basic_func: "BasicFunc"
complex_func: "ComplexFunc"
class Reaction(Variant, total=False):
Good: null
Bad: null
BasicFunc: "BasicFunc"
ComplexFunc: "ComplexFunc"
BasicFunc = Func(Query[[str], str])
ComplexFunc = Func(Update[[User, Reaction], nat64])
@query
def get_basic_func() -> BasicFunc:
return (Principal.from_str("rrkah-fqaaa-aaaaa-aaaaq-cai"), "simple_function_name")
@query
def get_complex_func() -> ComplexFunc:
return (Principal.from_str("ryjl3-tyaaa-aaaaa-aaaba-cai"), "complex_function_name")
Candid:
type User = record {
"id": text;
"basic_func": BasicFunc;
"complex_func": ComplexFunc;
};
type Reaction = variant { "Good": null; "Bad": null; "BasicFunc": BasicFunc; "ComplexFunc": ComplexFunc };
type BasicFunc = func (text) -> (text) query;
type ComplexFunc = func (User, Reaction) -> (nat64);
service: () -> {
"get_basic_func": () -> (BasicFunc) query;
"get_complex_func": () -> (ComplexFunc) query;
}
service
Python classes that inherit from the Kybra type Service
correspond to the Candid service type and will become child classes capable of creating instances that can perform cross-canister calls at runtime.
Python:
from kybra import (
Async,
CallResult,
Principal,
query,
Service,
service_query,
service_update,
update,
)
class SomeService(Service):
@service_query
def query1(self) -> bool:
...
@service_update
def update1(self) -> str:
...
@query
def get_service() -> SomeService:
return SomeService(Principal.from_str("aaaaa-aa"))
@update
def call_service(service: SomeService) -> Async[str]:
result: CallResult[str] = yield service.update1()
if result.Err is not None:
raise Exception(f"call to service.update1 failed with: {result.Err}")
return result.Ok
Candid:
service { query1 : () -> (bool) query; update1 : () -> (text) }
principal
The Kybra type Principal
corresponds to the Candid type principal and will become an ic-py Principal at runtime.
Python:
from kybra import ic, Principal, query
@query
def get_principal() -> Principal:
return Principal.from_str("rrkah-fqaaa-aaaaa-aaaaq-cai")
@query
def print_principal(principal: Principal) -> Principal:
ic.print(type(principal))
return principal
Candid:
service: {
"get_principal": () -> (principal) query;
"print_principal": (principal) -> (principal) query;
}
reserved
The Kybra type reserved
corresponds to the Candid type reserved and will become the Python Null Object at runtime.
Python:
from kybra import ic, query, reserved
@query
def get_reserved() -> reserved:
return "anything"
@query
def print_reserved(reserved: reserved) -> reserved:
ic.print(type(reserved))
return reserved
Candid:
service: {
"get_reserved": () -> (reserved) query;
"print_reserved": (reserved) -> (reserved) query;
}
empty
The Kybra type empty
corresponds to the Candid type empty and has no Python value at runtime.
Python:
from kybra import empty, ic, query
@query
def get_empty() -> empty:
raise Exception("Anything you want")
# Note: It is impossible to call this function because it requires an argument
# but there is no way to pass an "empty" value as an argument.
@query
def print_empty(empty: empty) -> empty:
ic.print(type(empty))
raise Exception("Anything you want")
Candid:
service: {
"get_empty": () -> (empty) query;
"print_empty": (empty) -> (empty) query;
}