Daytona MCP Python Interpreter

by nkkko
Verified
└── docs └── python-sdk ├── daytona.mdx ├── errors.mdx ├── file-system.mdx ├── git.mdx ├── lsp-server.mdx ├── process.mdx └── sandbox.mdx /docs/python-sdk/daytona.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sandbox Management 3 | --- 4 | 5 | Sandboxes are isolated development environments managed by Daytona. 6 | This guide covers how to create, manage, and remove Sandboxes using the SDK. 7 | 8 | **Examples**: 9 | 10 | Basic usage with environment variables: 11 | ```python 12 | from daytona_sdk import Daytona 13 | # Initialize using environment variables 14 | daytona = Daytona() # Uses env vars DAYTONA_API_KEY, DAYTONA_SERVER_URL, DAYTONA_TARGET 15 | 16 | # Create a default Python workspace with custom environment variables 17 | workspace = daytona.create(CreateWorkspaceParams( 18 | language="python", 19 | env_vars={"PYTHON_ENV": "development"} 20 | )) 21 | 22 | # Execute commands in the workspace 23 | response = workspace.process.execute_command('echo "Hello, World!"') 24 | print(response.result) 25 | 26 | # Run Python code securely inside the workspace 27 | response = workspace.process.code_run('print("Hello from Python!")') 28 | print(response.result) 29 | 30 | # Remove the workspace after use 31 | daytona.remove(workspace) 32 | ``` 33 | 34 | Usage with explicit configuration: 35 | ```python 36 | from daytona_sdk import Daytona, DaytonaConfig, CreateWorkspaceParams, WorkspaceResources 37 | 38 | # Initialize with explicit configuration 39 | config = DaytonaConfig( 40 | api_key="your-api-key", 41 | server_url="https://your-server.com", 42 | target="us" 43 | ) 44 | daytona = Daytona(config) 45 | 46 | # Create a custom workspace with specific resources and settings 47 | workspace = daytona.create(CreateWorkspaceParams( 48 | language="python", 49 | image="python:3.11", 50 | resources=WorkspaceResources( 51 | cpu=2, 52 | memory=4, # 4GB RAM 53 | disk=20 # 20GB disk 54 | ), 55 | env_vars={"PYTHON_ENV": "development"}, 56 | auto_stop_interval=60 # Auto-stop after 1 hour of inactivity 57 | )) 58 | 59 | # Use workspace features 60 | workspace.git.clone("https://github.com/user/repo.git") 61 | workspace.process.execute_command("python -m pytest") 62 | ``` 63 | 64 | <a id="daytona_sdk.daytona.Daytona"></a> 65 | ## Daytona 66 | 67 | ```python 68 | class Daytona() 69 | ``` 70 | 71 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L198) 72 | 73 | Main class for interacting with Daytona Server API. 74 | 75 | This class provides methods to create, manage, and interact with Daytona Sandboxes. 76 | It can be initialized either with explicit configuration or using environment variables. 77 | 78 | **Attributes**: 79 | 80 | - `api_key` _str_ - API key for authentication. 81 | - `server_url` _str_ - URL of the Daytona server. 82 | - `target` _str_ - Default target location for Sandboxes. 83 | 84 | 85 | **Example**: 86 | 87 | Using environment variables: 88 | ```python 89 | daytona = Daytona() # Uses DAYTONA_API_KEY, DAYTONA_SERVER_URL 90 | ``` 91 | 92 | Using explicit configuration: 93 | ```python 94 | config = DaytonaConfig( 95 | api_key="your-api-key", 96 | server_url="https://your-server.com", 97 | target="us" 98 | ) 99 | daytona = Daytona(config) 100 | ``` 101 | 102 | 103 | #### Daytona.\_\_init\_\_ 104 | 105 | ```python 106 | def __init__(config: Optional[DaytonaConfig] = None) 107 | ``` 108 | 109 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L226) 110 | 111 | Initializes Daytona instance with optional configuration. 112 | 113 | If no config is provided, reads from environment variables: 114 | - `DAYTONA_API_KEY`: Required API key for authentication 115 | - `DAYTONA_SERVER_URL`: Required server URL 116 | - `DAYTONA_TARGET`: Optional target environment (defaults to WorkspaceTargetRegion.US) 117 | 118 | **Arguments**: 119 | 120 | - `config` _Optional[DaytonaConfig]_ - Object containing api_key, server_url, and target. 121 | 122 | 123 | **Raises**: 124 | 125 | - `DaytonaError` - If API key or Server URL is not provided either through config or environment variables 126 | 127 | 128 | **Example**: 129 | 130 | ```python 131 | from daytona_sdk import Daytona, DaytonaConfig 132 | # Using environment variables 133 | daytona1 = Daytona() 134 | # Using explicit configuration 135 | config = DaytonaConfig( 136 | api_key="your-api-key", 137 | server_url="https://your-server.com", 138 | target="us" 139 | ) 140 | daytona2 = Daytona(config) 141 | ``` 142 | 143 | 144 | #### Daytona.create 145 | 146 | ```python 147 | @intercept_errors(message_prefix="Failed to create workspace: ") 148 | def create(params: Optional[CreateWorkspaceParams] = None, 149 | timeout: Optional[float] = 60) -> Workspace 150 | ``` 151 | 152 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L285) 153 | 154 | Creates Sandboxes with default or custom configurations. You can specify various parameters, 155 | including language, image, resources, environment variables, and volumes for the Sandbox. 156 | 157 | **Arguments**: 158 | 159 | - `params` _Optional[CreateWorkspaceParams]_ - Parameters for Sandbox creation. If not provided, 160 | defaults to Python language. 161 | - `timeout` _Optional[float]_ - Timeout (in seconds) for workspace creation. 0 means no timeout. Default is 60 seconds. 162 | 163 | 164 | **Returns**: 165 | 166 | - `Workspace` - The created Sandbox instance. 167 | 168 | 169 | **Raises**: 170 | 171 | - `DaytonaError` - If timeout or auto_stop_interval is negative; If workspace fails to start or times out 172 | 173 | 174 | **Example**: 175 | 176 | Create a default Python Sandbox: 177 | ```python 178 | workspace = daytona.create() 179 | ``` 180 | 181 | Create a custom Sandbox: 182 | ```python 183 | params = CreateWorkspaceParams( 184 | language="python", 185 | name="my-workspace", 186 | image="debian:12.9", 187 | env_vars={"DEBUG": "true"}, 188 | resources=WorkspaceResources(cpu=2, memory=4096), 189 | auto_stop_interval=0 190 | ) 191 | workspace = daytona.create(params, 40) 192 | ``` 193 | 194 | 195 | #### Daytona.remove 196 | 197 | ```python 198 | @intercept_errors(message_prefix="Failed to remove workspace: ") 199 | def remove(workspace: Workspace, timeout: Optional[float] = 60) -> None 200 | ``` 201 | 202 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L432) 203 | 204 | Removes a Sandbox. 205 | 206 | **Arguments**: 207 | 208 | - `workspace` _Workspace_ - The Sandbox instance to remove. 209 | - `timeout` _Optional[float]_ - Timeout (in seconds) for workspace removal. 0 means no timeout. Default is 60 seconds. 210 | 211 | 212 | **Raises**: 213 | 214 | - `DaytonaError` - If workspace fails to remove or times out 215 | 216 | 217 | **Example**: 218 | 219 | ```python 220 | workspace = daytona.create() 221 | # ... use workspace ... 222 | daytona.remove(workspace) # Clean up when done 223 | ``` 224 | 225 | 226 | #### Daytona.get\_current\_workspace 227 | 228 | ```python 229 | @intercept_errors(message_prefix="Failed to get workspace: ") 230 | def get_current_workspace(workspace_id: str) -> Workspace 231 | ``` 232 | 233 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L452) 234 | 235 | Get a Sandbox by its ID. 236 | 237 | **Arguments**: 238 | 239 | - `workspace_id` _str_ - The ID of the Sandbox to retrieve. 240 | 241 | 242 | **Returns**: 243 | 244 | - `Workspace` - The Sandbox instance. 245 | 246 | 247 | **Raises**: 248 | 249 | - `DaytonaError` - If workspace_id is not provided. 250 | 251 | 252 | **Example**: 253 | 254 | ```python 255 | workspace = daytona.get_current_workspace("my-workspace-id") 256 | print(workspace.status) 257 | ``` 258 | 259 | 260 | #### Daytona.list 261 | 262 | ```python 263 | @intercept_errors(message_prefix="Failed to list workspaces: ") 264 | def list() -> List[Workspace] 265 | ``` 266 | 267 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L490) 268 | 269 | Lists all Sandboxes. 270 | 271 | **Returns**: 272 | 273 | - `List[Workspace]` - List of all available Sandbox instances. 274 | 275 | 276 | **Example**: 277 | 278 | ```python 279 | workspaces = daytona.list() 280 | for workspace in workspaces: 281 | print(f"{workspace.id}: {workspace.status}") 282 | ``` 283 | 284 | 285 | #### Daytona.start 286 | 287 | ```python 288 | def start(workspace: Workspace, timeout: Optional[float] = 60) -> None 289 | ``` 290 | 291 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L555) 292 | 293 | Starts a Sandbox and waits for it to be ready. 294 | 295 | **Arguments**: 296 | 297 | - `workspace` _Workspace_ - The Sandbox to start. 298 | - `timeout` _Optional[float]_ - Optional timeout in seconds to wait for the Sandbox to start. 0 means no timeout. Default is 60 seconds. 299 | 300 | 301 | **Raises**: 302 | 303 | - `DaytonaError` - If timeout is negative; If Sandbox fails to start or times out 304 | 305 | 306 | #### Daytona.stop 307 | 308 | ```python 309 | def stop(workspace: Workspace, timeout: Optional[float] = 60) -> None 310 | ``` 311 | 312 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L567) 313 | 314 | Stops a Sandbox and waits for it to be stopped. 315 | 316 | **Arguments**: 317 | 318 | - `workspace` _Workspace_ - The workspace to stop 319 | - `timeout` _Optional[float]_ - Optional timeout (in seconds) for workspace stop. 0 means no timeout. Default is 60 seconds. 320 | 321 | 322 | **Raises**: 323 | 324 | - `DaytonaError` - If timeout is negative; If Sandbox fails to stop or times out 325 | 326 | 327 | Sandboxes are isolated development environments managed by Daytona. 328 | This guide covers how to create, manage, and remove Sandboxes using the SDK. 329 | 330 | **Examples**: 331 | 332 | Basic usage with environment variables: 333 | ```python 334 | from daytona_sdk import Daytona 335 | # Initialize using environment variables 336 | daytona = Daytona() # Uses env vars DAYTONA_API_KEY, DAYTONA_SERVER_URL, DAYTONA_TARGET 337 | 338 | # Create a default Python workspace with custom environment variables 339 | workspace = daytona.create(CreateWorkspaceParams( 340 | language="python", 341 | env_vars={"PYTHON_ENV": "development"} 342 | )) 343 | 344 | # Execute commands in the workspace 345 | response = workspace.process.execute_command('echo "Hello, World!"') 346 | print(response.result) 347 | 348 | # Run Python code securely inside the workspace 349 | response = workspace.process.code_run('print("Hello from Python!")') 350 | print(response.result) 351 | 352 | # Remove the workspace after use 353 | daytona.remove(workspace) 354 | ``` 355 | 356 | Usage with explicit configuration: 357 | ```python 358 | from daytona_sdk import Daytona, DaytonaConfig, CreateWorkspaceParams, WorkspaceResources 359 | 360 | # Initialize with explicit configuration 361 | config = DaytonaConfig( 362 | api_key="your-api-key", 363 | server_url="https://your-server.com", 364 | target="us" 365 | ) 366 | daytona = Daytona(config) 367 | 368 | # Create a custom workspace with specific resources and settings 369 | workspace = daytona.create(CreateWorkspaceParams( 370 | language="python", 371 | image="python:3.11", 372 | resources=WorkspaceResources( 373 | cpu=2, 374 | memory=4, # 4GB RAM 375 | disk=20 # 20GB disk 376 | ), 377 | env_vars={"PYTHON_ENV": "development"}, 378 | auto_stop_interval=60 # Auto-stop after 1 hour of inactivity 379 | )) 380 | 381 | # Use workspace features 382 | workspace.git.clone("https://github.com/user/repo.git") 383 | workspace.process.execute_command("python -m pytest") 384 | ``` 385 | 386 | 387 | <a id="daytona_sdk.daytona.CodeLanguage"></a> 388 | ## CodeLanguage 389 | 390 | ```python 391 | @dataclass 392 | class CodeLanguage(Enum) 393 | ``` 394 | 395 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L85) 396 | 397 | Programming languages supported by Daytona 398 | 399 | 400 | <a id="daytona_sdk.daytona.DaytonaConfig"></a> 401 | ## DaytonaConfig 402 | 403 | ```python 404 | @dataclass 405 | class DaytonaConfig() 406 | ``` 407 | 408 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L101) 409 | 410 | Configuration options for initializing the Daytona client. 411 | 412 | **Attributes**: 413 | 414 | - `api_key` _str_ - API key for authentication with Daytona server. 415 | - `server_url` _str_ - URL of the Daytona server. 416 | - `target` _str_ - Target environment for Sandbox. 417 | 418 | 419 | **Example**: 420 | 421 | ```python 422 | config = DaytonaConfig( 423 | api_key="your-api-key", 424 | server_url="https://your-server.com", 425 | target="us" 426 | ) 427 | daytona = Daytona(config) 428 | ``` 429 | 430 | 431 | <a id="daytona_sdk.daytona.WorkspaceResources"></a> 432 | ## WorkspaceResources 433 | 434 | ```python 435 | @dataclass 436 | class WorkspaceResources() 437 | ``` 438 | 439 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L125) 440 | 441 | Resources configuration for Sandbox. 442 | 443 | **Attributes**: 444 | 445 | - `cpu` _Optional[int]_ - Number of CPU cores to allocate. 446 | - `memory` _Optional[int]_ - Amount of memory in GB to allocate. 447 | - `disk` _Optional[int]_ - Amount of disk space in GB to allocate. 448 | - `gpu` _Optional[int]_ - Number of GPUs to allocate. 449 | 450 | 451 | **Example**: 452 | 453 | ```python 454 | resources = WorkspaceResources( 455 | cpu=2, 456 | memory=4, # 4GB RAM 457 | disk=20, # 20GB disk 458 | gpu=1 459 | ) 460 | params = CreateWorkspaceParams( 461 | language="python", 462 | resources=resources 463 | ) 464 | ``` 465 | 466 | 467 | <a id="daytona_sdk.daytona.CreateWorkspaceParams"></a> 468 | ## CreateWorkspaceParams 469 | 470 | ```python 471 | class CreateWorkspaceParams(BaseModel) 472 | ``` 473 | 474 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/daytona.py#L154) 475 | 476 | Parameters for creating a new Sandbox. 477 | 478 | **Attributes**: 479 | 480 | - `language` _CodeLanguage_ - Programming language for the Sandbox ("python", "javascript", "typescript"). 481 | - `id` _Optional[str]_ - Custom identifier for the Sandbox. If not provided, a random ID will be generated. 482 | - `name` _Optional[str]_ - Display name for the Sandbox. Defaults to Sandbox ID if not provided. 483 | - `image` _Optional[str]_ - Custom Docker image to use for the Sandbox. 484 | - `os_user` _Optional[str]_ - OS user for the Sandbox. Defaults to "daytona". 485 | - `env_vars` _Optional[Dict[str, str]]_ - Environment variables to set in the Sandbox. 486 | - `labels` _Optional[Dict[str, str]]_ - Custom labels for the Sandbox. 487 | - `public` _Optional[bool]_ - Whether the Sandbox should be public. 488 | - `target` _Optional[str]_ - Target location for the Sandbox. Can be "us", "eu", or "asia". 489 | - `resources` _Optional[WorkspaceResources]_ - Resource configuration for the Sandbox. 490 | - `timeout` _Optional[float]_ - Timeout in seconds for Sandbox to be created and started. 491 | - `auto_stop_interval` _Optional[int]_ - Interval in minutes after which Sandbox will automatically stop if no Sandbox event occurs during that time. Default is 15 minutes. 0 means no auto-stop. 492 | 493 | 494 | **Example**: 495 | 496 | ```python 497 | params = CreateWorkspaceParams( 498 | language="python", 499 | name="my-workspace", 500 | env_vars={"DEBUG": "true"}, 501 | resources=WorkspaceResources(cpu=2, memory=4), 502 | auto_stop_interval=20 503 | ) 504 | workspace = daytona.create(params, 50) 505 | ``` 506 | 507 | 508 | -------------------------------------------------------------------------------- /docs/python-sdk/errors.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Errors 3 | --- 4 | 5 | <a id="daytona_sdk.common.errors.DaytonaError"></a> 6 | ## DaytonaError 7 | 8 | ```python 9 | class DaytonaError(Exception) 10 | ``` 11 | 12 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/common/errors.py#L1) 13 | 14 | Base error for Daytona SDK. 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/python-sdk/file-system.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: File System Operations 3 | --- 4 | 5 | The Daytona SDK provides comprehensive file system operations through the `fs` module in Sandboxes. 6 | You can perform various operations like listing files, creating directories, reading and writing files, and more. 7 | This guide covers all available file system operations and best practices. 8 | 9 | **Examples**: 10 | 11 | Basic file operations: 12 | ```python 13 | workspace = daytona.create() 14 | 15 | # Create a directory 16 | workspace.fs.create_folder("/workspace/data", "755") 17 | 18 | # Upload a file 19 | with open("local_file.txt", "rb") as f: 20 | content = f.read() 21 | workspace.fs.upload_file("/workspace/data/file.txt", content) 22 | 23 | # List directory contents 24 | files = workspace.fs.list_files("/workspace") 25 | for file in files: 26 | print(f"Name: {file.name}") 27 | print(f"Is directory: {file.is_dir}") 28 | print(f"Size: {file.size}") 29 | print(f"Modified: {file.mod_time}") 30 | 31 | # Search file contents 32 | matches = workspace.fs.find_files( 33 | path="/workspace/src", 34 | pattern="text-of-interest" 35 | ) 36 | for match in matches: 37 | print(f"Absolute file path: {match.file}") 38 | print(f"Line number: {match.line}") 39 | print(f"Line content: {match.content}") 40 | print(" 41 | ") 42 | ``` 43 | 44 | File manipulation: 45 | ```python 46 | # Move files 47 | workspace.fs.move_files( 48 | "/workspace/data/old.txt", 49 | "/workspace/data/new.txt" 50 | ) 51 | 52 | # Replace text in files 53 | results = workspace.fs.replace_in_files( 54 | files=["/workspace/data/new.txt"], 55 | pattern="old_version", 56 | new_value="new_version" 57 | ) 58 | 59 | # Set permissions 60 | workspace.fs.set_file_permissions( 61 | path="/workspace/data/script.sh", 62 | mode="755", 63 | owner="daytona" 64 | ) 65 | ``` 66 | 67 | 68 | **Notes**: 69 | 70 | All paths should be absolute paths within the Sandbox if not explicitly 71 | stated otherwise. 72 | 73 | <a id="daytona_sdk.filesystem.FileSystem"></a> 74 | ## FileSystem 75 | 76 | ```python 77 | class FileSystem() 78 | ``` 79 | 80 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L80) 81 | 82 | Provides file system operations within a Sandbox. 83 | 84 | This class implements a high-level interface to file system operations that can 85 | be performed within a Daytona Sandbox. It supports common operations like 86 | creating, deleting, and moving files, as well as searching file contents and 87 | managing permissions. 88 | 89 | **Attributes**: 90 | 91 | - `instance` _WorkspaceInstance_ - The Sandbox instance this file system belongs to. 92 | 93 | 94 | #### FileSystem.\_\_init\_\_ 95 | 96 | ```python 97 | def __init__(instance: WorkspaceInstance, toolbox_api: ToolboxApi) 98 | ``` 99 | 100 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L92) 101 | 102 | Initializes a new FileSystem instance. 103 | 104 | **Arguments**: 105 | 106 | - `instance` _WorkspaceInstance_ - The Sandbox instance this file system belongs to. 107 | - `toolbox_api` _ToolboxApi_ - API client for Sandbox operations. 108 | 109 | 110 | #### FileSystem.create\_folder 111 | 112 | ```python 113 | @intercept_errors(message_prefix="Failed to create folder: ") 114 | def create_folder(path: str, mode: str) -> None 115 | ``` 116 | 117 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L103) 118 | 119 | Creates a new directory in the Sandbox. 120 | 121 | This method creates a new directory at the specified path with the given 122 | permissions. 123 | 124 | **Arguments**: 125 | 126 | - `path` _str_ - Absolute path where the folder should be created. 127 | - `mode` _str_ - Folder permissions in octal format (e.g., "755" for rwxr-xr-x). 128 | 129 | 130 | **Example**: 131 | 132 | ```python 133 | # Create a directory with standard permissions 134 | workspace.fs.create_folder("/workspace/data", "755") 135 | 136 | # Create a private directory 137 | workspace.fs.create_folder("/workspace/secrets", "700") 138 | ``` 139 | 140 | 141 | #### FileSystem.delete\_file 142 | 143 | ```python 144 | @intercept_errors(message_prefix="Failed to delete file: ") 145 | def delete_file(path: str) -> None 146 | ``` 147 | 148 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L127) 149 | 150 | Deletes a file from the Sandbox. 151 | 152 | This method permanently deletes a file from the Sandbox. 153 | 154 | **Arguments**: 155 | 156 | - `path` _str_ - Absolute path to the file to delete. 157 | 158 | 159 | **Example**: 160 | 161 | ```python 162 | # Delete a file 163 | workspace.fs.delete_file("/workspace/data/old_file.txt") 164 | ``` 165 | 166 | 167 | #### FileSystem.download\_file 168 | 169 | ```python 170 | @intercept_errors(message_prefix="Failed to download file: ") 171 | def download_file(path: str) -> bytes 172 | ``` 173 | 174 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L146) 175 | 176 | Downloads a file from the Sandbox. 177 | 178 | This method retrieves the contents of a file from the Sandbox. 179 | 180 | **Arguments**: 181 | 182 | - `path` _str_ - Absolute path to the file to download. 183 | 184 | 185 | **Returns**: 186 | 187 | - `bytes` - The file contents as a bytes object. 188 | 189 | 190 | **Example**: 191 | 192 | ```python 193 | # Download and save a file locally 194 | content = workspace.fs.download_file("/workspace/data/file.txt") 195 | with open("local_copy.txt", "wb") as f: 196 | f.write(content) 197 | 198 | # Download and process text content 199 | content = workspace.fs.download_file("/workspace/data/config.json") 200 | config = json.loads(content.decode('utf-8')) 201 | ``` 202 | 203 | 204 | #### FileSystem.find\_files 205 | 206 | ```python 207 | @intercept_errors(message_prefix="Failed to find files: ") 208 | def find_files(path: str, pattern: str) -> List[Match] 209 | ``` 210 | 211 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L174) 212 | 213 | Searches for files containing a pattern. 214 | 215 | This method searches file contents for a specified pattern, similar to 216 | the grep command. 217 | 218 | **Arguments**: 219 | 220 | - `path` _str_ - Absolute path to the file or directory to search. If the path is a directory, the search will be performed recursively. 221 | - `pattern` _str_ - Search pattern to match against file contents. 222 | 223 | 224 | **Returns**: 225 | 226 | - `List[Match]` - List of matches found in files. Each Match object includes: 227 | - file: Path to the file containing the match 228 | - line: The line number where the match was found 229 | - content: The matching line content 230 | 231 | 232 | **Example**: 233 | 234 | ```python 235 | # Search for TODOs in Python files 236 | matches = workspace.fs.find_files("/workspace/src", "TODO:") 237 | for match in matches: 238 | print(f"{match.file}:{match.line}: {match.content.strip()}") 239 | ``` 240 | 241 | 242 | #### FileSystem.get\_file\_info 243 | 244 | ```python 245 | @intercept_errors(message_prefix="Failed to get file info: ") 246 | def get_file_info(path: str) -> FileInfo 247 | ``` 248 | 249 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L203) 250 | 251 | Gets detailed information about a file. 252 | 253 | This method retrieves metadata about a file or directory, including its 254 | size, permissions, and timestamps. 255 | 256 | **Arguments**: 257 | 258 | - `path` _str_ - Absolute path to the file or directory. 259 | 260 | 261 | **Returns**: 262 | 263 | - `FileInfo` - Detailed file information including: 264 | - name: File name 265 | - is_dir: Whether the path is a directory 266 | - size: File size in bytes 267 | - mode: File permissions 268 | - mod_time: Last modification timestamp 269 | - permissions: File permissions in octal format 270 | - owner: File owner 271 | - group: File group 272 | 273 | 274 | **Example**: 275 | 276 | ```python 277 | # Get file metadata 278 | info = workspace.fs.get_file_info("/workspace/data/file.txt") 279 | print(f"Size: {info.size} bytes") 280 | print(f"Modified: {info.mod_time}") 281 | print(f"Mode: {info.mode}") 282 | 283 | # Check if path is a directory 284 | info = workspace.fs.get_file_info("/workspace/data") 285 | if info.is_dir: 286 | print("Path is a directory") 287 | ``` 288 | 289 | 290 | #### FileSystem.list\_files 291 | 292 | ```python 293 | @intercept_errors(message_prefix="Failed to list files: ") 294 | def list_files(path: str) -> List[FileInfo] 295 | ``` 296 | 297 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L242) 298 | 299 | Lists files and directories in a given path. 300 | 301 | This method returns information about all files and directories in the 302 | specified directory, similar to the ls -l command. 303 | 304 | **Arguments**: 305 | 306 | - `path` _str_ - Absolute path to the directory to list contents from. 307 | 308 | 309 | **Returns**: 310 | 311 | - `List[FileInfo]` - List of file and directory information. Each FileInfo 312 | object includes the same fields as described in get_file_info(). 313 | 314 | 315 | **Example**: 316 | 317 | ```python 318 | # List directory contents 319 | files = workspace.fs.list_files("/workspace/data") 320 | 321 | # Print files and their sizes 322 | for file in files: 323 | if not file.is_dir: 324 | print(f"{file.name}: {file.size} bytes") 325 | 326 | # List only directories 327 | dirs = [f for f in files if f.is_dir] 328 | print("Subdirectories:", ", ".join(d.name for d in dirs)) 329 | ``` 330 | 331 | 332 | #### FileSystem.move\_files 333 | 334 | ```python 335 | @intercept_errors(message_prefix="Failed to move files: ") 336 | def move_files(source: str, destination: str) -> None 337 | ``` 338 | 339 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L275) 340 | 341 | Moves files from one location to another. 342 | 343 | This method moves or renames a file or directory. The parent directory 344 | of the destination must exist. 345 | 346 | **Arguments**: 347 | 348 | - `source` _str_ - Absolute path to the source file or directory. 349 | - `destination` _str_ - Absolute path to the destination. 350 | 351 | 352 | **Example**: 353 | 354 | ```python 355 | # Rename a file 356 | workspace.fs.move_files( 357 | "/workspace/data/old_name.txt", 358 | "/workspace/data/new_name.txt" 359 | ) 360 | 361 | # Move a file to a different directory 362 | workspace.fs.move_files( 363 | "/workspace/data/file.txt", 364 | "/workspace/archive/file.txt" 365 | ) 366 | 367 | # Move a directory 368 | workspace.fs.move_files( 369 | "/workspace/old_dir", 370 | "/workspace/new_dir" 371 | ) 372 | ``` 373 | 374 | 375 | #### FileSystem.replace\_in\_files 376 | 377 | ```python 378 | @intercept_errors(message_prefix="Failed to replace in files: ") 379 | def replace_in_files(files: List[str], pattern: str, 380 | new_value: str) -> List[ReplaceResult] 381 | ``` 382 | 383 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L313) 384 | 385 | Replaces text in multiple files. 386 | 387 | This method performs search and replace operations across multiple files. 388 | 389 | **Arguments**: 390 | 391 | - `files` _List[str]_ - List of absolute file paths to perform replacements in. 392 | - `pattern` _str_ - Pattern to search for. 393 | - `new_value` _str_ - Text to replace matches with. 394 | 395 | 396 | **Returns**: 397 | 398 | - `List[ReplaceResult]` - List of results indicating replacements made in 399 | each file. Each ReplaceResult includes: 400 | - file: Path to the modified file 401 | - success: Whether the operation was successful 402 | - error: Error message if the operation failed 403 | 404 | 405 | **Example**: 406 | 407 | ```python 408 | # Replace in specific files 409 | results = workspace.fs.replace_in_files( 410 | files=["/workspace/src/file1.py", "/workspace/src/file2.py"], 411 | pattern="old_function", 412 | new_value="new_function" 413 | ) 414 | 415 | # Print results 416 | for result in results: 417 | if result.success: 418 | print(f"{result.file}: {result.success}") 419 | else: 420 | print(f"{result.file}: {result.error}") 421 | ``` 422 | 423 | 424 | #### FileSystem.search\_files 425 | 426 | ```python 427 | @intercept_errors(message_prefix="Failed to search files: ") 428 | def search_files(path: str, pattern: str) -> SearchFilesResponse 429 | ``` 430 | 431 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L358) 432 | 433 | Searches for files and directories matching a pattern in their names. 434 | 435 | This method searches for files and directories whose names match the 436 | specified pattern. The pattern can be a simple string or a glob pattern. 437 | 438 | **Arguments**: 439 | 440 | - `path` _str_ - Absolute path to the root directory to start search from. 441 | - `pattern` _str_ - Pattern to match against file names. Supports glob 442 | patterns (e.g., "*.py" for Python files). 443 | 444 | 445 | **Returns**: 446 | 447 | - `SearchFilesResponse` - Search results containing: 448 | - files: List of matching file and directory paths 449 | 450 | 451 | **Example**: 452 | 453 | ```python 454 | # Find all Python files 455 | result = workspace.fs.search_files("/workspace", "*.py") 456 | for file in result.files: 457 | print(file) 458 | 459 | # Find files with specific prefix 460 | result = workspace.fs.search_files("/workspace/data", "test_*") 461 | print(f"Found {len(result.files)} test files") 462 | ``` 463 | 464 | 465 | #### FileSystem.set\_file\_permissions 466 | 467 | ```python 468 | @intercept_errors(message_prefix="Failed to set file permissions: ") 469 | def set_file_permissions(path: str, 470 | mode: str = None, 471 | owner: str = None, 472 | group: str = None) -> None 473 | ``` 474 | 475 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L390) 476 | 477 | Sets permissions and ownership for a file or directory. 478 | 479 | This method allows changing the permissions and ownership of a file or 480 | directory. Any of the parameters can be None to leave that attribute 481 | unchanged. 482 | 483 | **Arguments**: 484 | 485 | - `path` _str_ - Absolute path to the file or directory. 486 | - `mode` _Optional[str]_ - File mode/permissions in octal format 487 | (e.g., "644" for rw-r--r--). 488 | - `owner` _Optional[str]_ - User owner of the file. 489 | - `group` _Optional[str]_ - Group owner of the file. 490 | 491 | 492 | **Example**: 493 | 494 | ```python 495 | # Make a file executable 496 | workspace.fs.set_file_permissions( 497 | path="/workspace/scripts/run.sh", 498 | mode="755" # rwxr-xr-x 499 | ) 500 | 501 | # Change file owner 502 | workspace.fs.set_file_permissions( 503 | path="/workspace/data/file.txt", 504 | owner="daytona", 505 | group="daytona" 506 | ) 507 | ``` 508 | 509 | 510 | #### FileSystem.upload\_file 511 | 512 | ```python 513 | @intercept_errors(message_prefix="Failed to upload file: ") 514 | def upload_file(path: str, file: bytes) -> None 515 | ``` 516 | 517 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/filesystem.py#L431) 518 | 519 | Uploads a file to the Sandbox. 520 | 521 | This method uploads a file to the specified path in the Sandbox. The 522 | parent directory must exist. If a file already exists at the destination 523 | path, it will be overwritten. 524 | 525 | **Arguments**: 526 | 527 | - `path` _str_ - Absolute destination path in the Sandbox. 528 | - `file` _bytes_ - File contents as a bytes object. 529 | 530 | 531 | **Example**: 532 | 533 | ```python 534 | # Upload a text file 535 | content = b"Hello, World!" 536 | workspace.fs.upload_file("/workspace/data/hello.txt", content) 537 | 538 | # Upload a local file 539 | with open("local_file.txt", "rb") as f: 540 | content = f.read() 541 | workspace.fs.upload_file("/workspace/data/file.txt", content) 542 | 543 | # Upload binary data 544 | import json 545 | data = {"key": "value"} 546 | content = json.dumps(data).encode('utf-8') 547 | workspace.fs.upload_file("/workspace/data/config.json", content) 548 | ``` 549 | 550 | 551 | The Daytona SDK provides comprehensive file system operations through the `fs` module in Sandboxes. 552 | You can perform various operations like listing files, creating directories, reading and writing files, and more. 553 | This guide covers all available file system operations and best practices. 554 | 555 | **Examples**: 556 | 557 | Basic file operations: 558 | ```python 559 | workspace = daytona.create() 560 | 561 | # Create a directory 562 | workspace.fs.create_folder("/workspace/data", "755") 563 | 564 | # Upload a file 565 | with open("local_file.txt", "rb") as f: 566 | content = f.read() 567 | workspace.fs.upload_file("/workspace/data/file.txt", content) 568 | 569 | # List directory contents 570 | files = workspace.fs.list_files("/workspace") 571 | for file in files: 572 | print(f"Name: {file.name}") 573 | print(f"Is directory: {file.is_dir}") 574 | print(f"Size: {file.size}") 575 | print(f"Modified: {file.mod_time}") 576 | 577 | # Search file contents 578 | matches = workspace.fs.find_files( 579 | path="/workspace/src", 580 | pattern="text-of-interest" 581 | ) 582 | for match in matches: 583 | print(f"Absolute file path: {match.file}") 584 | print(f"Line number: {match.line}") 585 | print(f"Line content: {match.content}") 586 | print(" 587 | ") 588 | ``` 589 | 590 | File manipulation: 591 | ```python 592 | # Move files 593 | workspace.fs.move_files( 594 | "/workspace/data/old.txt", 595 | "/workspace/data/new.txt" 596 | ) 597 | 598 | # Replace text in files 599 | results = workspace.fs.replace_in_files( 600 | files=["/workspace/data/new.txt"], 601 | pattern="old_version", 602 | new_value="new_version" 603 | ) 604 | 605 | # Set permissions 606 | workspace.fs.set_file_permissions( 607 | path="/workspace/data/script.sh", 608 | mode="755", 609 | owner="daytona" 610 | ) 611 | ``` 612 | 613 | 614 | **Notes**: 615 | 616 | All paths should be absolute paths within the Sandbox if not explicitly 617 | stated otherwise. 618 | 619 | 620 | -------------------------------------------------------------------------------- /docs/python-sdk/git.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Git Operations 3 | --- 4 | 5 | The Daytona SDK provides built-in Git support. This guide covers all available Git 6 | operations and best practices. Daytona SDK provides an option to clone, check status, 7 | and manage Git repositories in Sandboxes. You can interact with Git repositories using 8 | the `git` module. 9 | 10 | **Example**: 11 | 12 | Basic Git workflow: 13 | ```python 14 | workspace = daytona.create() 15 | 16 | # Clone a repository 17 | workspace.git.clone( 18 | url="https://github.com/user/repo.git", 19 | path="/workspace/repo" 20 | ) 21 | 22 | # Make some changes 23 | workspace.fs.upload_file("/workspace/repo/test.txt", "Hello, World!") 24 | 25 | # Stage and commit changes 26 | workspace.git.add("/workspace/repo", ["test.txt"]) 27 | workspace.git.commit( 28 | path="/workspace/repo", 29 | message="Add test file", 30 | author="John Doe", 31 | email="john@example.com" 32 | ) 33 | 34 | # Push changes (with authentication) 35 | workspace.git.push( 36 | path="/workspace/repo", 37 | username="user", 38 | password="token" 39 | ) 40 | ``` 41 | 42 | 43 | **Notes**: 44 | 45 | All paths should be absolute paths within the Sandbox if not explicitly 46 | stated otherwise. 47 | 48 | <a id="daytona_sdk.git.Git"></a> 49 | ## Git 50 | 51 | ```python 52 | class Git() 53 | ``` 54 | 55 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L60) 56 | 57 | Provides Git operations within a Sandbox. 58 | 59 | This class implements a high-level interface to Git operations that can be 60 | performed within a Daytona Sandbox. It supports common Git operations like 61 | cloning repositories, staging and committing changes, pushing and pulling 62 | changes, and checking repository status. 63 | 64 | **Attributes**: 65 | 66 | - `workspace` _Workspace_ - The parent Sandbox instance. 67 | - `instance` _WorkspaceInstance_ - The Sandbox instance this Git handler belongs to. 68 | 69 | 70 | **Example**: 71 | 72 | ```python 73 | # Clone a repository 74 | workspace.git.clone( 75 | url="https://github.com/user/repo.git", 76 | path="/workspace/repo" 77 | ) 78 | 79 | # Check repository status 80 | status = workspace.git.status("/workspace/repo") 81 | print(f"Modified files: {status.modified}") 82 | 83 | # Stage and commit changes 84 | workspace.git.add("/workspace/repo", ["file.txt"]) 85 | workspace.git.commit( 86 | path="/workspace/repo", 87 | message="Update file", 88 | author="John Doe", 89 | email="john@example.com" 90 | ) 91 | ``` 92 | 93 | 94 | #### Git.\_\_init\_\_ 95 | 96 | ```python 97 | def __init__(workspace: "Workspace", toolbox_api: ToolboxApi, 98 | instance: WorkspaceInstance) 99 | ``` 100 | 101 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L95) 102 | 103 | Initializes a new Git handler instance. 104 | 105 | **Arguments**: 106 | 107 | - `workspace` _Workspace_ - The parent Sandbox instance. 108 | - `toolbox_api` _ToolboxApi_ - API client for Sandbox operations. 109 | - `instance` _WorkspaceInstance_ - The Sandbox instance this Git handler belongs to. 110 | 111 | 112 | #### Git.add 113 | 114 | ```python 115 | @intercept_errors(message_prefix="Failed to add files: ") 116 | def add(path: str, files: List[str]) -> None 117 | ``` 118 | 119 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L113) 120 | 121 | Stages files for commit. 122 | 123 | This method stages the specified files for the next commit, similar to 124 | running 'git add' on the command line. 125 | 126 | **Arguments**: 127 | 128 | - `path` _str_ - Absolute path to the Git repository root. 129 | - `files` _List[str]_ - List of file paths or directories to stage, relative to the repository root. 130 | 131 | 132 | **Example**: 133 | 134 | ```python 135 | # Stage a single file 136 | workspace.git.add("/workspace/repo", ["file.txt"]) 137 | 138 | # Stage multiple files 139 | workspace.git.add("/workspace/repo", [ 140 | "src/main.py", 141 | "tests/test_main.py", 142 | "README.md" 143 | ]) 144 | ``` 145 | 146 | 147 | #### Git.branches 148 | 149 | ```python 150 | @intercept_errors(message_prefix="Failed to list branches: ") 151 | def branches(path: str) -> ListBranchResponse 152 | ``` 153 | 154 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L145) 155 | 156 | Lists branches in the repository. 157 | 158 | This method returns information about all branches in the repository. 159 | 160 | **Arguments**: 161 | 162 | - `path` _str_ - Absolute path to the Git repository root. 163 | 164 | 165 | **Returns**: 166 | 167 | - `ListBranchResponse` - List of branches in the repository. 168 | 169 | 170 | **Example**: 171 | 172 | ```python 173 | response = workspace.git.branches("/workspace/repo") 174 | print(f"Branches: {response.branches}") 175 | ``` 176 | 177 | 178 | #### Git.clone 179 | 180 | ```python 181 | @intercept_errors(message_prefix="Failed to clone repository: ") 182 | def clone(url: str, 183 | path: str, 184 | branch: Optional[str] = None, 185 | commit_id: Optional[str] = None, 186 | username: Optional[str] = None, 187 | password: Optional[str] = None) -> None 188 | ``` 189 | 190 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L168) 191 | 192 | Clones a Git repository. 193 | 194 | This method clones a Git repository into the specified path. It supports 195 | cloning specific branches or commits, and can authenticate with the remote 196 | repository if credentials are provided. 197 | 198 | **Arguments**: 199 | 200 | - `url` _str_ - Repository URL to clone from. 201 | - `path` _str_ - Absolute path where the repository should be cloned. 202 | - `branch` _Optional[str]_ - Specific branch to clone. If not specified, 203 | clones the default branch. 204 | - `commit_id` _Optional[str]_ - Specific commit to clone. If specified, 205 | the repository will be left in a detached HEAD state at this commit. 206 | - `username` _Optional[str]_ - Git username for authentication. 207 | - `password` _Optional[str]_ - Git password or token for authentication. 208 | 209 | 210 | **Example**: 211 | 212 | ```python 213 | # Clone the default branch 214 | workspace.git.clone( 215 | url="https://github.com/user/repo.git", 216 | path="/workspace/repo" 217 | ) 218 | 219 | # Clone a specific branch with authentication 220 | workspace.git.clone( 221 | url="https://github.com/user/private-repo.git", 222 | path="/workspace/private", 223 | branch="develop", 224 | username="user", 225 | password="token" 226 | ) 227 | 228 | # Clone a specific commit 229 | workspace.git.clone( 230 | url="https://github.com/user/repo.git", 231 | path="/workspace/repo-old", 232 | commit_id="abc123" 233 | ) 234 | ``` 235 | 236 | 237 | #### Git.commit 238 | 239 | ```python 240 | @intercept_errors(message_prefix="Failed to commit changes: ") 241 | def commit(path: str, message: str, author: str, email: str) -> None 242 | ``` 243 | 244 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L231) 245 | 246 | Commits staged changes. 247 | 248 | This method creates a new commit with the staged changes. Make sure to stage 249 | changes using the add() method before committing. 250 | 251 | **Arguments**: 252 | 253 | - `path` _str_ - Absolute path to the Git repository root. 254 | - `message` _str_ - Commit message describing the changes. 255 | - `author` _str_ - Name of the commit author. 256 | - `email` _str_ - Email address of the commit author. 257 | 258 | 259 | **Example**: 260 | 261 | ```python 262 | # Stage and commit changes 263 | workspace.git.add("/workspace/repo", ["README.md"]) 264 | workspace.git.commit( 265 | path="/workspace/repo", 266 | message="Update documentation", 267 | author="John Doe", 268 | email="john@example.com" 269 | ) 270 | ``` 271 | 272 | 273 | #### Git.push 274 | 275 | ```python 276 | @intercept_errors(message_prefix="Failed to push changes: ") 277 | def push(path: str, 278 | username: Optional[str] = None, 279 | password: Optional[str] = None) -> None 280 | ``` 281 | 282 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L266) 283 | 284 | Pushes local commits to the remote repository. 285 | 286 | This method pushes all local commits on the current branch to the remote 287 | repository. If the remote repository requires authentication, provide 288 | username and password/token. 289 | 290 | **Arguments**: 291 | 292 | - `path` _str_ - Absolute path to the Git repository root. 293 | - `username` _Optional[str]_ - Git username for authentication. 294 | - `password` _Optional[str]_ - Git password or token for authentication. 295 | 296 | 297 | **Example**: 298 | 299 | ```python 300 | # Push without authentication (for public repos or SSH) 301 | workspace.git.push("/workspace/repo") 302 | 303 | # Push with authentication 304 | workspace.git.push( 305 | path="/workspace/repo", 306 | username="user", 307 | password="github_token" 308 | ) 309 | ``` 310 | 311 | 312 | #### Git.pull 313 | 314 | ```python 315 | @intercept_errors(message_prefix="Failed to pull changes: ") 316 | def pull(path: str, 317 | username: Optional[str] = None, 318 | password: Optional[str] = None) -> None 319 | ``` 320 | 321 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L303) 322 | 323 | Pulls changes from the remote repository. 324 | 325 | This method fetches and merges changes from the remote repository into 326 | the current branch. If the remote repository requires authentication, 327 | provide username and password/token. 328 | 329 | **Arguments**: 330 | 331 | - `path` _str_ - Absolute path to the Git repository root. 332 | - `username` _Optional[str]_ - Git username for authentication. 333 | - `password` _Optional[str]_ - Git password or token for authentication. 334 | 335 | 336 | **Example**: 337 | 338 | ```python 339 | # Pull without authentication 340 | workspace.git.pull("/workspace/repo") 341 | 342 | # Pull with authentication 343 | workspace.git.pull( 344 | path="/workspace/repo", 345 | username="user", 346 | password="github_token" 347 | ) 348 | ``` 349 | 350 | 351 | #### Git.status 352 | 353 | ```python 354 | @intercept_errors(message_prefix="Failed to get status: ") 355 | def status(path: str) -> GitStatus 356 | ``` 357 | 358 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/git.py#L340) 359 | 360 | Gets the current Git repository status. 361 | 362 | This method returns detailed information about the current state of the 363 | repository, including staged, unstaged, and untracked files. 364 | 365 | **Arguments**: 366 | 367 | - `path` _str_ - Absolute path to the Git repository root. 368 | 369 | 370 | **Returns**: 371 | 372 | - `GitStatus` - Repository status information including: 373 | - current_branch: Current branch name 374 | - file_status: List of file statuses 375 | - ahead: Number of local commits not pushed to remote 376 | - behind: Number of remote commits not pulled locally 377 | - branch_published: Whether the branch has been published to the remote repository 378 | 379 | 380 | **Example**: 381 | 382 | ```python 383 | status = workspace.git.status("/workspace/repo") 384 | print(f"On branch: {status.current_branch}") 385 | print(f"Commits ahead: {status.ahead}") 386 | print(f"Commits behind: {status.behind}") 387 | ``` 388 | 389 | 390 | The Daytona SDK provides built-in Git support. This guide covers all available Git 391 | operations and best practices. Daytona SDK provides an option to clone, check status, 392 | and manage Git repositories in Sandboxes. You can interact with Git repositories using 393 | the `git` module. 394 | 395 | **Example**: 396 | 397 | Basic Git workflow: 398 | ```python 399 | workspace = daytona.create() 400 | 401 | # Clone a repository 402 | workspace.git.clone( 403 | url="https://github.com/user/repo.git", 404 | path="/workspace/repo" 405 | ) 406 | 407 | # Make some changes 408 | workspace.fs.upload_file("/workspace/repo/test.txt", "Hello, World!") 409 | 410 | # Stage and commit changes 411 | workspace.git.add("/workspace/repo", ["test.txt"]) 412 | workspace.git.commit( 413 | path="/workspace/repo", 414 | message="Add test file", 415 | author="John Doe", 416 | email="john@example.com" 417 | ) 418 | 419 | # Push changes (with authentication) 420 | workspace.git.push( 421 | path="/workspace/repo", 422 | username="user", 423 | password="token" 424 | ) 425 | ``` 426 | 427 | 428 | **Notes**: 429 | 430 | All paths should be absolute paths within the Sandbox if not explicitly 431 | stated otherwise. 432 | 433 | 434 | -------------------------------------------------------------------------------- /docs/python-sdk/lsp-server.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Language Server Protocol 3 | --- 4 | 5 | The Daytona SDK provides Language Server Protocol (LSP) support through Sandbox instances. 6 | This enables advanced language features like code completion, diagnostics, and more. 7 | 8 | **Example**: 9 | 10 | Basic LSP server usage: 11 | ```python 12 | workspace = daytona.create() 13 | 14 | # Create and start LSP server 15 | lsp = workspace.create_lsp_server("typescript", "/workspace/project") 16 | lsp.start() 17 | 18 | # Open a file for editing 19 | lsp.did_open("/workspace/project/src/index.ts") 20 | 21 | # Get completions at a position 22 | pos = Position(line=10, character=15) 23 | completions = lsp.completions("/workspace/project/src/index.ts", pos) 24 | print(f"Completions: {completions}") 25 | 26 | # Get document symbols 27 | symbols = lsp.document_symbols("/workspace/project/src/index.ts") 28 | for symbol in symbols: 29 | print(f"{symbol.name}: {symbol.kind}") 30 | 31 | # Clean up 32 | lsp.did_close("/workspace/project/src/index.ts") 33 | lsp.stop() 34 | ``` 35 | 36 | 37 | **Notes**: 38 | 39 | The LSP server must be started with start() before using any other methods, 40 | and should be stopped with stop() when no longer needed to free resources. 41 | 42 | <a id="daytona_sdk.lsp_server.LspServer"></a> 43 | ## LspServer 44 | 45 | ```python 46 | class LspServer() 47 | ``` 48 | 49 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L86) 50 | 51 | Provides Language Server Protocol functionality for code intelligence. 52 | 53 | This class implements a subset of the Language Server Protocol (LSP) to provide 54 | IDE-like features such as code completion, symbol search, and more. 55 | 56 | **Attributes**: 57 | 58 | - `language_id` _LspLanguageId_ - The language server type (e.g., "python", "typescript"). 59 | - `path_to_project` _str_ - Absolute path to the project root directory. 60 | - `instance` _WorkspaceInstance_ - The Sandbox instance this server belongs to. 61 | 62 | 63 | #### LspServer.\_\_init\_\_ 64 | 65 | ```python 66 | def __init__(language_id: LspLanguageId, path_to_project: str, 67 | toolbox_api: ToolboxApi, instance: WorkspaceInstance) 68 | ``` 69 | 70 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L98) 71 | 72 | Initializes a new LSP server instance. 73 | 74 | **Arguments**: 75 | 76 | - `language_id` _LspLanguageId_ - The language server type (e.g., LspLanguageId.TYPESCRIPT). 77 | - `path_to_project` _str_ - Absolute path to the project root directory. 78 | - `toolbox_api` _ToolboxApi_ - API client for Sandbox operations. 79 | - `instance` _WorkspaceInstance_ - The Sandbox instance this server belongs to. 80 | 81 | 82 | #### LspServer.start 83 | 84 | ```python 85 | @intercept_errors(message_prefix="Failed to start LSP server: ") 86 | def start() -> None 87 | ``` 88 | 89 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L119) 90 | 91 | Starts the language server. 92 | 93 | This method must be called before using any other LSP functionality. 94 | It initializes the language server for the specified language and project. 95 | 96 | **Example**: 97 | 98 | ```python 99 | lsp = workspace.create_lsp_server("typescript", "/workspace/project") 100 | lsp.start() # Initialize the server 101 | # Now ready for LSP operations 102 | ``` 103 | 104 | 105 | #### LspServer.stop 106 | 107 | ```python 108 | @intercept_errors(message_prefix="Failed to stop LSP server: ") 109 | def stop() -> None 110 | ``` 111 | 112 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L141) 113 | 114 | Stops the language server. 115 | 116 | This method should be called when the LSP server is no longer needed to 117 | free up system resources. 118 | 119 | **Example**: 120 | 121 | ```python 122 | # When done with LSP features 123 | lsp.stop() # Clean up resources 124 | ``` 125 | 126 | 127 | #### LspServer.did\_open 128 | 129 | ```python 130 | @intercept_errors(message_prefix="Failed to open file: ") 131 | def did_open(path: str) -> None 132 | ``` 133 | 134 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L162) 135 | 136 | Notifies the language server that a file has been opened. 137 | 138 | This method should be called when a file is opened in the editor to enable 139 | language features like diagnostics and completions for that file. The server 140 | will begin tracking the file's contents and providing language features. 141 | 142 | **Arguments**: 143 | 144 | - `path` _str_ - Absolute path to the opened file. 145 | 146 | 147 | **Example**: 148 | 149 | ```python 150 | # When opening a file for editing 151 | lsp.did_open("/workspace/project/src/index.ts") 152 | # Now can get completions, symbols, etc. for this file 153 | ``` 154 | 155 | 156 | #### LspServer.did\_close 157 | 158 | ```python 159 | @intercept_errors(message_prefix="Failed to close file: ") 160 | def did_close(path: str) -> None 161 | ``` 162 | 163 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L189) 164 | 165 | Notify the language server that a file has been closed. 166 | 167 | This method should be called when a file is closed in the editor to allow 168 | the language server to clean up any resources associated with that file. 169 | 170 | **Arguments**: 171 | 172 | - `path` _str_ - Absolute path to the closed file. 173 | 174 | 175 | **Example**: 176 | 177 | ```python 178 | # When done editing a file 179 | lsp.did_close("/workspace/project/src/index.ts") 180 | ``` 181 | 182 | 183 | #### LspServer.document\_symbols 184 | 185 | ```python 186 | @intercept_errors(message_prefix="Failed to get symbols from document: ") 187 | def document_symbols(path: str) -> List[LspSymbol] 188 | ``` 189 | 190 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L213) 191 | 192 | Gets symbol information from a document. 193 | 194 | This method returns information about all symbols (functions, classes, 195 | variables, etc.) defined in the specified document. 196 | 197 | **Arguments**: 198 | 199 | - `path` _str_ - Absolute path to the file to get symbols from. 200 | 201 | 202 | **Returns**: 203 | 204 | - `List[LspSymbol]` - List of symbols in the document. Each symbol includes: 205 | - name: The symbol's name 206 | - kind: The symbol's kind (function, class, variable, etc.) 207 | - location: The location of the symbol in the file 208 | 209 | 210 | **Example**: 211 | 212 | ```python 213 | # Get all symbols in a file 214 | symbols = lsp.document_symbols("/workspace/project/src/index.ts") 215 | for symbol in symbols: 216 | print(f"{symbol.kind} {symbol.name}: {symbol.location}") 217 | ``` 218 | 219 | 220 | #### LspServer.workspace\_symbols 221 | 222 | ```python 223 | @intercept_errors(message_prefix="Failed to get symbols from workspace: ") 224 | def workspace_symbols(query: str) -> List[LspSymbol] 225 | ``` 226 | 227 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L244) 228 | 229 | Searches for symbols across the entire Sandbox. 230 | 231 | This method searches for symbols matching the query string across all files 232 | in the Sandbox. It's useful for finding declarations and definitions 233 | without knowing which file they're in. 234 | 235 | **Arguments**: 236 | 237 | - `query` _str_ - Search query to match against symbol names. 238 | 239 | 240 | **Returns**: 241 | 242 | - `List[LspSymbol]` - List of matching symbols from all files. Each symbol 243 | includes: 244 | - name: The symbol's name 245 | - kind: The symbol's kind (function, class, variable, etc.) 246 | - location: The location of the symbol in the file 247 | 248 | 249 | **Example**: 250 | 251 | ```python 252 | # Search for all symbols containing "User" 253 | symbols = lsp.workspace_symbols("User") 254 | for symbol in symbols: 255 | print(f"{symbol.name} in {symbol.location}") 256 | ``` 257 | 258 | 259 | #### LspServer.completions 260 | 261 | ```python 262 | @intercept_errors(message_prefix="Failed to get completions: ") 263 | def completions(path: str, position: Position) -> CompletionList 264 | ``` 265 | 266 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L277) 267 | 268 | Gets completion suggestions at a position in a file. 269 | 270 | **Arguments**: 271 | 272 | - `path` _str_ - Absolute path to the file. 273 | - `position` _Position_ - Cursor position to get completions for. 274 | 275 | 276 | **Returns**: 277 | 278 | - `CompletionList` - List of completion suggestions. The list includes: 279 | - isIncomplete: Whether more items might be available 280 | - items: List of completion items, each containing: 281 | - label: The text to insert 282 | - kind: The kind of completion 283 | - detail: Additional details about the item 284 | - documentation: Documentation for the item 285 | - sortText: Text used to sort the item in the list 286 | - filterText: Text used to filter the item 287 | - insertText: The actual text to insert (if different from label) 288 | 289 | 290 | **Example**: 291 | 292 | ```python 293 | # Get completions at a specific position 294 | pos = Position(line=10, character=15) 295 | completions = lsp.completions("/workspace/project/src/index.ts", pos) 296 | for item in completions.items: 297 | print(f"{item.label} ({item.kind}): {item.detail}") 298 | ``` 299 | 300 | 301 | The Daytona SDK provides Language Server Protocol (LSP) support through Sandbox instances. 302 | This enables advanced language features like code completion, diagnostics, and more. 303 | 304 | **Example**: 305 | 306 | Basic LSP server usage: 307 | ```python 308 | workspace = daytona.create() 309 | 310 | # Create and start LSP server 311 | lsp = workspace.create_lsp_server("typescript", "/workspace/project") 312 | lsp.start() 313 | 314 | # Open a file for editing 315 | lsp.did_open("/workspace/project/src/index.ts") 316 | 317 | # Get completions at a position 318 | pos = Position(line=10, character=15) 319 | completions = lsp.completions("/workspace/project/src/index.ts", pos) 320 | print(f"Completions: {completions}") 321 | 322 | # Get document symbols 323 | symbols = lsp.document_symbols("/workspace/project/src/index.ts") 324 | for symbol in symbols: 325 | print(f"{symbol.name}: {symbol.kind}") 326 | 327 | # Clean up 328 | lsp.did_close("/workspace/project/src/index.ts") 329 | lsp.stop() 330 | ``` 331 | 332 | 333 | **Notes**: 334 | 335 | The LSP server must be started with start() before using any other methods, 336 | and should be stopped with stop() when no longer needed to free resources. 337 | 338 | 339 | <a id="daytona_sdk.lsp_server.Position"></a> 340 | ## Position 341 | 342 | ```python 343 | class Position() 344 | ``` 345 | 346 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L64) 347 | 348 | Represents a position in a text document. 349 | 350 | This class represents a zero-based position within a text document, 351 | specified by line number and character offset. 352 | 353 | **Attributes**: 354 | 355 | - `line` _int_ - Zero-based line number in the document. 356 | - `character` _int_ - Zero-based character offset on the line. 357 | 358 | 359 | #### Position.\_\_init\_\_ 360 | 361 | ```python 362 | def __init__(line: int, character: int) 363 | ``` 364 | 365 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/lsp_server.py#L75) 366 | 367 | Initialize a new Position instance. 368 | 369 | **Arguments**: 370 | 371 | - `line` _int_ - Zero-based line number in the document. 372 | - `character` _int_ - Zero-based character offset on the line. 373 | 374 | 375 | -------------------------------------------------------------------------------- /docs/python-sdk/process.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Process and Code Execution 3 | --- 4 | 5 | <a id="daytona_sdk.process.Process"></a> 6 | ## Process 7 | 8 | ```python 9 | class Process() 10 | ``` 11 | 12 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L57) 13 | 14 | Handles process and code execution within a Sandbox. 15 | 16 | This class provides methods for executing shell commands and running code in 17 | the Sandbox environment. 18 | 19 | **Attributes**: 20 | 21 | - `code_toolbox` _WorkspacePythonCodeToolbox_ - Language-specific code execution toolbox. 22 | - `toolbox_api` _ToolboxApi_ - API client for Sandbox operations. 23 | - `instance` _WorkspaceInstance_ - The Sandbox instance this process belongs to. 24 | 25 | 26 | #### Process.\_\_init\_\_ 27 | 28 | ```python 29 | def __init__(code_toolbox: WorkspacePythonCodeToolbox, toolbox_api: ToolboxApi, 30 | instance: WorkspaceInstance) 31 | ``` 32 | 33 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L69) 34 | 35 | Initialize a new Process instance. 36 | 37 | **Arguments**: 38 | 39 | - `code_toolbox` _WorkspacePythonCodeToolbox_ - Language-specific code execution toolbox. 40 | - `toolbox_api` _ToolboxApi_ - API client for Sandbox operations. 41 | - `instance` _WorkspaceInstance_ - The Sandbox instance this process belongs to. 42 | 43 | 44 | #### Process.exec 45 | 46 | ```python 47 | @intercept_errors(message_prefix="Failed to execute command: ") 48 | def exec(command: str, 49 | cwd: Optional[str] = None, 50 | timeout: Optional[int] = None) -> ExecuteResponse 51 | ``` 52 | 53 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L87) 54 | 55 | Execute a shell command in the Sandbox. 56 | 57 | **Arguments**: 58 | 59 | - `command` _str_ - Shell command to execute. 60 | - `cwd` _Optional[str]_ - Working directory for command execution. If not 61 | specified, uses the Sandbox root directory. 62 | - `timeout` _Optional[int]_ - Maximum time in seconds to wait for the command 63 | to complete. 0 means wait indefinitely. 64 | 65 | 66 | **Returns**: 67 | 68 | - `ExecuteResponse` - Command execution results containing: 69 | - exit_code: The command's exit status 70 | - result: Standard output from the command 71 | 72 | 73 | **Example**: 74 | 75 | ```python 76 | # Simple command 77 | response = workspace.process.exec("echo 'Hello'") 78 | print(response.result) # Prints: Hello 79 | 80 | # Command with working directory 81 | result = workspace.process.exec("ls", cwd="/workspace/src") 82 | 83 | # Command with timeout 84 | result = workspace.process.exec("sleep 10", timeout=5) 85 | ``` 86 | 87 | 88 | #### Process.code\_run 89 | 90 | ```python 91 | def code_run(code: str, 92 | params: Optional[CodeRunParams] = None, 93 | timeout: Optional[int] = None) -> ExecuteResponse 94 | ``` 95 | 96 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L126) 97 | 98 | Executes code in the Sandbox using the appropriate language runtime. 99 | 100 | **Arguments**: 101 | 102 | - `code` _str_ - Code to execute. 103 | - `params` _Optional[CodeRunParams]_ - Parameters for code execution. 104 | - `timeout` _Optional[int]_ - Maximum time in seconds to wait for the code 105 | to complete. 0 means wait indefinitely. 106 | 107 | 108 | **Returns**: 109 | 110 | - `ExecuteResponse` - Code execution result containing: 111 | - exit_code: The execution's exit status 112 | - result: Standard output from the code 113 | 114 | 115 | **Example**: 116 | 117 | ```python 118 | # Run Python code 119 | response = workspace.process.code_run(''' 120 | x = 10 121 | y = 20 122 | print(f"Sum: {x + y}") 123 | ''') 124 | print(response.result) # Prints: Sum: 30 125 | ``` 126 | 127 | 128 | #### Process.create\_session 129 | 130 | ```python 131 | @intercept_errors(message_prefix="Failed to create session: ") 132 | def create_session(session_id: str) -> None 133 | ``` 134 | 135 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L155) 136 | 137 | Create a new long-running background session in the Sandbox. 138 | 139 | Sessions are background processes that maintain state between commands, making them ideal for 140 | scenarios requiring multiple related commands or persistent environment setup. You can run 141 | long-running commands and monitor process status. 142 | 143 | **Arguments**: 144 | 145 | - `session_id` _str_ - Unique identifier for the new session. 146 | 147 | 148 | **Example**: 149 | 150 | ```python 151 | # Create a new session 152 | session_id = "my-session" 153 | workspace.process.create_session(session_id) 154 | session = workspace.process.get_session(session_id) 155 | # Do work... 156 | workspace.process.delete_session(session_id) 157 | ``` 158 | 159 | 160 | #### Process.get\_session 161 | 162 | ```python 163 | @intercept_errors(message_prefix="Failed to get session: ") 164 | def get_session(session_id: str) -> Session 165 | ``` 166 | 167 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L182) 168 | 169 | Get a session in the Sandbox. 170 | 171 | **Arguments**: 172 | 173 | - `session_id` _str_ - Unique identifier of the session to retrieve. 174 | 175 | 176 | **Returns**: 177 | 178 | - `Session` - Session information including: 179 | - session_id: The session's unique identifier 180 | - commands: List of commands executed in the session 181 | 182 | 183 | **Example**: 184 | 185 | ```python 186 | session = workspace.process.get_session("my-session") 187 | for cmd in session.commands: 188 | print(f"Command: {cmd.command}") 189 | ``` 190 | 191 | 192 | #### Process.get\_session\_command 193 | 194 | ```python 195 | @intercept_errors(message_prefix="Failed to get session command: ") 196 | def get_session_command(session_id: str, command_id: str) -> Command 197 | ``` 198 | 199 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L206) 200 | 201 | Get information about a specific command executed in a session. 202 | 203 | **Arguments**: 204 | 205 | - `session_id` _str_ - Unique identifier of the session. 206 | - `command_id` _str_ - Unique identifier of the command. 207 | 208 | 209 | **Returns**: 210 | 211 | - `Command` - Command information including: 212 | - id: The command's unique identifier 213 | - command: The executed command string 214 | - exit_code: Command's exit status (if completed) 215 | 216 | 217 | **Example**: 218 | 219 | ```python 220 | cmd = workspace.process.get_session_command("my-session", "cmd-123") 221 | if cmd.exit_code == 0: 222 | print(f"Command {cmd.command} completed successfully") 223 | ``` 224 | 225 | 226 | #### Process.execute\_session\_command 227 | 228 | ```python 229 | @intercept_errors(message_prefix="Failed to execute session command: ") 230 | def execute_session_command( 231 | session_id: str, 232 | req: SessionExecuteRequest, 233 | timeout: Optional[int] = None) -> SessionExecuteResponse 234 | ``` 235 | 236 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L233) 237 | 238 | Executes a command in the session. 239 | 240 | **Arguments**: 241 | 242 | - `session_id` _str_ - Unique identifier of the session to use. 243 | - `req` _SessionExecuteRequest_ - Command execution request containing: 244 | - command: The command to execute 245 | - var_async: Whether to execute asynchronously 246 | 247 | 248 | **Returns**: 249 | 250 | - `SessionExecuteResponse` - Command execution results containing: 251 | - cmd_id: Unique identifier for the executed command 252 | - output: Command output (if synchronous execution) 253 | - exit_code: Command exit status (if synchronous execution) 254 | 255 | 256 | **Example**: 257 | 258 | ```python 259 | # Execute commands in sequence, maintaining state 260 | session_id = "my-session" 261 | 262 | # Change directory 263 | req = SessionExecuteRequest(command="cd /workspace") 264 | workspace.process.execute_session_command(session_id, req) 265 | 266 | # Create a file 267 | req = SessionExecuteRequest(command="echo 'Hello' > test.txt") 268 | workspace.process.execute_session_command(session_id, req) 269 | 270 | # Read the file 271 | req = SessionExecuteRequest(command="cat test.txt") 272 | result = workspace.process.execute_session_command(session_id, req) 273 | print(result.output) # Prints: Hello 274 | ``` 275 | 276 | 277 | #### Process.get\_session\_command\_logs 278 | 279 | ```python 280 | @intercept_errors(message_prefix="Failed to get session command logs: ") 281 | def get_session_command_logs(session_id: str, command_id: str) -> str 282 | ``` 283 | 284 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L275) 285 | 286 | Get the logs for a command executed in a session. 287 | 288 | This method retrieves the complete output (stdout and stderr) from a 289 | command executed in a session. It's particularly useful for checking 290 | the output of asynchronous commands. 291 | 292 | **Arguments**: 293 | 294 | - `session_id` _str_ - Unique identifier of the session. 295 | - `command_id` _str_ - Unique identifier of the command. 296 | 297 | 298 | **Returns**: 299 | 300 | - `str` - Complete command output including both stdout and stderr. 301 | 302 | 303 | **Example**: 304 | 305 | ```python 306 | # Execute a long-running command asynchronously 307 | req = SessionExecuteRequest( 308 | command="sleep 5; echo 'Done'", 309 | var_async=True 310 | ) 311 | response = workspace.process.execute_session_command("my-session", req) 312 | 313 | # Wait a bit, then get the logs 314 | import time 315 | time.sleep(6) 316 | logs = workspace.process.get_session_command_logs( 317 | "my-session", 318 | response.command_id 319 | ) 320 | print(logs) # Prints: Done 321 | ``` 322 | 323 | 324 | #### Process.list\_sessions 325 | 326 | ```python 327 | @intercept_errors(message_prefix="Failed to list sessions: ") 328 | def list_sessions() -> List[Session] 329 | ``` 330 | 331 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L315) 332 | 333 | List all sessions in the Sandbox. 334 | 335 | **Returns**: 336 | 337 | - `List[Session]` - List of all sessions in the Sandbox. 338 | 339 | 340 | **Example**: 341 | 342 | ```python 343 | sessions = workspace.process.list_sessions() 344 | for session in sessions: 345 | print(f"Session {session.session_id}:") 346 | print(f" Commands: {len(session.commands)}") 347 | ``` 348 | 349 | 350 | #### Process.delete\_session 351 | 352 | ```python 353 | @intercept_errors(message_prefix="Failed to delete session: ") 354 | def delete_session(session_id: str) -> None 355 | ``` 356 | 357 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/process.py#L334) 358 | 359 | Delete an interactive session from the Sandbox. 360 | 361 | This method terminates and removes a session, cleaning up any resources 362 | associated with it. 363 | 364 | **Arguments**: 365 | 366 | - `session_id` _str_ - Unique identifier of the session to delete. 367 | 368 | 369 | **Example**: 370 | 371 | ```python 372 | # Create and use a session 373 | workspace.process.create_session("temp-session") 374 | # ... use the session ... 375 | 376 | # Clean up when done 377 | workspace.process.delete_session("temp-session") 378 | ``` 379 | 380 | 381 | 382 | <a id="daytona_sdk.common.code_run_params.CodeRunParams"></a> 383 | ## CodeRunParams 384 | 385 | ```python 386 | @dataclass 387 | class CodeRunParams() 388 | ``` 389 | 390 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/common/code_run_params.py#L5) 391 | 392 | Parameters for code execution. 393 | 394 | The Daytona SDK provides powerful process and code execution capabilities through 395 | the `process` module in Sandboxes. This guide covers all available process operations 396 | and best practices. 397 | 398 | **Example**: 399 | 400 | Basic command execution: 401 | ```python 402 | workspace = daytona.create() 403 | 404 | # Execute a shell command 405 | response = workspace.process.exec("ls -la") 406 | print(response.result) 407 | 408 | # Run Python code 409 | response = workspace.process.code_run("print('Hello, World!')") 410 | print(response.result) 411 | ``` 412 | 413 | Using interactive sessions: 414 | ```python 415 | # Create a new session 416 | session_id = "my-session" 417 | workspace.process.create_session(session_id) 418 | 419 | # Execute commands in the session 420 | req = SessionExecuteRequest(command="cd /workspace", var_async=False) 421 | workspace.process.execute_session_command(session_id, req) 422 | 423 | req = SessionExecuteRequest(command="pwd", var_async=False) 424 | response = workspace.process.execute_session_command(session_id, req) 425 | print(response.result) # Should print "/workspace" 426 | 427 | # Clean up 428 | workspace.process.delete_session(session_id) 429 | ``` 430 | 431 | 432 | -------------------------------------------------------------------------------- /docs/python-sdk/sandbox.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sandbox 3 | --- 4 | 5 | The Daytona SDK core Sandbox functionality. 6 | 7 | Provides the main Workspace class representing a Daytona Sandbox that coordinates file system, 8 | Git, process execution, and LSP functionality. It serves as the central point 9 | for interacting with Daytona Sandboxes. 10 | 11 | **Example**: 12 | 13 | Basic Sandbox operations: 14 | ```python 15 | from daytona_sdk import Daytona 16 | daytona = Daytona() 17 | workspace = daytona.create() 18 | 19 | # File operations 20 | workspace.fs.upload_file("/workspace/test.txt", b"Hello, World!") 21 | content = workspace.fs.download_file("/workspace/test.txt") 22 | 23 | # Git operations 24 | workspace.git.clone("https://github.com/user/repo.git") 25 | 26 | # Process execution 27 | response = workspace.process.exec("ls -la") 28 | print(response.result) 29 | 30 | # LSP functionality 31 | lsp = workspace.create_lsp_server("python", "/workspace/project") 32 | lsp.did_open("/workspace/project/src/index.ts") 33 | completions = lsp.completions("/workspace/project/src/index.ts", Position(line=10, character=15)) 34 | print(completions) 35 | ``` 36 | 37 | 38 | **Notes**: 39 | 40 | The Sandbox must be in a 'started' state before performing operations. 41 | 42 | <a id="daytona_sdk.workspace.Workspace"></a> 43 | ## Workspace 44 | 45 | ```python 46 | class Workspace() 47 | ``` 48 | 49 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L181) 50 | 51 | Represents a Daytona Sandbox. 52 | 53 | A Sandbox provides file system operations, Git operations, process execution, 54 | and LSP functionality. It serves as the main interface for interacting with 55 | a Daytona Sandbox. 56 | 57 | **Attributes**: 58 | 59 | - `id` _str_ - Unique identifier for the Sandbox. 60 | - `instance` _WorkspaceInstance_ - The underlying Sandbox instance. 61 | - `code_toolbox` _WorkspaceCodeToolbox_ - Language-specific toolbox implementation. 62 | - `fs` _FileSystem_ - File system operations interface. 63 | - `git` _Git_ - Git operations interface. 64 | - `process` _Process_ - Process execution interface. 65 | 66 | 67 | #### Workspace.\_\_init\_\_ 68 | 69 | ```python 70 | def __init__(id: str, instance: WorkspaceInstance, workspace_api: WorkspaceApi, 71 | toolbox_api: ToolboxApi, code_toolbox: WorkspaceCodeToolbox) 72 | ``` 73 | 74 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L197) 75 | 76 | Initialize a new Workspace instance. 77 | 78 | **Arguments**: 79 | 80 | - `id` _str_ - Unique identifier for the Sandbox. 81 | - `instance` _WorkspaceInstance_ - The underlying Sandbox instance. 82 | - `workspace_api` _WorkspaceApi_ - API client for Sandbox operations. 83 | - `toolbox_api` _ToolboxApi_ - API client for toolbox operations. 84 | - `code_toolbox` _WorkspaceCodeToolbox_ - Language-specific toolbox implementation. 85 | 86 | 87 | #### Workspace.info 88 | 89 | ```python 90 | def info() -> WorkspaceInfo 91 | ``` 92 | 93 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L227) 94 | 95 | Gets structured information about the Sandbox. 96 | 97 | **Returns**: 98 | 99 | - `WorkspaceInfo` - Detailed information about the Sandbox including its 100 | configuration, resources, and current state. 101 | 102 | 103 | **Example**: 104 | 105 | ```python 106 | info = workspace.info() 107 | print(f"Workspace {info.name}:") 108 | print(f"State: {info.state}") 109 | print(f"Resources: {info.resources.cpu} CPU, {info.resources.memory} RAM") 110 | ``` 111 | 112 | 113 | #### Workspace.get\_workspace\_root\_dir 114 | 115 | ```python 116 | @intercept_errors(message_prefix="Failed to get workspace root directory: ") 117 | def get_workspace_root_dir() -> str 118 | ``` 119 | 120 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L246) 121 | 122 | Gets the root directory path of the Sandbox. 123 | 124 | **Returns**: 125 | 126 | - `str` - The absolute path to the Sandbox root directory. 127 | 128 | 129 | **Example**: 130 | 131 | ```python 132 | root_dir = workspace.get_workspace_root_dir() 133 | print(f"Workspace root: {root_dir}") 134 | ``` 135 | 136 | 137 | #### Workspace.create\_lsp\_server 138 | 139 | ```python 140 | def create_lsp_server(language_id: LspLanguageId, 141 | path_to_project: str) -> LspServer 142 | ``` 143 | 144 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L263) 145 | 146 | Creates a new Language Server Protocol (LSP) server instance. 147 | 148 | The LSP server provides language-specific features like code completion, 149 | diagnostics, and more. 150 | 151 | **Arguments**: 152 | 153 | - `language_id` _LspLanguageId_ - The language server type (e.g., LspLanguageId.PYTHON). 154 | - `path_to_project` _str_ - Absolute path to the project root directory. 155 | 156 | 157 | **Returns**: 158 | 159 | - `LspServer` - A new LSP server instance configured for the specified language. 160 | 161 | 162 | **Example**: 163 | 164 | ```python 165 | lsp = workspace.create_lsp_server("python", "/workspace/project") 166 | ``` 167 | 168 | 169 | #### Workspace.set\_labels 170 | 171 | ```python 172 | @intercept_errors(message_prefix="Failed to set labels: ") 173 | def set_labels(labels: Dict[str, str]) -> Dict[str, str] 174 | ``` 175 | 176 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L286) 177 | 178 | Sets labels for the Sandbox. 179 | 180 | Labels are key-value pairs that can be used to organize and identify Sandboxes. 181 | 182 | **Arguments**: 183 | 184 | - `labels` _Dict[str, str]_ - Dictionary of key-value pairs representing Sandbox labels. 185 | 186 | 187 | **Returns**: 188 | 189 | Dict[str, str]: Dictionary containing the updated Sandbox labels. 190 | 191 | 192 | **Example**: 193 | 194 | ```python 195 | new_labels = workspace.set_labels({ 196 | "project": "my-project", 197 | "environment": "development", 198 | "team": "backend" 199 | }) 200 | print(f"Updated labels: {new_labels}") 201 | ``` 202 | 203 | 204 | #### Workspace.start 205 | 206 | ```python 207 | @intercept_errors(message_prefix="Failed to start workspace: ") 208 | @with_timeout( 209 | error_message=lambda self, timeout: 210 | f"Workspace {self.id} failed to start within the {timeout} seconds timeout period" 211 | ) 212 | def start(timeout: Optional[float] = 60) 213 | ``` 214 | 215 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L315) 216 | 217 | Starts the Sandbox. 218 | 219 | This method starts the Sandbox and waits for it to be ready. 220 | 221 | **Arguments**: 222 | 223 | - `timeout` _Optional[float]_ - Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 224 | 225 | 226 | **Raises**: 227 | 228 | - `DaytonaError` - If timeout is negative. If workspace fails to start or times out. 229 | 230 | 231 | **Example**: 232 | 233 | ```python 234 | workspace = daytona.get_current_workspace("my-workspace") 235 | workspace.start(timeout=40) # Wait up to 40 seconds 236 | print("Workspace started successfully") 237 | ``` 238 | 239 | 240 | #### Workspace.stop 241 | 242 | ```python 243 | @intercept_errors(message_prefix="Failed to stop workspace: ") 244 | @with_timeout( 245 | error_message=lambda self, timeout: 246 | f"Workspace {self.id} failed to stop within the {timeout} seconds timeout period" 247 | ) 248 | def stop(timeout: Optional[float] = 60) 249 | ``` 250 | 251 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L338) 252 | 253 | Stops the Sandbox. 254 | 255 | This method stops the Sandbox and waits for it to be fully stopped. 256 | 257 | **Arguments**: 258 | 259 | - `timeout` _Optional[float]_ - Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 260 | 261 | 262 | **Raises**: 263 | 264 | - `DaytonaError` - If timeout is negative; If workspace fails to stop or times out 265 | 266 | 267 | **Example**: 268 | 269 | ```python 270 | workspace = daytona.get_current_workspace("my-workspace") 271 | workspace.stop() 272 | print("Workspace stopped successfully") 273 | ``` 274 | 275 | 276 | #### Workspace.wait\_for\_workspace\_start 277 | 278 | ```python 279 | @intercept_errors( 280 | message_prefix="Failure during waiting for workspace to start: ") 281 | @with_timeout( 282 | error_message=lambda self, timeout: 283 | f"Workspace {self.id} failed to become ready within the {timeout} seconds timeout period" 284 | ) 285 | def wait_for_workspace_start(timeout: Optional[float] = 60) -> None 286 | ``` 287 | 288 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L361) 289 | 290 | Waits for the Sandbox to reach the 'started' state. 291 | 292 | This method polls the Sandbox status until it reaches the 'started' state 293 | or encounters an error. 294 | 295 | **Arguments**: 296 | 297 | - `timeout` _Optional[float]_ - Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 298 | 299 | 300 | **Raises**: 301 | 302 | - `DaytonaError` - If timeout is negative; If workspace fails to start or times out 303 | 304 | 305 | #### Workspace.wait\_for\_workspace\_stop 306 | 307 | ```python 308 | @intercept_errors( 309 | message_prefix="Failure during waiting for workspace to stop: ") 310 | @with_timeout( 311 | error_message=lambda self, timeout: 312 | f"Workspace {self.id} failed to become stopped within the {timeout} seconds timeout period" 313 | ) 314 | def wait_for_workspace_stop(timeout: Optional[float] = 60) -> None 315 | ``` 316 | 317 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L387) 318 | 319 | Waits for the Sandbox to reach the 'stopped' state. 320 | 321 | This method polls the Sandbox status until it reaches the 'stopped' state 322 | or encounters an error. It will wait up to 60 seconds for the Sandbox to stop. 323 | 324 | **Arguments**: 325 | 326 | - `timeout` _Optional[float]_ - Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 327 | 328 | 329 | **Raises**: 330 | 331 | - `DaytonaError` - If timeout is negative. If Sandbox fails to stop or times out. 332 | 333 | 334 | #### Workspace.set\_autostop\_interval 335 | 336 | ```python 337 | @intercept_errors(message_prefix="Failed to set auto-stop interval: ") 338 | def set_autostop_interval(interval: int) -> None 339 | ``` 340 | 341 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L418) 342 | 343 | Sets the auto-stop interval for the Sandbox. 344 | 345 | The Sandbox will automatically stop after being idle (no new events) for the specified interval. 346 | Events include any state changes or interactions with the Sandbox through the SDK. 347 | Interactions using Sandbox Previews are not included. 348 | 349 | **Arguments**: 350 | 351 | - `interval` _int_ - Number of minutes of inactivity before auto-stopping. 352 | Set to 0 to disable auto-stop. Defaults to 15. 353 | 354 | 355 | **Raises**: 356 | 357 | - `DaytonaError` - If interval is negative 358 | 359 | 360 | **Example**: 361 | 362 | ```python 363 | # Auto-stop after 1 hour 364 | workspace.set_autostop_interval(60) 365 | # Or disable auto-stop 366 | workspace.set_autostop_interval(0) 367 | ``` 368 | 369 | 370 | #### Workspace.get\_preview\_link 371 | 372 | ```python 373 | @intercept_errors(message_prefix="Failed to get preview link: ") 374 | def get_preview_link(port: int) -> str 375 | ``` 376 | 377 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L448) 378 | 379 | Gets the preview link for the workspace at a specific port. If the port is not open, it will open it and return the link. 380 | 381 | **Arguments**: 382 | 383 | - `port` _int_ - The port to open the preview link on 384 | 385 | 386 | **Returns**: 387 | 388 | The preview link for the workspace at the specified port 389 | 390 | 391 | #### Workspace.archive 392 | 393 | ```python 394 | @intercept_errors(message_prefix="Failed to archive workspace: ") 395 | def archive() -> None 396 | ``` 397 | 398 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L466) 399 | 400 | Archives the workspace, making it inactive and preserving its state. When sandboxes are archived, the entire filesystem 401 | state is moved to cost-effective object storage, making it possible to keep sandboxes available for an extended period. 402 | The tradeoff between archived and stopped states is that starting an archived sandbox takes more time, depending on its size. 403 | Workspace must be stopped before archiving. 404 | 405 | 406 | The Daytona SDK core Sandbox functionality. 407 | 408 | Provides the main Workspace class representing a Daytona Sandbox that coordinates file system, 409 | Git, process execution, and LSP functionality. It serves as the central point 410 | for interacting with Daytona Sandboxes. 411 | 412 | **Example**: 413 | 414 | Basic Sandbox operations: 415 | ```python 416 | from daytona_sdk import Daytona 417 | daytona = Daytona() 418 | workspace = daytona.create() 419 | 420 | # File operations 421 | workspace.fs.upload_file("/workspace/test.txt", b"Hello, World!") 422 | content = workspace.fs.download_file("/workspace/test.txt") 423 | 424 | # Git operations 425 | workspace.git.clone("https://github.com/user/repo.git") 426 | 427 | # Process execution 428 | response = workspace.process.exec("ls -la") 429 | print(response.result) 430 | 431 | # LSP functionality 432 | lsp = workspace.create_lsp_server("python", "/workspace/project") 433 | lsp.did_open("/workspace/project/src/index.ts") 434 | completions = lsp.completions("/workspace/project/src/index.ts", Position(line=10, character=15)) 435 | print(completions) 436 | ``` 437 | 438 | 439 | **Notes**: 440 | 441 | The Sandbox must be in a 'started' state before performing operations. 442 | 443 | 444 | <a id="daytona_sdk.workspace.WorkspaceTargetRegion"></a> 445 | ## WorkspaceTargetRegion 446 | 447 | ```python 448 | @dataclass 449 | class WorkspaceTargetRegion(Enum) 450 | ``` 451 | 452 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L58) 453 | 454 | Target regions for workspaces 455 | 456 | 457 | <a id="daytona_sdk.workspace.WorkspaceResources"></a> 458 | ## WorkspaceResources 459 | 460 | ```python 461 | @dataclass 462 | class WorkspaceResources() 463 | ``` 464 | 465 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L74) 466 | 467 | Resources allocated to a Sandbox. 468 | 469 | **Attributes**: 470 | 471 | - `cpu` _str_ - Number of CPU cores allocated (e.g., "1", "2"). 472 | - `gpu` _Optional[str]_ - Number of GPUs allocated (e.g., "1") or None if no GPU. 473 | - `memory` _str_ - Amount of memory allocated with unit (e.g., "2Gi", "4Gi"). 474 | - `disk` _str_ - Amount of disk space allocated with unit (e.g., "10Gi", "20Gi"). 475 | 476 | 477 | **Example**: 478 | 479 | ```python 480 | resources = WorkspaceResources( 481 | cpu="2", 482 | gpu="1", 483 | memory="4Gi", 484 | disk="20Gi" 485 | ) 486 | ``` 487 | 488 | 489 | <a id="daytona_sdk.workspace.WorkspaceState"></a> 490 | ## WorkspaceState 491 | 492 | ```python 493 | @dataclass 494 | class WorkspaceState(Enum) 495 | ``` 496 | 497 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L100) 498 | 499 | States of a Sandbox. 500 | 501 | 502 | <a id="daytona_sdk.workspace.WorkspaceInfo"></a> 503 | ## WorkspaceInfo 504 | 505 | ```python 506 | class WorkspaceInfo(ApiWorkspaceInfo) 507 | ``` 508 | 509 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L124) 510 | 511 | Structured information about a Sandbox. 512 | 513 | This class provides detailed information about a Sandbox's configuration, 514 | resources, and current state. 515 | 516 | **Attributes**: 517 | 518 | - `id` _str_ - Unique identifier for the Sandbox. 519 | - `name` _str_ - Display name of the Sandbox. 520 | - `image` _str_ - Docker image used for the Sandbox. 521 | - `user` _str_ - OS user running in the Sandbox. 522 | - `env` _Dict[str, str]_ - Environment variables set in the Sandbox. 523 | - `labels` _Dict[str, str]_ - Custom labels attached to the Sandbox. 524 | - `public` _bool_ - Whether the Sandbox is publicly accessible. 525 | - `target` _str_ - Target environment where the Sandbox runs. 526 | - `resources` _WorkspaceResources_ - Resource allocations for the Sandbox. 527 | - `state` _str_ - Current state of the Sandbox (e.g., "started", "stopped"). 528 | - `error_reason` _Optional[str]_ - Error message if Sandbox is in error state. 529 | - `snapshot_state` _Optional[str]_ - Current state of Sandbox snapshot. 530 | - `snapshot_state_created_at` _Optional[datetime]_ - When the snapshot state was created. 531 | 532 | 533 | **Example**: 534 | 535 | ```python 536 | workspace = daytona.create() 537 | info = workspace.info() 538 | print(f"Workspace {info.name} is {info.state}") 539 | print(f"Resources: {info.resources.cpu} CPU, {info.resources.memory} RAM") 540 | ``` 541 | 542 | 543 | <a id="daytona_sdk.workspace.WorkspaceInstance"></a> 544 | ## WorkspaceInstance 545 | 546 | ```python 547 | class WorkspaceInstance(ApiWorkspace) 548 | ``` 549 | 550 | [[view_source]](https://github.com/daytonaio/sdk/blob/e2c391f367e740a14945617b9e5c7b965ba4d7d9/packages/python/src/daytona_sdk/workspace.py#L176) 551 | 552 | Represents a Daytona workspace instance. 553 | 554 | 555 | -------------------------------------------------------------------------------- ├── examples └── python │ ├── exec-command │ ├── exec-session.py │ └── exec.py │ ├── file-operations │ └── main.py │ ├── git-lsp │ └── main.py │ └── lifecycle │ └── lifecycle.py └── packages └── python ├── setup.py └── src └── daytona_sdk ├── __init__.py ├── _utils ├── __init__.py ├── enum.py ├── errors.py └── timeout.py ├── code_toolbox ├── __init__.py ├── workspace_python_code_toolbox.py └── workspace_ts_code_toolbox.py ├── common ├── __init__.py ├── code_run_params.py └── errors.py ├── daytona.py ├── filesystem.py ├── git.py ├── lsp_server.py ├── process.py ├── protocols.py └── workspace.py /examples/python/exec-command/exec-session.py: -------------------------------------------------------------------------------- 1 | from daytona_sdk import Daytona, CreateWorkspaceParams, SessionExecuteRequest 2 | 3 | daytona = Daytona() 4 | workspace = daytona.create() 5 | 6 | exec_session_id = "exec-session-1" 7 | workspace.process.create_session(exec_session_id) 8 | 9 | # Get the session details any time 10 | session = workspace.process.get_session(exec_session_id) 11 | print(session) 12 | 13 | # Execute a first command in the session 14 | execCommand1 = workspace.process.execute_session_command(exec_session_id, SessionExecuteRequest( 15 | command="export FOO=BAR" 16 | )) 17 | if execCommand1.exit_code != 0: 18 | print(f"Error: {execCommand1.exit_code} {execCommand1.output}") 19 | 20 | # Get the session details again to see the command has been executed 21 | session = workspace.process.get_session(exec_session_id) 22 | print(session) 23 | 24 | # Get the command details 25 | session_command = workspace.process.get_session_command(exec_session_id, execCommand1.cmd_id) 26 | print(session_command) 27 | 28 | # Execute a second command in the session and see that the environment variable is set 29 | execCommand2 = workspace.process.execute_session_command(exec_session_id, SessionExecuteRequest( 30 | command="echo $FOO" 31 | )) 32 | if execCommand2.exit_code != 0: 33 | print(f"Error: {execCommand2.exit_code} {execCommand2.output}") 34 | else: 35 | print(execCommand2.output) 36 | 37 | print("Now getting logs for the second command") 38 | logs = workspace.process.get_session_command_logs(exec_session_id, execCommand2.cmd_id) 39 | print(logs) 40 | 41 | # You can also list all active sessions 42 | sessions = workspace.process.list_sessions() 43 | print(sessions) 44 | 45 | # And of course you can delete the session at any time 46 | workspace.process.delete_session(exec_session_id) 47 | 48 | daytona.remove(workspace) 49 | -------------------------------------------------------------------------------- /examples/python/exec-command/exec.py: -------------------------------------------------------------------------------- 1 | from daytona_sdk import Daytona, CreateWorkspaceParams 2 | 3 | daytona = Daytona() 4 | 5 | params = CreateWorkspaceParams( 6 | language="python", 7 | ) 8 | workspace = daytona.create(params) 9 | 10 | # Run the code securely inside the workspace 11 | response = workspace.process.code_run('print("Hello World!")') 12 | if response.exit_code != 0: 13 | print(f"Error: {response.exit_code} {response.result}") 14 | else: 15 | print(response.result) 16 | 17 | # Execute an os command in the workspace 18 | response = workspace.process.exec('echo "Hello World from exec!"', cwd="/home/daytona", timeout=10) 19 | if response.exit_code != 0: 20 | print(f"Error: {response.exit_code} {response.result}") 21 | else: 22 | print(response.result) 23 | 24 | daytona.remove(workspace) 25 | -------------------------------------------------------------------------------- /examples/python/file-operations/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from daytona_sdk import Daytona, CreateWorkspaceParams 3 | 4 | daytona = Daytona() 5 | params = CreateWorkspaceParams( 6 | language="python", 7 | ) 8 | 9 | # First, create a workspace 10 | workspace = daytona.create(params) 11 | 12 | # Get workspace root directory 13 | root_dir = workspace.get_workspace_root_dir() 14 | 15 | # List files in the workspace 16 | files = workspace.fs.list_files(root_dir) 17 | print("Files:", files) 18 | 19 | # Create a new directory in the workspace 20 | new_dir = os.path.join(root_dir, "new-dir") 21 | workspace.fs.create_folder(new_dir, "755") 22 | 23 | file_path = os.path.join(new_dir, "data.txt") 24 | 25 | # Add a new file to the workspace 26 | file_content = b"Hello, World!" 27 | workspace.fs.upload_file(file_path, file_content) 28 | 29 | # Search for the file we just added 30 | matches = workspace.fs.find_files(root_dir, "World!") 31 | print("Matches:", matches) 32 | 33 | # Replace the contents of the file 34 | workspace.fs.replace_in_files([file_path], "Hello, World!", "Goodbye, World!") 35 | 36 | # Read the file 37 | downloaded_file = workspace.fs.download_file(file_path) 38 | print("File content:", downloaded_file.decode("utf-8")) 39 | 40 | # Change the file permissions 41 | workspace.fs.set_file_permissions(file_path, mode="777") 42 | 43 | # Get file info 44 | file_info = workspace.fs.get_file_info(file_path) 45 | print("File info:", file_info) # Should show the new permissions 46 | 47 | # Move the file to the new location 48 | new_file_path = os.path.join(root_dir, "moved-data.txt") 49 | workspace.fs.move_files(file_path, new_file_path) 50 | 51 | # Find the file in the new location 52 | search_results = workspace.fs.search_files(root_dir, "moved-data.txt") 53 | print("Search results:", search_results) 54 | 55 | # Delete the file 56 | workspace.fs.delete_file(new_file_path) 57 | 58 | # daytona.remove(workspace) 59 | -------------------------------------------------------------------------------- /examples/python/git-lsp/main.py: -------------------------------------------------------------------------------- 1 | from daytona_sdk import Daytona 2 | import os 3 | 4 | 5 | def main(): 6 | daytona = Daytona() 7 | 8 | workspace = daytona.create() 9 | 10 | try: 11 | root_dir = workspace.get_workspace_root_dir() 12 | project_dir = os.path.join(root_dir, "learn-typescript") 13 | 14 | # Clone the repository 15 | workspace.git.clone( 16 | "https://github.com/panaverse/learn-typescript", project_dir, "master" 17 | ) 18 | 19 | workspace.git.pull(project_dir) 20 | 21 | # Search for the file we want to work on 22 | matches = workspace.fs.find_files(project_dir, "var obj1 = new Base();") 23 | print("Matches:", matches) 24 | 25 | # Start the language server 26 | lsp = workspace.create_lsp_server("typescript", project_dir) 27 | lsp.start() 28 | 29 | # Notify the language server of the document we want to work on 30 | lsp.did_open(matches[0].file) 31 | 32 | # Get symbols in the document 33 | symbols = lsp.document_symbols(matches[0].file) 34 | print("Symbols:", symbols) 35 | 36 | # Fix the error in the document 37 | workspace.fs.replace_in_files( 38 | [matches[0].file], "var obj1 = new Base();", "var obj1 = new E();" 39 | ) 40 | 41 | # Notify the language server of the document change 42 | lsp.did_close(matches[0].file) 43 | lsp.did_open(matches[0].file) 44 | 45 | # Get completions at a specific position 46 | completions = lsp.completions(matches[0].file, {"line": 12, "character": 18}) 47 | print("Completions:", completions) 48 | 49 | except Exception as error: 50 | print("Error creating workspace:", error) 51 | finally: 52 | # Cleanup 53 | daytona.remove(workspace) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /examples/python/lifecycle/lifecycle.py: -------------------------------------------------------------------------------- 1 | from daytona_sdk import Daytona 2 | from pprint import pprint 3 | 4 | daytona = Daytona() 5 | 6 | print("Creating workspace") 7 | workspace = daytona.create() 8 | print("Workspace created") 9 | 10 | workspace.set_labels({ 11 | "public": True, 12 | }) 13 | 14 | print("Stopping workspace") 15 | daytona.stop(workspace) 16 | print("Workspace stopped") 17 | 18 | print("Starting workspace") 19 | daytona.start(workspace) 20 | print("Workspace started") 21 | 22 | print("Getting existing workspace") 23 | existing_workspace = daytona.get_current_workspace(workspace.id) 24 | print("Get existing workspace") 25 | 26 | response = existing_workspace.process.exec('echo "Hello World from exec!"', cwd="/home/daytona", timeout=10) 27 | if response.exit_code != 0: 28 | print(f"Error: {response.exit_code} {response.result}") 29 | else: 30 | print(response.result) 31 | 32 | workspaces = daytona.list() 33 | print("Total workspaces count:" , len(workspaces)) 34 | pprint(vars(workspaces[0].info())) # This will show all attributes of the first workspace 35 | 36 | print("Removing workspace") 37 | daytona.remove(workspace) 38 | print("Workspace removed") 39 | -------------------------------------------------------------------------------- /packages/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="daytona_sdk", 5 | version="0.1.4", 6 | packages=find_packages(where="src"), 7 | package_dir={"": "src"}, 8 | install_requires=[ 9 | # Add dependencies here 10 | ], 11 | # Include both packages 12 | package_data={ 13 | "daytona_sdk": ["*"] 14 | }, 15 | ) 16 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | from .daytona import ( 2 | Daytona, 3 | DaytonaConfig, 4 | CreateWorkspaceParams, 5 | CodeLanguage, 6 | Workspace, 7 | SessionExecuteRequest, 8 | SessionExecuteResponse, 9 | DaytonaError, 10 | WorkspaceTargetRegion, 11 | ) 12 | from .lsp_server import LspLanguageId 13 | from .workspace import WorkspaceState 14 | from .common.code_run_params import CodeRunParams 15 | 16 | __all__ = [ 17 | "Daytona", 18 | "DaytonaConfig", 19 | "CreateWorkspaceParams", 20 | "CodeLanguage", 21 | "Workspace", 22 | "SessionExecuteRequest", 23 | "SessionExecuteResponse", 24 | "DaytonaError", 25 | "LspLanguageId", 26 | "WorkspaceTargetRegion", 27 | "WorkspaceState", 28 | "CodeRunParams" 29 | ] 30 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/sdk/dcf0ddc88a191f3b23a037f3539d78a53a2652b7/packages/python/src/daytona_sdk/_utils/__init__.py -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/_utils/enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | 5 | def to_enum(enum_class: type, value: str) -> Optional[Enum]: 6 | """Convert a string to an enum. 7 | 8 | Args: 9 | enum_class (type): The enum class to convert to. 10 | value (str): The value to convert to an enum. 11 | 12 | Returns: 13 | The enum value, or None if the value is not a valid enum. 14 | """ 15 | if isinstance(value, enum_class): 16 | return value 17 | str_value = str(value) 18 | if str_value in enum_class._value2member_map_: 19 | return enum_class(str_value) 20 | return None 21 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/_utils/errors.py: -------------------------------------------------------------------------------- 1 | import json 2 | import functools 3 | from typing import Callable, Optional, TypeVar, Any, ParamSpec 4 | from daytona_api_client.exceptions import OpenApiException 5 | from daytona_sdk.common.errors import DaytonaError 6 | 7 | 8 | P = ParamSpec('P') 9 | T = TypeVar('T') 10 | 11 | 12 | def intercept_errors(message_prefix: str = "") -> Callable[[Callable[P, T]], Callable[P, T]]: 13 | """Decorator to intercept errors, process them, and optionally add a message prefix. 14 | If the error is an OpenApiException, it will be processed to extract the most meaningful error message. 15 | 16 | Args: 17 | message_prefix (str): Custom message prefix for the error. 18 | """ 19 | def decorator(func: Callable[P, T]) -> Callable[P, T]: 20 | @functools.wraps(func) 21 | def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: 22 | try: 23 | return func(*args, **kwargs) 24 | except OpenApiException as e: 25 | message = _get_open_api_exception_message(e) 26 | 27 | raise DaytonaError(f"{message_prefix}{message}") from None 28 | except Exception as e: 29 | if message_prefix: 30 | message = f"{message_prefix}{str(e)}" 31 | raise DaytonaError(message) 32 | raise DaytonaError(str(e)) 33 | 34 | return wrapper 35 | return decorator 36 | 37 | 38 | def _get_open_api_exception_message(exception: OpenApiException) -> str: 39 | """Process API exceptions to extract the most meaningful error message. 40 | 41 | This method examines the exception's body attribute and attempts to extract 42 | the most informative error message using the following logic: 43 | 1. If the body is missing or empty, returns the original exception 44 | 2. If the body contains valid JSON with a 'message' field, uses that message 45 | 3. If the body is not valid JSON or does not contain a 'message' field, uses the raw body string 46 | 47 | Args: 48 | exception (OpenApiException): The OpenApiException to process 49 | 50 | Returns: 51 | Processed message 52 | """ 53 | if not hasattr(exception, 'body') or not exception.body: 54 | return str(exception) 55 | 56 | body_str = str(exception.body) 57 | try: 58 | data = json.loads(body_str) 59 | message = data.get('message', body_str) if isinstance( 60 | data, dict) else body_str 61 | except json.JSONDecodeError: 62 | message = body_str 63 | 64 | return message 65 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/_utils/timeout.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import concurrent.futures 3 | from typing import Callable, Optional, Any, TypeVar, ParamSpec 4 | from daytona_sdk._utils.errors import DaytonaError 5 | 6 | 7 | P = ParamSpec('P') 8 | T = TypeVar('T') 9 | 10 | 11 | def with_timeout(error_message: Optional[Callable[[Any, float], str]] = None) -> Callable[[Callable[P, T]], Callable[P, T]]: 12 | """Decorator to add a timeout mechanism with an optional custom error message. 13 | 14 | Args: 15 | error_message (Optional[Callable[[Any, float], str]]): A callable that accepts `self` and `timeout`, 16 | and returns a string error message. 17 | """ 18 | def decorator(func: Callable[P, T]) -> Callable[P, T]: 19 | @functools.wraps(func) 20 | def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: 21 | # Get function argument names 22 | arg_names = func.__code__.co_varnames[:func.__code__.co_argcount] 23 | arg_dict = dict(zip(arg_names, args)) 24 | 25 | # Extract self if method is bound 26 | self_instance = args[0] if args else None 27 | 28 | # Check for 'timeout' in kwargs first, then in positional arguments 29 | timeout = kwargs.get('timeout', arg_dict.get('timeout', None)) 30 | 31 | if timeout is None or timeout == 0: 32 | # If timeout is None or 0, run the function normally 33 | return func(*args, **kwargs) 34 | 35 | if timeout < 0: 36 | raise DaytonaError( 37 | "Timeout must be a non-negative number or None.") 38 | 39 | with concurrent.futures.ThreadPoolExecutor() as executor: 40 | future = executor.submit(func, *args, **kwargs) 41 | try: 42 | return future.result(timeout=timeout) 43 | except concurrent.futures.TimeoutError: 44 | # Use custom error message if provided, otherwise default 45 | msg = error_message( 46 | self_instance, timeout) if error_message else f"Function '{func.__name__}' exceeded timeout of {timeout} seconds." 47 | raise TimeoutError(msg) 48 | return wrapper 49 | return decorator 50 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/code_toolbox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/sdk/dcf0ddc88a191f3b23a037f3539d78a53a2652b7/packages/python/src/daytona_sdk/code_toolbox/__init__.py -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/code_toolbox/workspace_python_code_toolbox.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from typing import Optional 3 | from ..common.code_run_params import CodeRunParams 4 | 5 | 6 | class WorkspacePythonCodeToolbox: 7 | def get_run_command(self, code: str, params: Optional[CodeRunParams] = None) -> str: 8 | # Encode the provided code in base64 9 | base64_code = base64.b64encode(code.encode()).decode() 10 | 11 | # Build environment variables string 12 | env_vars = "" 13 | if params and params.env: 14 | env_vars = ' '.join(f"{key}='{value}'" for key, value in params.env.items()) 15 | 16 | # Build command-line arguments string 17 | argv = "" 18 | if params and params.argv: 19 | argv = ' '.join(params.argv) 20 | 21 | # Combine everything into the final command 22 | return f""" sh -c '{env_vars} python3 -c "exec(__import__(\\\"base64\\\").b64decode(\\\"{base64_code}\\\").decode())" {argv}' """ 23 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/code_toolbox/workspace_ts_code_toolbox.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from typing import Optional 3 | from ..common.code_run_params import CodeRunParams 4 | 5 | 6 | class WorkspaceTsCodeToolbox: 7 | def get_run_command(self, code: str, params: Optional[CodeRunParams] = None) -> str: 8 | # Encode the provided code in base64 9 | base64_code = base64.b64encode(code.encode()).decode() 10 | 11 | # Build environment variables string 12 | env_vars = "" 13 | if params and params.env: 14 | env_vars = ' '.join(f"{key}='{value}'" for key, value in params.env.items()) 15 | 16 | # Build command-line arguments string 17 | argv = "" 18 | if params and params.argv: 19 | argv = ' '.join(params.argv) 20 | 21 | # Combine everything into the final command for TypeScript 22 | return f""" sh -c 'echo {base64_code} | base64 --decode | {env_vars} npx ts-node -O "{{\\\"module\\\":\\\"CommonJS\\\"}}" -e "$(cat)" x {argv} 2>&1 | grep -vE "npm notice|npm warn exec"' """ 23 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/sdk/dcf0ddc88a191f3b23a037f3539d78a53a2652b7/packages/python/src/daytona_sdk/common/__init__.py -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/common/code_run_params.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional, List, Dict 3 | 4 | @dataclass 5 | class CodeRunParams: 6 | """Parameters for code execution.""" 7 | argv: Optional[List[str]] = None 8 | env: Optional[Dict[str, str]] = None -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/common/errors.py: -------------------------------------------------------------------------------- 1 | class DaytonaError(Exception): 2 | """Base error for Daytona SDK.""" 3 | pass 4 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/daytona.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sandboxes are isolated development environments managed by Daytona. 3 | This guide covers how to create, manage, and remove Sandboxes using the SDK. 4 | 5 | Examples: 6 | Basic usage with environment variables: 7 | ```python 8 | from daytona_sdk import Daytona 9 | # Initialize using environment variables 10 | daytona = Daytona() # Uses env vars DAYTONA_API_KEY, DAYTONA_SERVER_URL, DAYTONA_TARGET 11 | 12 | # Create a default Python workspace with custom environment variables 13 | workspace = daytona.create(CreateWorkspaceParams( 14 | language="python", 15 | env_vars={"PYTHON_ENV": "development"} 16 | )) 17 | 18 | # Execute commands in the workspace 19 | response = workspace.process.execute_command('echo "Hello, World!"') 20 | print(response.result) 21 | 22 | # Run Python code securely inside the workspace 23 | response = workspace.process.code_run('print("Hello from Python!")') 24 | print(response.result) 25 | 26 | # Remove the workspace after use 27 | daytona.remove(workspace) 28 | ``` 29 | 30 | Usage with explicit configuration: 31 | ```python 32 | from daytona_sdk import Daytona, DaytonaConfig, CreateWorkspaceParams, WorkspaceResources 33 | 34 | # Initialize with explicit configuration 35 | config = DaytonaConfig( 36 | api_key="your-api-key", 37 | server_url="https://your-server.com", 38 | target="us" 39 | ) 40 | daytona = Daytona(config) 41 | 42 | # Create a custom workspace with specific resources and settings 43 | workspace = daytona.create(CreateWorkspaceParams( 44 | language="python", 45 | image="python:3.11", 46 | resources=WorkspaceResources( 47 | cpu=2, 48 | memory=4, # 4GB RAM 49 | disk=20 # 20GB disk 50 | ), 51 | env_vars={"PYTHON_ENV": "development"}, 52 | auto_stop_interval=60 # Auto-stop after 1 hour of inactivity 53 | )) 54 | 55 | # Use workspace features 56 | workspace.git.clone("https://github.com/user/repo.git") 57 | workspace.process.execute_command("python -m pytest") 58 | ``` 59 | """ 60 | 61 | from enum import Enum 62 | import uuid 63 | from typing import Optional, Dict, List, Annotated 64 | from pydantic import BaseModel, Field 65 | from dataclasses import dataclass 66 | from environs import Env 67 | from daytona_api_client import ( 68 | Configuration, 69 | WorkspaceApi, 70 | ToolboxApi, 71 | ApiClient, 72 | CreateWorkspace, 73 | SessionExecuteRequest, 74 | SessionExecuteResponse 75 | ) 76 | from daytona_sdk._utils.errors import intercept_errors, DaytonaError 77 | from .code_toolbox.workspace_python_code_toolbox import WorkspacePythonCodeToolbox 78 | from .code_toolbox.workspace_ts_code_toolbox import WorkspaceTsCodeToolbox 79 | from ._utils.enum import to_enum 80 | from .workspace import Workspace, WorkspaceTargetRegion 81 | from ._utils.timeout import with_timeout 82 | 83 | 84 | @dataclass 85 | class CodeLanguage(Enum): 86 | """Programming languages supported by Daytona""" 87 | PYTHON = "python" 88 | TYPESCRIPT = "typescript" 89 | JAVASCRIPT = "javascript" 90 | 91 | def __str__(self): 92 | return self.value 93 | 94 | def __eq__(self, other): 95 | if isinstance(other, str): 96 | return self.value == other 97 | return super().__eq__(other) 98 | 99 | 100 | @dataclass 101 | class DaytonaConfig: 102 | """Configuration options for initializing the Daytona client. 103 | 104 | Attributes: 105 | api_key (str): API key for authentication with Daytona server. 106 | server_url (str): URL of the Daytona server. 107 | target (str): Target environment for Sandbox. 108 | 109 | Example: 110 | ```python 111 | config = DaytonaConfig( 112 | api_key="your-api-key", 113 | server_url="https://your-server.com", 114 | target="us" 115 | ) 116 | daytona = Daytona(config) 117 | ``` 118 | """ 119 | api_key: str 120 | server_url: str 121 | target: WorkspaceTargetRegion 122 | 123 | 124 | @dataclass 125 | class WorkspaceResources: 126 | """Resources configuration for Sandbox. 127 | 128 | Attributes: 129 | cpu (Optional[int]): Number of CPU cores to allocate. 130 | memory (Optional[int]): Amount of memory in GB to allocate. 131 | disk (Optional[int]): Amount of disk space in GB to allocate. 132 | gpu (Optional[int]): Number of GPUs to allocate. 133 | 134 | Example: 135 | ```python 136 | resources = WorkspaceResources( 137 | cpu=2, 138 | memory=4, # 4GB RAM 139 | disk=20, # 20GB disk 140 | gpu=1 141 | ) 142 | params = CreateWorkspaceParams( 143 | language="python", 144 | resources=resources 145 | ) 146 | ``` 147 | """ 148 | cpu: Optional[int] = None 149 | memory: Optional[int] = None 150 | disk: Optional[int] = None 151 | gpu: Optional[int] = None 152 | 153 | 154 | class CreateWorkspaceParams(BaseModel): 155 | """Parameters for creating a new Sandbox. 156 | 157 | Attributes: 158 | language (CodeLanguage): Programming language for the Sandbox ("python", "javascript", "typescript"). 159 | id (Optional[str]): Custom identifier for the Sandbox. If not provided, a random ID will be generated. 160 | name (Optional[str]): Display name for the Sandbox. Defaults to Sandbox ID if not provided. 161 | image (Optional[str]): Custom Docker image to use for the Sandbox. 162 | os_user (Optional[str]): OS user for the Sandbox. 163 | env_vars (Optional[Dict[str, str]]): Environment variables to set in the Sandbox. 164 | labels (Optional[Dict[str, str]]): Custom labels for the Sandbox. 165 | public (Optional[bool]): Whether the Sandbox should be public. 166 | target (Optional[str]): Target location for the Sandbox. Can be "us", "eu", or "asia". 167 | resources (Optional[WorkspaceResources]): Resource configuration for the Sandbox. 168 | timeout (Optional[float]): Timeout in seconds for Sandbox to be created and started. 169 | auto_stop_interval (Optional[int]): Interval in minutes after which Sandbox will automatically stop if no Sandbox event occurs during that time. Default is 15 minutes. 0 means no auto-stop. 170 | 171 | Example: 172 | ```python 173 | params = CreateWorkspaceParams( 174 | language="python", 175 | name="my-workspace", 176 | env_vars={"DEBUG": "true"}, 177 | resources=WorkspaceResources(cpu=2, memory=4), 178 | auto_stop_interval=20 179 | ) 180 | workspace = daytona.create(params, 50) 181 | ``` 182 | """ 183 | language: CodeLanguage 184 | id: Optional[str] = None 185 | name: Optional[str] = None 186 | image: Optional[str] = None 187 | os_user: Optional[str] = None 188 | env_vars: Optional[Dict[str, str]] = None 189 | labels: Optional[Dict[str, str]] = None 190 | public: Optional[bool] = None 191 | target: Optional[WorkspaceTargetRegion] = None 192 | resources: Optional[WorkspaceResources] = None 193 | timeout: Annotated[Optional[float], Field( 194 | default=None, deprecated='The `timeout` field is deprecated and will be removed in future versions. Use `timeout` argument in method calls instead.')] 195 | auto_stop_interval: Optional[int] = None 196 | 197 | 198 | class Daytona: 199 | """Main class for interacting with Daytona Server API. 200 | 201 | This class provides methods to create, manage, and interact with Daytona Sandboxes. 202 | It can be initialized either with explicit configuration or using environment variables. 203 | 204 | Attributes: 205 | api_key (str): API key for authentication. 206 | server_url (str): URL of the Daytona server. 207 | target (str): Default target location for Sandboxes. 208 | 209 | Example: 210 | Using environment variables: 211 | ```python 212 | daytona = Daytona() # Uses DAYTONA_API_KEY, DAYTONA_SERVER_URL 213 | ``` 214 | 215 | Using explicit configuration: 216 | ```python 217 | config = DaytonaConfig( 218 | api_key="your-api-key", 219 | server_url="https://your-server.com", 220 | target="us" 221 | ) 222 | daytona = Daytona(config) 223 | ``` 224 | """ 225 | 226 | def __init__(self, config: Optional[DaytonaConfig] = None): 227 | """Initializes Daytona instance with optional configuration. 228 | 229 | If no config is provided, reads from environment variables: 230 | - `DAYTONA_API_KEY`: Required API key for authentication 231 | - `DAYTONA_SERVER_URL`: Required server URL 232 | - `DAYTONA_TARGET`: Optional target environment (defaults to WorkspaceTargetRegion.US) 233 | 234 | Args: 235 | config (Optional[DaytonaConfig]): Object containing api_key, server_url, and target. 236 | 237 | Raises: 238 | DaytonaError: If API key or Server URL is not provided either through config or environment variables 239 | 240 | Example: 241 | ```python 242 | from daytona_sdk import Daytona, DaytonaConfig 243 | # Using environment variables 244 | daytona1 = Daytona() 245 | # Using explicit configuration 246 | config = DaytonaConfig( 247 | api_key="your-api-key", 248 | server_url="https://your-server.com", 249 | target="us" 250 | ) 251 | daytona2 = Daytona(config) 252 | ``` 253 | """ 254 | if config is None: 255 | # Initialize env - it automatically reads from .env and .env.local 256 | env = Env() 257 | env.read_env() # reads .env 258 | # reads .env.local and overrides values 259 | env.read_env(".env.local", override=True) 260 | 261 | self.api_key = env.str("DAYTONA_API_KEY") 262 | self.server_url = env.str("DAYTONA_SERVER_URL") 263 | self.target = env.str("DAYTONA_TARGET", WorkspaceTargetRegion.US) 264 | else: 265 | self.api_key = config.api_key 266 | self.server_url = config.server_url 267 | self.target = config.target 268 | 269 | if not self.api_key: 270 | raise DaytonaError("API key is required") 271 | 272 | if not self.server_url: 273 | raise DaytonaError("Server URL is required") 274 | 275 | if not self.target: 276 | self.target = WorkspaceTargetRegion.US 277 | 278 | # Create API configuration without api_key 279 | configuration = Configuration(host=self.server_url) 280 | api_client = ApiClient(configuration) 281 | api_client.default_headers["Authorization"] = f"Bearer {self.api_key}" 282 | 283 | # Initialize API clients with the api_client instance 284 | self.workspace_api = WorkspaceApi(api_client) 285 | self.toolbox_api = ToolboxApi(api_client) 286 | 287 | @intercept_errors(message_prefix="Failed to create workspace: ") 288 | def create(self, params: Optional[CreateWorkspaceParams] = None, timeout: Optional[float] = 60) -> Workspace: 289 | """Creates Sandboxes with default or custom configurations. You can specify various parameters, 290 | including language, image, resources, environment variables, and volumes for the Sandbox. 291 | 292 | Args: 293 | params (Optional[CreateWorkspaceParams]): Parameters for Sandbox creation. If not provided, 294 | defaults to Python language. 295 | timeout (Optional[float]): Timeout (in seconds) for workspace creation. 0 means no timeout. Default is 60 seconds. 296 | 297 | Returns: 298 | Workspace: The created Sandbox instance. 299 | 300 | Raises: 301 | DaytonaError: If timeout or auto_stop_interval is negative; If workspace fails to start or times out 302 | 303 | Example: 304 | Create a default Python Sandbox: 305 | ```python 306 | workspace = daytona.create() 307 | ``` 308 | 309 | Create a custom Sandbox: 310 | ```python 311 | params = CreateWorkspaceParams( 312 | language="python", 313 | name="my-workspace", 314 | image="debian:12.9", 315 | env_vars={"DEBUG": "true"}, 316 | resources=WorkspaceResources(cpu=2, memory=4096), 317 | auto_stop_interval=0 318 | ) 319 | workspace = daytona.create(params, 40) 320 | ``` 321 | """ 322 | # If no params provided, create default params for Python 323 | if params is None: 324 | params = CreateWorkspaceParams(language="python") 325 | 326 | params.id = params.id if params.id else f"sandbox-{str(uuid.uuid4())[:8]}" 327 | 328 | effective_timeout = params.timeout if params.timeout else timeout 329 | 330 | try: 331 | return self._create(params, effective_timeout) 332 | except Exception as e: 333 | try: 334 | self.workspace_api.delete_workspace( 335 | workspace_id=params.id, force=True) 336 | except Exception: 337 | pass 338 | raise e 339 | 340 | @with_timeout(error_message=lambda self, timeout: f"Failed to create and start workspace within {timeout} seconds timeout period.") 341 | def _create(self, params: Optional[CreateWorkspaceParams] = None, timeout: Optional[float] = 60) -> Workspace: 342 | """Creates a new Sandbox and waits for it to start. 343 | 344 | Args: 345 | params (Optional[CreateWorkspaceParams]): Parameters for Sandbox creation. If not provided, 346 | defaults to Python language. 347 | timeout (Optional[float]): Timeout (in seconds) for workspace creation. 0 means no timeout. Default is 60 seconds. 348 | 349 | Returns: 350 | Workspace: The created Sandbox instance. 351 | 352 | Raises: 353 | DaytonaError: If timeout or auto_stop_interval is negative; If workspace fails to start or times out 354 | """ 355 | code_toolbox = self._get_code_toolbox(params) 356 | 357 | if timeout < 0: 358 | raise DaytonaError("Timeout must be a non-negative number") 359 | 360 | if params.auto_stop_interval is not None and params.auto_stop_interval < 0: 361 | raise DaytonaError( 362 | "auto_stop_interval must be a non-negative integer") 363 | 364 | target = params.target if params.target else self.target 365 | 366 | # Create workspace using dictionary 367 | workspace_data = CreateWorkspace( 368 | id=params.id, 369 | name=params.name if params.name else params.id, 370 | image=params.image, 371 | user=params.os_user, 372 | env=params.env_vars if params.env_vars else {}, 373 | labels=params.labels, 374 | public=params.public, 375 | target=str(target) if target else None, 376 | auto_stop_interval=params.auto_stop_interval 377 | ) 378 | 379 | if params.resources: 380 | workspace_data.cpu = params.resources.cpu 381 | workspace_data.memory = params.resources.memory 382 | workspace_data.disk = params.resources.disk 383 | workspace_data.gpu = params.resources.gpu 384 | 385 | response = self.workspace_api.create_workspace( 386 | create_workspace=workspace_data, _request_timeout=timeout or None) 387 | workspace_info = Workspace._to_workspace_info(response) 388 | response.info = workspace_info 389 | 390 | workspace = Workspace( 391 | params.id, 392 | response, 393 | self.workspace_api, 394 | self.toolbox_api, 395 | code_toolbox 396 | ) 397 | 398 | # Wait for workspace to start 399 | try: 400 | workspace.wait_for_workspace_start() 401 | finally: 402 | # If not Daytona SaaS, we don't need to handle pulling image state 403 | pass 404 | 405 | return workspace 406 | 407 | def _get_code_toolbox(self, params: Optional[CreateWorkspaceParams] = None): 408 | """Helper method to get the appropriate code toolbox based on language. 409 | 410 | Args: 411 | params (Optional[CreateWorkspaceParams]): Sandbox parameters. If not provided, defaults to Python toolbox. 412 | 413 | Returns: 414 | The appropriate code toolbox instance for the specified language. 415 | 416 | Raises: 417 | DaytonaError: If an unsupported language is specified. 418 | """ 419 | if not params: 420 | return WorkspacePythonCodeToolbox() 421 | 422 | enum_language = to_enum(CodeLanguage, params.language) 423 | if enum_language is None: 424 | raise DaytonaError(f"Unsupported language: {params.language}") 425 | else: 426 | params.language = enum_language 427 | 428 | match params.language: 429 | case CodeLanguage.JAVASCRIPT | CodeLanguage.TYPESCRIPT: 430 | return WorkspaceTsCodeToolbox() 431 | case CodeLanguage.PYTHON: 432 | return WorkspacePythonCodeToolbox() 433 | case _: 434 | raise DaytonaError(f"Unsupported language: {params.language}") 435 | 436 | @intercept_errors(message_prefix="Failed to remove workspace: ") 437 | def remove(self, workspace: Workspace, timeout: Optional[float] = 60) -> None: 438 | """Removes a Sandbox. 439 | 440 | Args: 441 | workspace (Workspace): The Sandbox instance to remove. 442 | timeout (Optional[float]): Timeout (in seconds) for workspace removal. 0 means no timeout. Default is 60 seconds. 443 | 444 | Raises: 445 | DaytonaError: If workspace fails to remove or times out 446 | 447 | Example: 448 | ```python 449 | workspace = daytona.create() 450 | # ... use workspace ... 451 | daytona.remove(workspace) # Clean up when done 452 | ``` 453 | """ 454 | return self.workspace_api.delete_workspace(workspace_id=workspace.id, force=True, _request_timeout=timeout or None) 455 | 456 | @intercept_errors(message_prefix="Failed to get workspace: ") 457 | def get_current_workspace(self, workspace_id: str) -> Workspace: 458 | """Get a Sandbox by its ID. 459 | 460 | Args: 461 | workspace_id (str): The ID of the Sandbox to retrieve. 462 | 463 | Returns: 464 | Workspace: The Sandbox instance. 465 | 466 | Raises: 467 | DaytonaError: If workspace_id is not provided. 468 | 469 | Example: 470 | ```python 471 | workspace = daytona.get_current_workspace("my-workspace-id") 472 | print(workspace.status) 473 | ``` 474 | """ 475 | if not workspace_id: 476 | raise DaytonaError("workspace_id is required") 477 | 478 | # Get the workspace instance 479 | workspace_instance = self.workspace_api.get_workspace( 480 | workspace_id=workspace_id) 481 | workspace_info = Workspace._to_workspace_info(workspace_instance) 482 | workspace_instance.info = workspace_info 483 | 484 | # Create and return workspace with Python code toolbox as default 485 | code_toolbox = WorkspacePythonCodeToolbox() 486 | return Workspace( 487 | workspace_id, 488 | workspace_instance, 489 | self.workspace_api, 490 | self.toolbox_api, 491 | code_toolbox 492 | ) 493 | 494 | @intercept_errors(message_prefix="Failed to list workspaces: ") 495 | def list(self) -> List[Workspace]: 496 | """Lists all Sandboxes. 497 | 498 | Returns: 499 | List[Workspace]: List of all available Sandbox instances. 500 | 501 | Example: 502 | ```python 503 | workspaces = daytona.list() 504 | for workspace in workspaces: 505 | print(f"{workspace.id}: {workspace.status}") 506 | ``` 507 | """ 508 | workspaces = self.workspace_api.list_workspaces() 509 | 510 | for workspace in workspaces: 511 | workspace_info = Workspace._to_workspace_info(workspace) 512 | workspace.info = workspace_info 513 | 514 | return [ 515 | Workspace( 516 | workspace.id, 517 | workspace, 518 | self.workspace_api, 519 | self.toolbox_api, 520 | self._get_code_toolbox( 521 | CreateWorkspaceParams( 522 | language=self._validate_language_label( 523 | workspace.labels.get("code-toolbox-language")) 524 | ) 525 | ) 526 | ) 527 | for workspace in workspaces 528 | ] 529 | 530 | def _validate_language_label(self, language: Optional[str]) -> CodeLanguage: 531 | """Validates and normalizes the language label. 532 | 533 | Args: 534 | language (Optional[str]): The language label to validate. 535 | 536 | Returns: 537 | CodeLanguage: The validated language, defaults to "python" if None 538 | 539 | Raises: 540 | DaytonaError: If the language is not supported. 541 | """ 542 | if not language: 543 | return CodeLanguage.PYTHON 544 | 545 | enum_language = to_enum(CodeLanguage, language) 546 | if enum_language is None: 547 | raise DaytonaError(f"Invalid code-toolbox-language: {language}") 548 | else: 549 | return enum_language 550 | 551 | # def resize(self, workspace: Workspace, resources: WorkspaceResources) -> None: 552 | # """Resizes a workspace. 553 | 554 | # Args: 555 | # workspace: The workspace to resize 556 | # resources: The new resources to set 557 | # """ 558 | # self.workspace_api. (workspace_id=workspace.id, resources=resources) 559 | 560 | def start(self, workspace: Workspace, timeout: Optional[float] = 60) -> None: 561 | """Starts a Sandbox and waits for it to be ready. 562 | 563 | Args: 564 | workspace (Workspace): The Sandbox to start. 565 | timeout (Optional[float]): Optional timeout in seconds to wait for the Sandbox to start. 0 means no timeout. Default is 60 seconds. 566 | 567 | Raises: 568 | DaytonaError: If timeout is negative; If Sandbox fails to start or times out 569 | """ 570 | workspace.start(timeout) 571 | 572 | def stop(self, workspace: Workspace, timeout: Optional[float] = 60) -> None: 573 | """Stops a Sandbox and waits for it to be stopped. 574 | 575 | Args: 576 | workspace (Workspace): The workspace to stop 577 | timeout (Optional[float]): Optional timeout (in seconds) for workspace stop. 0 means no timeout. Default is 60 seconds. 578 | 579 | Raises: 580 | DaytonaError: If timeout is negative; If Sandbox fails to stop or times out 581 | """ 582 | workspace.stop(timeout) 583 | 584 | 585 | # Export these at module level 586 | __all__ = [ 587 | "Daytona", 588 | "DaytonaConfig", 589 | "CreateWorkspaceParams", 590 | "CodeLanguage", 591 | "Workspace", 592 | "SessionExecuteRequest", 593 | "SessionExecuteResponse" 594 | ] 595 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/filesystem.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Daytona SDK provides comprehensive file system operations through the `fs` module in Sandboxes. 3 | You can perform various operations like listing files, creating directories, reading and writing files, and more. 4 | This guide covers all available file system operations and best practices. 5 | 6 | Examples: 7 | Basic file operations: 8 | ```python 9 | workspace = daytona.create() 10 | 11 | # Create a directory 12 | workspace.fs.create_folder("/workspace/data", "755") 13 | 14 | # Upload a file 15 | with open("local_file.txt", "rb") as f: 16 | content = f.read() 17 | workspace.fs.upload_file("/workspace/data/file.txt", content) 18 | 19 | # List directory contents 20 | files = workspace.fs.list_files("/workspace") 21 | for file in files: 22 | print(f"Name: {file.name}") 23 | print(f"Is directory: {file.is_dir}") 24 | print(f"Size: {file.size}") 25 | print(f"Modified: {file.mod_time}") 26 | 27 | # Search file contents 28 | matches = workspace.fs.find_files( 29 | path="/workspace/src", 30 | pattern="text-of-interest" 31 | ) 32 | for match in matches: 33 | print(f"Absolute file path: {match.file}") 34 | print(f"Line number: {match.line}") 35 | print(f"Line content: {match.content}") 36 | print("\n") 37 | ``` 38 | 39 | File manipulation: 40 | ```python 41 | # Move files 42 | workspace.fs.move_files( 43 | "/workspace/data/old.txt", 44 | "/workspace/data/new.txt" 45 | ) 46 | 47 | # Replace text in files 48 | results = workspace.fs.replace_in_files( 49 | files=["/workspace/data/new.txt"], 50 | pattern="old_version", 51 | new_value="new_version" 52 | ) 53 | 54 | # Set permissions 55 | workspace.fs.set_file_permissions( 56 | path="/workspace/data/script.sh", 57 | mode="755", 58 | owner="daytona" 59 | ) 60 | ``` 61 | 62 | Note: 63 | All paths should be absolute paths within the Sandbox if not explicitly 64 | stated otherwise. 65 | """ 66 | 67 | from typing import List 68 | from daytona_api_client import ( 69 | FileInfo, 70 | Match, 71 | ReplaceRequest, 72 | ReplaceResult, 73 | SearchFilesResponse, 74 | ToolboxApi, 75 | ) 76 | from daytona_sdk._utils.errors import intercept_errors 77 | from .protocols import WorkspaceInstance 78 | 79 | 80 | class FileSystem: 81 | """Provides file system operations within a Sandbox. 82 | 83 | This class implements a high-level interface to file system operations that can 84 | be performed within a Daytona Sandbox. It supports common operations like 85 | creating, deleting, and moving files, as well as searching file contents and 86 | managing permissions. 87 | 88 | Attributes: 89 | instance (WorkspaceInstance): The Sandbox instance this file system belongs to. 90 | """ 91 | 92 | def __init__(self, instance: WorkspaceInstance, toolbox_api: ToolboxApi): 93 | """Initializes a new FileSystem instance. 94 | 95 | Args: 96 | instance (WorkspaceInstance): The Sandbox instance this file system belongs to. 97 | toolbox_api (ToolboxApi): API client for Sandbox operations. 98 | """ 99 | self.instance = instance 100 | self.toolbox_api = toolbox_api 101 | 102 | @intercept_errors(message_prefix="Failed to create folder: ") 103 | def create_folder(self, path: str, mode: str) -> None: 104 | """Creates a new directory in the Sandbox. 105 | 106 | This method creates a new directory at the specified path with the given 107 | permissions. 108 | 109 | Args: 110 | path (str): Absolute path where the folder should be created. 111 | mode (str): Folder permissions in octal format (e.g., "755" for rwxr-xr-x). 112 | 113 | Example: 114 | ```python 115 | # Create a directory with standard permissions 116 | workspace.fs.create_folder("/workspace/data", "755") 117 | 118 | # Create a private directory 119 | workspace.fs.create_folder("/workspace/secrets", "700") 120 | ``` 121 | """ 122 | self.toolbox_api.create_folder( 123 | workspace_id=self.instance.id, path=path, mode=mode 124 | ) 125 | 126 | @intercept_errors(message_prefix="Failed to delete file: ") 127 | def delete_file(self, path: str) -> None: 128 | """Deletes a file from the Sandbox. 129 | 130 | This method permanently deletes a file from the Sandbox. 131 | 132 | Args: 133 | path (str): Absolute path to the file to delete. 134 | 135 | Example: 136 | ```python 137 | # Delete a file 138 | workspace.fs.delete_file("/workspace/data/old_file.txt") 139 | ``` 140 | """ 141 | self.toolbox_api.delete_file( 142 | workspace_id=self.instance.id, path=path 143 | ) 144 | 145 | @intercept_errors(message_prefix="Failed to download file: ") 146 | def download_file(self, path: str) -> bytes: 147 | """Downloads a file from the Sandbox. 148 | 149 | This method retrieves the contents of a file from the Sandbox. 150 | 151 | Args: 152 | path (str): Absolute path to the file to download. 153 | 154 | Returns: 155 | bytes: The file contents as a bytes object. 156 | 157 | Example: 158 | ```python 159 | # Download and save a file locally 160 | content = workspace.fs.download_file("/workspace/data/file.txt") 161 | with open("local_copy.txt", "wb") as f: 162 | f.write(content) 163 | 164 | # Download and process text content 165 | content = workspace.fs.download_file("/workspace/data/config.json") 166 | config = json.loads(content.decode('utf-8')) 167 | ``` 168 | """ 169 | return self.toolbox_api.download_file( 170 | workspace_id=self.instance.id, path=path 171 | ) 172 | 173 | @intercept_errors(message_prefix="Failed to find files: ") 174 | def find_files(self, path: str, pattern: str) -> List[Match]: 175 | """Searches for files containing a pattern. 176 | 177 | This method searches file contents for a specified pattern, similar to 178 | the grep command. 179 | 180 | Args: 181 | path (str): Absolute path to the file or directory to search. If the path is a directory, the search will be performed recursively. 182 | pattern (str): Search pattern to match against file contents. 183 | 184 | Returns: 185 | List[Match]: List of matches found in files. Each Match object includes: 186 | - file: Path to the file containing the match 187 | - line: The line number where the match was found 188 | - content: The matching line content 189 | 190 | Example: 191 | ```python 192 | # Search for TODOs in Python files 193 | matches = workspace.fs.find_files("/workspace/src", "TODO:") 194 | for match in matches: 195 | print(f"{match.file}:{match.line}: {match.content.strip()}") 196 | ``` 197 | """ 198 | return self.toolbox_api.find_in_files( 199 | workspace_id=self.instance.id, path=path, pattern=pattern 200 | ) 201 | 202 | @intercept_errors(message_prefix="Failed to get file info: ") 203 | def get_file_info(self, path: str) -> FileInfo: 204 | """Gets detailed information about a file. 205 | 206 | This method retrieves metadata about a file or directory, including its 207 | size, permissions, and timestamps. 208 | 209 | Args: 210 | path (str): Absolute path to the file or directory. 211 | 212 | Returns: 213 | FileInfo: Detailed file information including: 214 | - name: File name 215 | - is_dir: Whether the path is a directory 216 | - size: File size in bytes 217 | - mode: File permissions 218 | - mod_time: Last modification timestamp 219 | - permissions: File permissions in octal format 220 | - owner: File owner 221 | - group: File group 222 | 223 | Example: 224 | ```python 225 | # Get file metadata 226 | info = workspace.fs.get_file_info("/workspace/data/file.txt") 227 | print(f"Size: {info.size} bytes") 228 | print(f"Modified: {info.mod_time}") 229 | print(f"Mode: {info.mode}") 230 | 231 | # Check if path is a directory 232 | info = workspace.fs.get_file_info("/workspace/data") 233 | if info.is_dir: 234 | print("Path is a directory") 235 | ``` 236 | """ 237 | return self.toolbox_api.get_file_info( 238 | workspace_id=self.instance.id, path=path 239 | ) 240 | 241 | @intercept_errors(message_prefix="Failed to list files: ") 242 | def list_files(self, path: str) -> List[FileInfo]: 243 | """Lists files and directories in a given path. 244 | 245 | This method returns information about all files and directories in the 246 | specified directory, similar to the ls -l command. 247 | 248 | Args: 249 | path (str): Absolute path to the directory to list contents from. 250 | 251 | Returns: 252 | List[FileInfo]: List of file and directory information. Each FileInfo 253 | object includes the same fields as described in get_file_info(). 254 | 255 | Example: 256 | ```python 257 | # List directory contents 258 | files = workspace.fs.list_files("/workspace/data") 259 | 260 | # Print files and their sizes 261 | for file in files: 262 | if not file.is_dir: 263 | print(f"{file.name}: {file.size} bytes") 264 | 265 | # List only directories 266 | dirs = [f for f in files if f.is_dir] 267 | print("Subdirectories:", ", ".join(d.name for d in dirs)) 268 | ``` 269 | """ 270 | return self.toolbox_api.list_files( 271 | workspace_id=self.instance.id, path=path 272 | ) 273 | 274 | @intercept_errors(message_prefix="Failed to move files: ") 275 | def move_files(self, source: str, destination: str) -> None: 276 | """Moves files from one location to another. 277 | 278 | This method moves or renames a file or directory. The parent directory 279 | of the destination must exist. 280 | 281 | Args: 282 | source (str): Absolute path to the source file or directory. 283 | destination (str): Absolute path to the destination. 284 | 285 | Example: 286 | ```python 287 | # Rename a file 288 | workspace.fs.move_files( 289 | "/workspace/data/old_name.txt", 290 | "/workspace/data/new_name.txt" 291 | ) 292 | 293 | # Move a file to a different directory 294 | workspace.fs.move_files( 295 | "/workspace/data/file.txt", 296 | "/workspace/archive/file.txt" 297 | ) 298 | 299 | # Move a directory 300 | workspace.fs.move_files( 301 | "/workspace/old_dir", 302 | "/workspace/new_dir" 303 | ) 304 | ``` 305 | """ 306 | self.toolbox_api.move_file( 307 | workspace_id=self.instance.id, 308 | source=source, 309 | destination=destination, 310 | ) 311 | 312 | @intercept_errors(message_prefix="Failed to replace in files: ") 313 | def replace_in_files( 314 | self, files: List[str], pattern: str, new_value: str 315 | ) -> List[ReplaceResult]: 316 | """Replaces text in multiple files. 317 | 318 | This method performs search and replace operations across multiple files. 319 | 320 | Args: 321 | files (List[str]): List of absolute file paths to perform replacements in. 322 | pattern (str): Pattern to search for. 323 | new_value (str): Text to replace matches with. 324 | 325 | Returns: 326 | List[ReplaceResult]: List of results indicating replacements made in 327 | each file. Each ReplaceResult includes: 328 | - file: Path to the modified file 329 | - success: Whether the operation was successful 330 | - error: Error message if the operation failed 331 | 332 | Example: 333 | ```python 334 | # Replace in specific files 335 | results = workspace.fs.replace_in_files( 336 | files=["/workspace/src/file1.py", "/workspace/src/file2.py"], 337 | pattern="old_function", 338 | new_value="new_function" 339 | ) 340 | 341 | # Print results 342 | for result in results: 343 | if result.success: 344 | print(f"{result.file}: {result.success}") 345 | else: 346 | print(f"{result.file}: {result.error}") 347 | ``` 348 | """ 349 | replace_request = ReplaceRequest( 350 | files=files, new_value=new_value, pattern=pattern 351 | ) 352 | 353 | return self.toolbox_api.replace_in_files( 354 | workspace_id=self.instance.id, replace_request=replace_request 355 | ) 356 | 357 | @intercept_errors(message_prefix="Failed to search files: ") 358 | def search_files(self, path: str, pattern: str) -> SearchFilesResponse: 359 | """Searches for files and directories matching a pattern in their names. 360 | 361 | This method searches for files and directories whose names match the 362 | specified pattern. The pattern can be a simple string or a glob pattern. 363 | 364 | Args: 365 | path (str): Absolute path to the root directory to start search from. 366 | pattern (str): Pattern to match against file names. Supports glob 367 | patterns (e.g., "*.py" for Python files). 368 | 369 | Returns: 370 | SearchFilesResponse: Search results containing: 371 | - files: List of matching file and directory paths 372 | 373 | Example: 374 | ```python 375 | # Find all Python files 376 | result = workspace.fs.search_files("/workspace", "*.py") 377 | for file in result.files: 378 | print(file) 379 | 380 | # Find files with specific prefix 381 | result = workspace.fs.search_files("/workspace/data", "test_*") 382 | print(f"Found {len(result.files)} test files") 383 | ``` 384 | """ 385 | return self.toolbox_api.search_files( 386 | workspace_id=self.instance.id, path=path, pattern=pattern 387 | ) 388 | 389 | @intercept_errors(message_prefix="Failed to set file permissions: ") 390 | def set_file_permissions( 391 | self, path: str, mode: str = None, owner: str = None, group: str = None 392 | ) -> None: 393 | """Sets permissions and ownership for a file or directory. 394 | 395 | This method allows changing the permissions and ownership of a file or 396 | directory. Any of the parameters can be None to leave that attribute 397 | unchanged. 398 | 399 | Args: 400 | path (str): Absolute path to the file or directory. 401 | mode (Optional[str]): File mode/permissions in octal format 402 | (e.g., "644" for rw-r--r--). 403 | owner (Optional[str]): User owner of the file. 404 | group (Optional[str]): Group owner of the file. 405 | 406 | Example: 407 | ```python 408 | # Make a file executable 409 | workspace.fs.set_file_permissions( 410 | path="/workspace/scripts/run.sh", 411 | mode="755" # rwxr-xr-x 412 | ) 413 | 414 | # Change file owner 415 | workspace.fs.set_file_permissions( 416 | path="/workspace/data/file.txt", 417 | owner="daytona", 418 | group="daytona" 419 | ) 420 | ``` 421 | """ 422 | self.toolbox_api.set_file_permissions( 423 | workspace_id=self.instance.id, 424 | path=path, 425 | mode=mode, 426 | owner=owner, 427 | group=group, 428 | ) 429 | 430 | @intercept_errors(message_prefix="Failed to upload file: ") 431 | def upload_file(self, path: str, file: bytes) -> None: 432 | """Uploads a file to the Sandbox. 433 | 434 | This method uploads a file to the specified path in the Sandbox. The 435 | parent directory must exist. If a file already exists at the destination 436 | path, it will be overwritten. 437 | 438 | Args: 439 | path (str): Absolute destination path in the Sandbox. 440 | file (bytes): File contents as a bytes object. 441 | 442 | Example: 443 | ```python 444 | # Upload a text file 445 | content = b"Hello, World!" 446 | workspace.fs.upload_file("/workspace/data/hello.txt", content) 447 | 448 | # Upload a local file 449 | with open("local_file.txt", "rb") as f: 450 | content = f.read() 451 | workspace.fs.upload_file("/workspace/data/file.txt", content) 452 | 453 | # Upload binary data 454 | import json 455 | data = {"key": "value"} 456 | content = json.dumps(data).encode('utf-8') 457 | workspace.fs.upload_file("/workspace/data/config.json", content) 458 | ``` 459 | """ 460 | self.toolbox_api.upload_file( 461 | workspace_id=self.instance.id, path=path, file=file 462 | ) 463 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/git.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Daytona SDK provides built-in Git support. This guide covers all available Git 3 | operations and best practices. Daytona SDK provides an option to clone, check status, 4 | and manage Git repositories in Sandboxes. You can interact with Git repositories using 5 | the `git` module. 6 | 7 | Example: 8 | Basic Git workflow: 9 | ```python 10 | workspace = daytona.create() 11 | 12 | # Clone a repository 13 | workspace.git.clone( 14 | url="https://github.com/user/repo.git", 15 | path="/workspace/repo" 16 | ) 17 | 18 | # Make some changes 19 | workspace.fs.upload_file("/workspace/repo/test.txt", "Hello, World!") 20 | 21 | # Stage and commit changes 22 | workspace.git.add("/workspace/repo", ["test.txt"]) 23 | workspace.git.commit( 24 | path="/workspace/repo", 25 | message="Add test file", 26 | author="John Doe", 27 | email="john@example.com" 28 | ) 29 | 30 | # Push changes (with authentication) 31 | workspace.git.push( 32 | path="/workspace/repo", 33 | username="user", 34 | password="token" 35 | ) 36 | ``` 37 | 38 | Note: 39 | All paths should be absolute paths within the Sandbox if not explicitly 40 | stated otherwise. 41 | """ 42 | 43 | from typing import List, Optional, TYPE_CHECKING 44 | from daytona_api_client import ( 45 | GitStatus, 46 | ListBranchResponse, 47 | ToolboxApi, 48 | GitAddRequest, 49 | GitCloneRequest, 50 | GitCommitRequest, 51 | GitRepoRequest, 52 | ) 53 | from daytona_sdk._utils.errors import intercept_errors 54 | from .protocols import WorkspaceInstance 55 | 56 | if TYPE_CHECKING: 57 | from .workspace import Workspace 58 | 59 | 60 | class Git: 61 | """Provides Git operations within a Sandbox. 62 | 63 | This class implements a high-level interface to Git operations that can be 64 | performed within a Daytona Sandbox. It supports common Git operations like 65 | cloning repositories, staging and committing changes, pushing and pulling 66 | changes, and checking repository status. 67 | 68 | Attributes: 69 | workspace (Workspace): The parent Sandbox instance. 70 | instance (WorkspaceInstance): The Sandbox instance this Git handler belongs to. 71 | 72 | Example: 73 | ```python 74 | # Clone a repository 75 | workspace.git.clone( 76 | url="https://github.com/user/repo.git", 77 | path="/workspace/repo" 78 | ) 79 | 80 | # Check repository status 81 | status = workspace.git.status("/workspace/repo") 82 | print(f"Modified files: {status.modified}") 83 | 84 | # Stage and commit changes 85 | workspace.git.add("/workspace/repo", ["file.txt"]) 86 | workspace.git.commit( 87 | path="/workspace/repo", 88 | message="Update file", 89 | author="John Doe", 90 | email="john@example.com" 91 | ) 92 | ``` 93 | """ 94 | 95 | def __init__( 96 | self, 97 | workspace: "Workspace", 98 | toolbox_api: ToolboxApi, 99 | instance: WorkspaceInstance, 100 | ): 101 | """Initializes a new Git handler instance. 102 | 103 | Args: 104 | workspace (Workspace): The parent Sandbox instance. 105 | toolbox_api (ToolboxApi): API client for Sandbox operations. 106 | instance (WorkspaceInstance): The Sandbox instance this Git handler belongs to. 107 | """ 108 | self.workspace = workspace 109 | self.toolbox_api = toolbox_api 110 | self.instance = instance 111 | 112 | @intercept_errors(message_prefix="Failed to add files: ") 113 | def add(self, path: str, files: List[str]) -> None: 114 | """Stages files for commit. 115 | 116 | This method stages the specified files for the next commit, similar to 117 | running 'git add' on the command line. 118 | 119 | Args: 120 | path (str): Absolute path to the Git repository root. 121 | files (List[str]): List of file paths or directories to stage, relative to the repository root. 122 | 123 | Example: 124 | ```python 125 | # Stage a single file 126 | workspace.git.add("/workspace/repo", ["file.txt"]) 127 | 128 | # Stage multiple files 129 | workspace.git.add("/workspace/repo", [ 130 | "src/main.py", 131 | "tests/test_main.py", 132 | "README.md" 133 | ]) 134 | ``` 135 | """ 136 | self.toolbox_api.git_add_files( 137 | workspace_id=self.instance.id, 138 | git_add_request=GitAddRequest( 139 | path=path, 140 | files=files 141 | ), 142 | ) 143 | 144 | @intercept_errors(message_prefix="Failed to list branches: ") 145 | def branches(self, path: str) -> ListBranchResponse: 146 | """Lists branches in the repository. 147 | 148 | This method returns information about all branches in the repository. 149 | 150 | Args: 151 | path (str): Absolute path to the Git repository root. 152 | 153 | Returns: 154 | ListBranchResponse: List of branches in the repository. 155 | 156 | Example: 157 | ```python 158 | response = workspace.git.branches("/workspace/repo") 159 | print(f"Branches: {response.branches}") 160 | ``` 161 | """ 162 | return self.toolbox_api.git_list_branches( 163 | workspace_id=self.instance.id, 164 | path=path, 165 | ) 166 | 167 | @intercept_errors(message_prefix="Failed to clone repository: ") 168 | def clone( 169 | self, 170 | url: str, 171 | path: str, 172 | branch: Optional[str] = None, 173 | commit_id: Optional[str] = None, 174 | username: Optional[str] = None, 175 | password: Optional[str] = None, 176 | ) -> None: 177 | """Clones a Git repository. 178 | 179 | This method clones a Git repository into the specified path. It supports 180 | cloning specific branches or commits, and can authenticate with the remote 181 | repository if credentials are provided. 182 | 183 | Args: 184 | url (str): Repository URL to clone from. 185 | path (str): Absolute path where the repository should be cloned. 186 | branch (Optional[str]): Specific branch to clone. If not specified, 187 | clones the default branch. 188 | commit_id (Optional[str]): Specific commit to clone. If specified, 189 | the repository will be left in a detached HEAD state at this commit. 190 | username (Optional[str]): Git username for authentication. 191 | password (Optional[str]): Git password or token for authentication. 192 | 193 | Example: 194 | ```python 195 | # Clone the default branch 196 | workspace.git.clone( 197 | url="https://github.com/user/repo.git", 198 | path="/workspace/repo" 199 | ) 200 | 201 | # Clone a specific branch with authentication 202 | workspace.git.clone( 203 | url="https://github.com/user/private-repo.git", 204 | path="/workspace/private", 205 | branch="develop", 206 | username="user", 207 | password="token" 208 | ) 209 | 210 | # Clone a specific commit 211 | workspace.git.clone( 212 | url="https://github.com/user/repo.git", 213 | path="/workspace/repo-old", 214 | commit_id="abc123" 215 | ) 216 | ``` 217 | """ 218 | self.toolbox_api.git_clone_repository( 219 | workspace_id=self.instance.id, 220 | git_clone_request=GitCloneRequest( 221 | url=url, 222 | branch=branch, 223 | path=path, 224 | username=username, 225 | password=password, 226 | commitId=commit_id, 227 | ) 228 | ) 229 | 230 | @intercept_errors(message_prefix="Failed to commit changes: ") 231 | def commit(self, path: str, message: str, author: str, email: str) -> None: 232 | """Commits staged changes. 233 | 234 | This method creates a new commit with the staged changes. Make sure to stage 235 | changes using the add() method before committing. 236 | 237 | Args: 238 | path (str): Absolute path to the Git repository root. 239 | message (str): Commit message describing the changes. 240 | author (str): Name of the commit author. 241 | email (str): Email address of the commit author. 242 | 243 | Example: 244 | ```python 245 | # Stage and commit changes 246 | workspace.git.add("/workspace/repo", ["README.md"]) 247 | workspace.git.commit( 248 | path="/workspace/repo", 249 | message="Update documentation", 250 | author="John Doe", 251 | email="john@example.com" 252 | ) 253 | ``` 254 | """ 255 | self.toolbox_api.git_commit_changes( 256 | workspace_id=self.instance.id, 257 | git_commit_request=GitCommitRequest( 258 | path=path, 259 | message=message, 260 | author=author, 261 | email=email 262 | ), 263 | ) 264 | 265 | @intercept_errors(message_prefix="Failed to push changes: ") 266 | def push( 267 | self, path: str, username: Optional[str] = None, password: Optional[str] = None 268 | ) -> None: 269 | """Pushes local commits to the remote repository. 270 | 271 | This method pushes all local commits on the current branch to the remote 272 | repository. If the remote repository requires authentication, provide 273 | username and password/token. 274 | 275 | Args: 276 | path (str): Absolute path to the Git repository root. 277 | username (Optional[str]): Git username for authentication. 278 | password (Optional[str]): Git password or token for authentication. 279 | 280 | Example: 281 | ```python 282 | # Push without authentication (for public repos or SSH) 283 | workspace.git.push("/workspace/repo") 284 | 285 | # Push with authentication 286 | workspace.git.push( 287 | path="/workspace/repo", 288 | username="user", 289 | password="github_token" 290 | ) 291 | ``` 292 | """ 293 | self.toolbox_api.git_push_changes( 294 | workspace_id=self.instance.id, 295 | git_repo_request=GitRepoRequest( 296 | path=path, 297 | username=username, 298 | password=password 299 | ), 300 | ) 301 | 302 | @intercept_errors(message_prefix="Failed to pull changes: ") 303 | def pull( 304 | self, path: str, username: Optional[str] = None, password: Optional[str] = None 305 | ) -> None: 306 | """Pulls changes from the remote repository. 307 | 308 | This method fetches and merges changes from the remote repository into 309 | the current branch. If the remote repository requires authentication, 310 | provide username and password/token. 311 | 312 | Args: 313 | path (str): Absolute path to the Git repository root. 314 | username (Optional[str]): Git username for authentication. 315 | password (Optional[str]): Git password or token for authentication. 316 | 317 | Example: 318 | ```python 319 | # Pull without authentication 320 | workspace.git.pull("/workspace/repo") 321 | 322 | # Pull with authentication 323 | workspace.git.pull( 324 | path="/workspace/repo", 325 | username="user", 326 | password="github_token" 327 | ) 328 | ``` 329 | """ 330 | self.toolbox_api.git_pull_changes( 331 | workspace_id=self.instance.id, 332 | git_repo_request=GitRepoRequest( 333 | path=path, 334 | username=username, 335 | password=password 336 | ), 337 | ) 338 | 339 | @intercept_errors(message_prefix="Failed to get status: ") 340 | def status(self, path: str) -> GitStatus: 341 | """Gets the current Git repository status. 342 | 343 | This method returns detailed information about the current state of the 344 | repository, including staged, unstaged, and untracked files. 345 | 346 | Args: 347 | path (str): Absolute path to the Git repository root. 348 | 349 | Returns: 350 | GitStatus: Repository status information including: 351 | - current_branch: Current branch name 352 | - file_status: List of file statuses 353 | - ahead: Number of local commits not pushed to remote 354 | - behind: Number of remote commits not pulled locally 355 | - branch_published: Whether the branch has been published to the remote repository 356 | 357 | Example: 358 | ```python 359 | status = workspace.git.status("/workspace/repo") 360 | print(f"On branch: {status.current_branch}") 361 | print(f"Commits ahead: {status.ahead}") 362 | print(f"Commits behind: {status.behind}") 363 | ``` 364 | """ 365 | return self.toolbox_api.git_get_status( 366 | workspace_id=self.instance.id, 367 | path=path, 368 | ) 369 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/lsp_server.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Daytona SDK provides Language Server Protocol (LSP) support through Sandbox instances. 3 | This enables advanced language features like code completion, diagnostics, and more. 4 | 5 | Example: 6 | Basic LSP server usage: 7 | ```python 8 | workspace = daytona.create() 9 | 10 | # Create and start LSP server 11 | lsp = workspace.create_lsp_server("typescript", "/workspace/project") 12 | lsp.start() 13 | 14 | # Open a file for editing 15 | lsp.did_open("/workspace/project/src/index.ts") 16 | 17 | # Get completions at a position 18 | pos = Position(line=10, character=15) 19 | completions = lsp.completions("/workspace/project/src/index.ts", pos) 20 | print(f"Completions: {completions}") 21 | 22 | # Get document symbols 23 | symbols = lsp.document_symbols("/workspace/project/src/index.ts") 24 | for symbol in symbols: 25 | print(f"{symbol.name}: {symbol.kind}") 26 | 27 | # Clean up 28 | lsp.did_close("/workspace/project/src/index.ts") 29 | lsp.stop() 30 | ``` 31 | 32 | Note: 33 | The LSP server must be started with start() before using any other methods, 34 | and should be stopped with stop() when no longer needed to free resources. 35 | """ 36 | from enum import Enum 37 | from typing import List 38 | from daytona_api_client import ( 39 | CompletionList, 40 | LspSymbol, 41 | ToolboxApi, 42 | LspServerRequest, 43 | LspDocumentRequest, 44 | LspCompletionParams 45 | ) 46 | from daytona_sdk._utils.errors import intercept_errors 47 | from .protocols import WorkspaceInstance 48 | 49 | 50 | class LspLanguageId(Enum): 51 | PYTHON = "python" 52 | TYPESCRIPT = "typescript" 53 | JAVASCRIPT = "javascript" 54 | 55 | def __str__(self): 56 | return self.value 57 | 58 | def __eq__(self, other): 59 | if isinstance(other, str): 60 | return self.value == other 61 | return super().__eq__(other) 62 | 63 | 64 | class Position: 65 | """Represents a position in a text document. 66 | 67 | This class represents a zero-based position within a text document, 68 | specified by line number and character offset. 69 | 70 | Attributes: 71 | line (int): Zero-based line number in the document. 72 | character (int): Zero-based character offset on the line. 73 | """ 74 | 75 | def __init__(self, line: int, character: int): 76 | """Initialize a new Position instance. 77 | 78 | Args: 79 | line (int): Zero-based line number in the document. 80 | character (int): Zero-based character offset on the line. 81 | """ 82 | self.line = line 83 | self.character = character 84 | 85 | 86 | class LspServer: 87 | """Provides Language Server Protocol functionality for code intelligence. 88 | 89 | This class implements a subset of the Language Server Protocol (LSP) to provide 90 | IDE-like features such as code completion, symbol search, and more. 91 | 92 | Attributes: 93 | language_id (LspLanguageId): The language server type (e.g., "python", "typescript"). 94 | path_to_project (str): Absolute path to the project root directory. 95 | instance (WorkspaceInstance): The Sandbox instance this server belongs to. 96 | """ 97 | 98 | def __init__( 99 | self, 100 | language_id: LspLanguageId, 101 | path_to_project: str, 102 | toolbox_api: ToolboxApi, 103 | instance: WorkspaceInstance, 104 | ): 105 | """Initializes a new LSP server instance. 106 | 107 | Args: 108 | language_id (LspLanguageId): The language server type (e.g., LspLanguageId.TYPESCRIPT). 109 | path_to_project (str): Absolute path to the project root directory. 110 | toolbox_api (ToolboxApi): API client for Sandbox operations. 111 | instance (WorkspaceInstance): The Sandbox instance this server belongs to. 112 | """ 113 | self.language_id = str(language_id) 114 | self.path_to_project = path_to_project 115 | self.toolbox_api = toolbox_api 116 | self.instance = instance 117 | 118 | @intercept_errors(message_prefix="Failed to start LSP server: ") 119 | def start(self) -> None: 120 | """Starts the language server. 121 | 122 | This method must be called before using any other LSP functionality. 123 | It initializes the language server for the specified language and project. 124 | 125 | Example: 126 | ```python 127 | lsp = workspace.create_lsp_server("typescript", "/workspace/project") 128 | lsp.start() # Initialize the server 129 | # Now ready for LSP operations 130 | ``` 131 | """ 132 | self.toolbox_api.lsp_start( 133 | workspace_id=self.instance.id, 134 | lsp_server_request=LspServerRequest( 135 | language_id=self.language_id, 136 | path_to_project=self.path_to_project, 137 | ), 138 | ) 139 | 140 | @intercept_errors(message_prefix="Failed to stop LSP server: ") 141 | def stop(self) -> None: 142 | """Stops the language server. 143 | 144 | This method should be called when the LSP server is no longer needed to 145 | free up system resources. 146 | 147 | Example: 148 | ```python 149 | # When done with LSP features 150 | lsp.stop() # Clean up resources 151 | ``` 152 | """ 153 | self.toolbox_api.lsp_stop( 154 | workspace_id=self.instance.id, 155 | lsp_server_request=LspServerRequest( 156 | language_id=self.language_id, 157 | path_to_project=self.path_to_project, 158 | ), 159 | ) 160 | 161 | @intercept_errors(message_prefix="Failed to open file: ") 162 | def did_open(self, path: str) -> None: 163 | """Notifies the language server that a file has been opened. 164 | 165 | This method should be called when a file is opened in the editor to enable 166 | language features like diagnostics and completions for that file. The server 167 | will begin tracking the file's contents and providing language features. 168 | 169 | Args: 170 | path (str): Absolute path to the opened file. 171 | 172 | Example: 173 | ```python 174 | # When opening a file for editing 175 | lsp.did_open("/workspace/project/src/index.ts") 176 | # Now can get completions, symbols, etc. for this file 177 | ``` 178 | """ 179 | self.toolbox_api.lsp_did_open( 180 | workspace_id=self.instance.id, 181 | lsp_document_request=LspDocumentRequest( 182 | language_id=self.language_id, 183 | path_to_project=self.path_to_project, 184 | uri=f"file://{path}", 185 | ), 186 | ) 187 | 188 | @intercept_errors(message_prefix="Failed to close file: ") 189 | def did_close(self, path: str) -> None: 190 | """Notify the language server that a file has been closed. 191 | 192 | This method should be called when a file is closed in the editor to allow 193 | the language server to clean up any resources associated with that file. 194 | Args: 195 | path (str): Absolute path to the closed file. 196 | 197 | Example: 198 | ```python 199 | # When done editing a file 200 | lsp.did_close("/workspace/project/src/index.ts") 201 | ``` 202 | """ 203 | self.toolbox_api.lsp_did_close( 204 | workspace_id=self.instance.id, 205 | lsp_document_request=LspDocumentRequest( 206 | language_id=self.language_id, 207 | path_to_project=self.path_to_project, 208 | uri=f"file://{path}", 209 | ), 210 | ) 211 | 212 | @intercept_errors(message_prefix="Failed to get symbols from document: ") 213 | def document_symbols(self, path: str) -> List[LspSymbol]: 214 | """Gets symbol information from a document. 215 | 216 | This method returns information about all symbols (functions, classes, 217 | variables, etc.) defined in the specified document. 218 | 219 | Args: 220 | path (str): Absolute path to the file to get symbols from. 221 | 222 | Returns: 223 | List[LspSymbol]: List of symbols in the document. Each symbol includes: 224 | - name: The symbol's name 225 | - kind: The symbol's kind (function, class, variable, etc.) 226 | - location: The location of the symbol in the file 227 | 228 | Example: 229 | ```python 230 | # Get all symbols in a file 231 | symbols = lsp.document_symbols("/workspace/project/src/index.ts") 232 | for symbol in symbols: 233 | print(f"{symbol.kind} {symbol.name}: {symbol.location}") 234 | ``` 235 | """ 236 | return self.toolbox_api.lsp_document_symbols( 237 | workspace_id=self.instance.id, 238 | language_id=self.language_id, 239 | path_to_project=self.path_to_project, 240 | uri=f"file://{path}", 241 | ) 242 | 243 | @intercept_errors(message_prefix="Failed to get symbols from workspace: ") 244 | def workspace_symbols(self, query: str) -> List[LspSymbol]: 245 | """Searches for symbols across the entire Sandbox. 246 | 247 | This method searches for symbols matching the query string across all files 248 | in the Sandbox. It's useful for finding declarations and definitions 249 | without knowing which file they're in. 250 | 251 | Args: 252 | query (str): Search query to match against symbol names. 253 | 254 | Returns: 255 | List[LspSymbol]: List of matching symbols from all files. Each symbol 256 | includes: 257 | - name: The symbol's name 258 | - kind: The symbol's kind (function, class, variable, etc.) 259 | - location: The location of the symbol in the file 260 | 261 | Example: 262 | ```python 263 | # Search for all symbols containing "User" 264 | symbols = lsp.workspace_symbols("User") 265 | for symbol in symbols: 266 | print(f"{symbol.name} in {symbol.location}") 267 | ``` 268 | """ 269 | return self.toolbox_api.lsp_workspace_symbols( 270 | workspace_id=self.instance.id, 271 | language_id=self.language_id, 272 | path_to_project=self.path_to_project, 273 | query=query, 274 | ) 275 | 276 | @intercept_errors(message_prefix="Failed to get completions: ") 277 | def completions(self, path: str, position: Position) -> CompletionList: 278 | """Gets completion suggestions at a position in a file. 279 | 280 | Args: 281 | path (str): Absolute path to the file. 282 | position (Position): Cursor position to get completions for. 283 | 284 | Returns: 285 | CompletionList: List of completion suggestions. The list includes: 286 | - isIncomplete: Whether more items might be available 287 | - items: List of completion items, each containing: 288 | - label: The text to insert 289 | - kind: The kind of completion 290 | - detail: Additional details about the item 291 | - documentation: Documentation for the item 292 | - sortText: Text used to sort the item in the list 293 | - filterText: Text used to filter the item 294 | - insertText: The actual text to insert (if different from label) 295 | 296 | Example: 297 | ```python 298 | # Get completions at a specific position 299 | pos = Position(line=10, character=15) 300 | completions = lsp.completions("/workspace/project/src/index.ts", pos) 301 | for item in completions.items: 302 | print(f"{item.label} ({item.kind}): {item.detail}") 303 | ``` 304 | """ 305 | return self.toolbox_api.lsp_completions( 306 | workspace_id=self.instance.id, 307 | lsp_completion_params=LspCompletionParams( 308 | language_id=self.language_id, 309 | path_to_project=self.path_to_project, 310 | uri=f"file://{path}", 311 | position=position, 312 | ), 313 | ) 314 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/process.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Daytona SDK provides powerful process and code execution capabilities through 3 | the `process` module in Sandboxes. This guide covers all available process operations 4 | and best practices. 5 | 6 | Example: 7 | Basic command execution: 8 | ```python 9 | workspace = daytona.create() 10 | 11 | # Execute a shell command 12 | response = workspace.process.exec("ls -la") 13 | print(response.result) 14 | 15 | # Run Python code 16 | response = workspace.process.code_run("print('Hello, World!')") 17 | print(response.result) 18 | ``` 19 | 20 | Using interactive sessions: 21 | ```python 22 | # Create a new session 23 | session_id = "my-session" 24 | workspace.process.create_session(session_id) 25 | 26 | # Execute commands in the session 27 | req = SessionExecuteRequest(command="cd /workspace", var_async=False) 28 | workspace.process.execute_session_command(session_id, req) 29 | 30 | req = SessionExecuteRequest(command="pwd", var_async=False) 31 | response = workspace.process.execute_session_command(session_id, req) 32 | print(response.result) # Should print "/workspace" 33 | 34 | # Clean up 35 | workspace.process.delete_session(session_id) 36 | ``` 37 | """ 38 | 39 | from typing import Optional, List, Dict 40 | from daytona_api_client import ( 41 | ToolboxApi, 42 | ExecuteResponse, 43 | ExecuteRequest, 44 | Session, 45 | SessionExecuteRequest, 46 | SessionExecuteResponse, 47 | CreateSessionRequest, 48 | Command 49 | ) 50 | 51 | from daytona_sdk._utils.errors import intercept_errors 52 | from .code_toolbox.workspace_python_code_toolbox import WorkspacePythonCodeToolbox 53 | from .protocols import WorkspaceInstance 54 | from .common.code_run_params import CodeRunParams 55 | 56 | 57 | class Process: 58 | """Handles process and code execution within a Sandbox. 59 | 60 | This class provides methods for executing shell commands and running code in 61 | the Sandbox environment. 62 | 63 | Attributes: 64 | code_toolbox (WorkspacePythonCodeToolbox): Language-specific code execution toolbox. 65 | toolbox_api (ToolboxApi): API client for Sandbox operations. 66 | instance (WorkspaceInstance): The Sandbox instance this process belongs to. 67 | """ 68 | 69 | def __init__( 70 | self, 71 | code_toolbox: WorkspacePythonCodeToolbox, 72 | toolbox_api: ToolboxApi, 73 | instance: WorkspaceInstance, 74 | ): 75 | """Initialize a new Process instance. 76 | 77 | Args: 78 | code_toolbox (WorkspacePythonCodeToolbox): Language-specific code execution toolbox. 79 | toolbox_api (ToolboxApi): API client for Sandbox operations. 80 | instance (WorkspaceInstance): The Sandbox instance this process belongs to. 81 | """ 82 | self.code_toolbox = code_toolbox 83 | self.toolbox_api = toolbox_api 84 | self.instance = instance 85 | 86 | @intercept_errors(message_prefix="Failed to execute command: ") 87 | def exec(self, command: str, cwd: Optional[str] = None, timeout: Optional[int] = None) -> ExecuteResponse: 88 | """Execute a shell command in the Sandbox. 89 | 90 | Args: 91 | command (str): Shell command to execute. 92 | cwd (Optional[str]): Working directory for command execution. If not 93 | specified, uses the Sandbox root directory. 94 | timeout (Optional[int]): Maximum time in seconds to wait for the command 95 | to complete. 0 means wait indefinitely. 96 | 97 | Returns: 98 | ExecuteResponse: Command execution results containing: 99 | - exit_code: The command's exit status 100 | - result: Standard output from the command 101 | 102 | Example: 103 | ```python 104 | # Simple command 105 | response = workspace.process.exec("echo 'Hello'") 106 | print(response.result) # Prints: Hello 107 | 108 | # Command with working directory 109 | result = workspace.process.exec("ls", cwd="/workspace/src") 110 | 111 | # Command with timeout 112 | result = workspace.process.exec("sleep 10", timeout=5) 113 | ``` 114 | """ 115 | execute_request = ExecuteRequest( 116 | command=command, 117 | cwd=cwd, 118 | timeout=timeout 119 | ) 120 | 121 | return self.toolbox_api.execute_command( 122 | workspace_id=self.instance.id, 123 | execute_request=execute_request 124 | ) 125 | 126 | def code_run(self, code: str, params: Optional[CodeRunParams] = None, timeout: Optional[int] = None) -> ExecuteResponse: 127 | """Executes code in the Sandbox using the appropriate language runtime. 128 | 129 | Args: 130 | code (str): Code to execute. 131 | params (Optional[CodeRunParams]): Parameters for code execution. 132 | timeout (Optional[int]): Maximum time in seconds to wait for the code 133 | to complete. 0 means wait indefinitely. 134 | 135 | Returns: 136 | ExecuteResponse: Code execution result containing: 137 | - exit_code: The execution's exit status 138 | - result: Standard output from the code 139 | 140 | Example: 141 | ```python 142 | # Run Python code 143 | response = workspace.process.code_run(''' 144 | x = 10 145 | y = 20 146 | print(f"Sum: {x + y}") 147 | ''') 148 | print(response.result) # Prints: Sum: 30 149 | ``` 150 | """ 151 | command = self.code_toolbox.get_run_command(code, params) 152 | return self.exec(command, timeout=timeout) 153 | 154 | @intercept_errors(message_prefix="Failed to create session: ") 155 | def create_session(self, session_id: str) -> None: 156 | """Create a new long-running background session in the Sandbox. 157 | 158 | Sessions are background processes that maintain state between commands, making them ideal for 159 | scenarios requiring multiple related commands or persistent environment setup. You can run 160 | long-running commands and monitor process status. 161 | 162 | Args: 163 | session_id (str): Unique identifier for the new session. 164 | 165 | Example: 166 | ```python 167 | # Create a new session 168 | session_id = "my-session" 169 | workspace.process.create_session(session_id) 170 | session = workspace.process.get_session(session_id) 171 | # Do work... 172 | workspace.process.delete_session(session_id) 173 | ``` 174 | """ 175 | request = CreateSessionRequest(sessionId=session_id) 176 | self.toolbox_api.create_session( 177 | workspace_id=self.instance.id, 178 | create_session_request=request 179 | ) 180 | 181 | @intercept_errors(message_prefix="Failed to get session: ") 182 | def get_session(self, session_id: str) -> Session: 183 | """Get a session in the Sandbox. 184 | 185 | Args: 186 | session_id (str): Unique identifier of the session to retrieve. 187 | 188 | Returns: 189 | Session: Session information including: 190 | - session_id: The session's unique identifier 191 | - commands: List of commands executed in the session 192 | 193 | Example: 194 | ```python 195 | session = workspace.process.get_session("my-session") 196 | for cmd in session.commands: 197 | print(f"Command: {cmd.command}") 198 | ``` 199 | """ 200 | return self.toolbox_api.get_session( 201 | workspace_id=self.instance.id, 202 | session_id=session_id 203 | ) 204 | 205 | @intercept_errors(message_prefix="Failed to get session command: ") 206 | def get_session_command(self, session_id: str, command_id: str) -> Command: 207 | """Get information about a specific command executed in a session. 208 | 209 | Args: 210 | session_id (str): Unique identifier of the session. 211 | command_id (str): Unique identifier of the command. 212 | 213 | Returns: 214 | Command: Command information including: 215 | - id: The command's unique identifier 216 | - command: The executed command string 217 | - exit_code: Command's exit status (if completed) 218 | 219 | Example: 220 | ```python 221 | cmd = workspace.process.get_session_command("my-session", "cmd-123") 222 | if cmd.exit_code == 0: 223 | print(f"Command {cmd.command} completed successfully") 224 | ``` 225 | """ 226 | return self.toolbox_api.get_session_command( 227 | workspace_id=self.instance.id, 228 | session_id=session_id, 229 | command_id=command_id 230 | ) 231 | 232 | @intercept_errors(message_prefix="Failed to execute session command: ") 233 | def execute_session_command(self, session_id: str, req: SessionExecuteRequest, timeout: Optional[int] = None) -> SessionExecuteResponse: 234 | """Executes a command in the session. 235 | 236 | Args: 237 | session_id (str): Unique identifier of the session to use. 238 | req (SessionExecuteRequest): Command execution request containing: 239 | - command: The command to execute 240 | - var_async: Whether to execute asynchronously 241 | 242 | Returns: 243 | SessionExecuteResponse: Command execution results containing: 244 | - cmd_id: Unique identifier for the executed command 245 | - output: Command output (if synchronous execution) 246 | - exit_code: Command exit status (if synchronous execution) 247 | 248 | Example: 249 | ```python 250 | # Execute commands in sequence, maintaining state 251 | session_id = "my-session" 252 | 253 | # Change directory 254 | req = SessionExecuteRequest(command="cd /workspace") 255 | workspace.process.execute_session_command(session_id, req) 256 | 257 | # Create a file 258 | req = SessionExecuteRequest(command="echo 'Hello' > test.txt") 259 | workspace.process.execute_session_command(session_id, req) 260 | 261 | # Read the file 262 | req = SessionExecuteRequest(command="cat test.txt") 263 | result = workspace.process.execute_session_command(session_id, req) 264 | print(result.output) # Prints: Hello 265 | ``` 266 | """ 267 | return self.toolbox_api.execute_session_command( 268 | workspace_id=self.instance.id, 269 | session_id=session_id, 270 | session_execute_request=req, 271 | _request_timeout=timeout or None 272 | ) 273 | 274 | @intercept_errors(message_prefix="Failed to get session command logs: ") 275 | def get_session_command_logs(self, session_id: str, command_id: str) -> str: 276 | """Get the logs for a command executed in a session. 277 | 278 | This method retrieves the complete output (stdout and stderr) from a 279 | command executed in a session. It's particularly useful for checking 280 | the output of asynchronous commands. 281 | 282 | Args: 283 | session_id (str): Unique identifier of the session. 284 | command_id (str): Unique identifier of the command. 285 | 286 | Returns: 287 | str: Complete command output including both stdout and stderr. 288 | 289 | Example: 290 | ```python 291 | # Execute a long-running command asynchronously 292 | req = SessionExecuteRequest( 293 | command="sleep 5; echo 'Done'", 294 | var_async=True 295 | ) 296 | response = workspace.process.execute_session_command("my-session", req) 297 | 298 | # Wait a bit, then get the logs 299 | import time 300 | time.sleep(6) 301 | logs = workspace.process.get_session_command_logs( 302 | "my-session", 303 | response.command_id 304 | ) 305 | print(logs) # Prints: Done 306 | ``` 307 | """ 308 | return self.toolbox_api.get_session_command_logs( 309 | workspace_id=self.instance.id, 310 | session_id=session_id, 311 | command_id=command_id 312 | ) 313 | 314 | @intercept_errors(message_prefix="Failed to list sessions: ") 315 | def list_sessions(self) -> List[Session]: 316 | """List all sessions in the Sandbox. 317 | 318 | Returns: 319 | List[Session]: List of all sessions in the Sandbox. 320 | 321 | Example: 322 | ```python 323 | sessions = workspace.process.list_sessions() 324 | for session in sessions: 325 | print(f"Session {session.session_id}:") 326 | print(f" Commands: {len(session.commands)}") 327 | ``` 328 | """ 329 | return self.toolbox_api.list_sessions( 330 | workspace_id=self.instance.id 331 | ) 332 | 333 | @intercept_errors(message_prefix="Failed to delete session: ") 334 | def delete_session(self, session_id: str) -> None: 335 | """Delete an interactive session from the Sandbox. 336 | 337 | This method terminates and removes a session, cleaning up any resources 338 | associated with it. 339 | 340 | Args: 341 | session_id (str): Unique identifier of the session to delete. 342 | 343 | Example: 344 | ```python 345 | # Create and use a session 346 | workspace.process.create_session("temp-session") 347 | # ... use the session ... 348 | 349 | # Clean up when done 350 | workspace.process.delete_session("temp-session") 351 | ``` 352 | """ 353 | self.toolbox_api.delete_session( 354 | workspace_id=self.instance.id, 355 | session_id=session_id 356 | ) 357 | -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/protocols.py: -------------------------------------------------------------------------------- 1 | from typing import Protocol, Dict, Any 2 | 3 | class WorkspaceCodeToolbox(Protocol): 4 | def get_default_image(self) -> str: ... 5 | def get_code_run_command(self, code: str) -> str: ... 6 | def get_code_run_args(self) -> list[str]: ... 7 | # ... other protocol methods 8 | 9 | class WorkspaceInstance(Protocol): 10 | id: str -------------------------------------------------------------------------------- /packages/python/src/daytona_sdk/workspace.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Daytona SDK core Sandbox functionality. 3 | 4 | Provides the main Workspace class representing a Daytona Sandbox that coordinates file system, 5 | Git, process execution, and LSP functionality. It serves as the central point 6 | for interacting with Daytona Sandboxes. 7 | 8 | Example: 9 | Basic Sandbox operations: 10 | ```python 11 | from daytona_sdk import Daytona 12 | daytona = Daytona() 13 | workspace = daytona.create() 14 | 15 | # File operations 16 | workspace.fs.upload_file("/workspace/test.txt", b"Hello, World!") 17 | content = workspace.fs.download_file("/workspace/test.txt") 18 | 19 | # Git operations 20 | workspace.git.clone("https://github.com/user/repo.git") 21 | 22 | # Process execution 23 | response = workspace.process.exec("ls -la") 24 | print(response.result) 25 | 26 | # LSP functionality 27 | lsp = workspace.create_lsp_server("python", "/workspace/project") 28 | lsp.did_open("/workspace/project/src/index.ts") 29 | completions = lsp.completions("/workspace/project/src/index.ts", Position(line=10, character=15)) 30 | print(completions) 31 | ``` 32 | 33 | Note: 34 | The Sandbox must be in a 'started' state before performing operations. 35 | """ 36 | 37 | import json 38 | import time 39 | from typing import Dict, Optional 40 | from daytona_sdk._utils.errors import intercept_errors 41 | from .filesystem import FileSystem 42 | from .git import Git 43 | from .process import Process 44 | from .lsp_server import LspServer, LspLanguageId 45 | from daytona_api_client import Workspace as ApiWorkspace, ToolboxApi, WorkspaceApi, WorkspaceInfo as ApiWorkspaceInfo 46 | from .protocols import WorkspaceCodeToolbox 47 | from dataclasses import dataclass 48 | from datetime import datetime 49 | from daytona_sdk._utils.errors import DaytonaError 50 | from enum import Enum 51 | from pydantic import Field 52 | from typing_extensions import Annotated 53 | from ._utils.enum import to_enum 54 | from ._utils.timeout import with_timeout 55 | 56 | 57 | @dataclass 58 | class WorkspaceTargetRegion(Enum): 59 | """Target regions for workspaces""" 60 | EU = "eu" 61 | US = "us" 62 | ASIA = "asia" 63 | 64 | def __str__(self): 65 | return self.value 66 | 67 | def __eq__(self, other): 68 | if isinstance(other, str): 69 | return self.value == other 70 | return super().__eq__(other) 71 | 72 | 73 | @dataclass 74 | class WorkspaceResources: 75 | """Resources allocated to a Sandbox. 76 | 77 | Attributes: 78 | cpu (str): Number of CPU cores allocated (e.g., "1", "2"). 79 | gpu (Optional[str]): Number of GPUs allocated (e.g., "1") or None if no GPU. 80 | memory (str): Amount of memory allocated with unit (e.g., "2Gi", "4Gi"). 81 | disk (str): Amount of disk space allocated with unit (e.g., "10Gi", "20Gi"). 82 | 83 | Example: 84 | ```python 85 | resources = WorkspaceResources( 86 | cpu="2", 87 | gpu="1", 88 | memory="4Gi", 89 | disk="20Gi" 90 | ) 91 | ``` 92 | """ 93 | cpu: str 94 | gpu: Optional[str] 95 | memory: str 96 | disk: str 97 | 98 | 99 | @dataclass 100 | class WorkspaceState(Enum): 101 | """States of a Sandbox.""" 102 | CREATING = "creating" 103 | RESTORING = "restoring" 104 | DESTROYED = "destroyed" 105 | DESTROYING = "destroying" 106 | STARTED = "started" 107 | STOPPED = "stopped" 108 | STARTING = "starting" 109 | STOPPING = "stopping" 110 | RESIZING = "resizing" 111 | ERROR = "error" 112 | UNKNOWN = "unknown" 113 | PULLING_IMAGE = "pulling_image" 114 | 115 | def __str__(self): 116 | return self.value 117 | 118 | def __eq__(self, other): 119 | if isinstance(other, str): 120 | return self.value == other 121 | return super().__eq__(other) 122 | 123 | 124 | class WorkspaceInfo(ApiWorkspaceInfo): 125 | """Structured information about a Sandbox. 126 | 127 | This class provides detailed information about a Sandbox's configuration, 128 | resources, and current state. 129 | 130 | Attributes: 131 | id (str): Unique identifier for the Sandbox. 132 | name (str): Display name of the Sandbox. 133 | image (str): Docker image used for the Sandbox. 134 | user (str): OS user running in the Sandbox. 135 | env (Dict[str, str]): Environment variables set in the Sandbox. 136 | labels (Dict[str, str]): Custom labels attached to the Sandbox. 137 | public (bool): Whether the Sandbox is publicly accessible. 138 | target (str): Target environment where the Sandbox runs. 139 | resources (WorkspaceResources): Resource allocations for the Sandbox. 140 | state (str): Current state of the Sandbox (e.g., "started", "stopped"). 141 | error_reason (Optional[str]): Error message if Sandbox is in error state. 142 | snapshot_state (Optional[str]): Current state of Sandbox snapshot. 143 | snapshot_state_created_at (Optional[datetime]): When the snapshot state was created. 144 | 145 | Example: 146 | ```python 147 | workspace = daytona.create() 148 | info = workspace.info() 149 | print(f"Workspace {info.name} is {info.state}") 150 | print(f"Resources: {info.resources.cpu} CPU, {info.resources.memory} RAM") 151 | ``` 152 | """ 153 | id: str 154 | name: str 155 | image: str 156 | user: str 157 | env: Dict[str, str] 158 | labels: Dict[str, str] 159 | public: bool 160 | target: WorkspaceTargetRegion 161 | resources: WorkspaceResources 162 | state: WorkspaceState 163 | error_reason: Optional[str] 164 | snapshot_state: Optional[str] 165 | snapshot_state_created_at: Optional[datetime] 166 | node_domain: str 167 | region: str 168 | class_name: str 169 | updated_at: str 170 | last_snapshot: Optional[str] 171 | auto_stop_interval: int 172 | provider_metadata: Annotated[Optional[str], Field( 173 | deprecated='The `provider_metadata` field is deprecated. Use `state`, `node_domain`, `region`, `class_name`, `updated_at`, `last_snapshot`, `resources`, `auto_stop_interval` instead.')] 174 | 175 | 176 | class WorkspaceInstance(ApiWorkspace): 177 | """Represents a Daytona workspace instance.""" 178 | info: Optional[WorkspaceInfo] 179 | 180 | 181 | class Workspace: 182 | """Represents a Daytona Sandbox. 183 | 184 | A Sandbox provides file system operations, Git operations, process execution, 185 | and LSP functionality. It serves as the main interface for interacting with 186 | a Daytona Sandbox. 187 | 188 | Attributes: 189 | id (str): Unique identifier for the Sandbox. 190 | instance (WorkspaceInstance): The underlying Sandbox instance. 191 | code_toolbox (WorkspaceCodeToolbox): Language-specific toolbox implementation. 192 | fs (FileSystem): File system operations interface. 193 | git (Git): Git operations interface. 194 | process (Process): Process execution interface. 195 | """ 196 | 197 | def __init__( 198 | self, 199 | id: str, 200 | instance: WorkspaceInstance, 201 | workspace_api: WorkspaceApi, 202 | toolbox_api: ToolboxApi, 203 | code_toolbox: WorkspaceCodeToolbox, 204 | ): 205 | """Initialize a new Workspace instance. 206 | 207 | Args: 208 | id (str): Unique identifier for the Sandbox. 209 | instance (WorkspaceInstance): The underlying Sandbox instance. 210 | workspace_api (WorkspaceApi): API client for Sandbox operations. 211 | toolbox_api (ToolboxApi): API client for toolbox operations. 212 | code_toolbox (WorkspaceCodeToolbox): Language-specific toolbox implementation. 213 | """ 214 | self.id = id 215 | self.instance = instance 216 | self.workspace_api = workspace_api 217 | self.toolbox_api = toolbox_api 218 | self.code_toolbox = code_toolbox 219 | 220 | # Initialize components 221 | # File system operations 222 | self.fs = FileSystem(instance, self.toolbox_api) 223 | self.git = Git(self, self.toolbox_api, instance) # Git operations 224 | self.process = Process( 225 | self.code_toolbox, self.toolbox_api, instance) # Process execution 226 | 227 | def info(self) -> WorkspaceInfo: 228 | """Gets structured information about the Sandbox. 229 | 230 | Returns: 231 | WorkspaceInfo: Detailed information about the Sandbox including its 232 | configuration, resources, and current state. 233 | 234 | Example: 235 | ```python 236 | info = workspace.info() 237 | print(f"Workspace {info.name}:") 238 | print(f"State: {info.state}") 239 | print(f"Resources: {info.resources.cpu} CPU, {info.resources.memory} RAM") 240 | ``` 241 | """ 242 | instance = self.workspace_api.get_workspace(self.id) 243 | return Workspace._to_workspace_info(instance) 244 | 245 | @intercept_errors(message_prefix="Failed to get workspace root directory: ") 246 | def get_workspace_root_dir(self) -> str: 247 | """Gets the root directory path of the Sandbox. 248 | 249 | Returns: 250 | str: The absolute path to the Sandbox root directory. 251 | 252 | Example: 253 | ```python 254 | root_dir = workspace.get_workspace_root_dir() 255 | print(f"Workspace root: {root_dir}") 256 | ``` 257 | """ 258 | response = self.toolbox_api.get_project_dir( 259 | workspace_id=self.instance.id 260 | ) 261 | return response.dir 262 | 263 | def create_lsp_server( 264 | self, language_id: LspLanguageId, path_to_project: str 265 | ) -> LspServer: 266 | """Creates a new Language Server Protocol (LSP) server instance. 267 | 268 | The LSP server provides language-specific features like code completion, 269 | diagnostics, and more. 270 | 271 | Args: 272 | language_id (LspLanguageId): The language server type (e.g., LspLanguageId.PYTHON). 273 | path_to_project (str): Absolute path to the project root directory. 274 | 275 | Returns: 276 | LspServer: A new LSP server instance configured for the specified language. 277 | 278 | Example: 279 | ```python 280 | lsp = workspace.create_lsp_server("python", "/workspace/project") 281 | ``` 282 | """ 283 | return LspServer(language_id, path_to_project, self.toolbox_api, self.instance) 284 | 285 | @intercept_errors(message_prefix="Failed to set labels: ") 286 | def set_labels(self, labels: Dict[str, str]) -> Dict[str, str]: 287 | """Sets labels for the Sandbox. 288 | 289 | Labels are key-value pairs that can be used to organize and identify Sandboxes. 290 | 291 | Args: 292 | labels (Dict[str, str]): Dictionary of key-value pairs representing Sandbox labels. 293 | 294 | Returns: 295 | Dict[str, str]: Dictionary containing the updated Sandbox labels. 296 | 297 | Example: 298 | ```python 299 | new_labels = workspace.set_labels({ 300 | "project": "my-project", 301 | "environment": "development", 302 | "team": "backend" 303 | }) 304 | print(f"Updated labels: {new_labels}") 305 | ``` 306 | """ 307 | # Convert all values to strings and create the expected labels structure 308 | string_labels = {k: str(v).lower() if isinstance( 309 | v, bool) else str(v) for k, v in labels.items()} 310 | labels_payload = {"labels": string_labels} 311 | return self.workspace_api.replace_labels(self.id, labels_payload) 312 | 313 | @intercept_errors(message_prefix="Failed to start workspace: ") 314 | @with_timeout(error_message=lambda self, timeout: f"Workspace {self.id} failed to start within the {timeout} seconds timeout period") 315 | def start(self, timeout: Optional[float] = 60): 316 | """Starts the Sandbox. 317 | 318 | This method starts the Sandbox and waits for it to be ready. 319 | 320 | Args: 321 | timeout (Optional[float]): Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 322 | 323 | Raises: 324 | DaytonaError: If timeout is negative. If workspace fails to start or times out. 325 | 326 | Example: 327 | ```python 328 | workspace = daytona.get_current_workspace("my-workspace") 329 | workspace.start(timeout=40) # Wait up to 40 seconds 330 | print("Workspace started successfully") 331 | ``` 332 | """ 333 | self.workspace_api.start_workspace(self.id, _request_timeout=timeout or None) 334 | self.wait_for_workspace_start() 335 | 336 | @intercept_errors(message_prefix="Failed to stop workspace: ") 337 | @with_timeout(error_message=lambda self, timeout: f"Workspace {self.id} failed to stop within the {timeout} seconds timeout period") 338 | def stop(self, timeout: Optional[float] = 60): 339 | """Stops the Sandbox. 340 | 341 | This method stops the Sandbox and waits for it to be fully stopped. 342 | 343 | Args: 344 | timeout (Optional[float]): Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 345 | 346 | Raises: 347 | DaytonaError: If timeout is negative; If workspace fails to stop or times out 348 | 349 | Example: 350 | ```python 351 | workspace = daytona.get_current_workspace("my-workspace") 352 | workspace.stop() 353 | print("Workspace stopped successfully") 354 | ``` 355 | """ 356 | self.workspace_api.stop_workspace(self.id, _request_timeout=timeout or None) 357 | self.wait_for_workspace_stop() 358 | 359 | @intercept_errors(message_prefix="Failure during waiting for workspace to start: ") 360 | @with_timeout(error_message=lambda self, timeout: f"Workspace {self.id} failed to become ready within the {timeout} seconds timeout period") 361 | def wait_for_workspace_start(self, timeout: Optional[float] = 60) -> None: 362 | """Waits for the Sandbox to reach the 'started' state. 363 | 364 | This method polls the Sandbox status until it reaches the 'started' state 365 | or encounters an error. 366 | 367 | Args: 368 | timeout (Optional[float]): Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 369 | 370 | Raises: 371 | DaytonaError: If timeout is negative; If workspace fails to start or times out 372 | """ 373 | state = None 374 | while state != "started": 375 | response = self.workspace_api.get_workspace(self.id) 376 | provider_metadata = json.loads(response.info.provider_metadata) 377 | state = provider_metadata.get('state', '') 378 | 379 | if state == "error": 380 | raise DaytonaError( 381 | f"Workspace {self.id} failed to start with state: {state}, error reason: {response.error_reason}") 382 | 383 | time.sleep(0.1) # Wait 100ms between checks 384 | 385 | @intercept_errors(message_prefix="Failure during waiting for workspace to stop: ") 386 | @with_timeout(error_message=lambda self, timeout: f"Workspace {self.id} failed to become stopped within the {timeout} seconds timeout period") 387 | def wait_for_workspace_stop(self, timeout: Optional[float] = 60) -> None: 388 | """Waits for the Sandbox to reach the 'stopped' state. 389 | 390 | This method polls the Sandbox status until it reaches the 'stopped' state 391 | or encounters an error. It will wait up to 60 seconds for the Sandbox to stop. 392 | 393 | Args: 394 | timeout (Optional[float]): Maximum time to wait in seconds. 0 means no timeout. Default is 60 seconds. 395 | 396 | Raises: 397 | DaytonaError: If timeout is negative. If Sandbox fails to stop or times out. 398 | """ 399 | state = None 400 | while state != "stopped": 401 | try: 402 | workspace_check = self.workspace_api.get_workspace(self.id) 403 | provider_metadata = json.loads( 404 | workspace_check.info.provider_metadata) 405 | state = provider_metadata.get('state') 406 | 407 | if state == "error": 408 | raise DaytonaError( 409 | f"Workspace {self.id} failed to stop with status: {state}, error reason: {workspace_check.error_reason}") 410 | except Exception as e: 411 | # If there's a validation error, continue waiting 412 | if "validation error" not in str(e): 413 | raise e 414 | 415 | time.sleep(0.1) # Wait 100ms between checks 416 | 417 | @intercept_errors(message_prefix="Failed to set auto-stop interval: ") 418 | def set_autostop_interval(self, interval: int) -> None: 419 | """Sets the auto-stop interval for the Sandbox. 420 | 421 | The Sandbox will automatically stop after being idle (no new events) for the specified interval. 422 | Events include any state changes or interactions with the Sandbox through the SDK. 423 | Interactions using Sandbox Previews are not included. 424 | 425 | Args: 426 | interval (int): Number of minutes of inactivity before auto-stopping. 427 | Set to 0 to disable auto-stop. Defaults to 15. 428 | 429 | Raises: 430 | DaytonaError: If interval is negative 431 | 432 | Example: 433 | ```python 434 | # Auto-stop after 1 hour 435 | workspace.set_autostop_interval(60) 436 | # Or disable auto-stop 437 | workspace.set_autostop_interval(0) 438 | ``` 439 | """ 440 | if not isinstance(interval, int) or interval < 0: 441 | raise DaytonaError( 442 | "Auto-stop interval must be a non-negative integer") 443 | 444 | self.workspace_api.set_autostop_interval(self.id, interval) 445 | self.instance.auto_stop_interval = interval 446 | 447 | @intercept_errors(message_prefix="Failed to get preview link: ") 448 | def get_preview_link(self, port: int) -> str: 449 | """Gets the preview link for the workspace at a specific port. If the port is not open, it will open it and return the link. 450 | 451 | Args: 452 | port (int): The port to open the preview link on 453 | 454 | Returns: 455 | The preview link for the workspace at the specified port 456 | """ 457 | provider_metadata = json.loads(self.instance.info.provider_metadata) 458 | node_domain = provider_metadata.get('nodeDomain', '') 459 | if not node_domain: 460 | raise DaytonaError( 461 | "Node domain not found in provider metadata. Please contact support.") 462 | 463 | return f"https://{port}-{self.id}.{node_domain}" 464 | 465 | @intercept_errors(message_prefix="Failed to archive workspace: ") 466 | def archive(self) -> None: 467 | """Archives the workspace, making it inactive and preserving its state. When sandboxes are archived, the entire filesystem 468 | state is moved to cost-effective object storage, making it possible to keep sandboxes available for an extended period. 469 | The tradeoff between archived and stopped states is that starting an archived sandbox takes more time, depending on its size. 470 | Workspace must be stopped before archiving. 471 | """ 472 | self.workspace_api.archive_workspace(self.id) 473 | 474 | @staticmethod 475 | def _to_workspace_info(instance: ApiWorkspace) -> WorkspaceInfo: 476 | """Converts an API workspace instance to a WorkspaceInfo object. 477 | 478 | Args: 479 | instance (ApiWorkspace): The API workspace instance to convert 480 | 481 | Returns: 482 | WorkspaceInfo: The converted WorkspaceInfo object 483 | """ 484 | provider_metadata = json.loads(instance.info.provider_metadata or '{}') 485 | resources_data = provider_metadata.get('resources', provider_metadata) 486 | 487 | # Extract resources with defaults 488 | resources = WorkspaceResources( 489 | cpu=str(resources_data.get('cpu', '1')), 490 | gpu=str(resources_data.get('gpu') 491 | ) if resources_data.get('gpu') else None, 492 | memory=str(resources_data.get('memory', '2')) + 'Gi', 493 | disk=str(resources_data.get('disk', '10')) + 'Gi' 494 | ) 495 | 496 | enum_state = to_enum( 497 | WorkspaceState, provider_metadata.get('state', '')) 498 | enum_target = to_enum(WorkspaceTargetRegion, instance.target) 499 | 500 | return WorkspaceInfo( 501 | id=instance.id, 502 | name=instance.name, 503 | image=instance.image, 504 | user=instance.user, 505 | env=instance.env or {}, 506 | labels=instance.labels or {}, 507 | public=instance.public, 508 | target=enum_target or instance.target, 509 | resources=resources, 510 | state=enum_state or provider_metadata.get('state', ''), 511 | error_reason=instance.error_reason, 512 | snapshot_state=provider_metadata.get('snapshotState'), 513 | snapshot_state_created_at=datetime.fromisoformat(provider_metadata.get( 514 | 'snapshotStateCreatedAt')) if provider_metadata.get('snapshotStateCreatedAt') else None, 515 | node_domain=provider_metadata.get('nodeDomain', ''), 516 | region=provider_metadata.get('region', ''), 517 | class_name=provider_metadata.get('class', ''), 518 | updated_at=provider_metadata.get('updatedAt', ''), 519 | last_snapshot=provider_metadata.get('lastSnapshot'), 520 | auto_stop_interval=provider_metadata.get('autoStopInterval', 0), 521 | created=instance.info.created or '', 522 | provider_metadata=instance.info.provider_metadata, 523 | ) 524 | --------------------------------------------------------------------------------