splunk-soar-sdk 2.3.6__py3-none-any.whl → 3.0.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 +10 -13
  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/vault_attachment.py +6 -6
  40. soar_sdk/models/view.py +10 -13
  41. soar_sdk/params.py +57 -39
  42. soar_sdk/shims/phantom/action_result.py +4 -4
  43. soar_sdk/shims/phantom/base_connector.py +13 -5
  44. soar_sdk/shims/phantom/install_info.py +15 -2
  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.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/METADATA +5 -6
  54. splunk_soar_sdk-3.0.0.dist-info/RECORD +104 -0
  55. splunk_soar_sdk-2.3.6.dist-info/RECORD +0 -103
  56. {splunk_soar_sdk-2.3.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/WHEEL +0 -0
  57. {splunk_soar_sdk-2.3.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/entry_points.txt +0 -0
  58. {splunk_soar_sdk-2.3.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel
2
- from typing import Optional, Any, Union
1
+ from pydantic import BaseModel, ConfigDict
2
+ from typing import Any
3
3
 
4
4
 
5
5
  class Container(BaseModel):
@@ -8,40 +8,37 @@ class Container(BaseModel):
8
8
  This class allows users to specify container properties when yielding from an on_poll function.
9
9
  """
10
10
 
11
- class Config:
12
- """Pydantic config."""
13
-
14
- extra = "forbid"
11
+ model_config = ConfigDict(extra="forbid")
15
12
 
16
13
  name: str
17
- label: Optional[str] = None
18
- description: Optional[str] = None
19
- source_data_identifier: Optional[str] = None
20
- severity: Optional[str] = None
21
- status: Optional[str] = None
22
- tags: Optional[Union[list[str], str]] = None
23
- owner_id: Optional[Union[int, str]] = None
24
- sensitivity: Optional[str] = None
25
- artifacts: Optional[list[dict[str, Any]]] = None
26
- asset_id: Optional[int] = None
27
- close_time: Optional[str] = None
28
- custom_fields: Optional[dict[str, Any]] = None
29
- data: Optional[dict[str, Any]] = None
30
- due_time: Optional[str] = None
31
- end_time: Optional[str] = None
32
- ingest_app_id: Optional[int] = None
33
- kill_chain: Optional[str] = None
34
- role_id: Optional[Union[int, str]] = None
14
+ label: str | None = None
15
+ description: str | None = None
16
+ source_data_identifier: str | None = None
17
+ severity: str | None = None
18
+ status: str | None = None
19
+ tags: list[str] | str | None = None
20
+ owner_id: int | str | None = None
21
+ sensitivity: str | None = None
22
+ artifacts: list[dict[str, Any]] | None = None
23
+ asset_id: int | None = None
24
+ close_time: str | None = None
25
+ custom_fields: dict[str, Any] | None = None
26
+ data: dict[str, Any] | None = None
27
+ due_time: str | None = None
28
+ end_time: str | None = None
29
+ ingest_app_id: int | None = None
30
+ kill_chain: str | None = None
31
+ role_id: int | str | None = None
35
32
  run_automation: bool = False
36
- start_time: Optional[str] = None
37
- open_time: Optional[str] = None
38
- tenant_id: Optional[Union[int, str]] = None
39
- container_type: Optional[str] = None
40
- template_id: Optional[int] = None
41
- authorized_users: Optional[list[int]] = None
42
- artifact_count: Optional[int] = None
43
- container_id: Optional[str] = None
33
+ start_time: str | None = None
34
+ open_time: str | None = None
35
+ tenant_id: int | str | None = None
36
+ container_type: str | None = None
37
+ template_id: int | None = None
38
+ authorized_users: list[int] | None = None
39
+ artifact_count: int | None = None
40
+ container_id: str | None = None
44
41
 
45
42
  def to_dict(self) -> dict[str, Any]:
46
43
  """Convert the container to a dictionary (needed for save_container)."""
47
- return self.dict(exclude_none=True)
44
+ return self.model_dump(exclude_none=True)
@@ -1,4 +1,4 @@
1
- from typing import IO, Optional, Union
1
+ from typing import IO
2
2
  from pydantic import BaseModel
3
3
 
4
4
 
@@ -11,15 +11,15 @@ class VaultAttachment(BaseModel):
11
11
  """
12
12
 
13
13
  id: int
14
- created_via: Optional[str] = None
14
+ created_via: str | None = None
15
15
  container: str
16
- task: Optional[str] = None
16
+ task: str | None = None
17
17
  create_time: str
18
18
  name: str
19
19
  user: str
20
20
  vault_document: int
21
- mime_type: Optional[str] = None
22
- es_attachment_id: Optional[str] = None
21
+ mime_type: str | None = None
22
+ es_attachment_id: str | None = None
23
23
  hash: str
24
24
  vault_id: str
25
25
  size: int
@@ -29,7 +29,7 @@ class VaultAttachment(BaseModel):
29
29
  container_id: int
30
30
  contains: list[str] = []
31
31
 
32
- def open(self, mode: str = "r") -> Union[IO[str], IO[bytes]]:
32
+ def open(self, mode: str = "r") -> IO[str] | IO[bytes]:
33
33
  """Open the vault attachment file.
34
34
 
35
35
  Args:
soar_sdk/models/view.py CHANGED
@@ -1,5 +1,5 @@
1
- from typing import Any, Optional, Union
2
- from pydantic import BaseModel
1
+ from typing import Any
2
+ from pydantic import BaseModel, ConfigDict
3
3
  from soar_sdk.action_results import ActionResult
4
4
 
5
5
 
@@ -10,17 +10,14 @@ class ViewContext(BaseModel):
10
10
  container: int
11
11
  app: int
12
12
  no_connection: bool
13
- google_maps_key: Union[bool, str]
14
- dark_title_logo: Optional[str] = None
15
- title_logo: Optional[str] = None
16
- app_name: Optional[str] = None
17
- results: Optional[list[dict[str, Any]]] = None
18
- html_content: Optional[str] = None
19
-
20
- class Config:
21
- """Pydantic config."""
22
-
23
- extra = "allow"
13
+ google_maps_key: bool | str
14
+ dark_title_logo: str | None = None
15
+ title_logo: str | None = None
16
+ app_name: str | None = None
17
+ results: list[dict[str, Any]] | None = None
18
+ html_content: str | None = None
19
+
20
+ model_config = ConfigDict(extra="allow")
24
21
 
25
22
 
26
23
  class ResultSummary(BaseModel):
soar_sdk/params.py CHANGED
@@ -1,11 +1,14 @@
1
- from typing import Optional, Union, Any, ClassVar
2
- from typing_extensions import NotRequired, TypedDict
1
+ from typing import Any, ClassVar
2
+ from typing_extensions import TypedDict
3
+ from typing import NotRequired
3
4
 
4
- from pydantic.fields import Field, Undefined
5
+ from pydantic import Field
6
+ from pydantic_core import PydanticUndefined
5
7
  from pydantic.main import BaseModel
6
8
 
7
9
  from soar_sdk.compat import remove_when_soar_newer_than
8
10
  from soar_sdk.meta.datatypes import as_datatype
11
+ from soar_sdk.field_utils import parse_json_schema_extra
9
12
 
10
13
  remove_when_soar_newer_than(
11
14
  "7.0.0", "NotRequired from typing_extensions is in typing in Python 3.11+"
@@ -13,16 +16,16 @@ remove_when_soar_newer_than(
13
16
 
14
17
 
15
18
  def Param(
16
- description: Optional[str] = None,
19
+ description: str | None = None,
17
20
  required: bool = True,
18
21
  primary: bool = False,
19
- default: Optional[Any] = None, # noqa: ANN401
20
- value_list: Optional[list] = None,
21
- cef_types: Optional[list] = None,
22
+ default: Any | None = None, # noqa: ANN401
23
+ value_list: list | None = None,
24
+ cef_types: list | None = None,
22
25
  allow_list: bool = False,
23
26
  sensitive: bool = False,
24
- alias: Optional[str] = None,
25
- column_name: Optional[str] = None,
27
+ alias: str | None = None,
28
+ column_name: str | None = None,
26
29
  ) -> Any: # noqa: ANN401
27
30
  """Representation of a single complex action parameter.
28
31
 
@@ -53,20 +56,30 @@ def Param(
53
56
  :param column_name: Optional name for the parameter when displayed in an output table.
54
57
  :return: returns the FieldInfo object as pydantic.Field
55
58
  """
56
- if value_list is None:
57
- value_list = []
59
+ json_schema_extra: dict[str, Any] = {}
60
+ if required is not None:
61
+ json_schema_extra["required"] = required
62
+ if primary is not None:
63
+ json_schema_extra["primary"] = primary
64
+ if value_list:
65
+ json_schema_extra["value_list"] = value_list
66
+ if cef_types is not None:
67
+ json_schema_extra["cef_types"] = cef_types
68
+ if allow_list is not None:
69
+ json_schema_extra["allow_list"] = allow_list
70
+ if sensitive is not None:
71
+ json_schema_extra["sensitive"] = sensitive
72
+ if column_name is not None:
73
+ json_schema_extra["column_name"] = column_name
74
+
75
+ # Use ... for required fields
76
+ field_default: Any = ... if default is None and required else default
58
77
 
59
78
  return Field(
60
- default=default,
79
+ default=field_default,
61
80
  description=description,
62
- required=required,
63
- primary=primary,
64
- value_list=value_list,
65
- cef_types=cef_types,
66
- allow_list=allow_list,
67
- sensitive=sensitive,
68
81
  alias=alias,
69
- column_name=column_name,
82
+ json_schema_extra=json_schema_extra if json_schema_extra else None,
70
83
  )
71
84
 
72
85
 
@@ -82,7 +95,7 @@ class InputFieldSpecification(TypedDict):
82
95
  primary: bool
83
96
  value_list: NotRequired[list[str]]
84
97
  allow_list: bool
85
- default: NotRequired[Union[str, int, float, bool]]
98
+ default: NotRequired[str | int | float | bool]
86
99
  column_name: NotRequired[str]
87
100
  column_order: NotRequired[int]
88
101
 
@@ -103,9 +116,12 @@ class Params(BaseModel):
103
116
  def _to_json_schema(cls) -> dict[str, InputFieldSpecification]:
104
117
  params: dict[str, InputFieldSpecification] = {}
105
118
 
106
- for field_order, (field_name, field) in enumerate(cls.__fields__.items()):
119
+ for field_order, (field_name, field) in enumerate(cls.model_fields.items()):
107
120
  field_type = field.annotation
108
121
 
122
+ if field_type is None:
123
+ raise TypeError(f"Parameter {field_name} has no type annotation")
124
+
109
125
  try:
110
126
  type_name = as_datatype(field_type)
111
127
  except TypeError as e:
@@ -113,14 +129,16 @@ class Params(BaseModel):
113
129
  f"Failed to serialize action parameter {field_name}: {e}"
114
130
  ) from None
115
131
 
116
- if field.field_info.extra.get("sensitive", False):
132
+ json_schema_extra = parse_json_schema_extra(field.json_schema_extra)
133
+
134
+ if json_schema_extra.get("sensitive", False):
117
135
  if field_type is not str:
118
136
  raise TypeError(
119
137
  f"Sensitive parameter {field_name} must be type str, not {field_type.__name__}"
120
138
  )
121
139
  type_name = "password"
122
140
 
123
- if not (description := field.field_info.description):
141
+ if not (description := field.description):
124
142
  description = cls._default_field_description(field_name)
125
143
 
126
144
  params_field = InputFieldSpecification(
@@ -128,19 +146,19 @@ class Params(BaseModel):
128
146
  name=field_name,
129
147
  description=description,
130
148
  data_type=type_name,
131
- required=field.field_info.extra.get("required", True),
132
- primary=field.field_info.extra.get("primary", False),
133
- allow_list=field.field_info.extra.get("allow_list", False),
149
+ required=bool(json_schema_extra.get("required", True)),
150
+ primary=bool(json_schema_extra.get("primary", False)),
151
+ allow_list=bool(json_schema_extra.get("allow_list", False)),
134
152
  )
135
153
 
136
- if cef_types := field.field_info.extra.get("cef_types"):
154
+ if cef_types := json_schema_extra.get("cef_types"):
137
155
  params_field["contains"] = cef_types
138
- if (default := field.field_info.default) and default != Undefined:
156
+ if (default := field.default) not in (PydanticUndefined, None):
139
157
  params_field["default"] = default
140
- if value_list := field.field_info.extra.get("value_list"):
158
+ if value_list := json_schema_extra.get("value_list"):
141
159
  params_field["value_list"] = value_list
142
160
 
143
- params[field.alias] = params_field
161
+ params[field.alias or field_name] = params_field
144
162
 
145
163
  return params
146
164
 
@@ -189,21 +207,21 @@ class MakeRequestParams(Params):
189
207
  "verify_ssl",
190
208
  }
191
209
 
192
- def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None:
210
+ def __init_subclass__(cls, **kwargs: Any) -> None: # noqa: ANN401
193
211
  """Validate that subclasses only define allowed fields."""
194
212
  super().__init_subclass__(**kwargs)
195
- cls._validate_make_request_fields()
196
213
 
197
- @classmethod
198
- def _validate_make_request_fields(cls) -> None:
199
- """Ensure subclasses only define allowed MakeRequest fields."""
214
+ def model_post_init(self, __context: Any) -> None: # noqa: ANN401
215
+ """Ensure model fields are validated after instance is created."""
216
+ super().model_post_init(__context)
200
217
  # Check if any fields are not in the allowed set
201
- invalid_fields = set(cls.__fields__.keys()) - cls._ALLOWED_FIELDS
202
-
218
+ invalid_fields = (
219
+ set(self.__class__.model_fields.keys()) - self.__class__._ALLOWED_FIELDS
220
+ )
203
221
  if invalid_fields:
204
222
  raise TypeError(
205
- f"MakeRequestParams subclass '{cls.__name__}' can only define these fields: "
206
- f"{sorted(cls._ALLOWED_FIELDS)}. Invalid fields: {sorted(invalid_fields)}"
223
+ f"MakeRequestParams subclass '{self.__class__.__name__}' can only define these fields: "
224
+ f"{sorted(self.__class__._ALLOWED_FIELDS)}. Invalid fields: {sorted(invalid_fields)}"
207
225
  )
208
226
 
209
227
  http_method: str = Param(
@@ -5,12 +5,12 @@ try:
5
5
  except ImportError:
6
6
  _soar_is_available = False
7
7
 
8
- from typing import Any, Optional, Union, TYPE_CHECKING
8
+ from typing import Any, TYPE_CHECKING
9
9
 
10
10
  if TYPE_CHECKING or not _soar_is_available:
11
11
 
12
12
  class ActionResult: # type: ignore[no-redef]
13
- def __init__(self, param: Optional[dict] = None) -> None:
13
+ def __init__(self, param: dict | None = None) -> None:
14
14
  self.status = False
15
15
  self.message = ""
16
16
  self.summary: dict[str, Any] = {}
@@ -24,9 +24,9 @@ if TYPE_CHECKING or not _soar_is_available:
24
24
 
25
25
  def set_status(
26
26
  self,
27
- status_code: Union[bool, int],
27
+ status_code: bool | int,
28
28
  _status_message: str = "",
29
- _exception: Optional[Exception] = None,
29
+ _exception: Exception | None = None,
30
30
  ) -> bool:
31
31
  self.status = bool(status_code)
32
32
  self.message = _status_message
@@ -5,8 +5,11 @@ try:
5
5
  except ImportError:
6
6
  _soar_is_available = False
7
7
 
8
+ from pathlib import Path
8
9
  from typing import TYPE_CHECKING
9
10
 
11
+ from soar_sdk.compat import remove_when_soar_newer_than
12
+
10
13
  if TYPE_CHECKING or not _soar_is_available:
11
14
  import json
12
15
  import abc
@@ -16,7 +19,7 @@ if TYPE_CHECKING or not _soar_is_available:
16
19
  from soar_sdk.shims.phantom.action_result import ActionResult
17
20
  from soar_sdk.shims.phantom.connector_result import ConnectorResult
18
21
 
19
- from typing import Union, Any, Optional
22
+ from typing import Any
20
23
  from contextlib import suppress
21
24
 
22
25
  class BaseConnector: # type: ignore[no-redef]
@@ -68,14 +71,14 @@ if TYPE_CHECKING or not _soar_is_available:
68
71
  def error_print(
69
72
  self,
70
73
  _tag: str,
71
- _dump_object: Union[str, list, dict, ActionResult, Exception] = "",
74
+ _dump_object: str | list | dict | ActionResult | Exception = "",
72
75
  ) -> None:
73
76
  print(_tag, _dump_object)
74
77
 
75
78
  def debug_print(
76
79
  self,
77
80
  _tag: str,
78
- _dump_object: Union[str, list, dict, ActionResult, Exception] = "",
81
+ _dump_object: str | list | dict | ActionResult | Exception = "",
79
82
  ) -> None:
80
83
  print(_tag, _dump_object)
81
84
 
@@ -106,12 +109,12 @@ if TYPE_CHECKING or not _soar_is_available:
106
109
 
107
110
  def save_container(
108
111
  self, container: dict, fail_on_duplicate: bool = False
109
- ) -> tuple[bool, str, Optional[int]]:
112
+ ) -> tuple[bool, str, int | None]:
110
113
  return True, "Container saved successfully", 1
111
114
 
112
115
  def save_artifacts(
113
116
  self, artifacts: list[dict]
114
- ) -> tuple[bool, str, Union[Optional[int], list[int]]]:
117
+ ) -> tuple[bool, str, int | None | list[int]]:
115
118
  return True, "Artifacts saved successfully", [1]
116
119
 
117
120
  def get_config(self) -> dict:
@@ -132,5 +135,10 @@ if TYPE_CHECKING or not _soar_is_available:
132
135
  def initialize(self) -> bool:
133
136
  return True
134
137
 
138
+ def get_app_dir(self) -> str:
139
+ # Remove when 7.1.0 is the min supported broker version
140
+ remove_when_soar_newer_than("7.1.1")
141
+ return Path.cwd().as_posix()
142
+
135
143
 
136
144
  __all__ = ["BaseConnector"]
@@ -1,5 +1,9 @@
1
1
  try:
2
- from phantom_common.install_info import get_verify_ssl_setting, get_product_version
2
+ from phantom_common.install_info import (
3
+ get_verify_ssl_setting,
4
+ get_product_version,
5
+ is_onprem_broker_install,
6
+ )
3
7
 
4
8
  _soar_is_available = True
5
9
  except ImportError:
@@ -17,6 +21,10 @@ if TYPE_CHECKING or not _soar_is_available:
17
21
  """Mock function to simulate the behavior of get_product_version."""
18
22
  return "6.4.1"
19
23
 
24
+ def is_onprem_broker_install() -> bool:
25
+ """Mock function to simulate the behavior of is_onprem_broker_install."""
26
+ return False
27
+
20
28
 
21
29
  def is_soar_available() -> bool:
22
30
  """
@@ -25,4 +33,9 @@ def is_soar_available() -> bool:
25
33
  return _soar_is_available
26
34
 
27
35
 
28
- __all__ = ["get_product_version", "get_verify_ssl_setting", "is_soar_available"]
36
+ __all__ = [
37
+ "get_product_version",
38
+ "get_verify_ssl_setting",
39
+ "is_onprem_broker_install",
40
+ "is_soar_available",
41
+ ]
@@ -5,7 +5,7 @@ try:
5
5
  except ImportError:
6
6
  _soar_is_available = False
7
7
 
8
- from typing import TYPE_CHECKING, Optional
8
+ from typing import TYPE_CHECKING
9
9
 
10
10
  if TYPE_CHECKING or not _soar_is_available:
11
11
  from soar_sdk.shims.phantom.install_info import get_product_version
@@ -34,12 +34,12 @@ if TYPE_CHECKING or not _soar_is_available:
34
34
 
35
35
  @staticmethod
36
36
  def sendstatus(
37
- handle: Optional[int], status: int, message: str, flag: bool
37
+ handle: int | None, status: int, message: str, flag: bool
38
38
  ) -> None:
39
39
  print(message)
40
40
 
41
41
  @staticmethod
42
- def debugprint(handle: Optional[int], message: str, level: int) -> None:
42
+ def debugprint(handle: int | None, message: str, level: int) -> None:
43
43
  print(message)
44
44
 
45
45
  @staticmethod
@@ -1,9 +1,10 @@
1
- from typing import TYPE_CHECKING, Any, Optional, Union
1
+ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  if TYPE_CHECKING:
4
4
  from soar_sdk.abstract import SOARClient
5
5
  from abc import abstractmethod
6
6
  from soar_sdk.exceptions import SoarAPIError
7
+ from datetime import UTC
7
8
 
8
9
 
9
10
  class VaultBase:
@@ -19,9 +20,9 @@ class VaultBase:
19
20
  def create_attachment(
20
21
  self,
21
22
  container_id: int,
22
- file_content: Union[str, bytes],
23
+ file_content: str | bytes,
23
24
  file_name: str,
24
- metadata: Optional[dict[str, str]] = None,
25
+ metadata: dict[str, str] | None = None,
25
26
  ) -> str:
26
27
  """Creates a vault attachment from file content. This differs from add_attachment because it doesn't require the file to exist locally."""
27
28
  pass
@@ -32,7 +33,7 @@ class VaultBase:
32
33
  container_id: int,
33
34
  file_location: str,
34
35
  file_name: str,
35
- metadata: Optional[dict[str, str]] = None,
36
+ metadata: dict[str, str] | None = None,
36
37
  ) -> str:
37
38
  """Add an attachment to vault. This requires the file to exist locally."""
38
39
  pass
@@ -40,9 +41,9 @@ class VaultBase:
40
41
  @abstractmethod
41
42
  def get_attachment(
42
43
  self,
43
- vault_id: Optional[str] = None,
44
- file_name: Optional[str] = None,
45
- container_id: Optional[int] = None,
44
+ vault_id: str | None = None,
45
+ file_name: str | None = None,
46
+ container_id: int | None = None,
46
47
  download_file: bool = True,
47
48
  ) -> list[dict[str, Any]]:
48
49
  """Returns vault attachments based on the provided query parameters."""
@@ -51,9 +52,9 @@ class VaultBase:
51
52
  @abstractmethod
52
53
  def delete_attachment(
53
54
  self,
54
- vault_id: Optional[str] = None,
55
- file_name: Optional[str] = None,
56
- container_id: Optional[int] = None,
55
+ vault_id: str | None = None,
56
+ file_name: str | None = None,
57
+ container_id: int | None = None,
57
58
  remove_all: bool = False,
58
59
  ) -> list[str]:
59
60
  """Deletes vault attachments based on the provided query parameters."""
@@ -79,9 +80,9 @@ if _soar_is_available:
79
80
  def create_attachment(
80
81
  self,
81
82
  container_id: int,
82
- file_content: Union[str, bytes],
83
+ file_content: str | bytes,
83
84
  file_name: str,
84
- metadata: Optional[dict[str, str]] = None,
85
+ metadata: dict[str, str] | None = None,
85
86
  ) -> str:
86
87
  resp_json = Vault.create_attachment(
87
88
  file_content, container_id, file_name, metadata
@@ -96,7 +97,7 @@ if _soar_is_available:
96
97
  container_id: int,
97
98
  file_location: str,
98
99
  file_name: str,
99
- metadata: Optional[dict[str, str]] = None,
100
+ metadata: dict[str, str] | None = None,
100
101
  ) -> str:
101
102
  resp_json = vault_add(container_id, file_location, file_name, metadata)
102
103
  if not resp_json.get("succeeded"):
@@ -106,9 +107,9 @@ if _soar_is_available:
106
107
 
107
108
  def get_attachment(
108
109
  self,
109
- vault_id: Optional[str] = None,
110
- file_name: Optional[str] = None,
111
- container_id: Optional[int] = None,
110
+ vault_id: str | None = None,
111
+ file_name: str | None = None,
112
+ container_id: int | None = None,
112
113
  download_file: bool = True,
113
114
  ) -> list[dict[str, Any]]:
114
115
  success, _, attachment = vault_info(
@@ -120,9 +121,9 @@ if _soar_is_available:
120
121
 
121
122
  def delete_attachment(
122
123
  self,
123
- vault_id: Optional[str] = None,
124
- file_name: Optional[str] = None,
125
- container_id: Optional[int] = None,
124
+ vault_id: str | None = None,
125
+ file_name: str | None = None,
126
+ container_id: int | None = None,
126
127
  remove_all: bool = False,
127
128
  ) -> list[str]:
128
129
  success, message, deleted_file_names = vault_delete(
@@ -143,7 +144,7 @@ else:
143
144
  from pathlib import Path
144
145
  import secrets
145
146
  import random
146
- from datetime import datetime, timezone
147
+ from datetime import datetime
147
148
  import hashlib
148
149
  from soar_sdk.logging import getLogger
149
150
  from soar_sdk.models.vault_attachment import VaultAttachment
@@ -162,9 +163,9 @@ else:
162
163
  def create_attachment(
163
164
  self,
164
165
  container_id: int,
165
- file_content: Union[str, bytes],
166
+ file_content: str | bytes,
166
167
  file_name: str,
167
- metadata: Optional[dict[str, str]] = None,
168
+ metadata: dict[str, str] | None = None,
168
169
  ) -> str:
169
170
  if is_client_authenticated(self.soar_client.client):
170
171
  data = {
@@ -203,7 +204,7 @@ else:
203
204
  container_id=container_id,
204
205
  container="test_container",
205
206
  created_via="upload",
206
- create_time=datetime.now(timezone.utc).isoformat(),
207
+ create_time=datetime.now(UTC).isoformat(),
207
208
  user="Phantom User",
208
209
  vault_document=doc_id,
209
210
  vault_id=vault_id,
@@ -224,7 +225,7 @@ else:
224
225
  container_id: int,
225
226
  file_location: str,
226
227
  file_name: str,
227
- metadata: Optional[dict[str, str]] = None,
228
+ metadata: dict[str, str] | None = None,
228
229
  ) -> str:
229
230
  metadata = metadata or {}
230
231
 
@@ -265,7 +266,7 @@ else:
265
266
  container_id=container_id,
266
267
  container="test_container",
267
268
  created_via="upload",
268
- create_time=datetime.now(timezone.utc).isoformat(),
269
+ create_time=datetime.now(UTC).isoformat(),
269
270
  user="Phantom User",
270
271
  vault_document=doc_id,
271
272
  vault_id=vault_id,
@@ -283,9 +284,9 @@ else:
283
284
 
284
285
  def get_attachment(
285
286
  self,
286
- vault_id: Optional[str] = None,
287
- file_name: Optional[str] = None,
288
- container_id: Optional[int] = None,
287
+ vault_id: str | None = None,
288
+ file_name: str | None = None,
289
+ container_id: int | None = None,
289
290
  download_file: bool = True,
290
291
  ) -> list[dict[str, Any]]:
291
292
  if not any([vault_id, file_name, container_id]):
@@ -295,7 +296,7 @@ else:
295
296
 
296
297
  results = []
297
298
  if is_client_authenticated(self.soar_client.client):
298
- query_params: dict[str, Union[str, int]] = {"pretty": ""}
299
+ query_params: dict[str, str | int] = {"pretty": ""}
299
300
  if vault_id:
300
301
  query_params["_filter_vault_document__hash"] = (
301
302
  f'"{vault_id.lower()}"'
@@ -319,22 +320,22 @@ else:
319
320
  if vault_id:
320
321
  res = self.__storage.get(vault_id)
321
322
  if res:
322
- results.append(res.dict())
323
+ results.append(res.model_dump())
323
324
 
324
325
  if any((container_id, file_name)):
325
326
  for _, res in self.__storage.items():
326
327
  if (
327
328
  file_name and file_name in res.file_path
328
329
  ) or container_id == res.container_id:
329
- results.append(res.dict())
330
+ results.append(res.model_dump())
330
331
 
331
332
  return results
332
333
 
333
334
  def delete_attachment(
334
335
  self,
335
- vault_id: Optional[str] = None,
336
- file_name: Optional[str] = None,
337
- container_id: Optional[int] = None,
336
+ vault_id: str | None = None,
337
+ file_name: str | None = None,
338
+ container_id: int | None = None,
338
339
  remove_all: bool = False,
339
340
  ) -> list[str]:
340
341
  vault_enteries = self.get_attachment(vault_id, file_name, container_id)