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,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)
soar_sdk/meta/app.py CHANGED
@@ -1,5 +1,4 @@
1
- from pydantic import BaseModel, Field, validator
2
- from typing import Optional, Union
1
+ from pydantic import BaseModel, Field, field_validator
3
2
 
4
3
  from soar_sdk.asset import AssetFieldSpecification
5
4
  from soar_sdk.compat import PythonVersion
@@ -42,13 +41,14 @@ class AppMeta(BaseModel):
42
41
  configuration: dict[str, AssetFieldSpecification] = Field(default_factory=dict)
43
42
  actions: list[ActionMeta] = Field(default_factory=list)
44
43
 
45
- pip39_dependencies: DependencyList = Field(default_factory=DependencyList)
46
44
  pip313_dependencies: DependencyList = Field(default_factory=DependencyList)
45
+ pip314_dependencies: DependencyList = Field(default_factory=DependencyList)
47
46
 
48
- webhook: Optional[WebhookMeta]
47
+ webhook: WebhookMeta | None = None
49
48
 
50
- @validator("python_version", pre=True)
51
- def convert_python_version_to_csv(cls, v: Union[list, str]) -> str:
49
+ @field_validator("python_version", mode="before")
50
+ @classmethod
51
+ def convert_python_version_to_csv(cls, v: list | str) -> str:
52
52
  """Converts python_version to a comma-separated string if it's a list and validates versions."""
53
53
  if isinstance(v, list):
54
54
  # Validate each version in the list and convert to CSV
@@ -64,4 +64,7 @@ class AppMeta(BaseModel):
64
64
 
65
65
  def to_json_manifest(self) -> dict:
66
66
  """Converts the AppMeta instance to a JSON-compatible dictionary."""
67
- return self.dict(exclude_none=True)
67
+ data = self.model_dump(exclude_none=True)
68
+ # In Pydantic v2 nested model_dump() overrides aren't automatically called
69
+ data["actions"] = [action.model_dump() for action in self.actions]
70
+ return data
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import io
2
3
  import os
3
4
  from pathlib import Path
@@ -6,7 +7,7 @@ import tarfile
6
7
  from tempfile import TemporaryDirectory
7
8
  import build
8
9
 
9
- from typing import Optional
10
+ from typing import ClassVar
10
11
  from collections.abc import Mapping, Sequence, AsyncGenerator
11
12
  from pydantic import BaseModel, Field
12
13
 
