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.
- soar_sdk/abstract.py +38 -41
- soar_sdk/action_results.py +41 -18
- soar_sdk/actions_manager.py +10 -13
- soar_sdk/apis/utils.py +3 -3
- soar_sdk/apis/vault.py +10 -10
- soar_sdk/app.py +58 -51
- soar_sdk/app_cli_runner.py +8 -8
- soar_sdk/app_client.py +10 -10
- soar_sdk/asset.py +45 -33
- soar_sdk/async_utils.py +2 -2
- soar_sdk/cli/init/cli.py +7 -9
- soar_sdk/cli/manifests/deserializers.py +15 -15
- soar_sdk/cli/manifests/processors.py +4 -10
- soar_sdk/cli/manifests/serializers.py +16 -8
- soar_sdk/cli/package/cli.py +6 -6
- soar_sdk/cli/package/utils.py +1 -1
- soar_sdk/code_renderers/action_renderer.py +35 -18
- soar_sdk/code_renderers/app_renderer.py +1 -2
- soar_sdk/code_renderers/asset_renderer.py +4 -5
- soar_sdk/code_renderers/renderer.py +2 -2
- soar_sdk/code_renderers/templates/pyproject.toml.jinja +1 -1
- soar_sdk/compat.py +6 -6
- soar_sdk/decorators/action.py +14 -15
- soar_sdk/decorators/make_request.py +4 -3
- soar_sdk/decorators/on_poll.py +5 -4
- soar_sdk/decorators/test_connectivity.py +2 -2
- soar_sdk/decorators/view_handler.py +11 -17
- soar_sdk/decorators/webhook.py +1 -2
- soar_sdk/exceptions.py +1 -4
- soar_sdk/field_utils.py +8 -0
- soar_sdk/input_spec.py +13 -17
- soar_sdk/logging.py +3 -3
- soar_sdk/meta/actions.py +6 -22
- soar_sdk/meta/app.py +10 -7
- soar_sdk/meta/dependencies.py +48 -42
- soar_sdk/meta/webhooks.py +12 -12
- soar_sdk/models/artifact.py +20 -23
- soar_sdk/models/container.py +30 -33
- soar_sdk/models/vault_attachment.py +6 -6
- soar_sdk/models/view.py +10 -13
- soar_sdk/params.py +57 -39
- soar_sdk/shims/phantom/action_result.py +4 -4
- soar_sdk/shims/phantom/base_connector.py +13 -5
- soar_sdk/shims/phantom/install_info.py +15 -2
- soar_sdk/shims/phantom/ph_ipc.py +3 -3
- soar_sdk/shims/phantom/vault.py +35 -34
- soar_sdk/types.py +3 -2
- soar_sdk/views/template_filters.py +4 -4
- soar_sdk/views/template_renderer.py +2 -2
- soar_sdk/views/view_parser.py +3 -4
- soar_sdk/webhooks/models.py +7 -6
- soar_sdk/webhooks/routing.py +4 -3
- {splunk_soar_sdk-2.3.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/METADATA +5 -6
- splunk_soar_sdk-3.0.0.dist-info/RECORD +104 -0
- splunk_soar_sdk-2.3.6.dist-info/RECORD +0 -103
- {splunk_soar_sdk-2.3.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/WHEEL +0 -0
- {splunk_soar_sdk-2.3.6.dist-info → splunk_soar_sdk-3.0.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
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:
|
|
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:
|
|
34
|
-
component_type:
|
|
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
|
-
|
|
96
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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}'"
|
soar_sdk/decorators/webhook.py
CHANGED
|
@@ -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:
|
|
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:
|
|
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)
|
soar_sdk/field_utils.py
ADDED
|
@@ -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,
|
|
3
|
-
from typing import Literal,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
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:
|
|
136
|
+
action: str | None = None
|
|
141
137
|
action_run_id: int = Field(default_factory=id_factory)
|
|
142
|
-
app_config:
|
|
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=
|
|
147
|
+
parameters: list[ActionParameter] = Field(default_factory=list)
|
|
152
148
|
user_session_token: str = ""
|
|
153
|
-
soar_auth:
|
|
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
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
24
|
-
view_handler:
|
|
25
|
-
summary_type:
|
|
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
|
|
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().
|
|
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,
|
|
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:
|
|
47
|
+
webhook: WebhookMeta | None = None
|
|
49
48
|
|
|
50
|
-
@
|
|
51
|
-
|
|
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
|
-
|
|
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
|
soar_sdk/meta/dependencies.py
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
@
|
|
73
|
+
@functools.cached_property
|
|
72
74
|
def basename(self) -> str:
|
|
73
75
|
"""The base name of the wheel file."""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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) ->
|
|
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:
|
|
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:
|
|
162
|
-
extra_environ:
|
|
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:
|
|
200
|
+
input_file_aarch64: str | None = None
|
|
195
201
|
|
|
196
|
-
wheel:
|
|
197
|
-
wheel_aarch64:
|
|
198
|
-
sdist:
|
|
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]
|
|
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.
|
|
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:
|
|
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
|
|
352
|
-
"""Resolve the dependency wheel for Python 3.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
361
|
-
"
|
|
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
|
|
367
|
-
"""Resolve the dependency wheel for Python 3.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
376
|
-
"
|
|
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
|
|
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=
|
|
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,
|
|
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:
|
|
12
|
-
declaration_lineno:
|
|
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:
|
|
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
|
-
@
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
soar_sdk/models/artifact.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from typing import
|
|
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
|
-
|
|
12
|
-
"""Pydantic config. Unknown keys are disallowed in this model."""
|
|
11
|
+
model_config = ConfigDict(extra="forbid")
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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:
|
|
26
|
-
cef:
|
|
27
|
-
cef_types:
|
|
28
|
-
ingest_app_id:
|
|
29
|
-
tags:
|
|
30
|
-
start_time:
|
|
31
|
-
end_time:
|
|
32
|
-
kill_chain:
|
|
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.
|
|
33
|
+
return self.model_dump(exclude_none=True)
|