splunk-soar-sdk 2.1.1__py3-none-any.whl → 2.3.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.
@@ -2,10 +2,10 @@ from typing import Optional, Union, get_origin, get_args, Any
2
2
  from collections.abc import Iterator
3
3
  from typing_extensions import NotRequired, TypedDict
4
4
  from pydantic import BaseModel, Field
5
+ import itertools
5
6
 
6
7
  from soar_sdk.compat import remove_when_soar_newer_than
7
8
  from soar_sdk.shims.phantom.action_result import ActionResult as PhantomActionResult
8
-
9
9
  from soar_sdk.meta.datatypes import as_datatype
10
10
 
11
11
  remove_when_soar_newer_than(
@@ -83,12 +83,15 @@ 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,
92
95
  ) -> Any: # noqa: ANN401
93
96
  """Define metadata for an action output field.
94
97
 
@@ -102,6 +105,10 @@ def OutputField(
102
105
  example_values: Optional list of example values for this field, used
103
106
  in documentation and for testing/validation purposes.
104
107
  alias: Optional alternative name for the field when serialized.
108
+ column_name: Optional name for the field when displayed in a table.
109
+
110
+ Note:
111
+ Column name and order must be set together, if one is set but the other is not, an error will be raised.
105
112
 
106
113
  Returns:
107
114
  A Pydantic Field object with the specified metadata.
@@ -116,8 +123,9 @@ def OutputField(
116
123
  """
117
124
  return Field(
118
125
  examples=example_values,
119
- cef_types=cef_types,
120
126
  alias=alias,
127
+ cef_types=cef_types,
128
+ column_name=column_name,
121
129
  )
122
130
 
123
131
 
@@ -152,7 +160,9 @@ class ActionOutput(BaseModel):
152
160
 
153
161
  @classmethod
154
162
  def _to_json_schema(
155
- cls, parent_datapath: str = "action_result.data.*"
163
+ cls,
164
+ parent_datapath: str = "action_result.data.*",
165
+ column_order_counter: Optional[itertools.count] = None,
156
166
  ) -> Iterator[OutputFieldSpecification]:
157
167
  """Convert the ActionOutput class to SOAR-compatible JSON schema.
158
168
 
@@ -163,6 +173,8 @@ class ActionOutput(BaseModel):
163
173
  Args:
164
174
  parent_datapath: The base datapath for fields in this output.
165
175
  Defaults to "action_result.data.*" for top-level outputs.
176
+ column_order_counter: Iterator for tracking column order across fields.
177
+ Used internally to maintain sequential column ordering. Defaults to itertools.count().
166
178
 
167
179
  Yields:
168
180
  OutputFieldSpecification objects describing each field in the schema.
@@ -176,6 +188,9 @@ class ActionOutput(BaseModel):
176
188
  Nested ActionOutput classes are recursively processed.
177
189
  Boolean fields automatically get [True, False] example values.
178
190
  """
191
+ if column_order_counter is None:
192
+ column_order_counter = itertools.count()
193
+
179
194
  for _field_name, field in cls.__fields__.items():
180
195
  field_name = alias if (alias := field.alias) else _field_name
181
196
 
@@ -208,7 +223,7 @@ class ActionOutput(BaseModel):
208
223
 
209
224
  if issubclass(field_type, ActionOutput):
210
225
  # If the field is another ActionOutput, recursively call _to_json_schema
211
- yield from field_type._to_json_schema(datapath)
226
+ yield from field_type._to_json_schema(datapath, column_order_counter)
212
227
  continue
213
228
  else:
214
229
  try:
@@ -230,6 +245,12 @@ class ActionOutput(BaseModel):
230
245
  if field_type is bool:
231
246
  schema_field["example_values"] = [True, False]
232
247
 
248
+ column_name = field.field_info.extra.get("column_name")
249
+
250
+ if column_name is not None:
251
+ schema_field["column_name"] = column_name
252
+ schema_field["column_order"] = next(column_order_counter)
253
+
233
254
  yield schema_field
