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.
@@ -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"""