@@ -61,22 +62,23 @@ DEPENDENCIES_TO_BUILD = {
61
62
  class UvWheel(BaseModel):
62
63
  """Represents a Python wheel file with metadata and methods to fetch and validate it."""
63
64
 
64
- url: str
65
+ url: str | None = None
66
+ filename: str | None = None
65
67
  hash: str
66
- size: Optional[int] = None
68
+ size: int | None = None
67
69
 
68
70
  # The wheel file name is specified by PEP427. It's either a 5- or 6-tuple:
69
71
  # {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
70
72
  # We can parse this to determine which configurations it supports.
71
- @property
73
+ @functools.cached_property
72
74
  def basename(self) -> str:
73
75
  """The base name of the wheel file."""
74
- remove_when_soar_newer_than(
75
- "6.4.0",
76
- "We should be able to adopt pydantic 2 now, and turn this into a cached property.",
77
- )
78
- filename = self.url.split("/")[-1]
79
- return filename.removesuffix(".whl")
76
+ if self.filename:
77
+ return self.filename.removesuffix(".whl")
78
+ if self.url:
79
+ filename = self.url.split("/")[-1]
80
+ return filename.removesuffix(".whl")
81
+ raise ValueError("UvWheel must have either url or filename")
80
82
 
81
83
  @property
82
84
  def distribution(self) -> str:
@@ -89,7 +91,7 @@ class UvWheel(BaseModel):
89
91
  return self.basename.split("-")[1]
90
92
 
91
93
  @property
92
- def build_tag(self) -> Optional[str]:
94
+ def build_tag(self) -> str | None:
93
95
  """An optional build tag for the wheel."""
94
96
  split = self.basename.split("-")
95
97
  if len(split) == 6:
@@ -122,6 +124,10 @@ class UvWheel(BaseModel):
122
124
 
123
125
  async def fetch(self) -> bytes:
124
126
  """Download the wheel file from the specified URL."""
127
+ if self.url is None:
128
+ raise ValueError(
129
+ f"Cannot fetch wheel {self.filename or 'unknown'}: no URL provided (local file reference?)"
130
+ )
125
131
  async with httpx.AsyncClient() as client:
126
132
  response = await client.get(self.url, timeout=10)
127
133
  response.raise_for_status()
@@ -135,7 +141,7 @@ class UvSourceDistribution(BaseModel):
135
141
 
136
142
  url: str
137
143
  hash: str
138
- size: Optional[int] = None
144
+ size: int | None = None
139
145
 
140
146
  def validate_hash(self, sdist: bytes) -> None:
141
147
  """Validate the hash of the downloaded sdist against the expected hash."""
@@ -158,8 +164,8 @@ class UvSourceDistribution(BaseModel):
158
164
  @staticmethod
159
165
  def _builder_runner(
160
166
  cmd: Sequence[str],
161
- cwd: Optional[str] = None,
162
- extra_environ: Optional[Mapping[str, str]] = None,
167
+ cwd: str | None = None,
168
+ extra_environ: Mapping[str, str] | None = None,
163
169
  ) -> None:
164
170
  """Run a command in a subprocess and return its exit code, stdout, and stderr."""
165
171
  proc = subprocess.run( # noqa: S603
@@ -191,13 +197,13 @@ class DependencyWheel(BaseModel):
191
197
 
192
198
  module: str
193
199
  input_file: str = ""
194
- input_file_aarch64: Optional[str] = None
200
+ input_file_aarch64: str | None = None
195
201
 
196
- wheel: Optional[UvWheel] = Field(exclude=True, default=None)
197
- wheel_aarch64: Optional[UvWheel] = Field(exclude=True, default=None)
198
- sdist: Optional[UvSourceDistribution] = Field(exclude=True, default=None)
202
+ wheel: UvWheel | None = Field(exclude=True, default=None)
203
+ wheel_aarch64: UvWheel | None = Field(exclude=True, default=None)
204
+ sdist: UvSourceDistribution | None = Field(exclude=True, default=None)
199
205
 
200
- async def collect_wheels(self) -> AsyncGenerator[tuple[str, bytes], None]:
206
+ async def collect_wheels(self) -> AsyncGenerator[tuple[str, bytes]]:
201
207
  """Collect a list of wheel files to fetch for this dependency across all platforms."""
202
208
  if self.wheel is None and self.sdist is not None:
203
209
  logger.info(f"Building sdist for {self.input_file}")
@@ -229,7 +235,7 @@ class DependencyWheel(BaseModel):
229
235
 
230
236
  def __hash__(self) -> int:
231
237
  """Compute a hash for the dependency wheel so we can dedupe wheel files in a later step."""
232
- return hash((type(self), *tuple(self.dict().items())))
238
+ return hash((type(self), *tuple(self.model_dump().items())))
233
239
 
234
240
 
235
241
  class DependencyList(BaseModel):
@@ -254,7 +260,7 @@ class UvPackage(BaseModel):
254
260
  default_factory=dict, alias="optional-dependencies"
255
261
  )
256
262
  wheels: list[UvWheel] = []
257
- sdist: Optional[UvSourceDistribution] = None
263
+ sdist: UvSourceDistribution | None = None
258
264
 
259
265
  def _find_wheel(
260
266
  self,
@@ -287,21 +293,21 @@ class UvPackage(BaseModel):
287
293
  f"Could not find a suitable wheel for {self.name=}, {self.version=}, {abi_precedence=}, {python_precedence=}, {platform_precedence=}"
288
294
  )
289
295
 
290
- _manylinux_precedence = [
296
+ _manylinux_precedence: ClassVar[list[str]] = [
291
297
  "_2_28", # glibc 2.28, latest stable version, supports Ubuntu 18.10+ and RHEL/Oracle 8+
292
298
  "_2_17", # glibc 2.17, LTS-ish, supports Ubuntu 13.10+ and RHEL/Oracle 7+
293
299
  "2014", # Synonym for _2_17
294
300
  ]
295
- platform_precedence_x86_64 = [
301
+ platform_precedence_x86_64: ClassVar[list[str]] = [
296
302
  *[f"manylinux{version}_x86_64" for version in _manylinux_precedence],
297
303
  "any",
298
304
  ]
299
- platform_precedence_aarch64 = [
305
+ platform_precedence_aarch64: ClassVar[list[str]] = [
300
306
  *[f"manylinux{version}_aarch64" for version in _manylinux_precedence],
301
307
  "any",
302
308
  ]
303
309
 
304
- build_from_source_warning_triggered = False
310
+ build_from_source_warning_triggered: bool = False
305
311
 
306
312
  def _resolve(
307
313
  self, abi_precedence: list[str], python_precedence: list[str]
@@ -348,32 +354,32 @@ class UvPackage(BaseModel):
348
354
 
349
355
  return wheel
350
356
 
351
- def resolve_py39(self) -> DependencyWheel:
352
- """Resolve the dependency wheel for Python 3.9."""
357
+ def resolve_py313(self) -> DependencyWheel:
358
+ """Resolve the dependency wheel for Python 3.13."""
353
359
  return self._resolve(
354
360
  abi_precedence=[
355
- "cp39", # Python 3.9-specific ABI
361
+ "cp313", # Python 3.13-specific ABI
356
362
  "abi3", # Python 3 stable ABI
357
363
  "none", # Source wheels -- no ABI
358
364
  ],
359
365
  python_precedence=[
360
- "cp39", # Binary wheel for Python 3.9
361
- "pp39", # Source wheel for Python 3.9
366
+ "cp313", # Binary wheel for Python 3.13
367
+ "pp313", # Source wheel for Python 3.13
362
368
  "py3", # Source wheel for any Python 3.x
363
369
  ],
364
370
  )
365
371
 
366
- def resolve_py313(self) -> DependencyWheel:
367
- """Resolve the dependency wheel for Python 3.13."""
372
+ def resolve_py314(self) -> DependencyWheel:
373
+ """Resolve the dependency wheel for Python 3.14."""
368
374
  return self._resolve(
369
375
  abi_precedence=[
370
- "cp313", # Python 3.13-specific ABI
376
+ "cp314", # Python 3.14-specific ABI
371
377
  "abi3", # Python 3 stable ABI
372
378
  "none", # Source wheels -- no ABI
373
379
  ],
374
380
  python_precedence=[
375
- "cp313", # Binary wheel for Python 3.13
376
- "pp313", # Source wheel for Python 3.13
381
+ "cp314", # Binary wheel for Python 3.14
382
+ "pp314", # Source wheel for Python 3.14
377
383
  "py3", # Source wheel for any Python 3.x
378
384
  ],
379
385
  )
@@ -446,21 +452,21 @@ class UvLock(BaseModel):
446
452
  packages: list[UvPackage],
447
453
  ) -> tuple[DependencyList, DependencyList]:
448
454
  """Resolve the dependencies for the given packages."""
449
- py39_wheels: list[DependencyWheel] = []
450
455
  py313_wheels: list[DependencyWheel] = []
456
+ py314_wheels: list[DependencyWheel] = []
451
457
 
452
458
  for package in packages:
453
- wheel_39 = package.resolve_py39()
454
459
  wheel_313 = package.resolve_py313()
460
+ wheel_314 = package.resolve_py314()
455
461
 
456
- if wheel_39 == wheel_313:
457
- wheel_39.add_platform_prefix("shared")
462
+ if wheel_313 == wheel_314:
458
463
  wheel_313.add_platform_prefix("shared")
464
+ wheel_314.add_platform_prefix("shared")
459
465
  else:
460
- wheel_39.add_platform_prefix("python39")
461
466
  wheel_313.add_platform_prefix("python313")
467
+ wheel_314.add_platform_prefix("python314")
462
468
 
463
- py39_wheels.append(wheel_39)
464
469
  py313_wheels.append(wheel_313)
470
+ py314_wheels.append(wheel_314)
465
471
 
466
- return DependencyList(wheel=py39_wheels), DependencyList(wheel=py313_wheels)
472
+ return DependencyList(wheel=py313_wheels), DependencyList(wheel=py314_wheels)
soar_sdk/meta/webhooks.py CHANGED
@@ -1,6 +1,5 @@
1
- from pydantic import BaseModel, Field, validator
1
+ from pydantic import BaseModel, Field, field_validator
2
2
  from ipaddress import ip_network
3
- from typing import Optional
4
3
 
5
4
 
6
5
  class WebhookRouteMeta(BaseModel):
@@ -8,25 +7,26 @@ class WebhookRouteMeta(BaseModel):
8
7
 
9
8
  url_pattern: str
10
9
  allowed_methods: list[str] = Field(default_factory=lambda: ["GET", "POST"])
11
- declaration_path: Optional[str] = None
12
- declaration_lineno: Optional[int] = None
10
+ declaration_path: str | None = None
11
+ declaration_lineno: int | None = None
13
12
 
14
13
 
15
14
  class WebhookMeta(BaseModel):
16
15
  """Metadata for a complex webhook definition which may contain multiple routes."""
17
16
 
18
- handler: Optional[str]
17
+ handler: str | None
19
18
  requires_auth: bool = True
20
19
  allowed_headers: list[str] = Field(default_factory=list)
21
20
  ip_allowlist: list[str] = Field(default_factory=lambda: ["0.0.0.0/0", "::/0"])
22
21
  routes: list[WebhookRouteMeta] = Field(default_factory=list)
23
22
 
24
- @validator("ip_allowlist", each_item=True)
25
- def validate_ip_allowlist(cls, value: str) -> str:
23
+ @field_validator("ip_allowlist")
24
+ @classmethod
25
+ def validate_ip_allowlist(cls, value: list[str]) -> list[str]:
26
26
  """Enforces all values of the 'ip_allowlist' field are valid IPv4 or IPv6 CIDRs."""
27
- try:
28
- ip_network(value)
29
- except ValueError as e:
30
- raise ValueError(f"{value} is not a valid IPv4 or IPv6 CIDR") from e
31
-
27
+ for item in value:
28
+ try:
29
+ ip_network(item)
30
+ except ValueError as e:
31
+ raise ValueError(f"{item} is not a valid IPv4 or IPv6 CIDR") from e
32
32
  return value
@@ -1,5 +1,5 @@
1
- from typing import Optional, Any, Union
2
- from pydantic import BaseModel
1
+ from typing import Any
2
+ from pydantic import BaseModel, ConfigDict
3
3
 
4
4
 
5
5
  class Artifact(BaseModel):
@@ -8,29 +8,26 @@ class Artifact(BaseModel):
8
8
  This class allows users to create artifacts when yielding from an 'on poll' action.
9
9
  """
10
10
 
11
- class Config:
12
- """Pydantic config. Unknown keys are disallowed in this model."""
11
+ model_config = ConfigDict(extra="forbid")
13
12
 
14
- extra = "forbid"
15
-
16
- name: Optional[str] = None
17
- label: Optional[str] = None
18
- description: Optional[str] = None
19
- type: Optional[str] = None
20
- severity: Optional[str] = None
21
- source_data_identifier: Optional[str] = None
22
- container_id: Optional[int] = None
23
- data: Optional[dict[str, Any]] = None
13
+ name: str | None = None
14
+ label: str | None = None
15
+ description: str | None = None
16
+ type: str | None = None
17
+ severity: str | None = None
18
+ source_data_identifier: str | None = None
19
+ container_id: int | None = None
20
+ data: dict[str, Any] | None = None
24
21
  run_automation: bool = False
25
- owner_id: Optional[Union[int, str]] = None
26
- cef: Optional[dict[str, Any]] = None
27
- cef_types: Optional[dict[str, list[str]]] = None
28
- ingest_app_id: Optional[Union[int, str]] = None
29
- tags: Optional[Union[list[str], str]] = None
30
- start_time: Optional[str] = None
31
- end_time: Optional[str] = None
32
- kill_chain: Optional[str] = None
22
+ owner_id: int | str | None = None
23
+ cef: dict[str, Any] | None = None
24
+ cef_types: dict[str, list[str]] | None = None
25
+ ingest_app_id: int | str | None = None
26
+ tags: list[str] | str | None = None
27
+ start_time: str | None = None
28
+ end_time: str | None = None
29
+ kill_chain: str | None = None
33
30
 
34
31
  def to_dict(self) -> dict[str, Any]:
35
32
  """Convert the artifact to a dictionary (needed for save_artifact)."""
36
- return self.dict(exclude_none=True)
33
+ return self.model_dump(exclude_none=True)