vswarm 0.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.
- vswarm/__init__.py +136 -0
- vswarm/client.py +193 -0
- vswarm/errors.py +78 -0
- vswarm/models/__init__.py +77 -0
- vswarm/models/artifacts.py +25 -0
- vswarm/models/browser.py +595 -0
- vswarm/models/common.py +119 -0
- vswarm/models/policy.py +40 -0
- vswarm/models/runs.py +40 -0
- vswarm/models/traces.py +173 -0
- vswarm/py.typed +1 -0
- vswarm/resources/__init__.py +48 -0
- vswarm/resources/artifacts.py +20 -0
- vswarm/resources/browser.py +716 -0
- vswarm/resources/events.py +32 -0
- vswarm/resources/files.py +50 -0
- vswarm/resources/ports.py +66 -0
- vswarm/resources/preview_attachments.py +23 -0
- vswarm/resources/processes.py +111 -0
- vswarm/resources/pty.py +40 -0
- vswarm/resources/runs.py +52 -0
- vswarm/resources/runtime.py +18 -0
- vswarm/resources/sandboxes.py +57 -0
- vswarm/resources/templates.py +15 -0
- vswarm/resources/traces.py +343 -0
- vswarm/resources/workspaces.py +40 -0
- vswarm/testing/__init__.py +3 -0
- vswarm/testing/fake_transport.py +33 -0
- vswarm/transport.py +154 -0
- vswarm-0.1.0.dist-info/METADATA +274 -0
- vswarm-0.1.0.dist-info/RECORD +35 -0
- vswarm-0.1.0.dist-info/WHEEL +5 -0
- vswarm-0.1.0.dist-info/licenses/LICENSE +201 -0
- vswarm-0.1.0.dist-info/licenses/NOTICE +4 -0
- vswarm-0.1.0.dist-info/top_level.txt +1 -0
vswarm/__init__.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from vswarm.client import VSwarmClient
|
|
4
|
+
from vswarm.resources import (
|
|
5
|
+
ArtifactClient,
|
|
6
|
+
BrowserClient,
|
|
7
|
+
BrowserSessionHandle,
|
|
8
|
+
BrowserSessionManager,
|
|
9
|
+
EventClient,
|
|
10
|
+
FileClient,
|
|
11
|
+
RunClient,
|
|
12
|
+
SandboxHandle,
|
|
13
|
+
SandboxManager,
|
|
14
|
+
TemplateClient,
|
|
15
|
+
TraceClient,
|
|
16
|
+
TraceHandle,
|
|
17
|
+
TraceSession,
|
|
18
|
+
TraceStep,
|
|
19
|
+
)
|
|
20
|
+
from vswarm.models import (
|
|
21
|
+
Admission,
|
|
22
|
+
BrowserAction,
|
|
23
|
+
BrowserActionResult,
|
|
24
|
+
BrowserAgentObservation,
|
|
25
|
+
BrowserCapacity,
|
|
26
|
+
BrowserClick,
|
|
27
|
+
BrowserDiagnostics,
|
|
28
|
+
BrowserElement,
|
|
29
|
+
BrowserEvidenceCapture,
|
|
30
|
+
BrowserFill,
|
|
31
|
+
BrowserNavigate,
|
|
32
|
+
BrowserObservation,
|
|
33
|
+
BrowserObserveInclude,
|
|
34
|
+
BrowserPolicy,
|
|
35
|
+
BrowserSiteChallenge,
|
|
36
|
+
BrowserTargetResolution,
|
|
37
|
+
BrowserTiming,
|
|
38
|
+
CapacityError,
|
|
39
|
+
CleanupResult,
|
|
40
|
+
Metadata,
|
|
41
|
+
NetworkPolicy,
|
|
42
|
+
PolicyError,
|
|
43
|
+
RequestOptions,
|
|
44
|
+
ResourceLimits,
|
|
45
|
+
RetryPolicy,
|
|
46
|
+
RunResult,
|
|
47
|
+
Timeout,
|
|
48
|
+
TraceArtifact,
|
|
49
|
+
TraceEvent,
|
|
50
|
+
TraceExportArtifact,
|
|
51
|
+
TraceExportBundle,
|
|
52
|
+
TraceRun,
|
|
53
|
+
Viewport,
|
|
54
|
+
)
|
|
55
|
+
from vswarm.errors import (
|
|
56
|
+
VSwarmAPIError,
|
|
57
|
+
VSwarmAuthenticationError,
|
|
58
|
+
VSwarmAuthorizationError,
|
|
59
|
+
VSwarmCapacityError,
|
|
60
|
+
VSwarmConflictError,
|
|
61
|
+
VSwarmNotFoundError,
|
|
62
|
+
VSwarmPolicyError,
|
|
63
|
+
VSwarmQuotaError,
|
|
64
|
+
VSwarmTimeoutError,
|
|
65
|
+
VSwarmValidationError,
|
|
66
|
+
VSwarmVersionError,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
__version__ = version("vswarm")
|
|
71
|
+
except PackageNotFoundError:
|
|
72
|
+
# Support source-tree imports used by local conformance scripts.
|
|
73
|
+
__version__ = "0.0.0+unknown"
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"__version__",
|
|
77
|
+
"Admission",
|
|
78
|
+
"ArtifactClient",
|
|
79
|
+
"BrowserAction",
|
|
80
|
+
"BrowserActionResult",
|
|
81
|
+
"BrowserAgentObservation",
|
|
82
|
+
"BrowserCapacity",
|
|
83
|
+
"BrowserClick",
|
|
84
|
+
"BrowserClient",
|
|
85
|
+
"BrowserDiagnostics",
|
|
86
|
+
"BrowserElement",
|
|
87
|
+
"BrowserEvidenceCapture",
|
|
88
|
+
"BrowserFill",
|
|
89
|
+
"BrowserNavigate",
|
|
90
|
+
"BrowserObservation",
|
|
91
|
+
"BrowserObserveInclude",
|
|
92
|
+
"BrowserPolicy",
|
|
93
|
+
"BrowserSessionHandle",
|
|
94
|
+
"BrowserSessionManager",
|
|
95
|
+
"BrowserSiteChallenge",
|
|
96
|
+
"BrowserTargetResolution",
|
|
97
|
+
"BrowserTiming",
|
|
98
|
+
"CapacityError",
|
|
99
|
+
"CleanupResult",
|
|
100
|
+
"EventClient",
|
|
101
|
+
"FileClient",
|
|
102
|
+
"Metadata",
|
|
103
|
+
"NetworkPolicy",
|
|
104
|
+
"PolicyError",
|
|
105
|
+
"RequestOptions",
|
|
106
|
+
"ResourceLimits",
|
|
107
|
+
"RetryPolicy",
|
|
108
|
+
"RunClient",
|
|
109
|
+
"RunResult",
|
|
110
|
+
"SandboxHandle",
|
|
111
|
+
"SandboxManager",
|
|
112
|
+
"TemplateClient",
|
|
113
|
+
"Timeout",
|
|
114
|
+
"TraceArtifact",
|
|
115
|
+
"TraceClient",
|
|
116
|
+
"TraceEvent",
|
|
117
|
+
"TraceExportArtifact",
|
|
118
|
+
"TraceExportBundle",
|
|
119
|
+
"TraceHandle",
|
|
120
|
+
"TraceRun",
|
|
121
|
+
"TraceSession",
|
|
122
|
+
"TraceStep",
|
|
123
|
+
"VSwarmAPIError",
|
|
124
|
+
"VSwarmAuthenticationError",
|
|
125
|
+
"VSwarmAuthorizationError",
|
|
126
|
+
"VSwarmCapacityError",
|
|
127
|
+
"VSwarmConflictError",
|
|
128
|
+
"VSwarmClient",
|
|
129
|
+
"VSwarmNotFoundError",
|
|
130
|
+
"VSwarmPolicyError",
|
|
131
|
+
"VSwarmQuotaError",
|
|
132
|
+
"VSwarmTimeoutError",
|
|
133
|
+
"VSwarmValidationError",
|
|
134
|
+
"VSwarmVersionError",
|
|
135
|
+
"Viewport",
|
|
136
|
+
]
|
vswarm/client.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextvars import ContextVar, Token
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from packaging.version import InvalidVersion, Version
|
|
7
|
+
|
|
8
|
+
from vswarm.errors import VSwarmVersionError
|
|
9
|
+
from vswarm.models import RequestOptions, to_api
|
|
10
|
+
from vswarm.resources import (
|
|
11
|
+
ArtifactClient,
|
|
12
|
+
BrowserClient,
|
|
13
|
+
EventClient,
|
|
14
|
+
PreviewAttachmentClient,
|
|
15
|
+
RunClient,
|
|
16
|
+
SandboxManager,
|
|
17
|
+
TemplateClient,
|
|
18
|
+
TraceClient,
|
|
19
|
+
WorkspaceManager,
|
|
20
|
+
)
|
|
21
|
+
from vswarm.transport import AsyncTransport, HTTPXTransport, decode_response
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VSwarmClient:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
base_url: str = "http://127.0.0.1:8080",
|
|
29
|
+
token: str | None = None,
|
|
30
|
+
project: str | None = None,
|
|
31
|
+
user: str | None = None,
|
|
32
|
+
transport: AsyncTransport | None = None,
|
|
33
|
+
timeout: float = 60.0,
|
|
34
|
+
connect_timeout: float | None = None,
|
|
35
|
+
min_api_version: str | None = None,
|
|
36
|
+
compatibility: str = "off",
|
|
37
|
+
):
|
|
38
|
+
self.base_url = base_url.rstrip("/")
|
|
39
|
+
self.token = token
|
|
40
|
+
self.project = project
|
|
41
|
+
self.user = user
|
|
42
|
+
self.timeout = timeout
|
|
43
|
+
self.connect_timeout = connect_timeout
|
|
44
|
+
self.min_api_version = min_api_version
|
|
45
|
+
self.compatibility = compatibility
|
|
46
|
+
self.transport = transport or HTTPXTransport(timeout=timeout, connect_timeout=connect_timeout)
|
|
47
|
+
self._trace_context: ContextVar[Any | None] = ContextVar("vswarm_trace_context", default=None)
|
|
48
|
+
self.templates = TemplateClient(self)
|
|
49
|
+
self.sandboxes = SandboxManager(self)
|
|
50
|
+
self.workspaces = WorkspaceManager(self)
|
|
51
|
+
self.artifacts = ArtifactClient(self)
|
|
52
|
+
self.browser = BrowserClient(self)
|
|
53
|
+
self.runs = RunClient(self)
|
|
54
|
+
self.events = EventClient(self)
|
|
55
|
+
self.preview_attachments = PreviewAttachmentClient(self)
|
|
56
|
+
self.traces = TraceClient(self)
|
|
57
|
+
|
|
58
|
+
async def close(self) -> None:
|
|
59
|
+
await self.transport.aclose()
|
|
60
|
+
|
|
61
|
+
async def __aenter__(self) -> "VSwarmClient":
|
|
62
|
+
if self.compatibility == "strict":
|
|
63
|
+
await self.check_compatibility()
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
async def __aexit__(self, *_: object) -> None:
|
|
67
|
+
await self.close()
|
|
68
|
+
|
|
69
|
+
async def health(self) -> dict[str, Any]:
|
|
70
|
+
return await self._request("GET", "/v1/health")
|
|
71
|
+
|
|
72
|
+
async def server_info(self) -> dict[str, Any]:
|
|
73
|
+
return await self.health()
|
|
74
|
+
|
|
75
|
+
async def features(self) -> dict[str, Any]:
|
|
76
|
+
info = await self.server_info()
|
|
77
|
+
return dict(info.get("features", {}) if isinstance(info.get("features"), Mapping) else {})
|
|
78
|
+
|
|
79
|
+
async def check_compatibility(self) -> dict[str, Any]:
|
|
80
|
+
info = await self.server_info()
|
|
81
|
+
if self.min_api_version and info.get("api_version"):
|
|
82
|
+
current_version = _parse_api_version(str(info["api_version"]), "server")
|
|
83
|
+
minimum_version = _parse_api_version(self.min_api_version, "minimum")
|
|
84
|
+
if current_version < minimum_version:
|
|
85
|
+
raise VSwarmVersionError(
|
|
86
|
+
f"VSwarm API version {info['api_version']} is older than required {self.min_api_version}",
|
|
87
|
+
payload=info,
|
|
88
|
+
)
|
|
89
|
+
return info
|
|
90
|
+
|
|
91
|
+
async def browser_capacity(self) -> dict[str, Any]:
|
|
92
|
+
return await self._request("GET", "/v1/browser/capacity")
|
|
93
|
+
|
|
94
|
+
async def capacity(self) -> dict[str, Any]:
|
|
95
|
+
return await self._request("GET", "/v1/capacity")
|
|
96
|
+
|
|
97
|
+
async def reconcile_browser(self) -> dict[str, Any]:
|
|
98
|
+
return await self._request("POST", "/v1/browser/reconcile", json={})
|
|
99
|
+
|
|
100
|
+
def trace(self, **request: Any):
|
|
101
|
+
return self.traces.session(**request)
|
|
102
|
+
|
|
103
|
+
def browser_session(
|
|
104
|
+
self,
|
|
105
|
+
*,
|
|
106
|
+
template: str = "browser-chromium",
|
|
107
|
+
sandbox: Mapping[str, Any] | None = None,
|
|
108
|
+
**session: Any,
|
|
109
|
+
):
|
|
110
|
+
return self.browser.session(template=template, sandbox=sandbox, **session)
|
|
111
|
+
|
|
112
|
+
async def request(
|
|
113
|
+
self,
|
|
114
|
+
method: str,
|
|
115
|
+
path: str,
|
|
116
|
+
*,
|
|
117
|
+
json: Any | None = None,
|
|
118
|
+
content: bytes | str | None = None,
|
|
119
|
+
headers: Mapping[str, str] | None = None,
|
|
120
|
+
options: RequestOptions | Mapping[str, Any] | None = None,
|
|
121
|
+
) -> Any:
|
|
122
|
+
return await self._request(method, path, json=json, content=content, headers=headers, options=options)
|
|
123
|
+
|
|
124
|
+
async def _request(
|
|
125
|
+
self,
|
|
126
|
+
method: str,
|
|
127
|
+
path: str,
|
|
128
|
+
*,
|
|
129
|
+
json: Any | None = None,
|
|
130
|
+
content: bytes | str | None = None,
|
|
131
|
+
headers: Mapping[str, str] | None = None,
|
|
132
|
+
trace: Any | None = None,
|
|
133
|
+
options: RequestOptions | Mapping[str, Any] | None = None,
|
|
134
|
+
) -> Any:
|
|
135
|
+
request_headers = self._headers(headers=headers, options=options, trace=trace)
|
|
136
|
+
response = await self.transport.request(
|
|
137
|
+
method,
|
|
138
|
+
self.base_url + path,
|
|
139
|
+
headers=request_headers,
|
|
140
|
+
json=to_api(json),
|
|
141
|
+
content=content,
|
|
142
|
+
)
|
|
143
|
+
return await decode_response(response)
|
|
144
|
+
|
|
145
|
+
def _headers(
|
|
146
|
+
self,
|
|
147
|
+
*,
|
|
148
|
+
headers: Mapping[str, str] | None = None,
|
|
149
|
+
options: RequestOptions | Mapping[str, Any] | None = None,
|
|
150
|
+
trace: Any | None = None,
|
|
151
|
+
) -> dict[str, str]:
|
|
152
|
+
request_headers: dict[str, str] = {}
|
|
153
|
+
opts = _request_options(options)
|
|
154
|
+
if opts.headers:
|
|
155
|
+
request_headers.update(dict(opts.headers))
|
|
156
|
+
if headers:
|
|
157
|
+
request_headers.update(dict(headers))
|
|
158
|
+
if opts.request_id:
|
|
159
|
+
request_headers["X-VSwarm-Request-ID"] = opts.request_id
|
|
160
|
+
if self.token:
|
|
161
|
+
request_headers["Authorization"] = f"Bearer {self.token}"
|
|
162
|
+
if self.project:
|
|
163
|
+
request_headers["X-VSwarm-Project"] = self.project
|
|
164
|
+
if self.user:
|
|
165
|
+
request_headers["X-VSwarm-User"] = self.user
|
|
166
|
+
trace_context = trace or self._trace_context.get()
|
|
167
|
+
if trace_context is not None and hasattr(trace_context, "headers"):
|
|
168
|
+
request_headers.update(trace_context.headers())
|
|
169
|
+
return request_headers
|
|
170
|
+
|
|
171
|
+
def _push_trace(self, trace: Any) -> Token:
|
|
172
|
+
return self._trace_context.set(trace)
|
|
173
|
+
|
|
174
|
+
def _pop_trace(self, token: Token) -> None:
|
|
175
|
+
self._trace_context.reset(token)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _request_options(options: RequestOptions | Mapping[str, Any] | None) -> RequestOptions:
|
|
179
|
+
if options is None:
|
|
180
|
+
return RequestOptions()
|
|
181
|
+
if isinstance(options, RequestOptions):
|
|
182
|
+
return options
|
|
183
|
+
return RequestOptions(
|
|
184
|
+
request_id=options.get("request_id"),
|
|
185
|
+
headers=options.get("headers"),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _parse_api_version(value: str, source: str) -> Version:
|
|
190
|
+
try:
|
|
191
|
+
return Version(value)
|
|
192
|
+
except InvalidVersion as exc:
|
|
193
|
+
raise VSwarmVersionError(f"Invalid {source} VSwarm API version: {value}") from exc
|
vswarm/errors.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VSwarmAPIError(RuntimeError):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
message: str,
|
|
10
|
+
*,
|
|
11
|
+
status_code: int | None = None,
|
|
12
|
+
payload: object | None = None,
|
|
13
|
+
request_id: str | None = None,
|
|
14
|
+
retry_after_seconds: float | None = None,
|
|
15
|
+
capacity: object | None = None,
|
|
16
|
+
resource_id: str | None = None,
|
|
17
|
+
operation_id: str | None = None,
|
|
18
|
+
trace_id: str | None = None,
|
|
19
|
+
):
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
self.status_code = status_code
|
|
22
|
+
self.payload = payload
|
|
23
|
+
self.request_id = request_id
|
|
24
|
+
self.trace_id = trace_id
|
|
25
|
+
self.retry_after_seconds = retry_after_seconds
|
|
26
|
+
self.capacity = capacity
|
|
27
|
+
self.resource_id = resource_id
|
|
28
|
+
self.operation_id = operation_id
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VSwarmValidationError(VSwarmAPIError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VSwarmAuthenticationError(VSwarmAPIError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class VSwarmAuthorizationError(VSwarmAPIError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class VSwarmNotFoundError(VSwarmAPIError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class VSwarmCapacityError(VSwarmAPIError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class VSwarmQuotaError(VSwarmAPIError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class VSwarmPolicyError(VSwarmAPIError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class VSwarmTimeoutError(VSwarmAPIError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class VSwarmConflictError(VSwarmAPIError):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class VSwarmVersionError(VSwarmAPIError):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def payload_field(payload: object, *names: str) -> Any | None:
|
|
72
|
+
if not isinstance(payload, dict):
|
|
73
|
+
return None
|
|
74
|
+
for name in names:
|
|
75
|
+
value = payload.get(name)
|
|
76
|
+
if value is not None:
|
|
77
|
+
return value
|
|
78
|
+
return None
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from vswarm.models.artifacts import ArtifactRef
|
|
2
|
+
from vswarm.models.browser import (
|
|
3
|
+
BrowserAction,
|
|
4
|
+
BrowserActionResult,
|
|
5
|
+
BrowserAgentObservation,
|
|
6
|
+
BrowserCapacity,
|
|
7
|
+
BrowserClick,
|
|
8
|
+
BrowserDiagnostics,
|
|
9
|
+
BrowserElement,
|
|
10
|
+
BrowserEvidenceCapture,
|
|
11
|
+
BrowserFill,
|
|
12
|
+
BrowserNavigate,
|
|
13
|
+
BrowserObservation,
|
|
14
|
+
BrowserObserveInclude,
|
|
15
|
+
BrowserSiteChallenge,
|
|
16
|
+
BrowserTargetResolution,
|
|
17
|
+
BrowserTiming,
|
|
18
|
+
CapacityError,
|
|
19
|
+
PolicyError,
|
|
20
|
+
Viewport,
|
|
21
|
+
)
|
|
22
|
+
from vswarm.models.common import (
|
|
23
|
+
Admission,
|
|
24
|
+
CleanupResult,
|
|
25
|
+
Metadata,
|
|
26
|
+
NetworkPolicy,
|
|
27
|
+
RequestOptions,
|
|
28
|
+
ResourceLimits,
|
|
29
|
+
RetryPolicy,
|
|
30
|
+
Timeout,
|
|
31
|
+
clean_mapping,
|
|
32
|
+
encode_base64,
|
|
33
|
+
to_api,
|
|
34
|
+
)
|
|
35
|
+
from vswarm.models.policy import BrowserPolicy
|
|
36
|
+
from vswarm.models.runs import RunResult
|
|
37
|
+
from vswarm.models.traces import TraceArtifact, TraceEvent, TraceExportArtifact, TraceExportBundle, TraceRun
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"Admission",
|
|
41
|
+
"ArtifactRef",
|
|
42
|
+
"BrowserAction",
|
|
43
|
+
"BrowserActionResult",
|
|
44
|
+
"BrowserAgentObservation",
|
|
45
|
+
"BrowserCapacity",
|
|
46
|
+
"BrowserClick",
|
|
47
|
+
"BrowserDiagnostics",
|
|
48
|
+
"BrowserElement",
|
|
49
|
+
"BrowserEvidenceCapture",
|
|
50
|
+
"BrowserFill",
|
|
51
|
+
"BrowserNavigate",
|
|
52
|
+
"BrowserObservation",
|
|
53
|
+
"BrowserObserveInclude",
|
|
54
|
+
"BrowserPolicy",
|
|
55
|
+
"BrowserSiteChallenge",
|
|
56
|
+
"BrowserTargetResolution",
|
|
57
|
+
"BrowserTiming",
|
|
58
|
+
"CapacityError",
|
|
59
|
+
"CleanupResult",
|
|
60
|
+
"Metadata",
|
|
61
|
+
"NetworkPolicy",
|
|
62
|
+
"PolicyError",
|
|
63
|
+
"RequestOptions",
|
|
64
|
+
"ResourceLimits",
|
|
65
|
+
"RetryPolicy",
|
|
66
|
+
"RunResult",
|
|
67
|
+
"Timeout",
|
|
68
|
+
"TraceArtifact",
|
|
69
|
+
"TraceEvent",
|
|
70
|
+
"TraceExportArtifact",
|
|
71
|
+
"TraceExportBundle",
|
|
72
|
+
"TraceRun",
|
|
73
|
+
"Viewport",
|
|
74
|
+
"clean_mapping",
|
|
75
|
+
"encode_base64",
|
|
76
|
+
"to_api",
|
|
77
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class ArtifactRef:
|
|
9
|
+
id: str
|
|
10
|
+
kind: str | None = None
|
|
11
|
+
path: str | None = None
|
|
12
|
+
content_type: str | None = None
|
|
13
|
+
size_bytes: int | None = None
|
|
14
|
+
raw: Mapping[str, Any] | None = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_api(cls, data: Mapping[str, Any]) -> "ArtifactRef":
|
|
18
|
+
return cls(
|
|
19
|
+
id=str(data.get("id", "")),
|
|
20
|
+
kind=data.get("kind"),
|
|
21
|
+
path=data.get("path"),
|
|
22
|
+
content_type=data.get("content_type"),
|
|
23
|
+
size_bytes=data.get("size_bytes"),
|
|
24
|
+
raw=data,
|
|
25
|
+
)
|