griptape-nodes 0.62.0__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.
- griptape_nodes-0.62.0/PKG-INFO +97 -0
- griptape_nodes-0.62.0/README.md +64 -0
- griptape_nodes-0.62.0/pyproject.toml +144 -0
- griptape_nodes-0.62.0/src/griptape_nodes/__init__.py +24 -0
- griptape_nodes-0.62.0/src/griptape_nodes/__main__.py +6 -0
- griptape_nodes-0.62.0/src/griptape_nodes/api_client/__init__.py +9 -0
- griptape_nodes-0.62.0/src/griptape_nodes/api_client/client.py +279 -0
- griptape_nodes-0.62.0/src/griptape_nodes/api_client/request_client.py +278 -0
- griptape_nodes-0.62.0/src/griptape_nodes/app/.python-version +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/app/__init__.py +5 -0
- griptape_nodes-0.62.0/src/griptape_nodes/app/app.py +414 -0
- griptape_nodes-0.62.0/src/griptape_nodes/app/watch.py +54 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/utils/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/utils/python_subprocess_executor.py +122 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +385 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +258 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +306 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/utils/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +58 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +43 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_publishers/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +44 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +86 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_publishers/utils/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +69 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/config.py +74 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/engine.py +69 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/init.py +684 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/libraries.py +116 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/models.py +506 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/commands/self.py +120 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/main.py +61 -0
- griptape_nodes-0.62.0/src/griptape_nodes/cli/shared.py +77 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/directed_graph.py +71 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/__init__.py +43 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/core.py +242 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/exceptions.py +122 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/formats.py +179 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/matching.py +137 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/parsing.py +212 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/resolution.py +186 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/macro_parser/segments.py +42 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/node_executor.py +642 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/__init__.py +49 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/default_project_template.py +87 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/defaults/README.md +36 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/directory.py +67 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/loader.py +342 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/project.py +252 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/situation.py +143 -0
- griptape_nodes-0.62.0/src/griptape_nodes/common/project_templates/validation.py +140 -0
- griptape_nodes-0.62.0/src/griptape_nodes/drivers/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/drivers/storage/__init__.py +8 -0
- griptape_nodes-0.62.0/src/griptape_nodes/drivers/storage/base_storage_driver.py +137 -0
- griptape_nodes-0.62.0/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +185 -0
- griptape_nodes-0.62.0/src/griptape_nodes/drivers/storage/local_storage_driver.py +101 -0
- griptape_nodes-0.62.0/src/griptape_nodes/drivers/storage/storage_backend.py +10 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/connections.py +261 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/core_types.py +2465 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/flow.py +145 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/node_types.py +2164 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/huggingface/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +168 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +38 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py +33 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py +136 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/log_parameter.py +136 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/progress_bar_component.py +57 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_components/seed_parameter.py +59 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_audio.py +243 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_bool.py +221 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_float.py +179 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_image.py +243 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_int.py +183 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_number.py +380 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_string.py +232 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_three_d.py +215 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/param_types/parameter_video.py +243 -0
- griptape_nodes-0.62.0/src/griptape_nodes/exe_types/type_validator.py +35 -0
- griptape_nodes-0.62.0/src/griptape_nodes/machines/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/machines/control_flow.py +602 -0
- griptape_nodes-0.62.0/src/griptape_nodes/machines/dag_builder.py +437 -0
- griptape_nodes-0.62.0/src/griptape_nodes/machines/fsm.py +78 -0
- griptape_nodes-0.62.0/src/griptape_nodes/machines/parallel_resolution.py +879 -0
- griptape_nodes-0.62.0/src/griptape_nodes/machines/sequential_resolution.py +435 -0
- griptape_nodes-0.62.0/src/griptape_nodes/mcp_server/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/node_library/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/node_library/advanced_node_library.py +51 -0
- griptape_nodes-0.62.0/src/griptape_nodes/node_library/library_registry.py +385 -0
- griptape_nodes-0.62.0/src/griptape_nodes/node_library/workflow_registry.py +252 -0
- griptape_nodes-0.62.0/src/griptape_nodes/py.typed +0 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/agent_events.py +167 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/app_events.py +328 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +53 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/base_events.py +671 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/config_events.py +226 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/connection_events.py +141 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/context_events.py +69 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/execution_events.py +418 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/flow_events.py +477 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +30 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/library_events.py +512 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/logger_events.py +25 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/mcp_events.py +363 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/model_events.py +296 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/node_events.py +910 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/object_events.py +83 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/os_events.py +476 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/parameter_events.py +671 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/payload_registry.py +60 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/project_events.py +528 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/resource_events.py +290 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/secrets_events.py +137 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/static_file_events.py +138 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/sync_events.py +60 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/validation_events.py +88 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/variable_events.py +361 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/events/workflow_events.py +712 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/griptape_nodes.py +460 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/agent_manager.py +327 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +71 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/config_manager.py +534 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/context_manager.py +630 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +297 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/event_manager.py +323 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/flow_manager.py +3605 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +353 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/library_manager.py +2083 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/mcp_manager.py +372 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/model_manager.py +1219 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/node_manager.py +3648 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/object_manager.py +282 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/operation_manager.py +476 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/os_manager.py +1634 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/project_manager.py +1067 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/secrets_manager.py +181 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/session_manager.py +365 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/settings.py +229 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/static_files_manager.py +253 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/sync_manager.py +511 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/user_manager.py +120 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +317 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/managers/workflow_manager.py +4237 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/retained_mode.py +1837 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/utils/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/utils/name_generator.py +162 -0
- griptape_nodes-0.62.0/src/griptape_nodes/retained_mode/variable_types.py +18 -0
- griptape_nodes-0.62.0/src/griptape_nodes/servers/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/servers/mcp.py +146 -0
- griptape_nodes-0.62.0/src/griptape_nodes/servers/static.py +190 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/add_param_button.py +21 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/button.py +368 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/clamp.py +36 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/color_picker.py +66 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/compare.py +20 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/compare_images.py +41 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/file_system_picker.py +145 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/minmax.py +43 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/multi_options.py +216 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/numbers_selector.py +77 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/options.py +133 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/slider.py +37 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/trait_registry.py +35 -0
- griptape_nodes-0.62.0/src/griptape_nodes/traits/traits.json +8 -0
- griptape_nodes-0.62.0/src/griptape_nodes/updater/__init__.py +78 -0
- griptape_nodes-0.62.0/src/griptape_nodes/updater/__main__.py +4 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/__init__.py +5 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/async_utils.py +156 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/dict_utils.py +221 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/huggingface_utils.py +136 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/image_preview.py +128 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/metaclasses.py +10 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/uv_utils.py +18 -0
- griptape_nodes-0.62.0/src/griptape_nodes/utils/version_utils.py +126 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/versions/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +82 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/workflow_versions/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/__init__.py +1 -0
- griptape_nodes-0.62.0/src/griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +49 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: griptape-nodes
|
|
3
|
+
Version: 0.62.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Dist: griptape>=1.8.12
|
|
6
|
+
Requires-Dist: pydantic>=2.10.6
|
|
7
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
8
|
+
Requires-Dist: xdg-base-dirs>=6.0.2
|
|
9
|
+
Requires-Dist: httpx>=0.28.0,<1.0.0
|
|
10
|
+
Requires-Dist: websockets>=15.0.1,<16.0.0
|
|
11
|
+
Requires-Dist: tomlkit>=0.13.2
|
|
12
|
+
Requires-Dist: uv>=0.6.16
|
|
13
|
+
Requires-Dist: fastapi>=0.115.12
|
|
14
|
+
Requires-Dist: uvicorn>=0.34.2
|
|
15
|
+
Requires-Dist: packaging>=25.0
|
|
16
|
+
Requires-Dist: python-multipart>=0.0.20
|
|
17
|
+
Requires-Dist: json-repair>=0.46.1
|
|
18
|
+
Requires-Dist: mcp[ws]>=1.10.1
|
|
19
|
+
Requires-Dist: binaryornot>=0.4.4
|
|
20
|
+
Requires-Dist: pillow>=11.3.0
|
|
21
|
+
Requires-Dist: watchfiles>=1.1.0
|
|
22
|
+
Requires-Dist: typer>=0.15.0
|
|
23
|
+
Requires-Dist: huggingface-hub>=1.0.1
|
|
24
|
+
Requires-Dist: rich>=14.1.0
|
|
25
|
+
Requires-Dist: semver>=3.0.4
|
|
26
|
+
Requires-Dist: aiofiles>=25.1.0
|
|
27
|
+
Requires-Dist: ruamel-yaml>=0.18.15
|
|
28
|
+
Requires-Dist: asyncio-thread-runner>=1.0
|
|
29
|
+
Requires-Dist: austin-dist>=3.7.0 ; extra == 'profiling'
|
|
30
|
+
Requires-Python: >=3.12.0, <3.13
|
|
31
|
+
Provides-Extra: profiling
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# 🎨 Griptape Nodes
|
|
35
|
+
|
|
36
|
+
Griptape Nodes is a powerful, visual, node-based workflow builder designed for professional artists and creators. Build and execute complex AI workflows through the cloud-based [Griptape Nodes IDE](https://app.nodes.griptape.ai/) - an intuitive drag-and-drop interface.
|
|
37
|
+
|
|
38
|
+
This repository contains the Griptape Nodes Engine - the local component that runs securely on your machine, providing a performant foundation for workflow execution.
|
|
39
|
+
|
|
40
|
+
[](https://vimeo.com/1064451891)
|
|
41
|
+
*(Clicking the image opens the video on Vimeo)*
|
|
42
|
+
|
|
43
|
+
**✨ Key Features:**
|
|
44
|
+
|
|
45
|
+
- **🎯 Visual Workflow Editor:** Design and connect nodes representing different AI tasks, tools, and logic through the cloud-based IDE
|
|
46
|
+
- **🏠 Local Engine:** Run workflows securely on your own machine or infrastructure
|
|
47
|
+
- **🐍 Portable Python Workflows:** Workflows are saved as self-executable Python files for portability, debugability, and learning
|
|
48
|
+
- **🌐 Multi-Device Access:** Client/server architecture lets you access your workflows from any device
|
|
49
|
+
- **🧩 Extensible:** Build your own custom nodes and libraries to extend functionality
|
|
50
|
+
- **⚡ Scriptable Interface:** Interact with and control flows programmatically
|
|
51
|
+
|
|
52
|
+
**🔗 Learn More:**
|
|
53
|
+
|
|
54
|
+
- **📚 Full Documentation:** [docs.griptapenodes.com](https://docs.griptapenodes.com)
|
|
55
|
+
- **⚙️ Installation:** [docs.griptapenodes.com/en/stable/installation/](https://docs.griptapenodes.com/en/latest/installation/)
|
|
56
|
+
- **🔧 Engine Configuration:** [docs.griptapenodes.com/en/stable/configuration/](https://docs.griptapenodes.com/en/latest/configuration/)
|
|
57
|
+
|
|
58
|
+
**🧩 Extending Griptape Nodes:**
|
|
59
|
+
|
|
60
|
+
Want to create custom nodes for your specific workflow needs? Griptape Nodes is designed to be extensible through custom libraries:
|
|
61
|
+
|
|
62
|
+
- **📦 Custom Library Template:** Get started with the [Griptape Nodes Library Template](https://github.com/griptape-ai/griptape-nodes-library-template)
|
|
63
|
+
- **🛠️ Build Custom Nodes:** Create specialized nodes tailored to your artistic and creative workflows
|
|
64
|
+
|
|
65
|
+
______________________________________________________________________
|
|
66
|
+
|
|
67
|
+
## 🚀 Quick Installation
|
|
68
|
+
|
|
69
|
+
Follow these steps to get the Griptape Nodes engine running on your system:
|
|
70
|
+
|
|
71
|
+
1. **🔐 Login:** Visit [Griptape Nodes](https://app.nodes.griptape.ai/) and log in or sign up using your Griptape Cloud credentials.
|
|
72
|
+
|
|
73
|
+
1. **💾 Install Command:** Once logged in, you'll find a setup screen. Copy the installation command provided in the "New Installation" section. It will look similar to this (use the **exact** command provided on the website):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl -LsSf https://raw.githubusercontent.com/griptape-ai/griptape-nodes/main/install.sh | bash
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
1. **⚡ Run Installer:** Open a terminal on your machine (local or cloud environment) and paste/run the command. The installer uses `uv` for fast installation; if `uv` isn't present, the script will typically handle installing it.
|
|
80
|
+
|
|
81
|
+
1. **⚙️ Initial Configuration (Automatic on First Run):**
|
|
82
|
+
|
|
83
|
+
- The first time you run the engine command (`griptape-nodes` or `gtn`), it will guide you through the initial setup:
|
|
84
|
+
- **📁 Workspace Directory:** You'll be prompted to choose a directory where Griptape Nodes will store configurations, project files, secrets (`.env`), and generated assets. You can accept the default (`<current_directory>/GriptapeNodes`) or specify a custom path.
|
|
85
|
+
- **🔑 Griptape Cloud API Key:** Return to the [Griptape Nodes setup page](https://app.nodes.griptape.ai/) in your browser, click "Generate API Key", copy the key, and paste it when prompted in the terminal.
|
|
86
|
+
|
|
87
|
+
1. **🚀 Start the Engine:** After configuration, start the engine by running:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
griptape-nodes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
*(or the shorter alias `gtn`)*
|
|
94
|
+
|
|
95
|
+
1. **🔗 Connect Workflow Editor:** Refresh the Griptape Nodes Workflow Editor page in your browser. It should now connect to your running engine.
|
|
96
|
+
|
|
97
|
+
You're now ready to start building flows! 🎉 For more detailed setup options and troubleshooting, see the full [Documentation](https://docs.griptapenodes.com/).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# 🎨 Griptape Nodes
|
|
2
|
+
|
|
3
|
+
Griptape Nodes is a powerful, visual, node-based workflow builder designed for professional artists and creators. Build and execute complex AI workflows through the cloud-based [Griptape Nodes IDE](https://app.nodes.griptape.ai/) - an intuitive drag-and-drop interface.
|
|
4
|
+
|
|
5
|
+
This repository contains the Griptape Nodes Engine - the local component that runs securely on your machine, providing a performant foundation for workflow execution.
|
|
6
|
+
|
|
7
|
+
[](https://vimeo.com/1064451891)
|
|
8
|
+
*(Clicking the image opens the video on Vimeo)*
|
|
9
|
+
|
|
10
|
+
**✨ Key Features:**
|
|
11
|
+
|
|
12
|
+
- **🎯 Visual Workflow Editor:** Design and connect nodes representing different AI tasks, tools, and logic through the cloud-based IDE
|
|
13
|
+
- **🏠 Local Engine:** Run workflows securely on your own machine or infrastructure
|
|
14
|
+
- **🐍 Portable Python Workflows:** Workflows are saved as self-executable Python files for portability, debugability, and learning
|
|
15
|
+
- **🌐 Multi-Device Access:** Client/server architecture lets you access your workflows from any device
|
|
16
|
+
- **🧩 Extensible:** Build your own custom nodes and libraries to extend functionality
|
|
17
|
+
- **⚡ Scriptable Interface:** Interact with and control flows programmatically
|
|
18
|
+
|
|
19
|
+
**🔗 Learn More:**
|
|
20
|
+
|
|
21
|
+
- **📚 Full Documentation:** [docs.griptapenodes.com](https://docs.griptapenodes.com)
|
|
22
|
+
- **⚙️ Installation:** [docs.griptapenodes.com/en/stable/installation/](https://docs.griptapenodes.com/en/latest/installation/)
|
|
23
|
+
- **🔧 Engine Configuration:** [docs.griptapenodes.com/en/stable/configuration/](https://docs.griptapenodes.com/en/latest/configuration/)
|
|
24
|
+
|
|
25
|
+
**🧩 Extending Griptape Nodes:**
|
|
26
|
+
|
|
27
|
+
Want to create custom nodes for your specific workflow needs? Griptape Nodes is designed to be extensible through custom libraries:
|
|
28
|
+
|
|
29
|
+
- **📦 Custom Library Template:** Get started with the [Griptape Nodes Library Template](https://github.com/griptape-ai/griptape-nodes-library-template)
|
|
30
|
+
- **🛠️ Build Custom Nodes:** Create specialized nodes tailored to your artistic and creative workflows
|
|
31
|
+
|
|
32
|
+
______________________________________________________________________
|
|
33
|
+
|
|
34
|
+
## 🚀 Quick Installation
|
|
35
|
+
|
|
36
|
+
Follow these steps to get the Griptape Nodes engine running on your system:
|
|
37
|
+
|
|
38
|
+
1. **🔐 Login:** Visit [Griptape Nodes](https://app.nodes.griptape.ai/) and log in or sign up using your Griptape Cloud credentials.
|
|
39
|
+
|
|
40
|
+
1. **💾 Install Command:** Once logged in, you'll find a setup screen. Copy the installation command provided in the "New Installation" section. It will look similar to this (use the **exact** command provided on the website):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -LsSf https://raw.githubusercontent.com/griptape-ai/griptape-nodes/main/install.sh | bash
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
1. **⚡ Run Installer:** Open a terminal on your machine (local or cloud environment) and paste/run the command. The installer uses `uv` for fast installation; if `uv` isn't present, the script will typically handle installing it.
|
|
47
|
+
|
|
48
|
+
1. **⚙️ Initial Configuration (Automatic on First Run):**
|
|
49
|
+
|
|
50
|
+
- The first time you run the engine command (`griptape-nodes` or `gtn`), it will guide you through the initial setup:
|
|
51
|
+
- **📁 Workspace Directory:** You'll be prompted to choose a directory where Griptape Nodes will store configurations, project files, secrets (`.env`), and generated assets. You can accept the default (`<current_directory>/GriptapeNodes`) or specify a custom path.
|
|
52
|
+
- **🔑 Griptape Cloud API Key:** Return to the [Griptape Nodes setup page](https://app.nodes.griptape.ai/) in your browser, click "Generate API Key", copy the key, and paste it when prompted in the terminal.
|
|
53
|
+
|
|
54
|
+
1. **🚀 Start the Engine:** After configuration, start the engine by running:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
griptape-nodes
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
*(or the shorter alias `gtn`)*
|
|
61
|
+
|
|
62
|
+
1. **🔗 Connect Workflow Editor:** Refresh the Griptape Nodes Workflow Editor page in your browser. It should now connect to your running engine.
|
|
63
|
+
|
|
64
|
+
You're now ready to start building flows! 🎉 For more detailed setup options and troubleshooting, see the full [Documentation](https://docs.griptapenodes.com/).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "griptape-nodes"
|
|
3
|
+
version = "0.62.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12.0, <3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"griptape>=1.8.12",
|
|
9
|
+
"pydantic>=2.10.6",
|
|
10
|
+
"python-dotenv>=1.0.1",
|
|
11
|
+
"xdg-base-dirs>=6.0.2",
|
|
12
|
+
"httpx>=0.28.0,<1.0.0",
|
|
13
|
+
"websockets>=15.0.1,<16.0.0",
|
|
14
|
+
"tomlkit>=0.13.2",
|
|
15
|
+
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/833
|
|
16
|
+
"uv>=0.6.16",
|
|
17
|
+
"fastapi>=0.115.12",
|
|
18
|
+
"uvicorn>=0.34.2",
|
|
19
|
+
"packaging>=25.0",
|
|
20
|
+
"python-multipart>=0.0.20",
|
|
21
|
+
"json-repair>=0.46.1",
|
|
22
|
+
"mcp[ws]>=1.10.1",
|
|
23
|
+
"binaryornot>=0.4.4",
|
|
24
|
+
"pillow>=11.3.0",
|
|
25
|
+
"watchfiles>=1.1.0",
|
|
26
|
+
"typer>=0.15.0",
|
|
27
|
+
"huggingface-hub>=1.0.1",
|
|
28
|
+
"rich>=14.1.0",
|
|
29
|
+
"semver>=3.0.4",
|
|
30
|
+
"aiofiles>=25.1.0",
|
|
31
|
+
"ruamel.yaml>=0.18.15",
|
|
32
|
+
"asyncio-thread-runner>=1.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
profiling = ["austin-dist>=3.7.0"]
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = [
|
|
40
|
+
"griptape-cloud-client",
|
|
41
|
+
"mdformat>=0.7.22",
|
|
42
|
+
"mdformat-gfm>=0.4.1",
|
|
43
|
+
"mdformat-frontmatter>=2.0.8",
|
|
44
|
+
"mdformat-footnote>=0.1.1",
|
|
45
|
+
"mdformat-mkdocs>=4.1.2",
|
|
46
|
+
"pyright>=1.1.396",
|
|
47
|
+
"ruff>=0.11.0",
|
|
48
|
+
"typos>=1.30.2",
|
|
49
|
+
]
|
|
50
|
+
docs = [
|
|
51
|
+
"mkdocs-material>=9.6.9",
|
|
52
|
+
"mkdocs>=1.5.2",
|
|
53
|
+
"mkdocstrings[python]>=0.29.1",
|
|
54
|
+
"mkdocs-mermaid2-plugin>=1.2.2",
|
|
55
|
+
]
|
|
56
|
+
test = [
|
|
57
|
+
"pytest>=8.3.5",
|
|
58
|
+
"pytest-asyncio>=1.1.0",
|
|
59
|
+
"pytest-mock>=3.14.0",
|
|
60
|
+
"pytest-xdist>=3.6.1",
|
|
61
|
+
"pytest-cov>=6.0.0",
|
|
62
|
+
"coverage>=7.0.0",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[project.scripts]
|
|
66
|
+
griptape-nodes = "griptape_nodes:main"
|
|
67
|
+
gtn = "griptape_nodes:main"
|
|
68
|
+
|
|
69
|
+
[build-system]
|
|
70
|
+
requires = ["uv_build"]
|
|
71
|
+
build-backend = "uv_build"
|
|
72
|
+
|
|
73
|
+
[tool.uv.sources]
|
|
74
|
+
griptape-cloud-client = { git = "https://github.com/griptape-ai/griptape-cloud-python-client", rev = "main" }
|
|
75
|
+
|
|
76
|
+
[tool.ruff]
|
|
77
|
+
line-length = 120
|
|
78
|
+
extend-exclude = ["libraries/**/templates/*.py"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = ["ALL"]
|
|
83
|
+
ignore = [
|
|
84
|
+
"TD002", # Intentional
|
|
85
|
+
"FIX002", # Intentional
|
|
86
|
+
"D101", # Intentional
|
|
87
|
+
"D102", # Intentional
|
|
88
|
+
"D107", # Intentional
|
|
89
|
+
"ANN002", # Intentional
|
|
90
|
+
"ANN003", # Intentional
|
|
91
|
+
"ANN401", # Intentional
|
|
92
|
+
"COM812", # Intentional
|
|
93
|
+
"E501", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/834
|
|
94
|
+
"D100", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/835
|
|
95
|
+
"BLE001", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/839
|
|
96
|
+
"SLF001", # TODO :https://github.com/griptape-ai/griptape-nodes/issues/838
|
|
97
|
+
"SIM108", # Intentional
|
|
98
|
+
"SIM110", # Intentional
|
|
99
|
+
"D105", # Intentional
|
|
100
|
+
"N802", # TODO: https://github.com/griptape-ai/griptape-nodes/issues/840
|
|
101
|
+
"TRY400", # Intentional
|
|
102
|
+
"FBT003", # Intentional
|
|
103
|
+
"RET504", # Intentional
|
|
104
|
+
"PLC0415", # Intentional
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
108
|
+
"nodes.griptape_nodes_library".msg = "Import from griptape_nodes_library instead of nodes.griptape_nodes_library"
|
|
109
|
+
"libraries.griptape_nodes_library".msg = "Import from griptape_nodes_library instead of libraries.griptape_nodes_library"
|
|
110
|
+
"libraries.griptape_nodes_advanced_media_library".msg = "Import from griptape_nodes_advanced_media_library instead of libraries.griptape_nodes_advanced_media_library"
|
|
111
|
+
"libraries.griptape_cloud".msg = "Import from griptape_cloud instead of libraries.griptape_cloud"
|
|
112
|
+
|
|
113
|
+
[tool.ruff.lint.per-file-ignores]
|
|
114
|
+
"tests/*" = ["S101", "D104"]
|
|
115
|
+
"libraries/**/tests/*" = ["S101", "D104"]
|
|
116
|
+
|
|
117
|
+
[tool.ruff.lint.flake8-annotations]
|
|
118
|
+
mypy-init-return = true
|
|
119
|
+
|
|
120
|
+
[tool.ruff.lint.pydocstyle]
|
|
121
|
+
convention = "google"
|
|
122
|
+
|
|
123
|
+
[tool.typos]
|
|
124
|
+
default.extend-ignore-re = [
|
|
125
|
+
# Ignore any line that ends with this comment:
|
|
126
|
+
"(?Rm)^.*(#|//)\\s*spellchecker:disable-line$",
|
|
127
|
+
# Or: ignore the *next* line after this comment:
|
|
128
|
+
"(?m)(#|//)\\s*spellchecker:ignore-next-line\\n.*",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
[tool.pyright]
|
|
132
|
+
venvPath = "."
|
|
133
|
+
venv = ".venv"
|
|
134
|
+
include = ["."]
|
|
135
|
+
exclude = [
|
|
136
|
+
"libraries/**/templates/*",
|
|
137
|
+
".venv",
|
|
138
|
+
"GriptapeNodes",
|
|
139
|
+
"**/node_modules",
|
|
140
|
+
"**/__pycache__",
|
|
141
|
+
"**/.*",
|
|
142
|
+
]
|
|
143
|
+
pythonVersion = "3.12"
|
|
144
|
+
reportIncompatibleMethodOverride = false
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Griptape Nodes package."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
"""Main entry point for the Griptape Nodes CLI."""
|
|
11
|
+
# Hack to make paths "just work". # noqa: FIX004
|
|
12
|
+
# Without this, packages like `nodes` don't properly import.
|
|
13
|
+
# Long term solution could be to make `nodes` a proper src-layout package
|
|
14
|
+
# but current engine relies on importing files rather than packages.
|
|
15
|
+
sys.path.append(str(Path.cwd()))
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
with console.status("[bold green]Loading Griptape Nodes...", spinner="dots"):
|
|
19
|
+
from griptape_nodes.cli.main import app
|
|
20
|
+
|
|
21
|
+
app()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Unified WebSocket client for Nodes API communication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
11
|
+
from urllib.parse import urljoin
|
|
12
|
+
|
|
13
|
+
from websockets.asyncio.client import connect
|
|
14
|
+
from websockets.exceptions import ConnectionClosed
|
|
15
|
+
|
|
16
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import AsyncIterator
|
|
20
|
+
from types import TracebackType
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("griptape_nodes_client")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_default_websocket_url() -> str:
|
|
26
|
+
"""Get the default WebSocket endpoint URL for connecting to Nodes API.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
WebSocket URL for Nodes API events endpoint
|
|
30
|
+
"""
|
|
31
|
+
return urljoin(
|
|
32
|
+
os.getenv("GRIPTAPE_NODES_API_BASE_URL", "https://api.nodes.griptape.ai").replace("http", "ws"),
|
|
33
|
+
"/ws/engines/events?version=v2",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Client:
|
|
38
|
+
"""WebSocket client for Nodes API pub/sub communication.
|
|
39
|
+
|
|
40
|
+
Provides connection management, topic-based pub/sub, and message routing.
|
|
41
|
+
Handles WebSocket reconnection and async event streaming.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
api_key: str | None = None,
|
|
47
|
+
url: str | None = None,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize Nodes API client.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
api_key: API key for authentication (defaults to GT_CLOUD_API_KEY from SecretsManager)
|
|
53
|
+
url: WebSocket URL to connect to (defaults to Nodes API endpoint)
|
|
54
|
+
"""
|
|
55
|
+
self.url = url if url is not None else get_default_websocket_url()
|
|
56
|
+
|
|
57
|
+
# Get API key from SecretsManager if not provided
|
|
58
|
+
if api_key is None:
|
|
59
|
+
api_key = GriptapeNodes.SecretsManager().get_secret("GT_CLOUD_API_KEY")
|
|
60
|
+
|
|
61
|
+
self.api_key = api_key
|
|
62
|
+
|
|
63
|
+
self.headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
|
64
|
+
|
|
65
|
+
# Event streaming management
|
|
66
|
+
self._message_queue: asyncio.Queue = asyncio.Queue()
|
|
67
|
+
self._subscribed_topics: set[str] = set()
|
|
68
|
+
self._receiving_task: asyncio.Task | None = None
|
|
69
|
+
self._sending_task: asyncio.Task | None = None
|
|
70
|
+
self._websocket: Any = None
|
|
71
|
+
self._connection_ready = asyncio.Event()
|
|
72
|
+
self._reconnect_delay = 2.0
|
|
73
|
+
|
|
74
|
+
async def __aenter__(self) -> Self:
|
|
75
|
+
"""Async context manager entry: connect to WebSocket server."""
|
|
76
|
+
await self._connect()
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
async def __aexit__(
|
|
80
|
+
self,
|
|
81
|
+
exc_type: type[BaseException] | None,
|
|
82
|
+
exc_val: BaseException | None,
|
|
83
|
+
exc_tb: TracebackType | None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Async context manager exit: disconnect from WebSocket server."""
|
|
86
|
+
await self._disconnect()
|
|
87
|
+
|
|
88
|
+
def __aiter__(self) -> AsyncIterator[dict[str, Any]]:
|
|
89
|
+
"""Return self as async iterator."""
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
async def __anext__(self) -> dict[str, Any]:
|
|
93
|
+
"""Get next message from the message queue.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Next message dictionary from subscribed topics
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
StopAsyncIteration: When iteration is cancelled
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
return await self._message_queue.get()
|
|
103
|
+
except asyncio.CancelledError:
|
|
104
|
+
raise StopAsyncIteration from None
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def messages(self) -> AsyncIterator[dict[str, Any]]:
|
|
108
|
+
"""Async iterator for receiving messages from subscribed topics.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Async iterator yielding message dictionaries
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
async with Client(...) as client:
|
|
115
|
+
await client.subscribe("topic")
|
|
116
|
+
async for message in client.messages:
|
|
117
|
+
print(message)
|
|
118
|
+
"""
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
async def subscribe(self, topic: str) -> None:
|
|
122
|
+
"""Subscribe to a topic by sending subscribe command to server.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
topic: Topic name to subscribe to
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
await client.subscribe("sessions/123/response")
|
|
129
|
+
"""
|
|
130
|
+
self._subscribed_topics.add(topic)
|
|
131
|
+
await self._send_subscribe_command(topic)
|
|
132
|
+
|
|
133
|
+
async def unsubscribe(self, topic: str) -> None:
|
|
134
|
+
"""Unsubscribe from a topic.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
topic: Topic name to unsubscribe from
|
|
138
|
+
"""
|
|
139
|
+
self._subscribed_topics.discard(topic)
|
|
140
|
+
await self._send_unsubscribe_command(topic)
|
|
141
|
+
|
|
142
|
+
async def publish(self, event_type: str, payload: dict[str, Any], topic: str) -> None:
|
|
143
|
+
"""Publish an event to the server.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
event_type: Type of event to publish
|
|
147
|
+
payload: Event payload data
|
|
148
|
+
topic: Topic to publish to
|
|
149
|
+
"""
|
|
150
|
+
message = {"type": event_type, "payload": payload, "topic": topic}
|
|
151
|
+
await self._send_message(message)
|
|
152
|
+
|
|
153
|
+
async def _connect(self) -> None:
|
|
154
|
+
"""Connect to the WebSocket server and start receiving messages.
|
|
155
|
+
|
|
156
|
+
This method starts the connection manager task.
|
|
157
|
+
It returns once the initial connection is established.
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ConnectionError: If connection fails
|
|
161
|
+
"""
|
|
162
|
+
# Start connection manager task
|
|
163
|
+
self._receiving_task = asyncio.create_task(self._manage_connection())
|
|
164
|
+
|
|
165
|
+
# Wait for initial connection to be established
|
|
166
|
+
try:
|
|
167
|
+
await asyncio.wait_for(self._connection_ready.wait(), timeout=10.0)
|
|
168
|
+
logger.debug("WebSocket client connected")
|
|
169
|
+
except TimeoutError as e:
|
|
170
|
+
logger.error("Failed to connect WebSocket client: timeout")
|
|
171
|
+
msg = "Connection timeout"
|
|
172
|
+
raise ConnectionError(msg) from e
|
|
173
|
+
|
|
174
|
+
async def _disconnect(self) -> None:
|
|
175
|
+
"""Disconnect from the WebSocket server and clean up tasks."""
|
|
176
|
+
# Cancel tasks
|
|
177
|
+
if self._receiving_task:
|
|
178
|
+
self._receiving_task.cancel()
|
|
179
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
180
|
+
await self._receiving_task
|
|
181
|
+
|
|
182
|
+
if self._sending_task:
|
|
183
|
+
self._sending_task.cancel()
|
|
184
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
185
|
+
await self._sending_task
|
|
186
|
+
|
|
187
|
+
# Close websocket connection
|
|
188
|
+
if self._websocket:
|
|
189
|
+
await self._websocket.close()
|
|
190
|
+
logger.info("WebSocket client disconnected")
|
|
191
|
+
|
|
192
|
+
async def _manage_connection(self) -> None:
|
|
193
|
+
"""Manage WebSocket connection lifecycle with automatic reconnection.
|
|
194
|
+
|
|
195
|
+
This method establishes and maintains the WebSocket connection,
|
|
196
|
+
automatically reconnecting on failures.
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
async for websocket in connect(self.url, additional_headers=self.headers):
|
|
200
|
+
self._websocket = websocket
|
|
201
|
+
self._connection_ready.set()
|
|
202
|
+
logger.debug("WebSocket connection established: %s", self.url)
|
|
203
|
+
|
|
204
|
+
# Resubscribe to all topics after reconnection
|
|
205
|
+
if self._subscribed_topics:
|
|
206
|
+
logger.debug("Resubscribing to %d topics after reconnection", len(self._subscribed_topics))
|
|
207
|
+
for topic in self._subscribed_topics:
|
|
208
|
+
await self._send_subscribe_command(topic)
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
await self._receive_messages(websocket)
|
|
212
|
+
except ConnectionClosed:
|
|
213
|
+
logger.info("WebSocket connection closed, reconnecting...")
|
|
214
|
+
self._connection_ready.clear()
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
except asyncio.CancelledError:
|
|
218
|
+
logger.debug("Connection manager task cancelled")
|
|
219
|
+
|
|
220
|
+
async def _receive_messages(self, websocket: Any) -> None:
|
|
221
|
+
"""Receive messages from WebSocket and put them in message queue.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
websocket: WebSocket connection to receive messages from
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
ConnectionClosed: When the WebSocket connection is closed
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
async for message in websocket:
|
|
231
|
+
try:
|
|
232
|
+
data = json.loads(message)
|
|
233
|
+
await self._message_queue.put(data)
|
|
234
|
+
except json.JSONDecodeError:
|
|
235
|
+
logger.error("Failed to parse message: %s", message)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error("Error receiving message: %s", e)
|
|
238
|
+
except asyncio.CancelledError:
|
|
239
|
+
logger.debug("Receive messages task cancelled")
|
|
240
|
+
raise
|
|
241
|
+
|
|
242
|
+
async def _send_message(self, message: dict[str, Any]) -> None:
|
|
243
|
+
"""Send a message through the WebSocket connection.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
message: Message dictionary to send
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
ConnectionError: If not connected
|
|
250
|
+
"""
|
|
251
|
+
if not self._websocket:
|
|
252
|
+
msg = "Not connected to WebSocket"
|
|
253
|
+
raise ConnectionError(msg)
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
await self._websocket.send(json.dumps(message))
|
|
257
|
+
logger.debug("Sent message type: %s", message.get("type"))
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error("Failed to send message: %s", e)
|
|
260
|
+
|
|
261
|
+
async def _send_subscribe_command(self, topic: str) -> None:
|
|
262
|
+
"""Send subscribe command to server.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
topic: Topic to subscribe to
|
|
266
|
+
"""
|
|
267
|
+
message = {"type": "subscribe", "topic": topic, "payload": {}}
|
|
268
|
+
await self._send_message(message)
|
|
269
|
+
logger.debug("Sent subscribe command for topic: %s", topic)
|
|
270
|
+
|
|
271
|
+
async def _send_unsubscribe_command(self, topic: str) -> None:
|
|
272
|
+
"""Send unsubscribe command to server.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
topic: Topic to unsubscribe from
|
|
276
|
+
"""
|
|
277
|
+
message = {"type": "unsubscribe", "topic": topic, "payload": {}}
|
|
278
|
+
await self._send_message(message)
|
|
279
|
+
logger.debug("Sent unsubscribe command for topic: %s", topic)
|