sharkmagic 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.
- sharkmagic/__init__.py +25 -0
- sharkmagic/_errors.py +172 -0
- sharkmagic/_transport.py +578 -0
- sharkmagic/client.py +1011 -0
- sharkmagic/integrations/__init__.py +15 -0
- sharkmagic/integrations/_common.py +132 -0
- sharkmagic/integrations/_smol_runner.py +183 -0
- sharkmagic/integrations/langchain.py +203 -0
- sharkmagic/integrations/langgraph.py +69 -0
- sharkmagic/integrations/prefect.py +66 -0
- sharkmagic/integrations/smolagents.py +240 -0
- sharkmagic/py.typed +0 -0
- sharkmagic-0.1.0.dist-info/METADATA +110 -0
- sharkmagic-0.1.0.dist-info/RECORD +17 -0
- sharkmagic-0.1.0.dist-info/WHEEL +5 -0
- sharkmagic-0.1.0.dist-info/licenses/LICENSE +202 -0
- sharkmagic-0.1.0.dist-info/top_level.txt +1 -0
sharkmagic/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Shark SDK for Python (docs/spec/v1/sdk.md). Standard library only.
|
|
2
|
+
|
|
3
|
+
from sharkmagic import Shark
|
|
4
|
+
shark = Shark() # SHARK_API_KEY, SHARK_API
|
|
5
|
+
space = shark.spaces.create(name="demo")
|
|
6
|
+
print(space.run("echo hi").stdout)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ._errors import (AuthenticationError, CapacityError, CommandFailedError, CommandTimeoutError, ConflictError,
|
|
10
|
+
CreditsExhaustedError, DurableUnavailableError, IdempotencyMismatchError, InfraError,
|
|
11
|
+
InvalidRequestError, LeaseCapError, NotFoundError, OpExpiredError, PermissionDeniedError,
|
|
12
|
+
QuotaExceededError, RateLimitError, ServerError, SharkError, SpaceLimitError, UnsupportedError,
|
|
13
|
+
WaitTimeoutError)
|
|
14
|
+
from ._errors import ConnectionError
|
|
15
|
+
from ._transport import ROUTES, VERSION, Event, Transport
|
|
16
|
+
from .client import (AccountPorts, AccountSecrets, AsyncShark, Egress, ExecResult, FileEntry, Op, Ports, Shark, Space, SpaceDefaults,
|
|
17
|
+
SpaceEgress, SpaceSecrets, new_op_id)
|
|
18
|
+
|
|
19
|
+
__version__ = VERSION
|
|
20
|
+
__all__ = ["Shark", "AsyncShark", "Space", "SpaceSecrets", "SpaceEgress", "Egress", "AccountSecrets", "SpaceDefaults", "Ports", "AccountPorts", "Op", "ExecResult", "FileEntry", "Event", "Transport", "ROUTES", "new_op_id",
|
|
21
|
+
"SharkError", "AuthenticationError", "PermissionDeniedError", "NotFoundError", "ConflictError",
|
|
22
|
+
"IdempotencyMismatchError", "OpExpiredError", "InvalidRequestError", "UnsupportedError", "RateLimitError",
|
|
23
|
+
"LeaseCapError", "SpaceLimitError", "QuotaExceededError", "CreditsExhaustedError", "ServerError",
|
|
24
|
+
"CapacityError", "DurableUnavailableError", "CommandFailedError", "InfraError", "CommandTimeoutError",
|
|
25
|
+
"WaitTimeoutError", "ConnectionError"]
|
sharkmagic/_errors.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Typed errors (docs/spec/v1/sdk.md §5): one class per error.code family."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SharkError(Exception):
|
|
9
|
+
"""Base of every SDK error. ``code`` is the API's stable error code."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, *, code: str = "", status: int = 0, request_id: str = "",
|
|
12
|
+
details: Optional[dict] = None, retryable: bool = False, retry_after: Optional[float] = None):
|
|
13
|
+
super().__init__(message)
|
|
14
|
+
self.message = message
|
|
15
|
+
self.code = code
|
|
16
|
+
self.status = status
|
|
17
|
+
self.request_id = request_id
|
|
18
|
+
self.details = details or {}
|
|
19
|
+
self.retryable = retryable
|
|
20
|
+
self.retry_after = retry_after
|
|
21
|
+
|
|
22
|
+
def __repr__(self) -> str:
|
|
23
|
+
return f"{type(self).__name__}(code={self.code!r}, status={self.status}, message={self.message!r})"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AuthenticationError(SharkError):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PermissionDeniedError(SharkError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NotFoundError(SharkError):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ConflictError(SharkError):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class IdempotencyMismatchError(ConflictError):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OpExpiredError(SharkError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class InvalidRequestError(SharkError):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class UnsupportedError(SharkError):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RateLimitError(SharkError):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LeaseCapError(RateLimitError):
|
|
63
|
+
@property
|
|
64
|
+
def scope(self) -> str:
|
|
65
|
+
return str(self.details.get("scope", ""))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SpaceLimitError(RateLimitError):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class QuotaExceededError(SharkError):
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CreditsExhaustedError(SharkError):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ServerError(SharkError):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class CapacityError(ServerError):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class DurableUnavailableError(ServerError):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ConnectionError(SharkError): # noqa: A001 - the SDK's own, raised after retries
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class CommandFailedError(SharkError):
|
|
97
|
+
"""Raised by run(..., check=True) when the command exits non-zero."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, result: Any):
|
|
100
|
+
super().__init__(f"command exited {result.exit}", code="command_failed")
|
|
101
|
+
self.result = result
|
|
102
|
+
self.exit = result.exit
|
|
103
|
+
self.stdout = result.stdout
|
|
104
|
+
self.stderr = result.stderr
|
|
105
|
+
self.op_id = result.op_id
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class InfraError(SharkError):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CommandTimeoutError(SharkError):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class WaitTimeoutError(SharkError):
|
|
117
|
+
"""op.wait(timeout=...) ran out of time (code ``wait_timeout``). The command keeps running: wait again,
|
|
118
|
+
``op.refresh()``, or ``op.signal()`` it. ``.op_id``, ``.timeout`` (seconds)."""
|
|
119
|
+
|
|
120
|
+
def __init__(self, op_id: str, timeout: float):
|
|
121
|
+
super().__init__(f"op {op_id} still running after {timeout:g} s", code="wait_timeout",
|
|
122
|
+
details={"op_id": op_id, "timeout_s": timeout})
|
|
123
|
+
self.op_id = op_id
|
|
124
|
+
self.timeout = timeout
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# code -> class (sdk.md §5)
|
|
128
|
+
CODE_CLASSES = {
|
|
129
|
+
"unauthenticated": AuthenticationError,
|
|
130
|
+
"auth_rate_limited": AuthenticationError,
|
|
131
|
+
"insufficient_scope": PermissionDeniedError,
|
|
132
|
+
"plan_restriction": PermissionDeniedError,
|
|
133
|
+
"tenant_not_allowed": PermissionDeniedError,
|
|
134
|
+
"not_found": NotFoundError,
|
|
135
|
+
"conflict": ConflictError,
|
|
136
|
+
"idempotency_mismatch": IdempotencyMismatchError,
|
|
137
|
+
"idempotency_in_progress": IdempotencyMismatchError,
|
|
138
|
+
"op_expired": OpExpiredError,
|
|
139
|
+
"invalid_request": InvalidRequestError,
|
|
140
|
+
"invalid_op_id": InvalidRequestError,
|
|
141
|
+
"path_outside_space": InvalidRequestError,
|
|
142
|
+
"payload_too_large": InvalidRequestError,
|
|
143
|
+
"precondition_failed": ConflictError,
|
|
144
|
+
"upgrade_required": InvalidRequestError,
|
|
145
|
+
"unsupported": UnsupportedError,
|
|
146
|
+
"rate_limited": RateLimitError,
|
|
147
|
+
"lease_cap_reached": LeaseCapError,
|
|
148
|
+
"space_limit_reached": SpaceLimitError,
|
|
149
|
+
"quota_exceeded": QuotaExceededError,
|
|
150
|
+
"credits_exhausted": CreditsExhaustedError,
|
|
151
|
+
"internal": ServerError,
|
|
152
|
+
"capacity_unavailable": CapacityError,
|
|
153
|
+
"out_of_capacity": CapacityError,
|
|
154
|
+
"insufficient_storage": QuotaExceededError,
|
|
155
|
+
"canceled": CapacityError,
|
|
156
|
+
"durable_unavailable": DurableUnavailableError,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# HTTP status -> class, for codes this SDK does not know yet
|
|
160
|
+
STATUS_CLASSES = {400: InvalidRequestError, 401: AuthenticationError, 402: CreditsExhaustedError, 403: PermissionDeniedError,
|
|
161
|
+
404: NotFoundError, 409: ConflictError, 410: OpExpiredError, 412: ConflictError, 413: InvalidRequestError,
|
|
162
|
+
422: UnsupportedError, 429: RateLimitError}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def error_class(code: str, status: int) -> type:
|
|
166
|
+
if code in CODE_CLASSES:
|
|
167
|
+
return CODE_CLASSES[code]
|
|
168
|
+
if status in STATUS_CLASSES:
|
|
169
|
+
return STATUS_CLASSES[status]
|
|
170
|
+
if status >= 500:
|
|
171
|
+
return ServerError
|
|
172
|
+
return SharkError
|