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.
Files changed (219) hide show
  1. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/PKG-INFO +1 -1
  2. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/pyproject.toml +1 -1
  3. splunk_soar_sdk-2.2.0/release_notes.txt +21 -0
  4. splunk_soar_sdk-2.2.0/release_version.txt +1 -0
  5. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/action_results.py +27 -1
  6. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app.py +14 -2
  7. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/action.py +11 -0
  8. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/actions.py +10 -4
  9. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_deserializers.py +5 -1
  10. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_serializers.py +11 -2
  11. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/app.json +202 -0
  12. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/app.py +39 -1
  13. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_actions.py +3 -2
  14. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_action_results.py +25 -0
  15. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_action.py +66 -0
  16. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/uv.lock +1 -1
  17. splunk_soar_sdk-2.1.0/release_notes.txt +0 -21
  18. splunk_soar_sdk-2.1.0/release_version.txt +0 -1
  19. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  20. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  21. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/pull_request_template.md +0 -0
  22. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/utils/github.js +0 -0
  23. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/utils/update_version.py +0 -0
  24. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/code_quality.yml +0 -0
  25. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/commit_hygiene.yml +0 -0
  26. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/generate_docs.yml +0 -0
  27. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.github/workflows/semantic_release.yml +0 -0
  28. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.gitignore +0 -0
  29. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.pre-commit-config.yaml +0 -0
  30. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/.releaserc +0 -0
  31. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/LICENSE +0 -0
  32. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/README.md +0 -0
  33. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/commitlint.config.js +0 -0
  34. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/api_reference.rst +0 -0
  35. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/index.rst +0 -0
  36. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/pre-commit-config.yaml.rst +0 -0
  37. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/pyproject.toml.rst +0 -0
  38. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/app_structure/src_app.rst +0 -0
  39. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/changelog.rst +0 -0
  40. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/cli_reference.rst +0 -0
  41. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/conf.py +0 -0
  42. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/index.rst +0 -0
  43. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/reusable_components.md +0 -0
  44. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/templates.md +0 -0
  45. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/custom_views/view_handlers.md +0 -0
  46. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/defining_asset.rst +0 -0
  47. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/first_action.rst +0 -0
  48. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/index.rst +0 -0
  49. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/init_app.rst +0 -0
  50. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/installation.rst +0 -0
  51. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/getting_started/testing_and_building.rst +0 -0
  52. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/docs/index.rst +0 -0
  53. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/__init__.py +0 -0
  54. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/abstract.py +0 -0
  55. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/actions_manager.py +0 -0
  56. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/__init__.py +0 -0
  57. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/artifact.py +0 -0
  58. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/container.py +0 -0
  59. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/utils.py +0 -0
  60. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/apis/vault.py +0 -0
  61. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_cli_runner.py +0 -0
  62. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_client.py +0 -0
  63. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/.gitignore +0 -0
  64. {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
  65. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/logo.svg +0 -0
  66. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/logo_dark.svg +0 -0
  67. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/src/__init__.py +0 -0
  68. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/app_templates/basic_app/uv.lock +0 -0
  69. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/asset.py +0 -0
  70. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/async_utils.py +0 -0
  71. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/__init__.py +0 -0
  72. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/cli.py +0 -0
  73. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/init/__init__.py +0 -0
  74. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/init/cli.py +0 -0
  75. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/__init__.py +0 -0
  76. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/cli.py +0 -0
  77. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/deserializers.py +0 -0
  78. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/processors.py +0 -0
  79. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/manifests/serializers.py +0 -0
  80. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/package/cli.py +0 -0
  81. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/package/utils.py +0 -0
  82. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/path_utils.py +0 -0
  83. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/cli/utils.py +0 -0
  84. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/__init__.py +0 -0
  85. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/action_renderer.py +0 -0
  86. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/app_renderer.py +0 -0
  87. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/asset_renderer.py +0 -0
  88. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/renderer.py +0 -0
  89. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/templates/pyproject.toml.jinja +0 -0
  90. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/code_renderers/toml_renderer.py +0 -0
  91. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/colors.py +0 -0
  92. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/compat.py +0 -0
  93. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/crypto.py +0 -0
  94. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/__init__.py +0 -0
  95. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/make_request.py +0 -0
  96. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/on_poll.py +0 -0
  97. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/test_connectivity.py +0 -0
  98. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/view_handler.py +0 -0
  99. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/decorators/webhook.py +0 -0
  100. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/exceptions.py +0 -0
  101. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/input_spec.py +0 -0
  102. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/logging.py +0 -0
  103. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/__init__.py +0 -0
  104. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/adapters.py +0 -0
  105. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/app.py +0 -0
  106. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/datatypes.py +0 -0
  107. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/dependencies.py +0 -0
  108. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/meta/webhooks.py +0 -0
  109. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/__init__.py +0 -0
  110. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/artifact.py +0 -0
  111. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/container.py +0 -0
  112. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/vault_attachment.py +0 -0
  113. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/models/view.py +0 -0
  114. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/params.py +0 -0
  115. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/paths.py +0 -0
  116. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/py.typed +0 -0
  117. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/action_result.py +0 -0
  118. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/app.py +0 -0
  119. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/base_connector.py +0 -0
  120. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/connector_result.py +0 -0
  121. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/consts.py +0 -0
  122. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/encryption_helper.py +0 -0
  123. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/install_info.py +0 -0
  124. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/json_keys.py +0 -0
  125. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/ph_ipc.py +0 -0
  126. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/shims/phantom/vault.py +0 -0
  127. {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
  128. {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
  129. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/base_template.html +0 -0
  130. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/error.html +0 -0
  131. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/header.html +0 -0
  132. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/base/logo_header.html +0 -0
  133. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/components/pie_chart.html +0 -0
  134. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/widgets/widget_resize_snippet.html +0 -0
  135. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/templates/widgets/widget_template.html +0 -0
  136. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/types.py +0 -0
  137. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/__init__.py +0 -0
  138. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/component_registry.py +0 -0
  139. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/components/__init__.py +0 -0
  140. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/components/pie_chart.py +0 -0
  141. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/template_filters.py +0 -0
  142. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/template_renderer.py +0 -0
  143. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/views/view_parser.py +0 -0
  144. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/webhooks/__init__.py +0 -0
  145. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/webhooks/models.py +0 -0
  146. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/src/soar_sdk/webhooks/routing.py +0 -0
  147. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/test.png +0 -0
  148. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/test.txt +0 -0
  149. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/__init__.py +0 -0
  150. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/__init__.py +0 -0
  151. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/datapath_parse.py +0 -0
  152. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/manifests/__init__.py +0 -0
  153. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/manifests/test_processors.py +0 -0
  154. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_assets/converted_app/actions.py.txt +0 -0
  155. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_cli.py +0 -0
  156. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_convert_cli.py +0 -0
  157. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_init_cli.py +0 -0
  158. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_manifests_cli.py +0 -0
  159. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_package_cli.py +0 -0
  160. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/cli/test_utils.py +0 -0
  161. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/code_renderers/test_action_renderer.py +0 -0
  162. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/conftest.py +0 -0
  163. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/example_asset.json +0 -0
  164. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/logo.svg +0 -0
  165. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/logo_dark.svg +0 -0
  166. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/pyproject.toml +0 -0
  167. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/__init__.py +0 -0
  168. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/__init__.py +0 -0
  169. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/async_action.py +0 -0
  170. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/generate_category.py +0 -0
  171. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/actions/reverse_string.py +0 -0
  172. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/src/ignoreme.txt +0 -0
  173. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/templates/reverse_string.html +0 -0
  174. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app/uv.lock +0 -0
  175. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/app.json +0 -0
  176. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/logo.svg +0 -0
  177. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/logo_dark.svg +0 -0
  178. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/pyproject.toml +0 -0
  179. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/src/__init__.py +0 -0
  180. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/src/app.py +0 -0
  181. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/example_app_with_webhook/uv.lock +0 -0
  182. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/__init__.py +0 -0
  183. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/test_artifact_interface.py +0 -0
  184. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/test_container_interface.py +0 -0
  185. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/interfaces/test_vault_interface.py +0 -0
  186. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/__init__.py +0 -0
  187. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_adapters.py +0 -0
  188. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_datatypes.py +0 -0
  189. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_dependencies.py +0 -0
  190. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/meta/test_webhooks.py +0 -0
  191. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/mocks/__init__.py +0 -0
  192. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/mocks/dynamic_mocks.py +0 -0
  193. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/mocks/importable_action.py +0 -0
  194. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/stubs.py +0 -0
  195. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_actions_manager.py +0 -0
  196. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app.py +0 -0
  197. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_action_params.py +0 -0
  198. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_action_results.py +0 -0
  199. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_client.py +0 -0
  200. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_app_runner.py +0 -0
  201. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_asset.py +0 -0
  202. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_assets/splunk-sdk-2.1.0.tar.gz +0 -0
  203. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_async_integration.py +0 -0
  204. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_async_utils.py +0 -0
  205. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_code_renderers.py +0 -0
  206. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_compat.py +0 -0
  207. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_container.py +0 -0
  208. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_custom_views.py +0 -0
  209. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_encryption.py +0 -0
  210. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_logging.py +0 -0
  211. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_make_request_action.py +0 -0
  212. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_on_poll.py +0 -0
  213. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_params.py +0 -0
  214. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_template_filters.py +0 -0
  215. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_template_renderer.py +0 -0
  216. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_test_connectivity.py +0 -0
  217. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/test_view_parser.py +0 -0
  218. {splunk_soar_sdk-2.1.0 → splunk_soar_sdk-2.2.0}/tests/webhooks/test_models.py +0 -0
  219. {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.1.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "splunk-soar-sdk"
3
- version = "2.1.0"
3
+ version = "2.2.0"
4
4
  description = "The official framework for developing and testing Splunk SOAR Apps"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9, <3.14"
@@ -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(item, actions_manager, action_params)
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
- return all(statuses)
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
- {"data_path": "action_result.data.*.result"},
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(cef_types=["ip"], example_values=["1.1.1.1"])
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 result["render"]["view"] == "single_module.mock_view"
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"]["view"] == "src.app.mock_view"
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