nanograph-sdk 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nanograph_sdk-0.1.1/.gitignore +50 -0
- nanograph_sdk-0.1.1/PKG-INFO +266 -0
- nanograph_sdk-0.1.1/README.md +240 -0
- nanograph_sdk-0.1.1/nanograph_sdk/__init__.py +71 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/__init__.py +1 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/asset_resolver.py +94 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/asset_uploader.py +50 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/asset_utils.py +73 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/file_watcher.py +136 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/interfaces.py +208 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/message_handler.py +178 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/node_executor.py +200 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/node_registry.py +122 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/sdk.py +301 -0
- nanograph_sdk-0.1.1/nanograph_sdk/core/server.py +195 -0
- nanograph_sdk-0.1.1/nanograph_sdk/types/__init__.py +1 -0
- nanograph_sdk-0.1.1/nanograph_sdk/types/assets.py +36 -0
- nanograph_sdk-0.1.1/nanograph_sdk/types/protocol.py +60 -0
- nanograph_sdk-0.1.1/nanograph_sdk/utils/__init__.py +1 -0
- nanograph_sdk-0.1.1/pyproject.toml +48 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Python specific
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
share/python-wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
MANIFEST
|
|
24
|
+
|
|
25
|
+
# Virtualenv
|
|
26
|
+
.env
|
|
27
|
+
.venv
|
|
28
|
+
venv/
|
|
29
|
+
ENV/
|
|
30
|
+
env/
|
|
31
|
+
env.bak/
|
|
32
|
+
venv.bak/
|
|
33
|
+
|
|
34
|
+
# IDE
|
|
35
|
+
*.code-workspace
|
|
36
|
+
.vscode/
|
|
37
|
+
|
|
38
|
+
# Misc
|
|
39
|
+
.DS_Store
|
|
40
|
+
.fleet/
|
|
41
|
+
.idea/
|
|
42
|
+
|
|
43
|
+
# Local env files
|
|
44
|
+
.env
|
|
45
|
+
.env.*
|
|
46
|
+
!.env.example
|
|
47
|
+
|
|
48
|
+
# Logs
|
|
49
|
+
logs
|
|
50
|
+
*.log
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nanograph-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Official Python SDK for Nanograph
|
|
5
|
+
Project-URL: Homepage, https://github.com/nanograph/sdk-py
|
|
6
|
+
Project-URL: Documentation, https://docs.nanograph.io/sdk/python
|
|
7
|
+
Project-URL: Repository, https://github.com/nanograph/sdk-py.git
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/nanograph/sdk-py/issues
|
|
9
|
+
Author-email: Nanograph <contact@nanograph.io>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: nanograph,python,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Requires-Dist: aiofiles>=0.8
|
|
22
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
23
|
+
Requires-Dist: watchdog>=2.0
|
|
24
|
+
Requires-Dist: websockets>=10.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Nano SDK for Python
|
|
28
|
+
|
|
29
|
+
This package provides the Python implementation of the Nano SDK, allowing you to create node servers that communicate with the Nano orchestrator.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
**This package is not yet published on PyPI. For development, install it in editable mode from your local clone:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or, if you are developing a NanoServer and want to use the SDK from a parent workspace:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e ../../nanosdk/python
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Creating a Server
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from nanosdk_py import NanoSDK
|
|
51
|
+
import asyncio
|
|
52
|
+
|
|
53
|
+
# Initialize SDK with a config dictionary
|
|
54
|
+
config = {
|
|
55
|
+
'domain': 'local-python.nanograph', # Domain of your server (required)
|
|
56
|
+
'server_name': 'My Python Server', # Name of your server (required)
|
|
57
|
+
'server_uid': 'my-python-server', # Unique server identifier (required)
|
|
58
|
+
'port': 3017, # HTTP port (default: 3017)
|
|
59
|
+
'nodes_path': 'nodes', # Path to nodes directory (default: 'nodes')
|
|
60
|
+
'auto_watch': True, # Automatically watch for node file changes (default: True)
|
|
61
|
+
'watch_debounce_time': 1000 # Debounce time (ms) for file watcher (default: 500)
|
|
62
|
+
}
|
|
63
|
+
sdk = NanoSDK(config)
|
|
64
|
+
|
|
65
|
+
# Start the server
|
|
66
|
+
async def main():
|
|
67
|
+
await sdk.start()
|
|
68
|
+
print('Python Server started')
|
|
69
|
+
|
|
70
|
+
# Handle shutdown
|
|
71
|
+
async def shutdown_handler():
|
|
72
|
+
print('Python Server is shutting down')
|
|
73
|
+
# Add any cleanup logic here
|
|
74
|
+
|
|
75
|
+
sdk.on_shutdown(shutdown_handler)
|
|
76
|
+
|
|
77
|
+
# Graceful shutdown
|
|
78
|
+
async def run():
|
|
79
|
+
try:
|
|
80
|
+
await main()
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
print('Interrupted, stopping server...')
|
|
83
|
+
finally:
|
|
84
|
+
await sdk.stop()
|
|
85
|
+
|
|
86
|
+
if __name__ == '__main__':
|
|
87
|
+
asyncio.run(run())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Configuration Options
|
|
91
|
+
|
|
92
|
+
| Key | Type | Default | Description |
|
|
93
|
+
|-----------------------|-----------|-----------|--------------------------------------------------------------------|
|
|
94
|
+
| `domain` | `str` | — | Domain to group servers (required) |
|
|
95
|
+
| `server_name` | `str` | — | Name of your server (required) |
|
|
96
|
+
| `server_uid` | `str` | — | Unique server identifier (required) |
|
|
97
|
+
| `port` | `int` | `3017` | HTTP port to listen on |
|
|
98
|
+
| `nodes_path` | `str` | `'nodes'` | Path to the directory containing node files |
|
|
99
|
+
| `auto_watch` | `bool` | `True` | If true, automatically reload nodes on file changes |
|
|
100
|
+
| `watch_debounce_time` | `int` | `500` | Debounce time in milliseconds for file watcher reloads |
|
|
101
|
+
|
|
102
|
+
### Creating Nodes
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from nanosdk_py import NanoSDK, NodeDefinition, NodeInstance, ExecutionContext
|
|
106
|
+
|
|
107
|
+
# Define the node
|
|
108
|
+
definition = {
|
|
109
|
+
'uid': 'my-unique-python-node-id',
|
|
110
|
+
'name': 'My Python Node',
|
|
111
|
+
'category': 'Processing',
|
|
112
|
+
'version': '1.0.0',
|
|
113
|
+
'description': 'Description of my python node',
|
|
114
|
+
'inputs': [
|
|
115
|
+
{'name': 'input1', 'type': 'string', 'description': 'First input'}
|
|
116
|
+
],
|
|
117
|
+
'outputs': [
|
|
118
|
+
{'name': 'output1', 'type': 'string', 'description': 'First output'}
|
|
119
|
+
],
|
|
120
|
+
'parameters': [
|
|
121
|
+
{
|
|
122
|
+
'name': 'param1',
|
|
123
|
+
'type': 'boolean',
|
|
124
|
+
'value': True,
|
|
125
|
+
'default': True,
|
|
126
|
+
'label': 'Parameter 1',
|
|
127
|
+
'description': 'Description of parameter 1'
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Register the node
|
|
133
|
+
my_node = NanoSDK.register_node(definition)
|
|
134
|
+
|
|
135
|
+
# Implement the execution logic
|
|
136
|
+
async def execute_node(ctx: ExecutionContext):
|
|
137
|
+
# Get input values
|
|
138
|
+
input1 = ctx.inputs.get('input1', '')
|
|
139
|
+
|
|
140
|
+
# Send status update
|
|
141
|
+
await ctx.context['send_status']({'type': 'running', 'message': 'Processing...'})
|
|
142
|
+
|
|
143
|
+
# Check for abort
|
|
144
|
+
if ctx.context['is_aborted']():
|
|
145
|
+
raise Exception('Execution aborted')
|
|
146
|
+
|
|
147
|
+
# Process the inputs
|
|
148
|
+
output1 = f'Processed by Python: {input1}'
|
|
149
|
+
|
|
150
|
+
# Return the outputs
|
|
151
|
+
return {'output1': output1}
|
|
152
|
+
|
|
153
|
+
my_node['execute'] = execute_node
|
|
154
|
+
|
|
155
|
+
# To export the node if it's in its own file:
|
|
156
|
+
# export = my_node
|
|
157
|
+
|
|
158
|
+
Nodes are defined in `node.py` files. You can organize your nodes by placing each `node.py`
|
|
159
|
+
file (along with any helper modules it might need) into its own subdirectory within the
|
|
160
|
+
main `nodes` directory (or the path specified in `nodes_path` in the SDK configuration).
|
|
161
|
+
The SDK will scan these directories for `node.py` files to load the definitions.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## ExecutionContext Reference
|
|
166
|
+
|
|
167
|
+
When you implement a node's `execute` function, it receives a single argument: `ctx` (the execution context). This object provides everything your node needs to process inputs, parameters, and interact with the workflow engine.
|
|
168
|
+
|
|
169
|
+
**The `ExecutionContext` object has the following structure:**
|
|
170
|
+
|
|
171
|
+
| Field | Type | Description |
|
|
172
|
+
|---------------|---------------------|-----------------------------------------------------------------------------|
|
|
173
|
+
| `inputs` | `dict` | Input values for this node, keyed by input name. |
|
|
174
|
+
| `parameters` | `list` | List of parameter dicts for this node (see your node definition). |
|
|
175
|
+
| `context` | `dict` | Runtime context utilities and metadata (see below). |
|
|
176
|
+
|
|
177
|
+
### `ctx.context` fields
|
|
178
|
+
|
|
179
|
+
| Key | Type | Description |
|
|
180
|
+
|----------------|-------------|-----------------------------------------------------------------------------|
|
|
181
|
+
| `send_status` | `callable` | `await ctx.context['send_status']({...})` to send a status/progress update. |
|
|
182
|
+
| `is_aborted` | `callable` | `ctx.context['is_aborted']()` returns `True` if execution was aborted. |
|
|
183
|
+
| `graph_node` | `dict` | The full graph node definition (with position, etc). |
|
|
184
|
+
| `instance_id` | `str` | The workflow instance ID for this execution. |
|
|
185
|
+
|
|
186
|
+
**Example usage in a node:**
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
async def execute_node(ctx):
|
|
190
|
+
# Access input
|
|
191
|
+
value = ctx.inputs.get('input1')
|
|
192
|
+
# Access parameter
|
|
193
|
+
param = next((p for p in ctx.parameters if p['name'] == 'param1'), None)
|
|
194
|
+
# Send a running status
|
|
195
|
+
await ctx.context['send_status']({'type': 'running', 'message': 'Working...'})
|
|
196
|
+
# Check for abort
|
|
197
|
+
if ctx.context['is_aborted']():
|
|
198
|
+
raise Exception('Aborted!')
|
|
199
|
+
# ...
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## NodeStatus Reference
|
|
205
|
+
|
|
206
|
+
The `NodeStatus` object is used to communicate the current status, progress, or result of a node execution back to the orchestrator. You send it using `await ctx.context['send_status'](status)` from within your node's `execute` function.
|
|
207
|
+
|
|
208
|
+
**NodeStatus fields:**
|
|
209
|
+
|
|
210
|
+
| Field | Type | Description |
|
|
211
|
+
|------------|---------------------|----------------------------------------------------------------------|
|
|
212
|
+
| `type` | `str` | One of: `'idle'`, `'running'`, `'complete'`, `'error'`, `'missing'` |
|
|
213
|
+
| `message` | `str` (optional) | Human-readable status or error message |
|
|
214
|
+
| `progress` | `dict` (optional) | Progress info, e.g. `{ 'step': 2, 'total': 5 }` |
|
|
215
|
+
| `outputs` | `dict` (optional) | Output values (only for `'complete'` status) |
|
|
216
|
+
|
|
217
|
+
**Example: Sending progress updates from a node**
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
async def execute_node(ctx):
|
|
221
|
+
total_steps = 5
|
|
222
|
+
for step in range(1, total_steps + 1):
|
|
223
|
+
# Abort fast if needed
|
|
224
|
+
if ctx.context['is_aborted']():
|
|
225
|
+
raise Exception('Aborted!')
|
|
226
|
+
# Simulate work
|
|
227
|
+
await asyncio.sleep(1)
|
|
228
|
+
# Send progress update
|
|
229
|
+
await ctx.context['send_status']({
|
|
230
|
+
'type': 'running',
|
|
231
|
+
'message': f'Processing step {step}/{total_steps}',
|
|
232
|
+
'progress': {'step': step, 'total': total_steps}
|
|
233
|
+
})
|
|
234
|
+
# Just return the outputs; the SDK will send the 'complete' status automatically
|
|
235
|
+
return {'result': 'done'}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
> **Note:** You do **not** need to manually send a `'complete'` status at the end. The SDK will automatically send a `'complete'` status with the outputs you return from your `execute` function.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Folder Structure
|
|
243
|
+
|
|
244
|
+
Recommended project structure for a Python NanoServer:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
my-python-nodeserver/
|
|
248
|
+
├── main.py # Entry point
|
|
249
|
+
├── nodes/ # Nodes directory (scans for node.py files in subdirectories)
|
|
250
|
+
│ ├── processing/ # Category directory (optional organization)
|
|
251
|
+
│ │ ├── simple_text_node/ # Directory for a single node
|
|
252
|
+
│ │ │ └── node.py # Node definition for simple_text_node
|
|
253
|
+
│ │ └── complex_math_node/ # Directory for a more complex node
|
|
254
|
+
│ │ ├── __init__.py # Optional, makes 'complex_math_node' a Python package
|
|
255
|
+
│ │ ├── node.py # Main node definition for complex_math_node
|
|
256
|
+
│ │ └── math_utils.py # Helper functions specific to this node
|
|
257
|
+
│ └── another_category/ # Another category directory
|
|
258
|
+
│ └── another_node/ # Directory for another_node
|
|
259
|
+
│ └── node.py # Node definition for another_node
|
|
260
|
+
├── pyproject.toml # Dependencies and package info
|
|
261
|
+
└── README.md
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Nano SDK for Python
|
|
2
|
+
|
|
3
|
+
This package provides the Python implementation of the Nano SDK, allowing you to create node servers that communicate with the Nano orchestrator.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
**This package is not yet published on PyPI. For development, install it in editable mode from your local clone:**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install -e .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or, if you are developing a NanoServer and want to use the SDK from a parent workspace:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e ../../nanosdk/python
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Creating a Server
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from nanosdk_py import NanoSDK
|
|
25
|
+
import asyncio
|
|
26
|
+
|
|
27
|
+
# Initialize SDK with a config dictionary
|
|
28
|
+
config = {
|
|
29
|
+
'domain': 'local-python.nanograph', # Domain of your server (required)
|
|
30
|
+
'server_name': 'My Python Server', # Name of your server (required)
|
|
31
|
+
'server_uid': 'my-python-server', # Unique server identifier (required)
|
|
32
|
+
'port': 3017, # HTTP port (default: 3017)
|
|
33
|
+
'nodes_path': 'nodes', # Path to nodes directory (default: 'nodes')
|
|
34
|
+
'auto_watch': True, # Automatically watch for node file changes (default: True)
|
|
35
|
+
'watch_debounce_time': 1000 # Debounce time (ms) for file watcher (default: 500)
|
|
36
|
+
}
|
|
37
|
+
sdk = NanoSDK(config)
|
|
38
|
+
|
|
39
|
+
# Start the server
|
|
40
|
+
async def main():
|
|
41
|
+
await sdk.start()
|
|
42
|
+
print('Python Server started')
|
|
43
|
+
|
|
44
|
+
# Handle shutdown
|
|
45
|
+
async def shutdown_handler():
|
|
46
|
+
print('Python Server is shutting down')
|
|
47
|
+
# Add any cleanup logic here
|
|
48
|
+
|
|
49
|
+
sdk.on_shutdown(shutdown_handler)
|
|
50
|
+
|
|
51
|
+
# Graceful shutdown
|
|
52
|
+
async def run():
|
|
53
|
+
try:
|
|
54
|
+
await main()
|
|
55
|
+
except KeyboardInterrupt:
|
|
56
|
+
print('Interrupted, stopping server...')
|
|
57
|
+
finally:
|
|
58
|
+
await sdk.stop()
|
|
59
|
+
|
|
60
|
+
if __name__ == '__main__':
|
|
61
|
+
asyncio.run(run())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Configuration Options
|
|
65
|
+
|
|
66
|
+
| Key | Type | Default | Description |
|
|
67
|
+
|-----------------------|-----------|-----------|--------------------------------------------------------------------|
|
|
68
|
+
| `domain` | `str` | — | Domain to group servers (required) |
|
|
69
|
+
| `server_name` | `str` | — | Name of your server (required) |
|
|
70
|
+
| `server_uid` | `str` | — | Unique server identifier (required) |
|
|
71
|
+
| `port` | `int` | `3017` | HTTP port to listen on |
|
|
72
|
+
| `nodes_path` | `str` | `'nodes'` | Path to the directory containing node files |
|
|
73
|
+
| `auto_watch` | `bool` | `True` | If true, automatically reload nodes on file changes |
|
|
74
|
+
| `watch_debounce_time` | `int` | `500` | Debounce time in milliseconds for file watcher reloads |
|
|
75
|
+
|
|
76
|
+
### Creating Nodes
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from nanosdk_py import NanoSDK, NodeDefinition, NodeInstance, ExecutionContext
|
|
80
|
+
|
|
81
|
+
# Define the node
|
|
82
|
+
definition = {
|
|
83
|
+
'uid': 'my-unique-python-node-id',
|
|
84
|
+
'name': 'My Python Node',
|
|
85
|
+
'category': 'Processing',
|
|
86
|
+
'version': '1.0.0',
|
|
87
|
+
'description': 'Description of my python node',
|
|
88
|
+
'inputs': [
|
|
89
|
+
{'name': 'input1', 'type': 'string', 'description': 'First input'}
|
|
90
|
+
],
|
|
91
|
+
'outputs': [
|
|
92
|
+
{'name': 'output1', 'type': 'string', 'description': 'First output'}
|
|
93
|
+
],
|
|
94
|
+
'parameters': [
|
|
95
|
+
{
|
|
96
|
+
'name': 'param1',
|
|
97
|
+
'type': 'boolean',
|
|
98
|
+
'value': True,
|
|
99
|
+
'default': True,
|
|
100
|
+
'label': 'Parameter 1',
|
|
101
|
+
'description': 'Description of parameter 1'
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Register the node
|
|
107
|
+
my_node = NanoSDK.register_node(definition)
|
|
108
|
+
|
|
109
|
+
# Implement the execution logic
|
|
110
|
+
async def execute_node(ctx: ExecutionContext):
|
|
111
|
+
# Get input values
|
|
112
|
+
input1 = ctx.inputs.get('input1', '')
|
|
113
|
+
|
|
114
|
+
# Send status update
|
|
115
|
+
await ctx.context['send_status']({'type': 'running', 'message': 'Processing...'})
|
|
116
|
+
|
|
117
|
+
# Check for abort
|
|
118
|
+
if ctx.context['is_aborted']():
|
|
119
|
+
raise Exception('Execution aborted')
|
|
120
|
+
|
|
121
|
+
# Process the inputs
|
|
122
|
+
output1 = f'Processed by Python: {input1}'
|
|
123
|
+
|
|
124
|
+
# Return the outputs
|
|
125
|
+
return {'output1': output1}
|
|
126
|
+
|
|
127
|
+
my_node['execute'] = execute_node
|
|
128
|
+
|
|
129
|
+
# To export the node if it's in its own file:
|
|
130
|
+
# export = my_node
|
|
131
|
+
|
|
132
|
+
Nodes are defined in `node.py` files. You can organize your nodes by placing each `node.py`
|
|
133
|
+
file (along with any helper modules it might need) into its own subdirectory within the
|
|
134
|
+
main `nodes` directory (or the path specified in `nodes_path` in the SDK configuration).
|
|
135
|
+
The SDK will scan these directories for `node.py` files to load the definitions.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## ExecutionContext Reference
|
|
140
|
+
|
|
141
|
+
When you implement a node's `execute` function, it receives a single argument: `ctx` (the execution context). This object provides everything your node needs to process inputs, parameters, and interact with the workflow engine.
|
|
142
|
+
|
|
143
|
+
**The `ExecutionContext` object has the following structure:**
|
|
144
|
+
|
|
145
|
+
| Field | Type | Description |
|
|
146
|
+
|---------------|---------------------|-----------------------------------------------------------------------------|
|
|
147
|
+
| `inputs` | `dict` | Input values for this node, keyed by input name. |
|
|
148
|
+
| `parameters` | `list` | List of parameter dicts for this node (see your node definition). |
|
|
149
|
+
| `context` | `dict` | Runtime context utilities and metadata (see below). |
|
|
150
|
+
|
|
151
|
+
### `ctx.context` fields
|
|
152
|
+
|
|
153
|
+
| Key | Type | Description |
|
|
154
|
+
|----------------|-------------|-----------------------------------------------------------------------------|
|
|
155
|
+
| `send_status` | `callable` | `await ctx.context['send_status']({...})` to send a status/progress update. |
|
|
156
|
+
| `is_aborted` | `callable` | `ctx.context['is_aborted']()` returns `True` if execution was aborted. |
|
|
157
|
+
| `graph_node` | `dict` | The full graph node definition (with position, etc). |
|
|
158
|
+
| `instance_id` | `str` | The workflow instance ID for this execution. |
|
|
159
|
+
|
|
160
|
+
**Example usage in a node:**
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
async def execute_node(ctx):
|
|
164
|
+
# Access input
|
|
165
|
+
value = ctx.inputs.get('input1')
|
|
166
|
+
# Access parameter
|
|
167
|
+
param = next((p for p in ctx.parameters if p['name'] == 'param1'), None)
|
|
168
|
+
# Send a running status
|
|
169
|
+
await ctx.context['send_status']({'type': 'running', 'message': 'Working...'})
|
|
170
|
+
# Check for abort
|
|
171
|
+
if ctx.context['is_aborted']():
|
|
172
|
+
raise Exception('Aborted!')
|
|
173
|
+
# ...
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## NodeStatus Reference
|
|
179
|
+
|
|
180
|
+
The `NodeStatus` object is used to communicate the current status, progress, or result of a node execution back to the orchestrator. You send it using `await ctx.context['send_status'](status)` from within your node's `execute` function.
|
|
181
|
+
|
|
182
|
+
**NodeStatus fields:**
|
|
183
|
+
|
|
184
|
+
| Field | Type | Description |
|
|
185
|
+
|------------|---------------------|----------------------------------------------------------------------|
|
|
186
|
+
| `type` | `str` | One of: `'idle'`, `'running'`, `'complete'`, `'error'`, `'missing'` |
|
|
187
|
+
| `message` | `str` (optional) | Human-readable status or error message |
|
|
188
|
+
| `progress` | `dict` (optional) | Progress info, e.g. `{ 'step': 2, 'total': 5 }` |
|
|
189
|
+
| `outputs` | `dict` (optional) | Output values (only for `'complete'` status) |
|
|
190
|
+
|
|
191
|
+
**Example: Sending progress updates from a node**
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
async def execute_node(ctx):
|
|
195
|
+
total_steps = 5
|
|
196
|
+
for step in range(1, total_steps + 1):
|
|
197
|
+
# Abort fast if needed
|
|
198
|
+
if ctx.context['is_aborted']():
|
|
199
|
+
raise Exception('Aborted!')
|
|
200
|
+
# Simulate work
|
|
201
|
+
await asyncio.sleep(1)
|
|
202
|
+
# Send progress update
|
|
203
|
+
await ctx.context['send_status']({
|
|
204
|
+
'type': 'running',
|
|
205
|
+
'message': f'Processing step {step}/{total_steps}',
|
|
206
|
+
'progress': {'step': step, 'total': total_steps}
|
|
207
|
+
})
|
|
208
|
+
# Just return the outputs; the SDK will send the 'complete' status automatically
|
|
209
|
+
return {'result': 'done'}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
> **Note:** You do **not** need to manually send a `'complete'` status at the end. The SDK will automatically send a `'complete'` status with the outputs you return from your `execute` function.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Folder Structure
|
|
217
|
+
|
|
218
|
+
Recommended project structure for a Python NanoServer:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
my-python-nodeserver/
|
|
222
|
+
├── main.py # Entry point
|
|
223
|
+
├── nodes/ # Nodes directory (scans for node.py files in subdirectories)
|
|
224
|
+
│ ├── processing/ # Category directory (optional organization)
|
|
225
|
+
│ │ ├── simple_text_node/ # Directory for a single node
|
|
226
|
+
│ │ │ └── node.py # Node definition for simple_text_node
|
|
227
|
+
│ │ └── complex_math_node/ # Directory for a more complex node
|
|
228
|
+
│ │ ├── __init__.py # Optional, makes 'complex_math_node' a Python package
|
|
229
|
+
│ │ ├── node.py # Main node definition for complex_math_node
|
|
230
|
+
│ │ └── math_utils.py # Helper functions specific to this node
|
|
231
|
+
│ └── another_category/ # Another category directory
|
|
232
|
+
│ └── another_node/ # Directory for another_node
|
|
233
|
+
│ └── node.py # Node definition for another_node
|
|
234
|
+
├── pyproject.toml # Dependencies and package info
|
|
235
|
+
└── README.md
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Official Python SDK for Nanograph
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .core.sdk import NanoSDK
|
|
6
|
+
from .core.interfaces import (
|
|
7
|
+
NanoSDKConfig,
|
|
8
|
+
NodeInstance,
|
|
9
|
+
ExecutionContext,
|
|
10
|
+
ExecuteFunction,
|
|
11
|
+
NodeDefinition,
|
|
12
|
+
Port,
|
|
13
|
+
PortType,
|
|
14
|
+
Parameter,
|
|
15
|
+
ParameterType,
|
|
16
|
+
NodeType,
|
|
17
|
+
NodeInputs,
|
|
18
|
+
NodeOutputs,
|
|
19
|
+
NodeStatus,
|
|
20
|
+
NodeContext,
|
|
21
|
+
NodeResponse,
|
|
22
|
+
NodeDefinitionsMessage
|
|
23
|
+
)
|
|
24
|
+
from .core.asset_resolver import (
|
|
25
|
+
parse_asset_ref,
|
|
26
|
+
resolve_asset,
|
|
27
|
+
get_asset_download_url,
|
|
28
|
+
get_asset_presigned_url,
|
|
29
|
+
ResolveAssetOptions,
|
|
30
|
+
AssetRef,
|
|
31
|
+
AssetPresignedUrl
|
|
32
|
+
)
|
|
33
|
+
from .core.asset_uploader import (
|
|
34
|
+
upload_asset,
|
|
35
|
+
UploadAssetOptions,
|
|
36
|
+
AssetUploadResult
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__version__ = "0.1.1"
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
'NanoSDK',
|
|
43
|
+
'NanoSDKConfig',
|
|
44
|
+
'NodeInstance',
|
|
45
|
+
'ExecutionContext',
|
|
46
|
+
'ExecuteFunction',
|
|
47
|
+
'NodeDefinition',
|
|
48
|
+
'Port',
|
|
49
|
+
'PortType',
|
|
50
|
+
'Parameter',
|
|
51
|
+
'ParameterType',
|
|
52
|
+
'NodeType',
|
|
53
|
+
'NodeInputs',
|
|
54
|
+
'NodeOutputs',
|
|
55
|
+
'NodeStatus',
|
|
56
|
+
'NodeContext',
|
|
57
|
+
'NodeResponse',
|
|
58
|
+
'NodeDefinitionsMessage',
|
|
59
|
+
# Asset resolver exports
|
|
60
|
+
'parse_asset_ref',
|
|
61
|
+
'resolve_asset',
|
|
62
|
+
'get_asset_download_url',
|
|
63
|
+
'get_asset_presigned_url',
|
|
64
|
+
'ResolveAssetOptions',
|
|
65
|
+
'AssetRef',
|
|
66
|
+
'AssetPresignedUrl',
|
|
67
|
+
# Asset uploader exports
|
|
68
|
+
'upload_asset',
|
|
69
|
+
'UploadAssetOptions',
|
|
70
|
+
'AssetUploadResult'
|
|
71
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core functionality of the Nanograph SDK"""
|