splunk-soar-sdk 1.4.1__py3-none-any.whl → 1.5.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.
soar_sdk/abstract.py CHANGED
@@ -34,10 +34,7 @@ class SOARClientAuth:
34
34
 
35
35
 
36
36
  class SOARClient(Generic[SummaryType]):
37
- """A unified API interface for performing actions on SOAR Platform.
38
-
39
- Replaces previously used BaseConnector API interface.
40
- """
37
+ """An API interface for interacting with the Splunk SOAR Platform."""
41
38
 
42
39
  @property
43
40
  @abstractmethod
@@ -71,7 +71,7 @@ class ActionsManager(BaseConnector):
71
71
  ) # TODO: replace with a valid lack of action handling
72
72
 
73
73
  def handle_action(self, param: dict[str, Any]) -> None:
74
- """The central action execution function BaseConnector expects to be overriden.
74
+ """The central action execution function BaseConnector expects to be overridden.
75
75
 
76
76
  Given the input parameter dictionary from Splunk SOAR, find the Action function
77
77
  referred to by the input, parse the parameters into the appropriate Pydantic model,
soar_sdk/app.py CHANGED
@@ -1,5 +1,7 @@
1
+ import importlib.util
1
2
  import inspect
2
3
  import json
4
+ from pathlib import Path
3
5
  import sys
4
6
  from typing import Any, Optional, Union, Callable
5
7
  from collections.abc import Iterator
