splunk-soar-sdk 2.1.0__py3-none-any.whl → 2.2.0__py3-none-any.whl

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.
@@ -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
 
soar_sdk/app.py CHANGED
@@ -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,
soar_sdk/meta/actions.py CHANGED
@@ -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}
@@ -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,8 +1,8 @@
1
1
  soar_sdk/__init__.py,sha256=RzAng-ARqpK01SY82lNy4uYJFVG0yW6Q3CccEqbToJ4,726
2
2
  soar_sdk/abstract.py,sha256=jGXs2Fv5TRpnh5Duz3mWjY8_DAOpY4RSSzvw_z4XN4I,7950
3
- soar_sdk/action_results.py,sha256=7uEl1KidC2gkNOuBzOq2_3EiAGfRQYrDYD3CR-G6DKg,10116
3
+ soar_sdk/action_results.py,sha256=5Lr3J1G2hcVVTQD35eo7OG3Cvr7eGZgb-FL2Eb5pBx8,11424
4
4
  soar_sdk/actions_manager.py,sha256=wJCyfzkI_6OKZ-Kmll4vRJpGvYdL93Uw-JyEEGnKcw0,5779
5
- soar_sdk/app.py,sha256=yCD4HqJKFhfehyXixHqO-kEwzycII-_ephRpxEINIE0,32906
5
+ soar_sdk/app.py,sha256=lYvaDUYEV6b_uF4CyPh6wlAIbCNISya7bJ8Z2xJyyGY,33356
6
6
  soar_sdk/app_cli_runner.py,sha256=fJoozhyAt7QUMuc02nE5RL_InpsjQBpr6U4rF9sey3E,11627
7
7
  soar_sdk/app_client.py,sha256=0r3jIvMM8szCEHXOgRu07VaovKH96pZut5rn2GfYcsc,6275
8
8
  soar_sdk/asset.py,sha256=deS8_B5hr7W2fED8_6wUpVriRgiQ5r8TkGVHiasIaro,10666
@@ -49,14 +49,14 @@ soar_sdk/code_renderers/renderer.py,sha256=oDlmU-MzDqpc4Yq6zupoT4XN-vRjgL4UV5lOG
49
49
  soar_sdk/code_renderers/toml_renderer.py,sha256=-zP8UzlYMCVVA5ex9slaNLeFTu4xLjkv88YLmRNLrTM,1505
50
50
  soar_sdk/code_renderers/templates/pyproject.toml.jinja,sha256=Ti6A5kWMb902Lbd1kmw8qPgVDPNNzlV6rd0pcVEbVUo,3917
51
51
  soar_sdk/decorators/__init__.py,sha256=ttvapTczeQpReZVYgjTw4qnEqKd7b8pR7lNaCpO0npQ,513
52
- soar_sdk/decorators/action.py,sha256=WgkOtlfUpRGkFv1Be1ycRm3s_1UYabCJqoI5MMFWsi8,6304
52
+ soar_sdk/decorators/action.py,sha256=ZuSsowsBeEuenGQw03iyd2uv-Ymp9kQYnhYsx7k7pX8,6695
53
53
  soar_sdk/decorators/make_request.py,sha256=W_ltGvryTvdKomiJ8gL7rE_KVc1VVodhFYstGxB8d4Q,5527
54
54
  soar_sdk/decorators/on_poll.py,sha256=xdT0QSa_dnh37XdJNGW-DAZsb9oQO5tjxPbIQmWpaZs,8232
55
55
  soar_sdk/decorators/test_connectivity.py,sha256=8uXMD4NW5bokpsAfBctUrfOR4K_geYLEZUY0Y6uI6aU,3568
56
56
  soar_sdk/decorators/view_handler.py,sha256=jhBzbJcokWOeUWR4_orDRWTXiiVwE9RZdRSvNUYF3S0,7362
57
57
  soar_sdk/decorators/webhook.py,sha256=Pde0MjxC2kckpxoKb3m4WVfLWtiFAAzAwZe5AF5VIbE,2400
58
58
  soar_sdk/meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- soar_sdk/meta/actions.py,sha256=L64T9DaWGiqjS2EpB72kO-63gPuVyCJ-lYlaKAvT2uM,2394
59
+ soar_sdk/meta/actions.py,sha256=nrTBwCnCwjOzof-_8Ww9vfoTIfZfauiL6C6DTRg2Mhs,2580
60
60
  soar_sdk/meta/adapters.py,sha256=KjSYIUtkCz2eesA_vhsNCjfi5C-Uz71tbSuDIjhuB8U,1112
61
61
  soar_sdk/meta/app.py,sha256=rwgoFfIFnLutLB9PF1kR7X1neKs175VA0Xx4KpJ6c7I,1765
62
62
  soar_sdk/meta/datatypes.py,sha256=piR-oBVAATiRciXSdVE7XaqjUZTgSaOvTEqcOcNvCS0,795
@@ -96,8 +96,8 @@ soar_sdk/views/components/pie_chart.py,sha256=LVTeHVJN6nf2vjUs9y7PDBhS0U1fKW750l
96
96
  soar_sdk/webhooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
97
  soar_sdk/webhooks/models.py,sha256=-rjuFA9cRX5zTLp7cHSHVTkt5eVJD6BdESGbj_qkyHI,4540
98
98
  soar_sdk/webhooks/routing.py,sha256=BKbURSrBPdOTS5UFL-mHzFEr-Fj04mJMx9KeiPrZ2VQ,6872
99
- splunk_soar_sdk-2.1.0.dist-info/METADATA,sha256=lk4jyDyQKtbWUH3aTODNW4eSQho4UKsQ4sNmiMB7MH8,7361
100
- splunk_soar_sdk-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
- splunk_soar_sdk-2.1.0.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
102
- splunk_soar_sdk-2.1.0.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
103
- splunk_soar_sdk-2.1.0.dist-info/RECORD,,
99
+ splunk_soar_sdk-2.2.0.dist-info/METADATA,sha256=N4S0W2q6KuT_hGulekYRuub3Ux29bFmEShcrwHguVzc,7361
100
+ splunk_soar_sdk-2.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
+ splunk_soar_sdk-2.2.0.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
102
+ splunk_soar_sdk-2.2.0.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
103
+ splunk_soar_sdk-2.2.0.dist-info/RECORD,,