Welcome to the new Golem Cloud Docs! 👋
Documentation
Python Language Guide
Defining Components

Defining Golem Components in Python

Creating a project

Using Golem's command line interface provides a set of predefined, Golem-specific examples to choose from as a starting point.

To see the available examples for Python, run:

$ golem-cli list-examples --language python

The set of examples provided by Golem CLI is defined in the open-source repository golem-examples (opens in a new tab).

Then to create a new project based on the default Python example, run:

$ golem-cli new --language python --package-name 'golem:demo' py-example

The command will create a new Golem project in the py-example directory, and print short, language-specific instructions on how to build the project.

Limitations

⚠️

Currently all imports must happen initialization-time, so every module used must be listed in the beginning of the main module.

This limitation is being tracked as issue #23 (opens in a new tab).

Specification-first approach

Golem and the componentize-py toolchain currently requires defining the component's interface using the WebAssembly Interface Type (WIT) format. See the official documentation of this format (opens in a new tab) for reference.

Each new project generated with golem-cli contains a wit directory with at least one .wit file defining a world. This world can contain exports (exported functions and interfaces) and these exports will be the compiled Golem component's public API.

To implement the specification written in WIT, the Python code must implement some of these abstract classes.

Exporting top-level functions

WIT allows exporting one or more top-level functions in the world section, for example:

package golem:demo;
 
world py-example {
    export hello-world: func() -> string;
}

To implement this function in Python, the following steps must be taken:

  • the module containing the base class to be implemented from the generated bindings needs to be imported
  • implement the exported function as a method in Python of a class with the same name as the base class in the bindings

Let's see in code:

# import the binding module - it's name corresponds with the WIT world's name
import py_example
 
# implement the exported function as a method in Python
class PyExample(py_example.PyExample):
    def hello_world(self) -> str:
        return "Hello World"
⚠️

Note that in WIT, identifiers are using the kebab-case naming convention, while Python uses the snake_case convention for methods, and PascalCase for class names. The generated bindings map between the two automatically.

Exporting interfaces

WIT supports defining and exporting whole interfaces, coupling together multiple functions and possibly custom data types.

Take the following example:

package golem:demo;
 
interface api {
  add: func(value: u64);
  get: func() -> u64;
}
 
world py-example {
  export api;
}

This is equivalent to having the two exported functions directly exported from the world section, so the implementation in Python is once again requires to implement the two exported functions as an implementation of an abstract class:

import py_example
from py_example import exports
 
state: int = 0
 
class Api(exports.Api):
    def add(self, value: int):
      global state
      state = state + value
 
    def get(self) -> int:
       global state
       return state

See the Managing state section below to learn the recommended way of managing state in Golem components, which is required to implement these two functions.

Exporting resources

The WIT format supports defining and exporting resources - entities defined by their constructor function and the available methods on them.

Golem supports exporting these resources as part of the worker's API.

The following example modifies the previously seen counter example to define it as a resource, getting the counter's name as a constructor parameter:

package golem:demo;
 
interface api {
  resource counter {
    constructor(name: string);
    add: func(value: u64);
    get: func() -> u64;
  }
}
 
world py-example {
  export api;
}

Resources can have multiple instances within a worker. Their constructor returns a handle which is then used to call the methods on the resource. Learn more about how resources can be implicitly created and invoked through Golem's APIs in the Invocations page.

To implement the above defined WIT resource in Python a few new steps must be taken:

  • the exported interface and the resource itself has two separate abstract classes generated within the bindings module, both need to be imported and implemented
  • the Counter class that implements the resource must have a constructor getting the name parameter
  • the implementations module structure must reflect the generated binding's module structure. In this case, the Counter implementation must be placed in a module named api.

Let's see in code:

main.py
import py_example
from py_example import exports
 
class Api(exports.Api):
    pass
 
api.py
import py_example
import py_example.exports
from py_example.exports import api
 
class Counter(api.Counter):
    def __init__(self, name: str):
        self.name = name
        self.state = 0
 
    def add(self, value: int):
        self.state = self.state + value
 
    def get(self) -> int:
        return self.state

Note that the Counter class can encapsulate it's own state, as every resource instance will be mapped to a corresponding instance of the Python class.

Data types defined in WIT

The WIT specifications contains some primitive and higher level data types and also allows defining custom data types which can be used as function parameters and return values on the exported functions, interfaces and resources.

The following table shows an example of each WIT data type and the corresponding Python type it is mapped to:

WIT typePython type
boolbool
s8, s16, s32, s64int
u8, u16, u32, u64int
f32, f64float
charstr
stringstr
list<string>List[str]
option<u64>Optional[int]
result<s32, string>Result[int, str] in input position, where Result is part of the generated bindings. When used in return position, the success value is used and the error must be raised as an exception.
tuple<u64, string, char>Tuple[int, str, str]
record user { id: u64, name: string }@dataclass class named User with two fields id and name
variant color { red, green, blue, rgb(u32) }Color = Union[ColorRed, ColorGreen, ColorBlue, ColorRgb] where each case is a @dataclass class
enum color { red, green, blue }class Color(Enum): with upper-case cases
flags access { read, write, lst }class Access(Flag): with upper-case flags

Worker configuration

It is often required to pass configuration values to workers when they are started.

In general Golem supports three different ways of doing this:

  1. Defining a list of string arguments passed to the worker, available as command line arguments
  2. Defining a list of key-value pairs passed to the worker, available as environment variables.
  3. Using resource constructors to pass configuration values to the worker.

Command line arguments

The command line arguments associated with the Golem worker can be accessed by reading the standard Python sys.argv variable:

import sys
print(sys.argv)

Command line arguments can only be specified when a worker is explicitly created and they are are empty by default, including in cases when the worker was implicitly created by an invocation.

Environment variables

Environment variables can be accessed in Python using the standard os.environ map:

import os
print(os.environ['KEY'])

Environment variables can be specified when a worker is explicitly created, but there are some environment variables that are always set by Golem:

  • GOLEM_WORKER_NAME - the name of the worker
  • GOLEM_COMPONENT_ID - the ID of the worker's component
  • GOLEM_COMPONENT_VERSION - the version of the component used for this worker

In addition to these, when using Worker to Worker communication, workers created by remote calls inherit the environment variables of the caller.

This feature makes environment variables a good fit for passing configuration such as hostnames, ports, or access tokens to trees of workers.

Resource constructors

As explained earlier, Golem workers can export resources and these resources can have constructor parameters.

Although resources can be used in many ways, one pattern for Golem is only create a single instance of the exported resource in each worker, and use it to pass configuration values to the worker. This is supported by Golem's worker invocation syntax directly, allowing to implicitly create workers and the corresponding resource by a single invocation as described on the Invocations page.

Managing state

Golem workers are stateful. There are two major techniques to store and manipulate state in a Golem worker implemented in Python:

  1. Using global variables
  2. Using resources and storing state in the resource's class

Both techniques has been demonstrated in the code examples above.