Skip to main content

Nodes and Fields

Nodes and Fields are the fundamental building blocks of Capela applications. Understanding how to use them effectively is crucial for building robust applications.

Nodes

A Node is the basic unit of data in Capela. It represents an entity in your application and serves as the building block for your application's data model.

Creating a Node

To create a Node, you need to define a class that inherits from the Node class:

from builtins import Node, Field

class User(Node):
name: str = Field(default="")
age: int = Field(default=0)

Node Methods

Nodes can have methods that operate on their data:

class Counter(Node):
count: int = Field(default=0)

def increment(self):
self.count += 1
return self.count

def reset(self):
self.count = 0
return self.count

Node Lifecycle

Nodes have a lifecycle that includes:

  1. Creation: A Node is created when it is instantiated or when a partition is created.
  2. Initialization: Fields are initialized with their default values.
  3. Persistence: Nodes are automatically persisted to the database.
  4. Retrieval: Nodes can be retrieved from the database using their reference.

Fields

Fields define the properties of a Node. They are strongly typed and can have default values.

Field Types

Capela supports various field types:

Basic Types

  • int: Integer values
  • bool: Boolean values (True/False)
  • float: Floating point numbers
  • str: String values
  • bytes: Binary data

Collection Types

  • list[T]: Ordered list of elements of type T (e.g., list[int])
  • dict[K, V]: Key-value mapping where K is the key type and V is the value type (e.g., dict[str, int])
  • set[T]: Unordered collection of unique elements of type T (e.g., set[int])

Collection types can be accessed using array notation or quoted keys for dictionaries:

# Access dictionary values
http GET 'http://localhost:22440/g/!partition_id/users/"Alice"'
http POST 'http://localhost:22440/g/!partition_id/users/"Alice"' --raw '{"name": "Alice", "age": 30}'

# Access array values
http GET 'http://localhost:22440/g/!partition_id/users/0'
http POST 'http://localhost:22440/g/!partition_id/users/0' --raw '{"name": "Alice", "age": 30}'

Custom Types

You can create custom types by defining a class that inherits from Model. These types can be used as fields in your Nodes:

class Address(Model):
street: str = Field(default="")
city: str = Field(default="")
state: str = Field(default="")
zip_code: str = Field(default="")

class User(Node):
name: str = Field(default="")
address: Address = Field(default_factory=Address)

Field Definition

Fields are defined using the Field class with the following syntax:

variable_name: type = Field(default=default_value)

For collection types, you can use default_factory to provide a function that creates a new instance of the collection:

class User(Node):
name: str = Field(default="")
preferences: dict[str, bool] = Field(default_factory=dict)
tags: list[str] = Field(default_factory=list)

Field Validation

Fields can have validation rules to ensure that the data is valid:

class User(Node):
name: str = Field(default="")
age: int = Field(default=0, ge=0, le=120) # age must be between 0 and 120
email: str = Field(default="", regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") # email must be valid

Example: A Complete Node

Here's a complete example of a Node with various field types:

from builtins import Node, Field, List, Dict
from datetime import datetime

class Address(Model):
street: str = Field(default="")
city: str = Field(default="")
state: str = Field(default="")
zip_code: str = Field(default="")

class User(Node):
# Basic types
name: str = Field(default="")
age: int = Field(default=0)
is_active: bool = Field(default=True)

# Collection types
preferences: Dict[str, bool] = Field(default_factory=dict)
tags: List[str] = Field(default_factory=list)

# Custom types
address: Address = Field(default_factory=Address)

# Methods
def activate(self):
self.is_active = True
return self.is_active

def deactivate(self):
self.is_active = False
return self.is_active

def add_tag(self, tag: str):
if tag not in self.tags:
self.tags.append(tag)
return self.tags

def remove_tag(self, tag: str):
if tag in self.tags:
self.tags.remove(tag)
return self.tags