Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 45
v 0.0.7b5#169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
v 0.0.7b5 #169
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,19 @@ | ||
| from dotenv import load_dotenv | ||
| from exospherehost import Runtime | ||
| from nodes.list_s3_files import ListS3FilesNode | ||
| from nodes.download_s3_file import DownloadS3FileNode | ||
| # Load environment variables from .env file | ||
| # EXOSPHERE_STATE_MANAGER_URI is the URI of the state manager | ||
| # EXOSPHERE_API_KEY is the key of the runtime | ||
| load_dotenv() | ||
| # Note on node ordering: | ||
| # The order of node classes in the `nodes` list does not define execution sequence. | ||
| # Nodes are registered with the state manager; orchestration and dependencies are handled externally. | ||
| # `ListS3FilesNode` is listed before `DownloadS3FileNode` for readability only. | ||
| Runtime( | ||
| name="cloud-storage-runtime", | ||
| namespace="exospherehost", | ||
| nodes=[ListS3FilesNode] | ||
| nodes=[ListS3FilesNode, DownloadS3FileNode] | ||
| ).start() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import boto3 | ||
| from exospherehost import BaseNode | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| from pydantic import BaseModel | ||
| class DownloadS3FileNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| bucket_name: str | ||
| key: str | ||
| class Outputs(BaseModel): | ||
| file_path: str | ||
| class Secrets(BaseModel): | ||
| aws_access_key_id: str | ||
| aws_secret_access_key: str | ||
| aws_region: str | ||
| async def execute(self) -> Outputs: | ||
| s3_client = boto3.client( | ||
| 's3', | ||
| aws_access_key_id=self.secrets.aws_access_key_id, | ||
| aws_secret_access_key=self.secrets.aws_secret_access_key, | ||
| region_name=self.secrets.aws_region | ||
| ) | ||
| file_name = self.inputs.key.split('/')[-1] | ||
| s3_client.download_file(self.inputs.bucket_name, self.inputs.key, file_name) | ||
| return self.Outputs(file_path=self.outputs.file_path) | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -23,6 +23,8 @@ pip install exospherehost | ||
| ## Quick Start | ||
| > Important: In v1, all fields in `Inputs`, `Outputs`, and `Secrets` must be strings. If you need to pass complex data (e.g., JSON), serialize the data to a string first, then parse that string within your node. | ||
| ### Basic Node Creation | ||
| Create a simple node that processes data: | ||
| @@ -34,24 +36,24 @@ from pydantic import BaseModel | ||
| class SampleNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| name: str | ||
| data: dict | ||
| data: str # v1: strings only | ||
| class Outputs(BaseModel): | ||
| message: str | ||
| processed_data: dict | ||
| processed_data: str # v1: strings only | ||
| async def execute(self) -> Outputs: | ||
| print(f"Processing data for: {self.inputs.name}") | ||
| # Your processing logic here | ||
| processed_data = {"status": "completed", "input": self.inputs.data} | ||
| # Your processing logic here; serialize complex data to strings (e.g., JSON) | ||
| processed_data = f"completed:{self.inputs.data}" | ||
| return self.Outputs( | ||
| message="success", | ||
| message="success", | ||
| processed_data=processed_data | ||
| ) | ||
| # Initialize the runtime | ||
| Runtime( | ||
| namespace="MyProject", | ||
| namespace="MyProject", | ||
| name="DataProcessor", | ||
| nodes=[SampleNode] | ||
| ).start() | ||
| @@ -71,6 +73,7 @@ export EXOSPHERE_API_KEY="your-api-key" | ||
| - **Distributed Execution**: Run nodes across multiple compute resources | ||
| - **State Management**: Automatic state persistence and recovery | ||
| - **Type Safety**: Full Pydantic integration for input/output validation | ||
| - **String-only data model (v1)**: All `Inputs`, `Outputs`, and `Secrets` fields are strings. Serialize non-string data (e.g., JSON) as needed. | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| - **Async Support**: Native async/await support for high-performance operations | ||
| - **Error Handling**: Built-in retry mechanisms and error recovery | ||
| - **Scalability**: Designed for high-volume batch processing and workflows | ||
| @@ -103,15 +106,16 @@ Nodes are the building blocks of your workflows. Each node: | ||
| class ConfigurableNode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| text: str | ||
| max_length: int = 100 | ||
| max_length: str = "100" # v1: strings only | ||
| class Outputs(BaseModel): | ||
| result: str | ||
| length: int | ||
| length: str # v1: strings only | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| async def execute(self) -> Outputs: | ||
| result = self.inputs.text[:self.inputs.max_length] | ||
| return self.Outputs(result=result, length=len(result)) | ||
| max_length = int(self.inputs.max_length) | ||
| result = self.inputs.text[:max_length] | ||
| return self.Outputs(result=result, length=str(len(result))) | ||
| ``` | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ### Error Handling | ||
| @@ -122,7 +126,7 @@ class RobustNode(BaseNode): | ||
| data: str | ||
| class Outputs(BaseModel): | ||
| success: bool | ||
| success: str | ||
| result: str | ||
| async def execute(self) -> Outputs: | ||
| @@ -137,14 +141,15 @@ Secrets allow you to securely manage sensitive configuration data like API keys, | ||
| ```python | ||
| from exospherehost import Runtime, BaseNode | ||
| from pydantic import BaseModel | ||
| import json | ||
| class APINode(BaseNode): | ||
| class Inputs(BaseModel): | ||
| user_id: str | ||
| query: str | ||
| class Outputs(BaseModel): | ||
| response: dict | ||
| response: str # v1: strings only | ||
| status: str | ||
| class Secrets(BaseModel): | ||
| @@ -159,14 +164,24 @@ class APINode(BaseNode): | ||
| # Use secrets for API calls | ||
| import httpx | ||
| async with httpx.AsyncClient() as client: | ||
| response = await client.post( | ||
| http_response = await client.post( | ||
| f"{self.secrets.api_endpoint}/process", | ||
| headers=headers, | ||
| json={"user_id": self.inputs.user_id, "query": self.inputs.query} | ||
| ) | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| # Serialize body: prefer JSON if valid; fallback to text or empty string | ||
| response_text = http_response.text or "" | ||
| if response_text: | ||
| try: | ||
| response_str = json.dumps(http_response.json()) | ||
| except Exception: | ||
| response_str = response_text | ||
| else: | ||
| response_str = "" | ||
| return self.Outputs( | ||
| response=response.json(), | ||
| response=response_str, | ||
| status="success" | ||
| ) | ||
| ``` | ||
| @@ -175,6 +190,7 @@ class APINode(BaseNode): | ||
| - **Security**: Secrets are stored securely by the ExosphereHost Runtime and are never exposed in logs or error messages | ||
| - **Validation**: The `Secrets` class uses Pydantic for automatic validation of secret values | ||
| - **String-only (v1)**: All `Secrets` fields must be strings. | ||
| - **Access**: Secrets are available via `self.secrets` during node execution | ||
| - **Types**: Common secret types include API keys, database credentials, encryption keys, and authentication tokens | ||
| - **Injection**: Secrets are injected by the Runtime at execution time, so you don't need to handle them manually | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| version = "0.0.7b4" | ||
| version = "0.0.7b5" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -11,6 +11,5 @@ | ||
| """ | ||
| from .BaseNode import BaseNode | ||
| from .status import Status | ||
| __all__ = ["BaseNode", "Status"] | ||
| __all__ = ["BaseNode"] | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -279,6 +279,15 @@ def _validate_nodes(self): | ||
| errors.append(f"{node.__name__} does not have an Secrets class") | ||
| if not issubclass(node.Secrets, BaseModel): | ||
| errors.append(f"{node.__name__} does not have an Secrets class that inherits from pydantic.BaseModel") | ||
| # check all data objects are strings | ||
| for field_name, field_info in node.Inputs.model_fields.items(): | ||
| if field_info.annotation is not str: | ||
| errors.append(f"{node.__name__}.Inputs field '{field_name}' must be of type str, got {field_info.annotation}") | ||
| for field_name, field_info in node.Outputs.model_fields.items(): | ||
| if field_info.annotation is not str: | ||
| errors.append(f"{node.__name__}.Outputs field '{field_name}' must be of type str, got {field_info.annotation}") | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| for field_name, field_info in node.Secrets.model_fields.items(): | ||
| if field_info.annotation is not str: | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.