234
255
 
235
256
 
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,
@@ -1,6 +1,7 @@
1
1
  from typing import Any, Optional
2
2
  from collections.abc import Iterator
3
3
  from logging import getLogger
4
+ import itertools
4
5
 
5
6
  from soar_sdk.meta.datatypes import as_datatype
6
7
  from soar_sdk.params import Params
@@ -29,8 +30,12 @@ class OutputsSerializer:
29
30
  @staticmethod
30
31
  def serialize_parameter_datapaths(
31
32
  params_class: type[Params],
33
+ column_order_counter: Optional[itertools.count] = None,
32
34
  ) -> Iterator[OutputFieldSpecification]:
33
35
  """Serializes the parameter data paths of a Params class to JSON schema."""
36
+ if column_order_counter is None:
37
+ column_order_counter = itertools.count()
38
+
34
39
  for field_name, field in params_class.__fields__.items():
35
40
  spec = OutputFieldSpecification(
36
41
  data_path=f"action_result.parameter.{field_name}",
@@ -38,6 +43,12 @@ class OutputsSerializer:
38
43
  )
39
44
  if cef_types := field.field_info.extra.get("cef_types"):
40
45
  spec["contains"] = cef_types
46
+
47
+ column_name = field.field_info.extra.get("column_name")
48
+
49
+ if column_name is not None:
50
+ spec["column_name"] = column_name
51
+ spec["column_order"] = next(column_order_counter)
41
52
  yield spec
42
53
 
43
54
  @classmethod
@@ -57,10 +68,13 @@ class OutputsSerializer:
57
68
  data_path="action_result.message",
58
69
  data_type="string",
59
70
  )
60
- params = cls.serialize_parameter_datapaths(params_class)
61
- outputs = outputs_class._to_json_schema()
71
+ column_order_counter = itertools.count()
72
+ params = cls.serialize_parameter_datapaths(params_class, column_order_counter)
73
+ outputs = outputs_class._to_json_schema(
74
+ column_order_counter=column_order_counter
75
+ )
62
76
  summary = (
63
- summary_class._to_json_schema("action_result.summary")
77
+ summary_class._to_json_schema("action_result.summary", column_order_counter)
64
78
  if summary_class
65
79
  else []
66
80
  )
@@ -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}
soar_sdk/params.py CHANGED
@@ -22,6 +22,7 @@ def Param(
22
22
  allow_list: bool = False,
23
23
  sensitive: bool = False,
24
24
  alias: Optional[str] = None,
25
+ column_name: Optional[str] = None,
25
26
  ) -> Any: # noqa: ANN401
26
27
  """Representation of a single complex action parameter.
27
28
 
@@ -49,6 +50,7 @@ def Param(
49
50
  :param allow_list: Use this key to specify if the parameter supports specifying
50
51
  multiple values as a comma separated string.
51
52
  :param kwargs: additional kwargs accepted by pydantic.Field
53
+ :param column_name: Optional name for the parameter when displayed in an output table.
52
54
  :return: returns the FieldInfo object as pydantic.Field
53
55
  """
54
56
  if value_list is None:
@@ -64,6 +66,7 @@ def Param(
64
66
  allow_list=allow_list,
65
67
  sensitive=sensitive,
66
68
  alias=alias,
69
+ column_name=column_name,
67
70
  )
68
71
 
69
72
 
@@ -80,6 +83,8 @@ class InputFieldSpecification(TypedDict):
80
83
  value_list: NotRequired[list[str]]
81
84
  allow_list: bool
82
85
  default: NotRequired[Union[str, int, float, bool]]
86
+ column_name: NotRequired[str]
87
+ column_order: NotRequired[int]
83
88
 
84
89
 
