splunk-soar-sdk 2.3.7__py3-none-any.whl → 3.1.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.
Files changed (58) hide show
  1. soar_sdk/abstract.py +38 -41
  2. soar_sdk/action_results.py +41 -18
  3. soar_sdk/actions_manager.py +5 -7
  4. soar_sdk/apis/utils.py +3 -3
  5. soar_sdk/apis/vault.py +10 -10
  6. soar_sdk/app.py +58 -51
  7. soar_sdk/app_cli_runner.py +8 -8
  8. soar_sdk/app_client.py +10 -10
  9. soar_sdk/asset.py +45 -33
  10. soar_sdk/async_utils.py +2 -2
  11. soar_sdk/cli/init/cli.py +7 -9
  12. soar_sdk/cli/manifests/deserializers.py +15 -15
  13. soar_sdk/cli/manifests/processors.py +4 -10
  14. soar_sdk/cli/manifests/serializers.py +16 -8
  15. soar_sdk/cli/package/cli.py +6 -6
  16. soar_sdk/cli/package/utils.py +1 -1
  17. soar_sdk/code_renderers/action_renderer.py +35 -18
  18. soar_sdk/code_renderers/app_renderer.py +1 -2
  19. soar_sdk/code_renderers/asset_renderer.py +4 -5
  20. soar_sdk/code_renderers/renderer.py +2 -2
  21. soar_sdk/code_renderers/templates/pyproject.toml.jinja +1 -1
  22. soar_sdk/compat.py +6 -6
  23. soar_sdk/decorators/action.py +14 -15
  24. soar_sdk/decorators/make_request.py +4 -3
  25. soar_sdk/decorators/on_poll.py +5 -4
  26. soar_sdk/decorators/test_connectivity.py +2 -2
  27. soar_sdk/decorators/view_handler.py +11 -17
  28. soar_sdk/decorators/webhook.py +1 -2
  29. soar_sdk/exceptions.py +1 -4
  30. soar_sdk/field_utils.py +8 -0
  31. soar_sdk/input_spec.py +13 -17
  32. soar_sdk/logging.py +3 -3
  33. soar_sdk/meta/actions.py +6 -22
  34. soar_sdk/meta/app.py +10 -7
  35. soar_sdk/meta/dependencies.py +48 -42
  36. soar_sdk/meta/webhooks.py +12 -12
  37. soar_sdk/models/artifact.py +20 -23
  38. soar_sdk/models/container.py +30 -33
  39. soar_sdk/models/finding.py +54 -0
  40. soar_sdk/models/vault_attachment.py +6 -6
  41. soar_sdk/models/view.py +10 -13
  42. soar_sdk/params.py +57 -39
  43. soar_sdk/shims/phantom/action_result.py +4 -4
  44. soar_sdk/shims/phantom/base_connector.py +5 -5
  45. soar_sdk/shims/phantom/ph_ipc.py +3 -3
  46. soar_sdk/shims/phantom/vault.py +35 -34
  47. soar_sdk/types.py +3 -2
  48. soar_sdk/views/template_filters.py +4 -4
  49. soar_sdk/views/template_renderer.py +2 -2
  50. soar_sdk/views/view_parser.py +3 -4
  51. soar_sdk/webhooks/models.py +7 -6
  52. soar_sdk/webhooks/routing.py +4 -3
  53. {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/METADATA +5 -6
  54. splunk_soar_sdk-3.1.0.dist-info/RECORD +105 -0
  55. splunk_soar_sdk-2.3.7.dist-info/RECORD +0 -103
  56. {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/WHEEL +0 -0
  57. {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/entry_points.txt +0 -0
  58. {splunk_soar_sdk-2.3.7.dist-info → splunk_soar_sdk-3.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,15 @@
1
- from typing import ClassVar, Optional
1
+ from typing import ClassVar
2
2
  from collections.abc import Iterator
3
3
  import typing
4
4
  import ast
5
- from pydantic.fields import Undefined
5
+ from pydantic_core import PydanticUndefined
6
6
 
7
7
  from soar_sdk.action_results import ActionOutput
8
8
  from soar_sdk.cli.utils import normalize_field_name
9
9
  from soar_sdk.code_renderers.renderer import AstRenderer
10
10
  from soar_sdk.meta.actions import ActionMeta
11
11
  from soar_sdk.params import Params
12
+ from soar_sdk.field_utils import parse_json_schema_extra
12
13
 
13
14
 
14
15
  class ActionRenderer(AstRenderer[ActionMeta]):
@@ -201,7 +202,7 @@ class ActionRenderer(AstRenderer[ActionMeta]):
201
202
  yield ast.fix_missing_locations(node)
202
203
 
203
204
  def render_outputs_ast(
204
- self, model: Optional[type[ActionOutput]] = None
205
+ self, model: type[ActionOutput] | None = None
205
206
  ) -> Iterator[ast.ClassDef]:
206
207
  """Generates the AST for the action outputs.
207
208
 
@@ -221,16 +222,24 @@ class ActionRenderer(AstRenderer[ActionMeta]):
221
222
 
222
223
  field_defs: list[ast.stmt] = []
223
224
 
224
- for field_name_str, field in model.__fields__.items():
225
+ for field_name_str, field in model.model_fields.items():
225
226
  annotation = field.annotation
227
+ if annotation is None:
228
+ continue
229
+
226
230
  annotation_str = "{name}"
227
231
  while typing.get_origin(annotation) is list:
228
232
  annotation_str = f"list[{annotation_str}]"
229
233
  annotation = typing.get_args(annotation)[0]
234
+
235
+ # Ensure annotation is a valid type after unwrapping
236
+ if not isinstance(annotation, type):
237
+ continue
238
+
230
239
  annotation_str = annotation_str.format(name=annotation.__name__)
231
240
 
232
241
  field_name = normalize_field_name(field_name_str)
233
- if field.alias != field_name.normalized:
242
+ if field.alias is not None and field.alias != field_name.normalized:
234
243
  field_name.original = field.alias
235
244
  field_name.modified = True
236
245
 
@@ -240,9 +249,10 @@ class ActionRenderer(AstRenderer[ActionMeta]):
240
249
  simple=1,
241
250
  )
242
251
 
243
- if issubclass(annotation, ActionOutput):
252
+ if isinstance(annotation, type) and issubclass(annotation, ActionOutput):
244
253
  # If the field is a Pydantic model, recursively print its fields
245
- for model_ast in self.render_outputs_ast(field.type_):
254
+ # In Pydantic v2, use annotation directly (no field.type_)
255
+ for model_ast in self.render_outputs_ast(annotation):
246
256
  model_tree[model_ast.name] = model_ast
247
257
 
248
258
  if field_name.modified:
@@ -258,7 +268,9 @@ class ActionRenderer(AstRenderer[ActionMeta]):
258
268
  )
259
269
  else:
260
270
  keywords = []
261
- if (extras := {**field.field_info.extra}) or field_name.modified:
271
+ extras = {**parse_json_schema_extra(field.json_schema_extra)}
272
+
273
+ if extras or field_name.modified:
262
274
  extras["example_values"] = extras.pop("examples", None)
263
275
  if extras["example_values"] == [True, False]:
264
276
  extras["example_values"] = None
@@ -315,7 +327,10 @@ class ActionRenderer(AstRenderer[ActionMeta]):
315
327
  keywords=[],
316
328
  )
317
329
 
318
- for field_name, field_def in self.action_meta.parameters.__fields__.items():
330
+ for field_name, field_def in self.action_meta.parameters.model_fields.items():
331
+ if field_def.annotation is None:
332
+ continue
333
+
319
334
  field_type = ast.Name(id=field_def.annotation.__name__, ctx=ast.Load())
320
335
 
321
336
  param = ast.Call(
@@ -324,29 +339,31 @@ class ActionRenderer(AstRenderer[ActionMeta]):
324
339
  keywords=[],
325
340
  )
326
341
 
327
- if field_def.field_info.description:
342
+ json_schema_extra = parse_json_schema_extra(field_def.json_schema_extra)
343
+
344
+ if field_def.description:
328
345
  param.keywords.append(
329
346
  ast.keyword(
330
347
  arg="description",
331
- value=ast.Constant(value=field_def.field_info.description),
348
+ value=ast.Constant(value=field_def.description),
332
349
  )
333
350
  )
334
- if not field_def.field_info.extra.get("required", True):
351
+ if not json_schema_extra.get("required", True):
335
352
  param.keywords.append(
336
353
  ast.keyword(arg="required", value=ast.Constant(value=False))
337
354
  )
338
- if field_def.field_info.extra.get("primary", False):
355
+ if json_schema_extra.get("primary", False):
339
356
  param.keywords.append(
340
357
  ast.keyword(arg="primary", value=ast.Constant(value=True))
341
358
  )
342
- if (default := field_def.field_info.default) and default != Undefined:
359
+ if field_def.default not in (PydanticUndefined, None):
343
360
  param.keywords.append(
344
361
  ast.keyword(
345
362
  arg="default",
346
- value=ast.Constant(value=field_def.field_info.default),
363
+ value=ast.Constant(value=field_def.default),
347
364
  )
348
365
  )
349
- if value_list := field_def.field_info.extra.get("value_list"):
366
+ if value_list := json_schema_extra.get("value_list"):
350
367
  param.keywords.append(
351
368
  ast.keyword(
352
369
  arg="value_list",
@@ -356,7 +373,7 @@ class ActionRenderer(AstRenderer[ActionMeta]):
356
373
  ),
357
374
  )
358
375
  )
359
- if cef_types := field_def.field_info.extra.get("cef_types"):
376
+ if cef_types := json_schema_extra.get("cef_types"):
360
377
  param.keywords.append(
361
378
  ast.keyword(
362
379
  arg="cef_types",
@@ -366,7 +383,7 @@ class ActionRenderer(AstRenderer[ActionMeta]):
366
383
  ),
367
384
  )
368
385
  )
369
- if field_def.field_info.extra.get("allow_list", False):
386
+ if json_schema_extra.get("allow_list", False):
370
387
  param.keywords.append(
371
388
  ast.keyword(arg="allow_list", value=ast.Constant(value=True))
372
389
  )
@@ -1,6 +1,5 @@
1
1
  import dataclasses
2
2
  import ast
3
- from typing import Union
4
3
  from collections.abc import Iterator
5
4
 
6
5
 
@@ -50,7 +49,7 @@ class AppRenderer:
50
49
  self.context = context
51
50
 
52
51
  @staticmethod
53
- def create_default_imports() -> Iterator[Union[ast.Import, ast.ImportFrom]]:
52
+ def create_default_imports() -> Iterator[ast.Import | ast.ImportFrom]:
54
53
  """Create default imports for the App module.
55
54
 
56
55
  Returns:
@@ -1,5 +1,4 @@
1
1
  import dataclasses
2
- from typing import Optional, Union
3
2
  import ast
4
3
  from collections.abc import Iterator
5
4
 
@@ -12,12 +11,12 @@ class AssetContext:
12
11
  """Context for rendering individual configuration keys of an Asset class."""
13
12
 
14
13
  name: str
15
- description: Optional[str]
14
+ description: str | None
16
15
  required: bool
17
- default: Union[str, int, float, bool, None]
16
+ default: str | int | float | bool | None
18
17
  data_type: str
19
- value_list: Optional[list[str]]
20
- alias: Optional[str] = None
18
+ value_list: list[str] | None
19
+ alias: str | None = None
21
20
 
22
21
  @property
23
22
  def is_str(self) -> bool:
@@ -1,5 +1,5 @@
1
1
  import abc
2
- from typing import TypeVar, Generic, Optional
2
+ from typing import TypeVar, Generic
3
3
  import jinja2 as j2
4
4
  import ast
5
5
  from collections.abc import Iterator
@@ -12,7 +12,7 @@ class Renderer(Generic[ContextT], abc.ABC):
12
12
  """Abstract base class for rendering code using Jinja2 templates."""
13
13
 
14
14
  def __init__(
15
- self, context: ContextT, jinja_env: Optional[j2.Environment] = None
15
+ self, context: ContextT, jinja_env: j2.Environment | None = None
16
16
  ) -> None:
17
17
  self.context = context
18
18
  self.jinja_env = jinja_env or j2.Environment(
@@ -52,7 +52,7 @@ required-environments = [
52
52
  [tool.ruff]
53
53
  output-format = "full" # <full|concise>
54
54
  fix = true
55
- target-version = "py39"
55
+ target-version = "py313"
56
56
 
57
57
  [tool.ruff.lint]
58
58
  select = [
soar_sdk/compat.py CHANGED
@@ -2,7 +2,7 @@ from enum import Enum
2
2
  import functools
3
3
  from packaging.version import Version
4
4
 
5
- MIN_PHANTOM_VERSION = "6.4.0"
5
+ MIN_PHANTOM_VERSION = "7.0.0"
6
6
 
7
7
  UPDATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
8
8
 
@@ -27,8 +27,8 @@ def remove_when_soar_newer_than(
27
27
  class PythonVersion(str, Enum):
28
28
  """Enum to represent supported Python versions."""
29
29
 
30
- PY_3_9 = "3.9"
31
30
  PY_3_13 = "3.13"
31
+ PY_3_14 = "3.14"
32
32
 
33
33
  def __str__(self) -> str:
34
34
  """Returns the string representation of the Python version."""
@@ -41,10 +41,10 @@ class PythonVersion(str, Enum):
41
41
  Raises ValueError if the version is not supported.
42
42
  """
43
43
  # "3" is a special case for connectors that don't properly define their Python version
44
- if version_str in ("3", "3.9"):
45
- return cls.PY_3_9
46
- if version_str == "3.13":
44
+ if version_str in ("3", "3.13"):
47
45
  return cls.PY_3_13
46
+ if version_str == "3.14":
47
+ return cls.PY_3_14
48
48
 
49
49
  raise ValueError(f"Unsupported Python version: {version_str}")
50
50
 
@@ -67,7 +67,7 @@ class PythonVersion(str, Enum):
67
67
  @classmethod
68
68
  def all(cls) -> list["PythonVersion"]:
69
69
  """Returns a list of all supported Python versions."""
70
- return [cls.PY_3_9, cls.PY_3_13]
70
+ return [cls.PY_3_13, cls.PY_3_14]
71
71
 
72
72
  @classmethod
73
73
  def all_csv(cls) -> str:
@@ -1,7 +1,8 @@
1
1
  import inspect
2
2
  from functools import wraps
3
3
  from collections.abc import Iterator
4
- from typing import Callable, Optional, Any, Union, get_args, get_origin
4
+ from typing import Any, get_args, get_origin
5
+ from collections.abc import Callable
5
6
  from collections.abc import AsyncGenerator
6
7
 
7
8
  from soar_sdk.abstract import SOARClient
@@ -25,24 +26,22 @@ class ActionDecorator:
25
26
  def __init__(
26
27
  self,
27
28
  app: "App",
28
- name: Optional[str] = None,
29
- identifier: Optional[str] = None,
30
- description: Optional[str] = None,
29
+ name: str | None = None,
30
+ identifier: str | None = None,
31
+ description: str | None = None,
31
32
  verbose: str = "",
32
33
  action_type: str = "generic",
33
34
  read_only: bool = True,
34
- params_class: Optional[type[Params]] = None,
35
- output_class: Union[
36
- None,
37
- type[ActionOutput],
38
- Iterator[type[ActionOutput]],
39
- AsyncGenerator[type[ActionOutput]],
40
- list[type[ActionOutput]],
41
- ] = None,
42
- render_as: Optional[str] = None,
43
- view_handler: Optional[Callable] = None,
35
+ params_class: type[Params] | None = None,
36
+ output_class: None
37
+ | type[ActionOutput]
38
+ | Iterator[type[ActionOutput]]
39
+ | AsyncGenerator[type[ActionOutput]]
40
+ | list[type[ActionOutput]] = None,
41
+ render_as: str | None = None,
42
+ view_handler: Callable | None = None,
44
43
  versions: str = "EQ(*)",
45
- summary_type: Optional[type[ActionOutput]] = None,
44
+ summary_type: type[ActionOutput] | None = None,
46
45
  enable_concurrency_lock: bool = False,
47
46
  ) -> None:
48
47
  self.app = app
@@ -12,7 +12,8 @@ from soar_sdk.logging import getLogger
12
12
  from functools import wraps
13
13
  import traceback
14
14
 
15
- from typing import TYPE_CHECKING, Callable, Any, Optional
15
+ from typing import TYPE_CHECKING, Any
16
+ from collections.abc import Callable
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from soar_sdk.app import App
@@ -24,7 +25,7 @@ class MakeRequestDecorator:
24
25
  def __init__(
25
26
  self,
26
27
  app: "App",
27
- output_class: Optional[type[ActionOutput]] = None,
28
+ output_class: type[ActionOutput] | None = None,
28
29
  ) -> None:
29
30
  self.app = app
30
31
  self.output_class = output_class
@@ -97,7 +98,7 @@ class MakeRequestDecorator:
97
98
  **kwargs: Any, # noqa: ANN401
98
99
  ) -> bool:
99
100
  try:
100
- action_params = validated_params_class.parse_obj(params)
101
+ action_params = validated_params_class.model_validate(params)
101
102
  except Exception as e:
102
103
  logger.info(f"Parameter validation error: {e!s}")
103
104
  return self.app._adapt_action_result(
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  from functools import wraps
3
- from typing import Callable, Any
3
+ from typing import Any
4
+ from collections.abc import Callable
4
5
  from collections.abc import Iterator
5
6
 
6
7
  from soar_sdk.abstract import SOARClient
@@ -80,7 +81,7 @@ class OnPollDecorator:
80
81
  try:
81
82
  # Validate poll params
82
83
  try:
83
- action_params = validated_params_class.parse_obj(params)
84
+ action_params = validated_params_class.model_validate(params)
84
85
  except Exception as e:
85
86
  logger.info(f"Parameter validation error: {e!s}")
86
87
  return self.app._adapt_action_result(
@@ -177,8 +178,8 @@ class OnPollDecorator:
177
178
 
178
179
  # Custom ActionMeta class for on_poll (has no output)
179
180
  class OnPollActionMeta(ActionMeta):
180
- def dict(self, *args: object, **kwargs: object) -> dict[str, Any]:
181
- data = super().dict(*args, **kwargs)
181
+ def model_dump(self, *args: object, **kwargs: object) -> dict[str, Any]:
182
+ data = super().model_dump(*args, **kwargs)
182
183
  # Poll actions have no output
183
184
  data["output"] = []
184
185
  return data
@@ -1,6 +1,6 @@
1
1
  import inspect
2
2
  from functools import wraps
3
- from typing import Callable, Optional
3
+ from collections.abc import Callable
4
4
 
5
5
  from soar_sdk.abstract import SOARClient
6
6
  from soar_sdk.action_results import ActionResult
@@ -48,7 +48,7 @@ class ConnectivityTestDecorator:
48
48
  @action_protocol
49
49
  @wraps(function)
50
50
  def inner(
51
- _param: Optional[dict] = None,
51
+ _param: dict | None = None,
52
52
  soar: SOARClient = self.app.soar_client,
53
53
  ) -> bool:
54
54
  kwargs = self.app._build_magic_args(function, soar=soar)
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  from functools import wraps
3
- from typing import Callable, Optional, Any
3
+ from typing import Any
4
+ from collections.abc import Callable
4
5
 
5
6
  from soar_sdk.action_results import ActionResult
6
7
  from soar_sdk.views.component_registry import COMPONENT_REGISTRY
@@ -10,9 +11,7 @@ from soar_sdk.views.view_parser import ViewFunctionParser
10
11
  from soar_sdk.views.template_renderer import (
11
12
  get_template_renderer,
12
13
  get_templates_dir,
13
- BASE_TEMPLATE_PATH,
14
14
  )
15
- from soar_sdk.compat import remove_when_soar_newer_than
16
15
 
17
16
  from typing import TYPE_CHECKING
18
17
 
@@ -23,15 +22,15 @@ if TYPE_CHECKING:
23
22
  class ViewHandlerDecorator:
24
23
  """Class-based decorator for view handler functionality."""
25
24
 
26
- def __init__(self, app: "App", *, template: Optional[str] = None) -> None:
25
+ def __init__(self, app: "App", *, template: str | None = None) -> None:
27
26
  self.app = app
28
27
  self.template = template
29
28
 
30
29
  @staticmethod
31
30
  def _validate_view_function_signature(
32
31
  function: Callable,
33
- template: Optional[str] = None,
34
- component_type: Optional[str] = None,
32
+ template: str | None = None,
33
+ component_type: str | None = None,
35
34
  ) -> None:
36
35
  """Validate that the function signature is compatible with view handlers."""
37
36
  signature = inspect.signature(function)
@@ -92,14 +91,9 @@ class ViewHandlerDecorator:
92
91
  **kwargs: Any, # noqa: ANN401
93
92
  ) -> str:
94
93
  def handle_html_output(html: str) -> str:
95
- remove_when_soar_newer_than(
96
- "6.4.1", "SOAR now fully supports prerendering views"
97
- )
98
- if context.get("accepts_prerender"):
99
- context["prerender"] = True
100
- return html
101
- context["html_content"] = html
102
- return BASE_TEMPLATE_PATH
94
+ # SOAR 7.0+ fully supports prerendering
95
+ context["prerender"] = True
96
+ return html
103
97
 
104
98
  def render_with_error_handling(
105
99
  render_func: Callable[[], str], error_type: str, target_name: str
@@ -121,12 +115,12 @@ class ViewHandlerDecorator:
121
115
  parser: ViewFunctionParser = ViewFunctionParser(function)
122
116
 
123
117
  # Parse context to ViewContext (coming from app_interface)
124
- parsed_context = ViewContext.parse_obj(context)
118
+ parsed_context = ViewContext.model_validate(context)
125
119
 
126
120
  # Parse all_app_runs to AllAppRuns (coming from app_interface)
127
121
  parsed_all_app_runs: AllAppRuns = []
128
122
  for app_run_data, action_results in all_app_runs:
129
- result_summary = ResultSummary.parse_obj(app_run_data)
123
+ result_summary = ResultSummary.model_validate(app_run_data)
130
124
  parsed_all_app_runs.append((result_summary, action_results))
131
125
 
132
126
  result = parser.execute(
@@ -158,7 +152,7 @@ class ViewHandlerDecorator:
158
152
 
159
153
  # Reusable component
160
154
  if isinstance(result, BaseModel):
161
- result_dict = result.dict()
155
+ result_dict = result.model_dump()
162
156
  template_name = f"components/{component_type}.html"
163
157
  err_msg = "Component Rendering Failed"
164
158
  err_context = f"component '{component_type}'"
@@ -1,6 +1,5 @@
1
1
  import inspect
2
2
  from functools import wraps
3
- from typing import Optional
4
3
  from pathlib import Path
5
4
 
6
5
  from soar_sdk.cli.path_utils import relative_to_cwd
@@ -19,7 +18,7 @@ class WebhookDecorator:
19
18
  """Class-based decorator for webhook functionality."""
20
19
 
21
20
  def __init__(
22
- self, app: "App", url_pattern: str, allowed_methods: Optional[list[str]] = None
21
+ self, app: "App", url_pattern: str, allowed_methods: list[str] | None = None
23
22
  ) -> None:
24
23
  self.app = app
25
24
  self.url_pattern = url_pattern
soar_sdk/exceptions.py CHANGED
@@ -1,10 +1,7 @@
1
- from typing import Optional
2
-
3
-
4
1
  class ActionFailure(Exception):
5
2
  """Exception raised when an action fails to execute successfully."""
6
3
 
7
- def __init__(self, message: str, action_name: Optional[str] = None) -> None:
4
+ def __init__(self, message: str, action_name: str | None = None) -> None:
8
5
  self.message = message
9
6
  self.action_name = action_name
10
7
  super().__init__(self.message)
@@ -0,0 +1,8 @@
1
+ from typing import Any
2
+
3
+
4
+ def parse_json_schema_extra(json_schema_extra: Any) -> dict[str, Any]: # noqa: ANN401
5
+ """Extract json_schema_extra as a dict, handling both dict and callable forms."""
6
+ if callable(json_schema_extra):
7
+ return {}
8
+ return json_schema_extra or {}
soar_sdk/input_spec.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from uuid import uuid4
2
- from pydantic import BaseModel, Field, validator
3
- from typing import Literal, Optional, Any
2
+ from pydantic import BaseModel, Field, field_validator, ConfigDict
3
+ from typing import Literal, Any
4
4
  import random
5
5
 
6
6
 
@@ -20,20 +20,18 @@ class AppConfig(BaseModel):
20
20
 
21
21
  app_version: str
22
22
  directory: str
23
- ingest: Optional[IngestConfig] = None
23
+ ingest: IngestConfig | None = None
24
24
  main_module: str
25
25
  # TODO: The platform should deprecate this unused field
26
26
  appname: Literal["-"] = "-"
27
27
 
28
28
  # NOTE: Inputs will intermix the keys of the asset config with the keys here
29
- class Config:
30
- """Configuration for the AppConfig model."""
31
-
32
- extra = "allow"
29
+ model_config = ConfigDict(extra="allow")
33
30
 
34
31
  def get_asset_config(self) -> dict[str, Any]:
35
32
  """Get the asset configuration from the app config."""
36
- return {k: v for k, v in self.__dict__.items() if k not in self.__fields__}
33
+ # In Pydantic v2 extra fields are stored in __pydantic_extra__
34
+ return dict(self.__pydantic_extra__) if self.__pydantic_extra__ else {}
37
35
 
38
36
 
39
37
  class EnvironmentVariable(BaseModel):
@@ -65,10 +63,7 @@ class ActionParameter(BaseModel):
65
63
  # context: Optional[ParameterContext] = None # noqa: ERA001
66
64
 
67
65
  # Additional keys are action-specific and not predictable here.
68
- class Config:
69
- """Configuration for the ActionParameter model."""
70
-
71
- extra = "allow"
66
+ model_config = ConfigDict(extra="allow")
72
67
 
73
68
 
74
69
  class SoarAuth(BaseModel):
@@ -78,7 +73,8 @@ class SoarAuth(BaseModel):
78
73
  username: str
79
74
  password: str
80
75
 
81
- @validator("phantom_url")
76
+ @field_validator("phantom_url")
77
+ @classmethod
82
78
  def validate_phantom_url(cls, value: str) -> str:
83
79
  """Ensure the URL starts with http:// or https://."""
84
80
  return (
@@ -137,9 +133,9 @@ class InputSpecification(BaseModel):
137
133
  }
138
134
  """
139
135
 
140
- action: Optional[str] = None
136
+ action: str | None = None
141
137
  action_run_id: int = Field(default_factory=id_factory)
142
- app_config: Optional[Any] = None
138
+ app_config: Any | None = None
143
139
  asset_id: str = Field(default_factory=lambda: str(id_factory()))
144
140
  config: AppConfig
145
141
  connector_run_id: int = Field(default_factory=id_factory)
@@ -148,6 +144,6 @@ class InputSpecification(BaseModel):
148
144
  dec_key: str = Field(default_factory=lambda: str(id_factory()))
149
145
  environment_variables: dict[str, EnvironmentVariable] = Field(default_factory=dict)
150
146
  identifier: str
151
- parameters: list[ActionParameter] = Field(default_factory=lambda: [{}])
147
+ parameters: list[ActionParameter] = Field(default_factory=list)
152
148
  user_session_token: str = ""
153
- soar_auth: Optional[SoarAuth] = None
149
+ soar_auth: SoarAuth | None = None
soar_sdk/logging.py CHANGED
@@ -4,7 +4,7 @@ from soar_sdk.colors import ANSIColor
4
4
  from soar_sdk.shims.phantom.install_info import is_soar_available, get_product_version
5
5
  from soar_sdk.shims.phantom.ph_ipc import ph_ipc
6
6
  from packaging.version import Version
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
  from soar_sdk.compat import remove_when_soar_newer_than
9
9
 
10
10
  PROGRESS_LEVEL = 25
@@ -38,7 +38,7 @@ class SOARHandler(logging.Handler):
38
38
  self,
39
39
  ) -> None:
40
40
  super().__init__()
41
- self.__handle: Optional[int] = None
41
+ self.__handle: int | None = None
42
42
 
43
43
  def emit(self, record: logging.LogRecord) -> None:
44
44
  is_new_soar = Version(get_product_version()) >= Version("7.0.0")
@@ -79,7 +79,7 @@ class SOARHandler(logging.Handler):
79
79
  except Exception:
80
80
  self.handleError(record)
81
81
 
82
- def set_handle(self, handle: Optional[int]) -> None:
82
+ def set_handle(self, handle: int | None) -> None:
83
83
  """Set the action handle for the SOAR client."""
84
84
  self.__handle = handle
85
85
 
soar_sdk/meta/actions.py CHANGED
@@ -1,9 +1,8 @@
1
- from typing import Any, Type, Optional, Callable # noqa: UP035
1
+ from typing import Any, Type, Callable # noqa: UP035
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
5
  from soar_sdk.cli.manifests.serializers import ParamsSerializer, OutputsSerializer
6
- from soar_sdk.compat import remove_when_soar_newer_than
7
6
  from soar_sdk.params import Params
8
7
  from soar_sdk.action_results import ActionOutput
9
8
 
@@ -20,14 +19,14 @@ class ActionMeta(BaseModel):
20
19
  verbose: str = ""
21
20
  parameters: Type[Params] = Field(default=Params) # noqa: UP006
22
21
  output: Type[ActionOutput] = Field(default=ActionOutput) # noqa: UP006
23
- render_as: Optional[str] = None
24
- view_handler: Optional[Callable] = None
25
- summary_type: Optional[Type[ActionOutput]] = Field(default=None, exclude=True) # noqa: UP006
22
+ render_as: str | None = None
23
+ view_handler: Callable | None = None
24
+ summary_type: Type[ActionOutput] | None = Field(default=None, exclude=True) # noqa: UP006
26
25
  enable_concurrency_lock: bool = False
27
26
 
28
- def dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: # noqa: ANN401
27
+ def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]: # noqa: ANN401
29
28
  """Serializes the action metadata to a dictionary."""
30
- data = super().dict(*args, **kwargs)
29
+ data = super().model_dump(*args, **kwargs)
31
30
  data["parameters"] = ParamsSerializer.serialize_fields_info(self.parameters)
32
31
  data["output"] = OutputsSerializer.serialize_datapaths(
33
32
  self.parameters, self.output, summary_class=self.summary_type
@@ -40,21 +39,6 @@ class ActionMeta(BaseModel):
40
39
  "type": self.render_as,
41
40
  }
42
41
 
43
- if self.view_handler:
44
- remove_when_soar_newer_than("6.4.1")
45
- # Get the module path and function name for the view
46
- module = self.view_handler.__module__
47
- # Convert module path from dot notation to the expected format
48
- # e.g., "example_app.src.app" -> "src.app"
49
- module_parts = module.split(".")
50
- if len(module_parts) > 1:
51
- # Remove the package name (first part) to get relative module path
52
- relative_module = ".".join(module_parts[1:])
53
- else:
54
- relative_module = module
55
-
56
- data["render"]["view"] = f"{relative_module}.{self.view_handler.__name__}"
57
-
58
42
  # Remove view_handler from the output since in render
59
43
  data.pop("view_handler", None)
60
44
  data.pop("render_as", None)