@@ -226,7 +228,7 @@ class App:
226
228
  def register_action(
227
229
  self,
228
230
  /,
229
- action: Callable,
231
+ action: Union[str, Callable],
230
232
  *,
231
233
  name: Optional[str] = None,
232
234
  identifier: Optional[str] = None,
@@ -236,7 +238,7 @@ class App:
236
238
  read_only: bool = True,
237
239
  params_class: Optional[type[Params]] = None,
238
240
  output_class: Optional[type[ActionOutput]] = None,
239
- view_handler: Optional[Callable] = None,
241
+ view_handler: Union[str, Callable, None] = None,
240
242
  view_template: Optional[str] = None,
241
243
  versions: str = "EQ(*)",
242
244
  summary_type: Optional[type[ActionOutput]] = None,
@@ -248,8 +250,30 @@ class App:
248
250
  actions without using decorators directly on the action function.
249
251
 
250
252
  Args:
251
- action: Function import for the action. Must be a callable function that
252
- follows SOAR action function conventions and is imported from another module.
253
+ action: Either an import string to find the function that implements an action, or the imported function itself.
254
+
255
+ .. warning::
256
+ If you import the function directly, and provide the callable to this function, this sometimes messes with the app's CLI invocation. For example, if you import your function with a relative import, like::
257
+
258
+ from .actions.my_action import my_action_function
259
+
260
+ then executing your app directly, like::
261
+
262
+ python src/app.py action my_action_function
263
+
264
+ will fail, but running as a module, like::
265
+
266
+ python -m src.app action my_action_function
267
+
268
+ will work. By contrast, if you use an absolute import, like::
269
+
270
+ from actions.my_action import my_action_function
271
+
272
+ then the `direct CLI invocation` will work, while the `module invocation` will fail. This is a limitation with Python itself.
273
+
274
+ To avoid this confusion, it is recommended to provide the action as an import string, like ``"actions.my_action:my_action_function"``. This way, both invocation methods will work consistently.
275
+
276
+
253
277
  name: Human-readable name for the action. If not provided, defaults
254
278
  to the function name with underscores replaced by spaces.
255
279
  identifier: Unique identifier for the action. If not provided, defaults
@@ -265,12 +289,19 @@ class App:
265
289
  If not provided, uses generic parameter validation.
266
290
  output_class: Pydantic model class for structuring action output.
267
291
  If not provided, uses generic output format.
268
- view_handler: Optional raw view handler function to associate with this action.
292
+ view_handler: Optional import string to a view handler function,
293
+ or the imported function itself, to associate with this action.
269
294
  Will be automatically decorated with the view_handler decorator.
295
+
296
+ .. warning::
297
+
298
+ See the warning above about importing action functions as opposed to using import strings. The same issues apply to view handler functions as well.
299
+
270
300
  view_template: Template name to use with the view handler. Only
271
301
  relevant if view_handler is provided.
272
302
  versions: Version constraint string for when this action is available.
273
303
  Defaults to "EQ(*)" (all versions).
304
+ summary_type: Pydantic model class for structuring action summary output.
274
305
 
275
306
  Returns:
276
307
  The registered Action instance with all metadata and handlers configured.
@@ -280,17 +311,20 @@ class App:
280
311
  found in its original module for replacement.
281
312
 
282
313
  Example:
283
- >>> from my_actions_module import my_action_function
284
- >>> from my_views_module import my_view_handler
285
- >>>
286
314
  >>> action = app.register_action(
287
- ... my_action_function,
315
+ ... "action.my_action:my_action_function",
288
316
  ... name="Dynamic Action",
289
317
  ... description="Action imported from another module",
290
- ... view_handler=my_view_handler,
318
+ ... view_handler="my_views_module:my_view_handler",
291
319
  ... view_template="custom_template.html",
292
320
  ... )
293
321
  """
322
+ if isinstance(action, str):
323
+ action = self._resolve_function_import(action)
324
+
325
+ if isinstance(view_handler, str):
326
+ view_handler = self._resolve_function_import(view_handler)
327
+
294
328
  if view_handler:
295
329
  decorated_view_handler = self.view_handler(template=view_template)(
296
330
  view_handler
@@ -298,8 +332,6 @@ class App:
298
332
 
299
333
  # Replace the function in its original module with the decorated version
300
334
  if hasattr(view_handler, "__module__") and view_handler.__module__:
301
- import sys
302
-
303
335
  if (
304
336
  original_module := sys.modules.get(view_handler.__module__)
305
337
  ) and hasattr(original_module, view_handler.__name__):
@@ -327,6 +359,55 @@ class App:
327
359
  summary_type=summary_type,
328
360
  )(action)
329
361
 
362
+ def _resolve_function_import(self, action_path: str) -> Callable:
363
+ """Resolves a callable action function from a dot-separated import string.
364
+
365
+ This method takes a string representing the full import path of a function,
366
+ imports the necessary module, and retrieves the function object.
367
+
368
+ Args:
369
+ action_path: Dot-separated string representing the import path of the function.
370
+ Example: "my_module.my_submodule:my_function"
371
+
372
+ Returns:
373
+ The callable function object.
374
+
375
+ Raises:
376
+ ValueError: If the action_path is not properly formatted or if the
377
+ module/function cannot be imported.
378
+ """
379
+ # Split the action_path into module and function parts,
380
+ # handling both dot notation and file paths
381
+ module_root = Path(inspect.stack()[2].filename).parent
382
+ func_delim = ":" if ":" in action_path else "."
383
+ module_name, action_func_name = action_path.rsplit(func_delim, 1)
384
+ module_name = module_name.removesuffix(".py").replace(".", "/") + ".py"
385
+ module_path = module_root / module_name
386
+
387
+ module_name = (
388
+ # Jump up from module -> src -> package root
389
+ module_path.relative_to(module_root.parent.parent)
390
+ # Remove .py suffix, convert to dot notation
391
+ .with_suffix("")
392
+ .as_posix()
393
+ .replace("/", ".")
394
+ )
395
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
396
+ # Not sure how to actually make spec None,
397
+ # but the type hint says it's technically possible
398
+ if spec is None or spec.loader is None: # pragma: no cover
399
+ raise ActionRegistrationError(action_func_name)
400
+
401
+ module = importlib.util.module_from_spec(spec)
402
+ sys.modules[module_name] = module
403
+ try:
404
+ spec.loader.exec_module(module)
405
+ action_func = getattr(module, action_func_name)
406
+ except Exception as e:
407
+ raise ActionRegistrationError(action_func_name) from e
408
+
409
+ return action_func
410
+
330
411
  def action(
331
412
  self,
332
413
  *,
@@ -415,7 +496,9 @@ class App:
415
496
  The decorated function receives parsed ActionOutput objects and can return either a dict for template rendering, HTML string, or component data model.
416
497
  If a template is provided, dict results will be rendered using the template. Component type is automatically inferred from the return type annotation.
417
498
 
418
- For more information on custom views, see the following :doc:`custom views documentation </custom_views/index>`:
499
+ .. seealso::
500
+
501
+ For more information on custom views, see the following :doc:`custom views documentation </custom_views/index>`:
419
502
 
420
503
  Example:
421
504
  >>> @app.view_handler(template="my_template.html")
@@ -7,7 +7,7 @@ from pprint import pprint
7
7
 
8
8
  from soar_sdk.app import App
9
9
  from soar_sdk.cli.path_utils import context_directory
10
- from soar_sdk.compat import remove_when_soar_newer_than
10
+ from soar_sdk.compat import remove_when_soar_newer_than, UPDATE_TIME_FORMAT
11
11
  from soar_sdk.meta.adapters import TOMLDataAdapter
12
12
  from soar_sdk.meta.app import AppMeta
13
13
  from soar_sdk.meta.dependencies import UvLock
@@ -31,7 +31,7 @@ class ManifestProcessor:
31
31
  app_meta.configuration = app.asset_cls.to_json_schema()
32
32
  app_meta.actions = app.actions_manager.get_actions_meta_list()
33
33
  app_meta.utctime_updated = datetime.now(timezone.utc).strftime(
34
- "%Y-%m-%dT%H:%M:%S.%fZ"
34
+ UPDATE_TIME_FORMAT
35
35
  )
36
36
  for field, value in app.app_meta_info.items():
37
37
  setattr(app_meta, field, value)
soar_sdk/compat.py CHANGED
@@ -4,6 +4,8 @@ from packaging.version import Version
4
4
 
5
5
  MIN_PHANTOM_VERSION = "6.4.0"
6
6
 
7
+ UPDATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
8
+
7
9
 
8
10
  @functools.lru_cache(maxsize=32)
9
11
  def remove_when_soar_newer_than(
soar_sdk/meta/app.py CHANGED
@@ -20,7 +20,7 @@ class AppMeta(BaseModel):
20
20
 
21
21
  name: str = ""
22
22
  description: str
23
- appid: str = "1e1618e7-2f70-4fc0-916a-f96facc2d2e4" # placeholder value to pass inital validation
23
+ appid: str = "1e1618e7-2f70-4fc0-916a-f96facc2d2e4" # placeholder value to pass initial validation
24
24
  type: str = ""
25
25
  product_vendor: str = ""
26
26
  app_version: str
soar_sdk/params.py CHANGED
@@ -88,7 +88,7 @@ class Params(BaseModel):
88
88
  """Params defines the full set of inputs for an action.
89
89
 
90
90
  It can contain strings, booleans, or numbers -- no lists or dictionaries.
91
- Params fields can be optional if desired, or optionally have a default value, CEF type, and other metadata defined in soar_sdk.params.Param.
91
+ Params fields can be optional if desired, or optionally have a default value, CEF type, and other metadata defined in :func:`soar_sdk.params.Param`.
92
92
  """
93
93
 
94
94
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splunk-soar-sdk
3
- Version: 1.4.1
3
+ Version: 1.5.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,19 +1,19 @@
1
1
  soar_sdk/__init__.py,sha256=RzAng-ARqpK01SY82lNy4uYJFVG0yW6Q3CccEqbToJ4,726
2
- soar_sdk/abstract.py,sha256=2fYEc_xdhz635RnUn5Fn7odTuNkzqQ_YjKF25A-KjNM,7863
2
+ soar_sdk/abstract.py,sha256=ExsLCY99aoPRFHsUfw7yIVdWJeZjq1ahQCVwQmZyYh8,7798
3
3
  soar_sdk/action_results.py,sha256=gAQwHjXbkkzOJTmQnLwBjKjwbuz8mSPyqIVcirVS598,10114
4
- soar_sdk/actions_manager.py,sha256=vYglKejmPX1MldTzafCVE2K60UiSLhNGfYcVOxD4YwU,5884
5
- soar_sdk/app.py,sha256=lCCOLKfpUxE9WgLYn_S83riHe4vL2Kzcgda2ra4xNs4,29207
4
+ soar_sdk/actions_manager.py,sha256=U8OZmNCjuwtLKlC19BsVIvDyeJDS_ZiTyZIHA4SS1go,5885
5
+ soar_sdk/app.py,sha256=9zI8RM6uo4V0Bj4cgCvJzSOjIBPrvt2meXVAMaPhVrE,32882
6
6
  soar_sdk/app_cli_runner.py,sha256=uk9V-cbAHx1tzK_1gCYpfdF7IJ_ZwuWV2Ak4NMOvKGA,11873
7
7
  soar_sdk/app_client.py,sha256=UVCMFYweaYcFk7WrtoVsCoprYZ7JlzMBR108ZQRV8SE,6051
8
8
  soar_sdk/asset.py,sha256=deS8_B5hr7W2fED8_6wUpVriRgiQ5r8TkGVHiasIaro,10666
9
9
  soar_sdk/async_utils.py,sha256=gND8ZiVTqDYLQ88Ua6SN1mInJaEcfa168eOaRoURt3E,1441
10
10
  soar_sdk/colors.py,sha256=--i_iXqfyITUz4O95HMjfZQGbwFZ34bLmBhtfpXXqlQ,1095
11
- soar_sdk/compat.py,sha256=jW3l9vJ7jLUicZ9SvfFrL4l3H84DQHUYSBy0-51VE3s,2620
11
+ soar_sdk/compat.py,sha256=H7At6OSsZQMtlarjyWNcPfsa4_8FC5k0JLdGrmU-O1M,2666
12
12
  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=K16_umW_dAfvqa2QcHR2XHc93py-k7SVOHdz3GUGFGk,7615
16
+ soar_sdk/params.py,sha256=JKhlyBlYDdgUpQv1G6QEnJiY5G1XkiQ364_iNmyjxks,7623
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
@@ -37,7 +37,7 @@ soar_sdk/cli/init/cli.py,sha256=bVp8t28FYCS_wP2FhLWqmIvCkFbqmO1-7Dw8PpTFNq0,1449
37
37
  soar_sdk/cli/manifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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
- soar_sdk/cli/manifests/processors.py,sha256=anR_7TledkYvPWtcMakzO4JWO1iNWlj6lQBx7cD5hmE,4140
40
+ soar_sdk/cli/manifests/processors.py,sha256=6B1fQC2WGVaUP-7E9Y5g7BipaVwEomJCkUQ_7gRfSn8,4155
41
41
  soar_sdk/cli/manifests/serializers.py,sha256=HA7a3DSzB3P-v2yomAMZW0M1__wY2yCNLU_v0QZv7Vo,3245
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=JmVf_PwoXiuypE1DZ1-k4sFjZszD0c_BYTiLtwy9jDk,1448
@@ -58,7 +58,7 @@ soar_sdk/decorators/webhook.py,sha256=Pde0MjxC2kckpxoKb3m4WVfLWtiFAAzAwZe5AF5VIb
58
58
  soar_sdk/meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  soar_sdk/meta/actions.py,sha256=-Py_AbQ9BOr3P-J9OrKTTdKAQvNZ5g6xvgYLCjJktlg,2215
60
60
  soar_sdk/meta/adapters.py,sha256=KjSYIUtkCz2eesA_vhsNCjfi5C-Uz71tbSuDIjhuB8U,1112
61
- soar_sdk/meta/app.py,sha256=cmbfACoFiBNvZ3IWT_f3C-Yz8hyZgqa1KURCYQV5N6k,1764
61
+ soar_sdk/meta/app.py,sha256=rwgoFfIFnLutLB9PF1kR7X1neKs175VA0Xx4KpJ6c7I,1765
62
62
  soar_sdk/meta/datatypes.py,sha256=piR-oBVAATiRciXSdVE7XaqjUZTgSaOvTEqcOcNvCS0,795
63
63
  soar_sdk/meta/dependencies.py,sha256=AQlkdm6A7JEfLI0IAXKU6WzPkUX7oPfxgDkGZ3AofHk,17927
64
64
  soar_sdk/meta/webhooks.py,sha256=1sh4-3I3_UiXFNzVEyoz2Tp6NhgTEZWpERYIzV8OYNY,1188
@@ -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-1.4.1.dist-info/METADATA,sha256=mAYVSNDk067JSRxMrKll00jaNgNZWH5tF5syV5fZxoQ,7355
100
- splunk_soar_sdk-1.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
- splunk_soar_sdk-1.4.1.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
102
- splunk_soar_sdk-1.4.1.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
103
- splunk_soar_sdk-1.4.1.dist-info/RECORD,,
99
+ splunk_soar_sdk-1.5.0.dist-info/METADATA,sha256=l89BBxU2lNASu9LoIr9GU9iEBItjPj3Z-RY1zhN3kX0,7355
100
+ splunk_soar_sdk-1.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
101
+ splunk_soar_sdk-1.5.0.dist-info/entry_points.txt,sha256=CgBjo2ZWpYNkt9TgvToL26h2Tg1yt8FbvYTb5NVgNuc,51
102
+ splunk_soar_sdk-1.5.0.dist-info/licenses/LICENSE,sha256=gNCGrGhrSQb1PUzBOByVUN1tvaliwLZfna-QU2r2hQ8,11345
103
+ splunk_soar_sdk-1.5.0.dist-info/RECORD,,