splunk-soar-sdk 2.1.0__tar.gz → 2.2.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.
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/PKG-INFO +1 -1
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/pyproject.toml +1 -1
- splunk_soar_sdk-2.2.0/release_notes.txt +21 -0
- splunk_soar_sdk-2.2.0/release_version.txt +1 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/action_results.py +27 -1
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app.py +14 -2
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/action.py +11 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/actions.py +10 -4
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_deserializers.py +5 -1
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_serializers.py +11 -2
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/app.json +202 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/app.py +39 -1
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_actions.py +3 -2
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_action_results.py +25 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_action.py +66 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/uv.lock +1 -1
- splunk_soar_sdk-2.1.0/release_notes.txt +0 -21
- splunk_soar_sdk-2.1.0/release_version.txt +0 -1
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/pull_request_template.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/utils/github.js +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/utils/update_version.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/code_quality.yml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/commit_hygiene.yml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/generate_docs.yml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/semantic_release.yml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.gitignore +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.pre-commit-config.yaml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.releaserc +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/LICENSE +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/README.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/commitlint.config.js +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/api_reference.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/index.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/pre-commit-config.yaml.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/pyproject.toml.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/src_app.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/changelog.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/cli_reference.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/conf.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/index.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/reusable_components.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/templates.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/view_handlers.md +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/defining_asset.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/first_action.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/index.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/init_app.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/installation.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/testing_and_building.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/index.rst +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/abstract.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/actions_manager.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/artifact.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/container.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/vault.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_cli_runner.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_client.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/.gitignore +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/.pre-commit-config.yaml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/logo.svg +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/logo_dark.svg +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/src/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/uv.lock +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/asset.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/async_utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/init/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/init/cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/deserializers.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/processors.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/serializers.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/package/cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/package/utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/path_utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/action_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/app_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/asset_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/templates/pyproject.toml.jinja +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/toml_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/colors.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/compat.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/crypto.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/make_request.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/on_poll.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/test_connectivity.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/view_handler.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/webhook.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/exceptions.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/input_spec.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/logging.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/adapters.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/app.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/datatypes.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/dependencies.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/webhooks.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/artifact.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/container.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/vault_attachment.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/view.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/params.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/paths.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/py.typed +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/action_result.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/app.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/base_connector.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/connector_result.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/consts.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/encryption_helper.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/install_info.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/json_keys.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/ph_ipc.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/vault.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom_common/app_interface/app_interface.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom_common/encryption/encryption_manager_factory.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/base_template.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/error.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/header.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/logo_header.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/components/pie_chart.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/widgets/widget_resize_snippet.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/widgets/widget_template.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/types.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/component_registry.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/components/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/components/pie_chart.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/template_filters.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/template_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/view_parser.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/webhooks/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/webhooks/models.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/webhooks/routing.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/test.png +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/test.txt +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/datapath_parse.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/manifests/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/manifests/test_processors.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_assets/converted_app/actions.py.txt +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_convert_cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_init_cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_manifests_cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_package_cli.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/code_renderers/test_action_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/conftest.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/example_asset.json +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/logo.svg +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/logo_dark.svg +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/pyproject.toml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/async_action.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/generate_category.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/reverse_string.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/ignoreme.txt +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/templates/reverse_string.html +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/uv.lock +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/app.json +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/logo.svg +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/logo_dark.svg +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/pyproject.toml +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/src/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/src/app.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/uv.lock +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/test_artifact_interface.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/test_container_interface.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/test_vault_interface.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_adapters.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_datatypes.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_dependencies.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_webhooks.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/mocks/__init__.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/mocks/dynamic_mocks.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/mocks/importable_action.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/stubs.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_actions_manager.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_action_params.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_action_results.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_client.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_runner.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_asset.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_assets/splunk-sdk-2.1.0.tar.gz +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_async_integration.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_async_utils.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_code_renderers.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_compat.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_container.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_custom_views.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_encryption.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_logging.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_make_request_action.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_on_poll.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_params.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_template_filters.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_template_renderer.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_test_connectivity.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_view_parser.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/webhooks/test_models.py +0 -0
- {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/webhooks/test_routing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splunk-soar-sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: The official framework for developing and testing Splunk SOAR Apps
|
|
5
5
|
Project-URL: Homepage, https://github.com/phantomcyber/splunk-soar-sdk
|
|
6
6
|
Project-URL: Documentation, https://github.com/phantomcyber/splunk-soar-sdk
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# [2.2.0](https://github.com/phantomcyber/splunk-soar-sdk/compare/2.1.1...2.2.0) (2025-10-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* render as option ([08405b5](https://github.com/phantomcyber/splunk-soar-sdk/commit/08405b5b38d3df1239fecf7ddc8175727c9b2827))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# [2.2.0](https://github.com/phantomcyber/splunk-soar-sdk/compare/2.1.1...2.2.0) (2025-10-01)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* render as option ([08405b5](https://github.com/phantomcyber/splunk-soar-sdk/commit/08405b5b38d3df1239fecf7ddc8175727c9b2827))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.0
|
|
@@ -83,12 +83,16 @@ class OutputFieldSpecification(TypedDict):
|
|
|
83
83
|
data_type: str
|
|
84
84
|
contains: NotRequired[list[str]]
|
|
85
85
|
example_values: NotRequired[list[Union[str, float, bool]]]
|
|
86
|
+
column_name: NotRequired[str]
|
|
87
|
+
column_order: NotRequired[int]
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
def OutputField(
|
|
89
91
|
cef_types: Optional[list[str]] = None,
|
|
90
92
|
example_values: Optional[list[Union[str, float, bool]]] = None,
|
|
91
93
|
alias: Optional[str] = None,
|
|
94
|
+
column_name: Optional[str] = None,
|
|
95
|
+
column_order: Optional[int] = None,
|
|
92
96
|
) -> Any: # noqa: ANN401
|
|
93
97
|
"""Define metadata for an action output field.
|
|
94
98
|
|
|
@@ -102,6 +106,11 @@ def OutputField(
|
|
|
102
106
|
example_values: Optional list of example values for this field, used
|
|
103
107
|
in documentation and for testing/validation purposes.
|
|
104
108
|
alias: Optional alternative name for the field when serialized.
|
|
109
|
+
column_name: Optional name for the field when displayed in a table.
|
|
110
|
+
column_order: Optional order for the field when displayed in a table (0-indexed).
|
|
111
|
+
|
|
112
|
+
Note:
|
|
113
|
+
Column name and order must be set together, if one is set but the other is not, an error will be raised.
|
|
105
114
|
|
|
106
115
|
Returns:
|
|
107
116
|
A Pydantic Field object with the specified metadata.
|
|
@@ -116,8 +125,10 @@ def OutputField(
|
|
|
116
125
|
"""
|
|
117
126
|
return Field(
|
|
118
127
|
examples=example_values,
|
|
119
|
-
cef_types=cef_types,
|
|
120
128
|
alias=alias,
|
|
129
|
+
cef_types=cef_types,
|
|
130
|
+
column_name=column_name,
|
|
131
|
+
column_order=column_order,
|
|
121
132
|
)
|
|
122
133
|
|
|
123
134
|
|
|
@@ -230,6 +241,21 @@ class ActionOutput(BaseModel):
|
|
|
230
241
|
if field_type is bool:
|
|
231
242
|
schema_field["example_values"] = [True, False]
|
|
232
243
|
|
|
244
|
+
# Validate column metadata - both column_name and column_order must be present together
|
|
245
|
+
column_name = field.field_info.extra.get("column_name")
|
|
246
|
+
column_order = field.field_info.extra.get("column_order")
|
|
247
|
+
|
|
248
|
+
# Check if exactly one is set (XOR condition - invalid)
|
|
249
|
+
if (column_name is None) != (column_order is None):
|
|
250
|
+
raise ValueError(
|
|
251
|
+
f"Field '{field_name}' must have both 'column_name' and 'column_order' "
|
|
252
|
+
f"or neither. Found: column_name={column_name}, column_order={column_order}"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if column_name is not None and column_order is not None:
|
|
256
|
+
schema_field["column_name"] = column_name
|
|
257
|
+
schema_field["column_order"] = column_order
|
|
258
|
+
|
|
233
259
|
yield schema_field
|
|
234
260
|
|
|
235
261
|
|
|
@@ -237,6 +237,7 @@ class App:
|
|
|
237
237
|
read_only: bool = True,
|
|
238
238
|
params_class: Optional[type[Params]] = None,
|
|
239
239
|
output_class: Optional[type[ActionOutput]] = None,
|
|
240
|
+
render_as: Optional[str] = None,
|
|
240
241
|
view_handler: Union[str, Callable, None] = None,
|
|
241
242
|
view_template: Optional[str] = None,
|
|
242
243
|
versions: str = "EQ(*)",
|
|
@@ -355,6 +356,7 @@ class App:
|
|
|
355
356
|
read_only=read_only,
|
|
356
357
|
params_class=params_class,
|
|
357
358
|
output_class=output_class,
|
|
359
|
+
render_as=render_as,
|
|
358
360
|
view_handler=view_handler,
|
|
359
361
|
versions=versions,
|
|
360
362
|
summary_type=summary_type,
|
|
@@ -421,6 +423,7 @@ class App:
|
|
|
421
423
|
read_only: bool = True,
|
|
422
424
|
params_class: Optional[type[Params]] = None,
|
|
423
425
|
output_class: Optional[type[ActionOutput]] = None,
|
|
426
|
+
render_as: Optional[str] = None,
|
|
424
427
|
view_handler: Optional[Callable] = None,
|
|
425
428
|
versions: str = "EQ(*)",
|
|
426
429
|
summary_type: Optional[type[ActionOutput]] = None,
|
|
@@ -440,6 +443,7 @@ class App:
|
|
|
440
443
|
read_only=read_only,
|
|
441
444
|
params_class=params_class,
|
|
442
445
|
output_class=output_class,
|
|
446
|
+
render_as=render_as,
|
|
443
447
|
view_handler=view_handler,
|
|
444
448
|
versions=versions,
|
|
445
449
|
summary_type=summary_type,
|
|
@@ -647,9 +651,17 @@ class App:
|
|
|
647
651
|
statuses = []
|
|
648
652
|
for item in result:
|
|
649
653
|
statuses.append(
|
|
650
|
-
App._adapt_action_result(
|
|
654
|
+
App._adapt_action_result(
|
|
655
|
+
item, actions_manager, action_params, message, summary
|
|
656
|
+
)
|
|
657
|
+
)
|
|
658
|
+
# Handle empty list/iterator case
|
|
659
|
+
if not statuses:
|
|
660
|
+
result = ActionOutput(
|
|
661
|
+
status=True, message=message or "Action completed successfully"
|
|
651
662
|
)
|
|
652
|
-
|
|
663
|
+
else:
|
|
664
|
+
return all(statuses)
|
|
653
665
|
|
|
654
666
|
if isinstance(result, ActionOutput):
|
|
655
667
|
output_dict = result.dict(by_alias=True)
|
|
@@ -39,6 +39,7 @@ class ActionDecorator:
|
|
|
39
39
|
AsyncGenerator[type[ActionOutput]],
|
|
40
40
|
list[type[ActionOutput]],
|
|
41
41
|
] = None,
|
|
42
|
+
render_as: Optional[str] = None,
|
|
42
43
|
view_handler: Optional[Callable] = None,
|
|
43
44
|
versions: str = "EQ(*)",
|
|
44
45
|
summary_type: Optional[type[ActionOutput]] = None,
|
|
@@ -53,6 +54,7 @@ class ActionDecorator:
|
|
|
53
54
|
self.read_only = read_only
|
|
54
55
|
self.params_class = params_class
|
|
55
56
|
self.output_class = output_class
|
|
57
|
+
self.render_as = render_as
|
|
56
58
|
self.view_handler = view_handler
|
|
57
59
|
self.versions = versions
|
|
58
60
|
self.summary_type = summary_type
|
|
@@ -100,6 +102,14 @@ class ActionDecorator:
|
|
|
100
102
|
"Return type for action function must be derived from ActionOutput class."
|
|
101
103
|
)
|
|
102
104
|
|
|
105
|
+
if self.view_handler:
|
|
106
|
+
self.render_as = "custom"
|
|
107
|
+
|
|
108
|
+
if self.render_as and self.render_as not in ("table", "json", "custom"):
|
|
109
|
+
raise ValueError(
|
|
110
|
+
"Please only specify render_as as 'table' or 'json' or 'custom'."
|
|
111
|
+
)
|
|
112
|
+
|
|
103
113
|
@action_protocol
|
|
104
114
|
@wraps(function)
|
|
105
115
|
def inner(
|
|
@@ -152,6 +162,7 @@ class ActionDecorator:
|
|
|
152
162
|
parameters=validated_params_class,
|
|
153
163
|
output=validated_output_class,
|
|
154
164
|
versions=self.versions,
|
|
165
|
+
render_as=self.render_as,
|
|
155
166
|
view_handler=self.view_handler,
|
|
156
167
|
summary_type=self.summary_type,
|
|
157
168
|
enable_concurrency_lock=self.enable_concurrency_lock,
|
|
@@ -20,6 +20,7 @@ class ActionMeta(BaseModel):
|
|
|
20
20
|
verbose: str = ""
|
|
21
21
|
parameters: Type[Params] = Field(default=Params) # noqa: UP006
|
|
22
22
|
output: Type[ActionOutput] = Field(default=ActionOutput) # noqa: UP006
|
|
23
|
+
render_as: Optional[str] = None
|
|
23
24
|
view_handler: Optional[Callable] = None
|
|
24
25
|
summary_type: Optional[Type[ActionOutput]] = Field(default=None, exclude=True) # noqa: UP006
|
|
25
26
|
enable_concurrency_lock: bool = False
|
|
@@ -31,6 +32,13 @@ class ActionMeta(BaseModel):
|
|
|
31
32
|
data["output"] = OutputsSerializer.serialize_datapaths(
|
|
32
33
|
self.parameters, self.output, summary_class=self.summary_type
|
|
33
34
|
)
|
|
35
|
+
if self.view_handler:
|
|
36
|
+
self.render_as = "custom"
|
|
37
|
+
|
|
38
|
+
if self.render_as:
|
|
39
|
+
data["render"] = {
|
|
40
|
+
"type": self.render_as,
|
|
41
|
+
}
|
|
34
42
|
|
|
35
43
|
if self.view_handler:
|
|
36
44
|
remove_when_soar_newer_than("6.4.1")
|
|
@@ -45,13 +53,11 @@ class ActionMeta(BaseModel):
|
|
|
45
53
|
else:
|
|
46
54
|
relative_module = module
|
|
47
55
|
|
|
48
|
-
data["render"] = {
|
|
49
|
-
"type": "custom",
|
|
50
|
-
"view": f"{relative_module}.{self.view_handler.__name__}",
|
|
51
|
-
}
|
|
56
|
+
data["render"]["view"] = f"{relative_module}.{self.view_handler.__name__}"
|
|
52
57
|
|
|
53
58
|
# Remove view_handler from the output since in render
|
|
54
59
|
data.pop("view_handler", None)
|
|
60
|
+
data.pop("render_as", None)
|
|
55
61
|
|
|
56
62
|
if self.enable_concurrency_lock:
|
|
57
63
|
data["lock"] = {"enabled": True}
|
|
@@ -438,7 +438,11 @@ def test_from_action_json_with_parameters_and_output(mock_action_deserializer):
|
|
|
438
438
|
},
|
|
439
439
|
},
|
|
440
440
|
"output": [
|
|
441
|
-
{
|
|
441
|
+
{
|
|
442
|
+
"data_path": "action_result.data.*.result",
|
|
443
|
+
"column_name": "Result",
|
|
444
|
+
"column_order": 0,
|
|
445
|
+
},
|
|
442
446
|
{"data_path": "action_result.summary.total_items"},
|
|
443
447
|
],
|
|
444
448
|
}
|
|
@@ -153,13 +153,18 @@ def test_outputs_serialize_with_defaults():
|
|
|
153
153
|
|
|
154
154
|
def test_outputs_serialize_output_class():
|
|
155
155
|
class SampleNestedOutput(ActionOutput):
|
|
156
|
-
bool_value: bool
|
|
156
|
+
bool_value: bool = OutputField(column_name="Nested Value", column_order=1)
|
|
157
157
|
|
|
158
158
|
class SampleOutput(ActionOutput):
|
|
159
159
|
string_value: str
|
|
160
160
|
int_value: int
|
|
161
161
|
list_value: list[str]
|
|
162
|
-
cef_value: str = OutputField(
|
|
162
|
+
cef_value: str = OutputField(
|
|
163
|
+
cef_types=["ip"],
|
|
164
|
+
example_values=["1.1.1.1"],
|
|
165
|
+
column_name="CEF Value",
|
|
166
|
+
column_order=0,
|
|
167
|
+
)
|
|
163
168
|
nested_value: SampleNestedOutput
|
|
164
169
|
underscored_value: str = OutputField(alias="_underscored_value")
|
|
165
170
|
|
|
@@ -192,11 +197,15 @@ def test_outputs_serialize_output_class():
|
|
|
192
197
|
"data_type": "string",
|
|
193
198
|
"contains": ["ip"],
|
|
194
199
|
"example_values": ["1.1.1.1"],
|
|
200
|
+
"column_name": "CEF Value",
|
|
201
|
+
"column_order": 0,
|
|
195
202
|
},
|
|
196
203
|
{
|
|
197
204
|
"data_path": "action_result.data.*.nested_value.bool_value",
|
|
198
205
|
"data_type": "boolean",
|
|
199
206
|
"example_values": [True, False],
|
|
207
|
+
"column_name": "Nested Value",
|
|
208
|
+
"column_order": 1,
|
|
200
209
|
},
|
|
201
210
|
{
|
|
202
211
|
"data_path": "action_result.data.*._underscored_value",
|
|
@@ -99,6 +99,208 @@
|
|
|
99
99
|
}
|
|
100
100
|
]
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
"action": "test summary with list output",
|
|
104
|
+
"description": "test summary with list output",
|
|
105
|
+
"identifier": "test_summary_with_list_output",
|
|
106
|
+
"output": [
|
|
107
|
+
{
|
|
108
|
+
"data_path": "action_result.status",
|
|
109
|
+
"data_type": "string",
|
|
110
|
+
"example_values": [
|
|
111
|
+
"success",
|
|
112
|
+
"failure"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"data_path": "action_result.message",
|
|
117
|
+
"data_type": "string"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"data_path": "summary.total_objects",
|
|
121
|
+
"data_type": "numeric",
|
|
122
|
+
"example_values": [
|
|
123
|
+
1
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"data_path": "summary.total_objects_successful",
|
|
128
|
+
"data_type": "numeric",
|
|
129
|
+
"example_values": [
|
|
130
|
+
1
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"parameters": {},
|
|
135
|
+
"read_only": true,
|
|
136
|
+
"type": "generic",
|
|
137
|
+
"verbose": "",
|
|
138
|
+
"versions": "EQ(*)"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"action": "test empty list output",
|
|
142
|
+
"description": "test empty list output",
|
|
143
|
+
"identifier": "test_empty_list_output",
|
|
144
|
+
"output": [
|
|
145
|
+
{
|
|
146
|
+
"data_path": "action_result.status",
|
|
147
|
+
"data_type": "string",
|
|
148
|
+
"example_values": [
|
|
149
|
+
"success",
|
|
150
|
+
"failure"
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"data_path": "action_result.message",
|
|
155
|
+
"data_type": "string"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"data_path": "summary.total_objects",
|
|
159
|
+
"data_type": "numeric",
|
|
160
|
+
"example_values": [
|
|
161
|
+
1
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"data_path": "summary.total_objects_successful",
|
|
166
|
+
"data_type": "numeric",
|
|
167
|
+
"example_values": [
|
|
168
|
+
1
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
"parameters": {},
|
|
173
|
+
"read_only": true,
|
|
174
|
+
"type": "generic",
|
|
175
|
+
"verbose": "",
|
|
176
|
+
"versions": "EQ(*)"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"action": "test json output",
|
|
180
|
+
"identifier": "test_json_output",
|
|
181
|
+
"description": "test json output",
|
|
182
|
+
"type": "generic",
|
|
183
|
+
"read_only": true,
|
|
184
|
+
"versions": "EQ(*)",
|
|
185
|
+
"verbose": "",
|
|
186
|
+
"parameters": {},
|
|
187
|
+
"output": [
|
|
188
|
+
{
|
|
189
|
+
"data_path": "action_result.status",
|
|
190
|
+
"data_type": "string",
|
|
191
|
+
"example_values": [
|
|
192
|
+
"success",
|
|
193
|
+
"failure"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"data_path": "action_result.message",
|
|
198
|
+
"data_type": "string"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"data_path": "action_result.data.*.name",
|
|
202
|
+
"data_type": "string",
|
|
203
|
+
"example_values": [
|
|
204
|
+
"John",
|
|
205
|
+
"Jane",
|
|
206
|
+
"Jim"
|
|
207
|
+
],
|
|
208
|
+
"column_name": "Name",
|
|
209
|
+
"column_order": 0
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"data_path": "action_result.data.*.age",
|
|
213
|
+
"data_type": "numeric",
|
|
214
|
+
"example_values": [
|
|
215
|
+
25,
|
|
216
|
+
30,
|
|
217
|
+
35
|
|
218
|
+
],
|
|
219
|
+
"column_name": "Age",
|
|
220
|
+
"column_order": 1
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"data_path": "summary.total_objects",
|
|
224
|
+
"data_type": "numeric",
|
|
225
|
+
"example_values": [
|
|
226
|
+
1
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"data_path": "summary.total_objects_successful",
|
|
231
|
+
"data_type": "numeric",
|
|
232
|
+
"example_values": [
|
|
233
|
+
1
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
"render": {
|
|
238
|
+
"type": "json"
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"action": "test table output",
|
|
243
|
+
"identifier": "test_table_output",
|
|
244
|
+
"description": "test table output",
|
|
245
|
+
"type": "generic",
|
|
246
|
+
"read_only": true,
|
|
247
|
+
"versions": "EQ(*)",
|
|
248
|
+
"verbose": "",
|
|
249
|
+
"parameters": {},
|
|
250
|
+
"output": [
|
|
251
|
+
{
|
|
252
|
+
"data_path": "action_result.status",
|
|
253
|
+
"data_type": "string",
|
|
254
|
+
"example_values": [
|
|
255
|
+
"success",
|
|
256
|
+
"failure"
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"data_path": "action_result.message",
|
|
261
|
+
"data_type": "string"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"data_path": "action_result.data.*.name",
|
|
265
|
+
"data_type": "string",
|
|
266
|
+
"example_values": [
|
|
267
|
+
"John",
|
|
268
|
+
"Jane",
|
|
269
|
+
"Jim"
|
|
270
|
+
],
|
|
271
|
+
"column_name": "Name",
|
|
272
|
+
"column_order": 0
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"data_path": "action_result.data.*.age",
|
|
276
|
+
"data_type": "numeric",
|
|
277
|
+
"example_values": [
|
|
278
|
+
25,
|
|
279
|
+
30,
|
|
280
|
+
35
|
|
281
|
+
],
|
|
282
|
+
"column_name": "Age",
|
|
283
|
+
"column_order": 1
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"data_path": "summary.total_objects",
|
|
287
|
+
"data_type": "numeric",
|
|
288
|
+
"example_values": [
|
|
289
|
+
1
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"data_path": "summary.total_objects_successful",
|
|
294
|
+
"data_type": "numeric",
|
|
295
|
+
"example_values": [
|
|
296
|
+
1
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
"render": {
|
|
301
|
+
"type": "table"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
102
304
|
{
|
|
103
305
|
"action": "reverse string",
|
|
104
306
|
"identifier": "reverse_string",
|
|
@@ -8,7 +8,7 @@ from soar_sdk.asset import AssetField, BaseAsset
|
|
|
8
8
|
from soar_sdk.params import OnPollParams, MakeRequestParams, Params
|
|
9
9
|
from soar_sdk.models.container import Container
|
|
10
10
|
from soar_sdk.models.artifact import Artifact
|
|
11
|
-
from soar_sdk.action_results import ActionOutput, MakeRequestOutput
|
|
11
|
+
from soar_sdk.action_results import ActionOutput, MakeRequestOutput, OutputField
|
|
12
12
|
from soar_sdk.logging import getLogger
|
|
13
13
|
|
|
14
14
|
logger = getLogger()
|
|
@@ -53,6 +53,44 @@ def test_connectivity(soar: SOARClient, asset: Asset) -> None:
|
|
|
53
53
|
logger.progress("this is a progress message")
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
class ActionOutputSummary(ActionOutput):
|
|
57
|
+
is_success: bool
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.action()
|
|
61
|
+
def test_summary_with_list_output(
|
|
62
|
+
params: Params, asset: Asset, soar: SOARClient
|
|
63
|
+
) -> list[ActionOutput]:
|
|
64
|
+
soar.set_summary(ActionOutputSummary(is_success=True))
|
|
65
|
+
return [ActionOutput(), ActionOutput()]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@app.action()
|
|
69
|
+
def test_empty_list_output(
|
|
70
|
+
params: Params, asset: Asset, soar: SOARClient
|
|
71
|
+
) -> list[ActionOutput]:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class JsonOutput(ActionOutput):
|
|
76
|
+
name: str = OutputField(
|
|
77
|
+
example_values=["John", "Jane", "Jim"], column_name="Name", column_order=0
|
|
78
|
+
)
|
|
79
|
+
age: int = OutputField(
|
|
80
|
+
example_values=[25, 30, 35], column_name="Age", column_order=1
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.action(render_as="json")
|
|
85
|
+
def test_json_output(params: Params, asset: Asset, soar: SOARClient) -> JsonOutput:
|
|
86
|
+
return JsonOutput(name="John", age=25)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.action(render_as="table")
|
|
90
|
+
def test_table_output(params: Params, asset: Asset, soar: SOARClient) -> JsonOutput:
|
|
91
|
+
return JsonOutput(name="John", age=25)
|
|
92
|
+
|
|
93
|
+
|
|
56
94
|
app.register_action(
|
|
57
95
|
"actions.reverse_string:reverse_string",
|
|
58
96
|
action_type="investigate",
|
|
@@ -23,7 +23,8 @@ def test_action_meta_dict_with_view_handler():
|
|
|
23
23
|
|
|
24
24
|
result = meta.dict()
|
|
25
25
|
|
|
26
|
-
assert
|
|
26
|
+
assert "render" in result
|
|
27
|
+
assert result["render"]["type"] == "custom"
|
|
27
28
|
assert "view_handler" not in result
|
|
28
29
|
|
|
29
30
|
|
|
@@ -49,7 +50,7 @@ def test_action_meta_dict_with_view_handler_multi_part_module():
|
|
|
49
50
|
|
|
50
51
|
result = meta.dict()
|
|
51
52
|
|
|
52
|
-
assert result["render"]["
|
|
53
|
+
assert result["render"]["type"] == "custom"
|
|
53
54
|
assert "view_handler" not in result
|
|
54
55
|
|
|
55
56
|
|
|
@@ -153,3 +153,28 @@ def test_action_output_to_dict():
|
|
|
153
153
|
"optional_list_of_types": None,
|
|
154
154
|
}
|
|
155
155
|
assert action_output.dict(by_alias=True) == expected_dict
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_action_output_to_json_schema_with_column_name_and_column_order():
|
|
159
|
+
class ExampleActionOutputWithColumnNames(ActionOutput):
|
|
160
|
+
stringy_field: str = OutputField(column_name="Stringy Field", column_order=0)
|
|
161
|
+
|
|
162
|
+
schema = list(ExampleActionOutputWithColumnNames._to_json_schema())
|
|
163
|
+
assert schema == [
|
|
164
|
+
{
|
|
165
|
+
"data_path": "action_result.data.*.stringy_field",
|
|
166
|
+
"data_type": "string",
|
|
167
|
+
"column_name": "Stringy Field",
|
|
168
|
+
"column_order": 0,
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_action_output_to_json_schema_with_column_name_and_column_order_missing():
|
|
174
|
+
class ExampleActionOutputWithColumnNames(ActionOutput):
|
|
175
|
+
stringy_field: str = OutputField(column_name="Stringy Field")
|
|
176
|
+
|
|
177
|
+
with pytest.raises(
|
|
178
|
+
ValueError, match="must have both 'column_name' and 'column_order'"
|
|
179
|
+
):
|
|
180
|
+
list(ExampleActionOutputWithColumnNames._to_json_schema())
|
|
@@ -184,6 +184,49 @@ def test_action_decoration_with_meta(simple_app: App):
|
|
|
184
184
|
assert simple_app.actions_manager.get_action("test_function_id") == foo
|
|
185
185
|
|
|
186
186
|
|
|
187
|
+
def test_action_decoration_with_render_as(simple_app: App):
|
|
188
|
+
@simple_app.action(
|
|
189
|
+
name="Test Function", identifier="test_function_id", render_as="table"
|
|
190
|
+
)
|
|
191
|
+
def foo(params: Params) -> ActionOutput:
|
|
192
|
+
"""
|
|
193
|
+
This action does nothing for now.
|
|
194
|
+
"""
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
assert sorted(foo.meta.dict().keys()) == sorted(
|
|
198
|
+
[
|
|
199
|
+
"action",
|
|
200
|
+
"identifier",
|
|
201
|
+
"description",
|
|
202
|
+
"verbose",
|
|
203
|
+
"type",
|
|
204
|
+
"parameters",
|
|
205
|
+
"read_only",
|
|
206
|
+
"output",
|
|
207
|
+
"versions",
|
|
208
|
+
"render",
|
|
209
|
+
]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
assert foo.meta.action == "Test Function"
|
|
213
|
+
assert foo.meta.description == "This action does nothing for now."
|
|
214
|
+
assert simple_app.actions_manager.get_action("test_function_id") == foo
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_action_with_bad_render_as(simple_app: App):
|
|
218
|
+
with pytest.raises(
|
|
219
|
+
ValueError,
|
|
220
|
+
match="Please only specify render_as as 'table' or 'json' or 'custom'.",
|
|
221
|
+
):
|
|
222
|
+
|
|
223
|
+
@simple_app.action(
|
|
224
|
+
name="Test Function", identifier="test_function_id", render_as="bad"
|
|
225
|
+
)
|
|
226
|
+
def foo(params: Params) -> ActionOutput:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
|
|
187
230
|
def test_action_decoration_uses_function_name_for_action_name(simple_app):
|
|
188
231
|
@simple_app.action()
|
|
189
232
|
def action_function(params: Params) -> ActionOutput:
|
|
@@ -506,3 +549,26 @@ def test_register_action_with_view_handler_module_not_in_sys_modules(simple_app:
|
|
|
506
549
|
view_handler=fake_module_view_handler,
|
|
507
550
|
view_template="sample_template.html",
|
|
508
551
|
)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def test_empty_list_output(simple_app: App, mocker: pytest_mock.MockerFixture):
|
|
555
|
+
@simple_app.action()
|
|
556
|
+
def action_example(params: Params, soar: SOARClient) -> list[ActionOutput]:
|
|
557
|
+
return []
|
|
558
|
+
|
|
559
|
+
add_result_mock = mocker.patch.object(simple_app.actions_manager, "add_result")
|
|
560
|
+
result = action_example(Params(), soar=simple_app.soar_client)
|
|
561
|
+
assert result is True
|
|
562
|
+
assert add_result_mock.call_count == 1
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def test_list_output_with_summary(simple_app: App, mocker: pytest_mock.MockerFixture):
|
|
566
|
+
@simple_app.action()
|
|
567
|
+
def action_example(params: Params, soar: SOARClient) -> list[ActionOutput]:
|
|
568
|
+
soar.set_summary(ActionOutput())
|
|
569
|
+
return [ActionOutput()]
|
|
570
|
+
|
|
571
|
+
set_summary_mock = mocker.patch.object(simple_app.soar_client, "set_summary")
|
|
572
|
+
result = action_example(Params(), soar=simple_app.soar_client)
|
|
573
|
+
assert result is True
|
|
574
|
+
assert set_summary_mock.call_count == 1
|