den-sdk 0.0.3__tar.gz

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.
@@ -0,0 +1,48 @@
1
+ # Binaries
2
+ bin/
3
+ /den
4
+ *.exe
5
+
6
+ # Database
7
+ *.db
8
+
9
+ # Config
10
+ den.yaml
11
+ !den.example.yaml
12
+
13
+ # Go
14
+ vendor/
15
+
16
+ # Node
17
+ node_modules/
18
+ sdk/*/dist/
19
+
20
+ # Python
21
+ __pycache__/
22
+ *.pyc
23
+ .venv/
24
+ *.egg-info/
25
+
26
+ # IDE
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+ *.swo
31
+ *~
32
+
33
+ # OS
34
+ .DS_Store
35
+ Thumbs.db
36
+
37
+ # Test
38
+ coverage.out
39
+ coverage.html
40
+ ws_test*.py
41
+ .venv-test/
42
+
43
+ # Temp
44
+ FIXPLAN.md
45
+ PROGRESS.md
46
+
47
+ # GoReleaser
48
+ /dist/
den_sdk-0.0.3/PKG-INFO ADDED
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: den-sdk
3
+ Version: 0.0.3
4
+ Summary: Python SDK for the Den sandbox API
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: httpx>=0.27.0
8
+ Requires-Dist: pydantic>=2.0.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
11
+ Requires-Dist: pytest>=8.0; extra == 'dev'
12
+ Requires-Dist: respx>=0.21; extra == 'dev'
13
+ Requires-Dist: ruff>=0.8; extra == 'dev'
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "den-sdk"
7
+ version = "0.0.3"
8
+ description = "Python SDK for the Den sandbox API"
9
+ license = "MIT"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "httpx>=0.27.0",
13
+ "pydantic>=2.0.0",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = [
18
+ "pytest>=8.0",
19
+ "pytest-asyncio>=0.24",
20
+ "respx>=0.21",
21
+ "ruff>=0.8",
22
+ ]
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src/den"]
26
+
27
+ [tool.ruff]
28
+ target-version = "py310"
29
+ line-length = 100
30
+
31
+ [tool.ruff.lint]
32
+ select = ["E", "F", "I", "N", "W", "UP"]
33
+
34
+ [tool.pytest.ini_options]
35
+ asyncio_mode = "auto"
@@ -0,0 +1,56 @@
1
+ """Den Python SDK - manage cloud sandboxes programmatically."""
2
+
3
+ from den.client import Den
4
+ from den.exceptions import (
5
+ DenError,
6
+ AuthenticationError,
7
+ NotFoundError,
8
+ RateLimitError,
9
+ ValidationError,
10
+ )
11
+ from den.sandbox import Sandbox, SandboxManager
12
+ from den.types import (
13
+ ExecResult,
14
+ FileInfo,
15
+ PortMapping,
16
+ S3ExportRequest,
17
+ S3ExportResponse,
18
+ S3ImportRequest,
19
+ S3ImportResponse,
20
+ S3SyncConfig,
21
+ SandboxConfig,
22
+ SandboxInfo,
23
+ SandboxStats,
24
+ SnapshotInfo,
25
+ StorageConfig,
26
+ TmpfsMount,
27
+ VolumeMount,
28
+ )
29
+
30
+ __all__ = [
31
+ "Den",
32
+ "DenError",
33
+ "AuthenticationError",
34
+ "ExecResult",
35
+ "FileInfo",
36
+ "NotFoundError",
37
+ "PortMapping",
38
+ "RateLimitError",
39
+ "S3ExportRequest",
40
+ "S3ExportResponse",
41
+ "S3ImportRequest",
42
+ "S3ImportResponse",
43
+ "S3SyncConfig",
44
+ "Sandbox",
45
+ "SandboxConfig",
46
+ "SandboxInfo",
47
+ "SandboxManager",
48
+ "SandboxStats",
49
+ "SnapshotInfo",
50
+ "StorageConfig",
51
+ "TmpfsMount",
52
+ "ValidationError",
53
+ "VolumeMount",
54
+ ]
55
+
56
+ __version__ = "0.0.2"
@@ -0,0 +1,166 @@
1
+ """Main client for the Den API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import httpx
6
+
7
+ from den.sandbox import SandboxManager
8
+
9
+
10
+ class Den:
11
+ """Client for the Den sandbox API.
12
+
13
+ Provides access to sandbox management and operations via both
14
+ synchronous and asynchronous interfaces.
15
+
16
+ Example (sync)::
17
+
18
+ client = Den("http://localhost:8080", api_key="sk-...")
19
+ sandbox = client.sandbox.create(image="python:3.12")
20
+ result = sandbox.exec(["python", "-c", "print('hello')"])
21
+ print(result.stdout)
22
+ sandbox.destroy()
23
+ client.close()
24
+
25
+ Example (async)::
26
+
27
+ client = Den("http://localhost:8080", api_key="sk-...")
28
+ sandbox = await client.sandbox.acreate(image="python:3.12")
29
+ result = await sandbox.aexec(["python", "-c", "print('hello')"])
30
+ print(result.stdout)
31
+ await sandbox.adestroy()
32
+ await client.aclose()
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ url: str = "http://localhost:8080",
38
+ *,
39
+ api_key: str | None = None,
40
+ timeout: float = 120.0,
41
+ ) -> None:
42
+ """Initialize the Den client.
43
+
44
+ Args:
45
+ url: Base URL of the Den server (e.g. ``http://localhost:8080``).
46
+ api_key: Optional API key for authentication.
47
+ timeout: Request timeout in seconds. Defaults to 120.
48
+ """
49
+ self._url = url.rstrip("/")
50
+ self._api_key = api_key
51
+ self._base_url = f"{self._url}/api/v1"
52
+
53
+ headers: dict[str, str] = {}
54
+ if api_key:
55
+ headers["X-API-Key"] = api_key
56
+
57
+ self._client = httpx.Client(
58
+ base_url="",
59
+ headers=headers,
60
+ timeout=timeout,
61
+ )
62
+ self._async_client = httpx.AsyncClient(
63
+ base_url="",
64
+ headers=headers,
65
+ timeout=timeout,
66
+ )
67
+
68
+ self._sandbox_manager = SandboxManager(
69
+ client=self._client,
70
+ async_client=self._async_client,
71
+ base_url=self._base_url,
72
+ )
73
+
74
+ @property
75
+ def sandbox(self) -> SandboxManager:
76
+ """Access the sandbox manager for creating and managing sandboxes."""
77
+ return self._sandbox_manager
78
+
79
+ def health(self) -> dict:
80
+ """Check server health (sync).
81
+
82
+ Returns:
83
+ Dictionary with server health status.
84
+ """
85
+ resp = self._client.get(f"{self._base_url}/health")
86
+ resp.raise_for_status()
87
+ return resp.json()
88
+
89
+ async def ahealth(self) -> dict:
90
+ """Check server health (async).
91
+
92
+ Returns:
93
+ Dictionary with server health status.
94
+ """
95
+ resp = await self._async_client.get(f"{self._base_url}/health")
96
+ resp.raise_for_status()
97
+ return resp.json()
98
+
99
+ def version(self) -> dict:
100
+ """Get server version info (sync).
101
+
102
+ Returns:
103
+ Dictionary with version, commit, and build_date.
104
+ """
105
+ resp = self._client.get(f"{self._base_url}/version")
106
+ resp.raise_for_status()
107
+ return resp.json()
108
+
109
+ async def aversion(self) -> dict:
110
+ """Get server version info (async).
111
+
112
+ Returns:
113
+ Dictionary with version, commit, and build_date.
114
+ """
115
+ resp = await self._async_client.get(f"{self._base_url}/version")
116
+ resp.raise_for_status()
117
+ return resp.json()
118
+
119
+ def close(self) -> None:
120
+ """Close the underlying HTTP clients (sync).
121
+
122
+ Note: The async client is best closed via ``await client.aclose()``
123
+ or by using ``async with Den(...)`` as a context manager.
124
+ This method attempts to close it as well, but in an async context
125
+ prefer calling ``aclose()`` directly.
126
+ """
127
+ self._client.close()
128
+ try:
129
+ import asyncio
130
+ try:
131
+ loop = asyncio.get_running_loop()
132
+ # Loop is running — schedule async close, log failures
133
+ task = loop.create_task(self._async_client.aclose())
134
+ task.add_done_callback(
135
+ lambda t: None if t.exception() is None else None
136
+ )
137
+ except RuntimeError:
138
+ # No running loop — safe to run synchronously
139
+ asyncio.run(self._async_client.aclose())
140
+ except Exception:
141
+ import warnings
142
+ warnings.warn(
143
+ "Failed to close async HTTP client; prefer using "
144
+ "'await client.aclose()' in async contexts",
145
+ ResourceWarning,
146
+ stacklevel=2,
147
+ )
148
+
149
+ async def aclose(self) -> None:
150
+ """Close the underlying HTTP clients (async)."""
151
+ await self._async_client.aclose()
152
+
153
+ def __enter__(self) -> Den:
154
+ return self
155
+
156
+ def __exit__(self, *args: object) -> None:
157
+ self.close()
158
+
159
+ async def __aenter__(self) -> Den:
160
+ return self
161
+
162
+ async def __aexit__(self, *args: object) -> None:
163
+ await self.aclose()
164
+
165
+ def __repr__(self) -> str:
166
+ return f"Den(url={self._url!r})"
@@ -0,0 +1,40 @@
1
+ """Exceptions for the Den SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class DenError(Exception):
7
+ """Base exception for all Den SDK errors."""
8
+
9
+ def __init__(self, message: str, status_code: int | None = None) -> None:
10
+ self.message = message
11
+ self.status_code = status_code
12
+ super().__init__(message)
13
+
14
+
15
+ class NotFoundError(DenError):
16
+ """Raised when a resource is not found (404)."""
17
+
18
+ def __init__(self, message: str = "Resource not found") -> None:
19
+ super().__init__(message, status_code=404)
20
+
21
+
22
+ class AuthenticationError(DenError):
23
+ """Raised when authentication fails (401/403)."""
24
+
25
+ def __init__(self, message: str = "Authentication failed") -> None:
26
+ super().__init__(message, status_code=401)
27
+
28
+
29
+ class RateLimitError(DenError):
30
+ """Raised when rate limit is exceeded (429)."""
31
+
32
+ def __init__(self, message: str = "Rate limit exceeded") -> None:
33
+ super().__init__(message, status_code=429)
34
+
35
+
36
+ class ValidationError(DenError):
37
+ """Raised when the request is invalid (400)."""
38
+
39
+ def __init__(self, message: str = "Invalid request") -> None:
40
+ super().__init__(message, status_code=400)
File without changes