This library helps you develop a runtime that can be run in a Object as a Service (OaaS) serverless platform. For more information on the OaaS model, visit https://github.com/hpcclab/OaaS.
For a comprehensive guide and API reference, please see the docs directory:
- Tutorial: A step-by-step guide to getting started with the OaaS SDK.
- API Reference: A detailed reference of all classes, methods, and functions.
To install oaas-sdk2-py, you can use pip:
pip install oaas-sdk2-pyOr, if you are using uv:
# Add to your project using uv
uv add oaas-sdk2-py- Simplified API: Easy-to-use decorators and type-safe method definitions
- Type Safety: Full Pydantic model support with automatic validation
- Async/Sync Support: Built with
async/awaitfor non-blocking operations - Data Persistence: Object data is persisted and can be retrieved
- Remote Procedure Calls (RPC): Invoke methods on objects remotely
- Mocking Framework: Includes a mocking utility for testing your OaaS applications
- Rust-Powered Core: High-performance core components written in Rust for speed and efficiency
- Accessor Methods: Explicit
@oaas.getter/@oaas.setterfor typed persisted fields (not exported as standalone functions)
from oaas_sdk2_py import oaas, OaasObject, OaasConfig
from pydantic import BaseModel
# Configure OaaS
config = OaasConfig(async_mode=True, mock_mode=False)
oaas.configure(config)
# Define request/response models
class GreetRequest(BaseModel):
name: str
class GreetResponse(BaseModel):
message: str
# Define your service
@oaas.service("Greeter", package="example")
class Greeter(OaasObject):
counter: int = 0
@oaas.method()
async def greet(self, req: GreetRequest) -> GreetResponse:
return GreetResponse(message=f"Hello, {req.name}!")
@oaas.method()
async def count_greetings(self) -> int:
self.counter += 1
return self.counter
@oaas.method()
async def is_popular(self, threshold: int = 10) -> bool:
return self.counter > threshold
# Accessor methods (not exported as RPC functions)
@oaas.getter()
async def get_counter(self) -> int: ...
@oaas.setter()
async def set_counter(self, value: int) -> int: ...
# Usage
async def main():
# Create and use locally
greeter = Greeter.create(local=True)
# Test with different types
response = await greeter.greet(GreetRequest(name="World"))
count = await greeter.count_greetings() # Returns int
popular = await greeter.is_popular(5) # Returns bool
current_counter = await greeter.get_counter() # Accessor getter
_ = await greeter.set_counter(42) # Accessor setter
print(f"{response.message} (Count: {count}, Popular: {popular})")# Start gRPC server (for external access). Uses HTTP_PORT if set
oaas.start_server(port=8080)
# Start agent (for background processing)
agent_id = await oaas.start_agent(Greeter, obj_id=123)
# Check status
print(f"Server running: {oaas.is_server_running()}")
print(f"Agents: {oaas.list_agents()}")
# Cleanup
await oaas.stop_agent(agent_id)
oaas.stop_server()To generate/export your package spec without running a server, either call oaas.print_pkg() in-process or use the entry oaas.run_or_gen() (used by examples) with gen flags.
@oaas.service: Decorator to define OaaS services@oaas.method: Decorator to expose methods as RPC endpoints@oaas.getter/@oaas.setter: Accessor decorators for persisted fields (not exported as RPC functions)OaasObject: Base class for all OaaS objects with persistenceOaasConfig: Configuration for OaaS runtime
The SDK natively supports these Python types:
- Primitives:
int,float,bool,str - Collections:
list,dict - Binary:
bytes - Models: Pydantic
BaseModelclasses
import psutil
from oaas_sdk2_py import oaas, OaasObject
@oaas.service("ComputeDevice", package="monitoring")
class ComputeDevice(OaasObject):
metrics: dict = {}
@oaas.method()
async def get_cpu_usage(self) -> float:
"""Get current CPU usage as a percentage."""
return psutil.cpu_percent(interval=0.1)
@oaas.method()
async def get_process_count(self) -> int:
"""Get number of running processes."""
return len(psutil.pids())
@oaas.method()
async def is_healthy(self, cpu_threshold: float = 80.0) -> bool:
"""Check if system is healthy."""
cpu_usage = await self.get_cpu_usage()
return cpu_usage < cpu_threshold
@oaas.method()
async def monitor_continuously(self, duration: int) -> dict:
"""Monitor for specified duration and return metrics."""
samples = []
for _ in range(duration):
cpu = psutil.cpu_percent(interval=1.0)
samples.append(cpu)
return {
"avg_cpu": sum(samples) / len(samples),
"samples": len(samples),
"duration": duration
}
# Usage
device = ComputeDevice.create(local=True)
cpu_usage = await device.get_cpu_usage() # Returns float
process_count = await device.get_process_count() # Returns int
is_healthy = await device.is_healthy(75.0) # Returns bool
metrics = await device.monitor_continuously(5) # Returns dictfrom oaas_sdk2_py import oaas, OaasObject
@oaas.service("Counter", package="example")
class Counter(OaasObject):
count: int = 0
history: list = []
@oaas.method()
async def increment(self, amount: int = 1) -> int:
"""Increment counter by amount."""
self.count += amount
self.history.append(f"Added {amount}")
return self.count
@oaas.method()
async def reset(self) -> bool:
"""Reset counter to zero."""
self.count = 0
self.history.clear()
return True
# Accessors for the state fields
@oaas.getter()
async def get_count(self) -> int: ...
@oaas.setter()
async def set_count(self, value: int) -> int: ...
@oaas.getter("history")
async def get_history(self) -> list:
...
# Usage
counter = Counter.create(local=True)
value = await counter.increment(5) # Returns int: 5
current = await counter.get_count() # Accessor getter: 5
history = await counter.get_history() # Accessor getter: ["Added 5"]
reset = await counter.reset() # Returns bool: True
await counter.set_count(5) # Accessor setter: set to 5
value = await counter.get_count() # Accessor getter: 5Primary goal: avoid redundant RPC for simple state access. A method does RPC → run code → access data → RPC reply; an accessor reads/writes the persisted data directly.
Accessors (@oaas.getter/@oaas.setter) are the preferred way to expose simple reads/writes of persisted fields:
- Smaller external surface: accessors are not exported as standalone RPC functions, reducing accidental public APIs.
- Type-safe binding: validated against the field’s annotation at registration; name-based inference cuts boilerplate.
- Clear semantics: getters don’t have side effects; setters just write. Easy to reason about and review.
- Projection support: getters can return a projected sub-value from structured fields (e.g., dict/model paths).
Before (method):
@oaas.method()
async def get_count(self) -> int:
return self.countAfter (accessor):
@oaas.getter("count")
async def get_count(self) -> int:
... # body not required; semantics = read persisted fieldimport pytest
from oaas_sdk2_py import oaas, OaasConfig
# Configure for testing
@pytest.fixture
def setup_mock():
config = OaasConfig(mock_mode=True, async_mode=True)
oaas.configure(config)
@pytest.mark.asyncio
async def test_counter_service(setup_mock):
counter = Counter.create(local=True)
# Test increment
result = await counter.increment(10)
assert result == 10
assert isinstance(result, int)
# Test history
history = await counter.get_history()
assert history == ["Added 10"]
assert isinstance(history, list)
# Test reset
reset_result = await counter.reset()
assert reset_result is True
assert isinstance(reset_result, bool)Create a main module to run your service:
# main.py
import asyncio
import sys
from oaas_sdk2_py import oaas, OaasConfig
async def run_server():
"""Run gRPC server for external access."""
config = OaasConfig(async_mode=True, mock_mode=False)
oaas.configure(config)
oaas.start_server(port=8080) # or set HTTP_PORT env var
print("🚀 Server running on port 8080")
try:
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
print("🛑 Shutting down...")
finally:
oaas.stop_server()
async def run_agent():
"""Run agent for background processing."""
config = OaasConfig(async_mode=True, mock_mode=False)
oaas.configure(config)
# Start both server and agent
oaas.start_server(port=8080)
agent_id = await oaas.start_agent(Counter, obj_id=1)
print(f"🤖 Agent started: {agent_id}")
try:
while True:
await asyncio.sleep(5)
print(f"📊 Server: {oaas.is_server_running()}, Agents: {len(oaas.list_agents())}")
except KeyboardInterrupt:
print("🛑 Shutting down...")
finally:
await oaas.stop_all_agents()
oaas.stop_server()
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "agent":
asyncio.run(run_agent())
else:
asyncio.run(run_server())Run with:
# Server only
python main.py
# Server + Agent
python main.py agent- cargo (install via rust)
- oprc-cli
cargo install --git https://github.com/pawissanutt/oaas-rs.git oprc-cli - OaaS Platform (Oparaca)
- Kubernetes Cluster (e.g., k3d with Docker runtime)
You don't need to follow this guide unless you want to build the Python package on your own.
uv sync
uv build