85
90
  class Params(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splunk-soar-sdk
3
- Version: 2.1.1
3
+ Version: 2.3.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=NQTcQ3NJcYB6h-YNRqO0bCG_hILmTidv2D4FdvCzyDE,11092
4
4
  soar_sdk/actions_manager.py,sha256=wJCyfzkI_6OKZ-Kmll4vRJpGvYdL93Uw-JyEEGnKcw0,5779
5
- soar_sdk/app.py,sha256=-Iq_0btBwZwRksSfrv7gzlBFJBblNM8VKZK6_rgMGog,33208
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
@@ -13,7 +13,7 @@ soar_sdk/crypto.py,sha256=qiBMHUQqgn5lPI1DbujSj700s89FuLJrkQgCO9_eBn4,392
13
13
  soar_sdk/exceptions.py,sha256=CxJ_Q6N1jlknO_3ItDQNhHEw2pNWZr3sMLqutYmr5HA,1863
14
14
  soar_sdk/input_spec.py,sha256=BAa36l8IKDvM8SVMjgZ1XcnWZ2F7O052n2415tLeKK8,4690
15
15
  soar_sdk/logging.py,sha256=lSz8PA6hOCw2MHGE0ZSKbw-FzSr1WdbfQ7BHnXBUUY0,11440
16
- soar_sdk/params.py,sha256=8wo6buia91Hh-bxq0jVUU9MPN3XZVRzkMfKZ2BDuMLQ,7488
16
+ soar_sdk/params.py,sha256=Q2tWOzZWNl2Spvnu29iSUfMleW8U7OD-DdCWtkOXRMw,7720
17
17
  soar_sdk/paths.py,sha256=XhpanQCAiTXaulRx440oKu36mnll7P05TethHXgMpgQ,239
18
18
  soar_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  soar_sdk/types.py,sha256=uMFnNOHpmCLrbAhQOgmXjScXiGE67sM8ySN04MhkC3U,602
@@ -38,7 +38,7 @@ soar_sdk/cli/manifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
38
38
  soar_sdk/cli/manifests/cli.py,sha256=cly5xVdj4bBIdZVMQPIWTXRgUfd1ON3qKO-76Fwql18,524
39
39
  soar_sdk/cli/manifests/deserializers.py,sha256=FtXv-OywYGb_O5TptS2Hg6niWcfihRPF_RaS5zDBXRk,17045
40
40
  soar_sdk/cli/manifests/processors.py,sha256=6B1fQC2WGVaUP-7E9Y5g7BipaVwEomJCkUQ_7gRfSn8,4155
41
- soar_sdk/cli/manifests/serializers.py,sha256=tlW8QPuaVUF8u384lN02xIK2bS2XkDL52kPNwwPmj9c,2859
41
+ soar_sdk/cli/manifests/serializers.py,sha256=0qps_2jsySXa4ytX3DvFt0tbbZlZ7mowIjMu9FEhQMc,3417
42
42
  soar_sdk/cli/package/cli.py,sha256=oCpP9E3PtXq-zCdzQD8Z-4dowKF1YT-uKjTpbt_YT-A,9516
43
43
  soar_sdk/cli/package/utils.py,sha256=NQgMxWZSf20hqd4Orov77b7qK23QAO3zIEvRTj9HW-o,1590
44
44
  soar_sdk/code_renderers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -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.1.dist-info/METADATA,sha256=DX2tSp9QhI815naidp9yRobLgAyhnu5LvbHljrRm_Zc,7361
100
- splunk_soar_sdk-2.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
- splunk_soar_sdk-2.1.1.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
102
- splunk_soar_sdk-2.1.1.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
103
- splunk_soar_sdk-2.1.1.dist-info/RECORD,,
99
+ splunk_soar_sdk-2.3.0.dist-info/METADATA,sha256=WGypNiW3i7PR8Eef-5QYJUwWSR1ua062kpRU86c7g8Y,7361
100
+ splunk_soar_sdk-2.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
+ splunk_soar_sdk-2.3.0.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
102
+ splunk_soar_sdk-2.3.0.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
103
+ splunk_soar_sdk-2.3.0.dist-info/RECORD,,