snowflake-sandbox-python 0.2.2a4__tar.gz → 0.2.2a6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/PKG-INFO +55 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/README.md +50 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/pyproject.toml +33 -9
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_egress_flags.py +34 -25
- snowflake_sandbox_python-0.2.2a6/src/snowflake/cli_sandbox/_git_repo_flags.py +58 -0
- snowflake_sandbox_python-0.2.2a6/src/snowflake/cli_sandbox/_lifecycle_command.py +108 -0
- snowflake_sandbox_python-0.2.2a6/src/snowflake/cli_sandbox/_remote_command.py +168 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_run_command.py +64 -3
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_shell_command.py +116 -38
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_upload_plan.py +1 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/commands.py +86 -48
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/__init__.py +13 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/__main__.py +4 -5
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_args.py +9 -65
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_bundle.py +2 -2
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_code_delivery.py +88 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_deploy_spec.py +13 -5
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_diagnostics.py +5 -2
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_env.py +1 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_fs_ops.py +3 -4
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_preview.py +173 -0
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_proto/sandbox_pb2.py +175 -0
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_proto/sandbox_pb2.pyi +704 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_retry.py +15 -8
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_runtime/_shims.py +32 -4
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_sandbox_state.py +83 -18
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_session.py +143 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_sse.py +2 -2
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_ssh.py +232 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_sync_transport.py +23 -18
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_transport.py +50 -54
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_tunnel.py +185 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_upload_plan.py +1 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_version.py +1 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_wire.py +64 -6
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/app.py +2 -2
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/client.py +558 -352
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/connect.py +14 -14
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/deploy.py +122 -22
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/egress.py +92 -23
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/endpoint.py +515 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/exceptions.py +54 -5
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/failure.py +16 -17
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/git_repo.py +191 -0
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/image.py +570 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/jobs.py +59 -34
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/log_stream.py +431 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/mcp.py +2 -2
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/py.typed +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/secret.py +88 -22
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/session_app.py +18 -3
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/shell.py +2 -3
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/snowflake_connection.py +83 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/sync_client.py +555 -348
- snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/sync_log_stream.py +118 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/sync_shell.py +2 -3
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/warm_session.py +7 -7
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake_sandbox_python.egg-info/PKG-INFO +55 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake_sandbox_python.egg-info/SOURCES.txt +17 -1
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake_sandbox_python.egg-info/requires.txt +4 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/tests/test_docs_references.py +5 -6
- snowflake_sandbox_python-0.2.2a6/tests/test_gate_tristate.py +249 -0
- snowflake_sandbox_python-0.2.2a6/tests/test_tutorial_sources.py +485 -0
- snowflake_sandbox_python-0.2.2a4/src/snowflake/sandbox/image.py +0 -46
- snowflake_sandbox_python-0.2.2a4/src/snowflake/sandbox/log_stream.py +0 -227
- snowflake_sandbox_python-0.2.2a4/src/snowflake/sandbox/sync_log_stream.py +0 -150
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/LICENSE +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/setup.cfg +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/setup.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/__init__.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_adapter.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_common.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/_get_command.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/plugin_spec.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/cli_sandbox/py.typed +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_ansi.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_assemble.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_connection_resolve.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_files_mixin.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_globs.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_hosts.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_mcp_discovery.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_mcp_status.py +0 -0
- /snowflake_sandbox_python-0.2.2a4/src/snowflake/sandbox/py.typed → /snowflake_sandbox_python-0.2.2a6/src/snowflake/sandbox/_proto/__init__.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_runtime/__init__.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_runtime/_fs_helper.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_runtime/_job_runner.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_runtime/_protocol.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_session_registry.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_stage.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_sync_files_mixin.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_sync_fs_ops.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_sync_watch.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_transport_errors.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/_watch.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/config.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/exec_stream.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/files.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/function.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/lifecycle.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/mount.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/sync_exec_stream.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/sync_files.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake/sandbox/types.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake_sandbox_python.egg-info/dependency_links.txt +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake_sandbox_python.egg-info/entry_points.txt +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/src/snowflake_sandbox_python.egg-info/top_level.txt +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/tests/test_imports.py +0 -0
- {snowflake_sandbox_python-0.2.2a4 → snowflake_sandbox_python-0.2.2a6}/tests/test_smoke_offline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-sandbox-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2a6
|
|
4
4
|
Summary: Snowflake sandboxes — Python SDK and CLI.
|
|
5
5
|
Author: Snowflake, Inc.
|
|
6
6
|
License:
|
|
@@ -222,11 +222,15 @@ License-File: LICENSE
|
|
|
222
222
|
Requires-Dist: httpx[http2]<1.0,>=0.27
|
|
223
223
|
Requires-Dist: typing-extensions>=4.6
|
|
224
224
|
Requires-Dist: snowflake-connector-python>=3.6
|
|
225
|
+
Requires-Dist: websockets<16,>=15
|
|
226
|
+
Requires-Dist: protobuf<6,>=5.29
|
|
225
227
|
Provides-Extra: cli
|
|
226
228
|
Requires-Dist: snowflake-cli>=3.0; extra == "cli"
|
|
227
229
|
Provides-Extra: connector
|
|
228
230
|
Provides-Extra: dev
|
|
229
231
|
Requires-Dist: pytest>=9; extra == "dev"
|
|
232
|
+
Requires-Dist: grpcio-tools==1.71.0; extra == "dev"
|
|
233
|
+
Requires-Dist: types-protobuf>=5.29; extra == "dev"
|
|
230
234
|
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
|
|
231
235
|
Requires-Dist: pytest-asyncio>=1.4; extra == "dev"
|
|
232
236
|
Requires-Dist: respx>=0.23; extra == "dev"
|
|
@@ -344,6 +348,46 @@ Auth via `SNOWFLAKE_ACCOUNT` + `SNOWFLAKE_TOKEN` env vars, a named connection in
|
|
|
344
348
|
With none of those the SDK raises `"snowflake.sandbox has no endpoint to talk to: ..."`,
|
|
345
349
|
which names every way to supply one.
|
|
346
350
|
|
|
351
|
+
## HTTP and WebSocket endpoints
|
|
352
|
+
|
|
353
|
+
Use `endpoint(port)` for ordinary HTTP or WebSocket applications. The SDK resolves
|
|
354
|
+
an owned HTTPS origin and uses the current Snowflake credential for each request or
|
|
355
|
+
handshake. Backend ingress converts the traffic into the shared Session gRPC protocol.
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
from snowflake.sandbox import Sandbox
|
|
359
|
+
|
|
360
|
+
sb = Sandbox.create(
|
|
361
|
+
image="/DB/SCHEMA/REPO/my-app:latest",
|
|
362
|
+
command=["python", "/app/server.py"], # Your server listens on port 8080.
|
|
363
|
+
connection=connection,
|
|
364
|
+
)
|
|
365
|
+
with sb.endpoint(8080) as endpoint:
|
|
366
|
+
response = endpoint.request("GET", "/api/state")
|
|
367
|
+
response.raise_for_status()
|
|
368
|
+
print(response.json())
|
|
369
|
+
with endpoint.connect_websocket("/events") as socket:
|
|
370
|
+
socket.send("hello")
|
|
371
|
+
print(socket.recv())
|
|
372
|
+
sb.disconnect() # Preserve the running sandbox.
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Resolving an endpoint does not start the guest application or wait for readiness.
|
|
376
|
+
The first request or handshake reports availability; callers decide when to try again.
|
|
377
|
+
|
|
378
|
+
`AsyncSandbox` exposes the same methods with `await` and `async with`. Use
|
|
379
|
+
`endpoint.stream(method, path)` for incremental HTTP response reads; `request()` buffers
|
|
380
|
+
the body. Both accept `headers`, `content` and `json`. Paths begin with `/` and remain
|
|
381
|
+
within the endpoint's authority. Credential and Host headers are SDK-owned.
|
|
382
|
+
Native HTTP and WebSocket clients omit browser Origin headers. Authentication
|
|
383
|
+
still uses the current Snowflake credential and TLS verifies the owned authority.
|
|
384
|
+
Origin remains reserved and cannot be supplied by callers.
|
|
385
|
+
HTTP redirects are returned without being followed; WebSocket redirects are rejected.
|
|
386
|
+
TLS verification defaults on, with request/handshake waits bounded by the connection
|
|
387
|
+
configuration. Closing an endpoint closes its streams and sockets. Async cancellation
|
|
388
|
+
also cancels pending operations. The application owns reconnect and protocol state;
|
|
389
|
+
the transport makes one attempt and raises sanitized authentication or transport errors.
|
|
390
|
+
|
|
347
391
|
## CLI
|
|
348
392
|
|
|
349
393
|
Installs as a [`snow sandbox`](https://github.com/snowflakedb/snowflake-cli) plugin:
|
|
@@ -375,3 +419,13 @@ uv run --extra docs mkdocs build --strict
|
|
|
375
419
|
|
|
376
420
|
See `CONTRIBUTING.md` in the source repository for dev setup, how to add new
|
|
377
421
|
methods, exceptions, and CLI commands.
|
|
422
|
+
|
|
423
|
+
### Command execution
|
|
424
|
+
|
|
425
|
+
`exec()` executes the program and its arguments directly. Ordinary commands such
|
|
426
|
+
as `exec(["echo", "hello"])` do not require Python in the sandbox image. For shell
|
|
427
|
+
builtins, use `exec(["sh", "-c", "cd /tmp && pwd"])`; for a script without a shebang,
|
|
428
|
+
use `exec(["sh", "/path/script"])`. Arguments are literal, without shell expansion.
|
|
429
|
+
`python -c` retains its historical alias to `python3 -c`.
|
|
430
|
+
|
|
431
|
+
This requires a server with command-array support. Python jobs, sessions, and directory helpers still require Python in the image.
|
|
@@ -104,6 +104,46 @@ Auth via `SNOWFLAKE_ACCOUNT` + `SNOWFLAKE_TOKEN` env vars, a named connection in
|
|
|
104
104
|
With none of those the SDK raises `"snowflake.sandbox has no endpoint to talk to: ..."`,
|
|
105
105
|
which names every way to supply one.
|
|
106
106
|
|
|
107
|
+
## HTTP and WebSocket endpoints
|
|
108
|
+
|
|
109
|
+
Use `endpoint(port)` for ordinary HTTP or WebSocket applications. The SDK resolves
|
|
110
|
+
an owned HTTPS origin and uses the current Snowflake credential for each request or
|
|
111
|
+
handshake. Backend ingress converts the traffic into the shared Session gRPC protocol.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from snowflake.sandbox import Sandbox
|
|
115
|
+
|
|
116
|
+
sb = Sandbox.create(
|
|
117
|
+
image="/DB/SCHEMA/REPO/my-app:latest",
|
|
118
|
+
command=["python", "/app/server.py"], # Your server listens on port 8080.
|
|
119
|
+
connection=connection,
|
|
120
|
+
)
|
|
121
|
+
with sb.endpoint(8080) as endpoint:
|
|
122
|
+
response = endpoint.request("GET", "/api/state")
|
|
123
|
+
response.raise_for_status()
|
|
124
|
+
print(response.json())
|
|
125
|
+
with endpoint.connect_websocket("/events") as socket:
|
|
126
|
+
socket.send("hello")
|
|
127
|
+
print(socket.recv())
|
|
128
|
+
sb.disconnect() # Preserve the running sandbox.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Resolving an endpoint does not start the guest application or wait for readiness.
|
|
132
|
+
The first request or handshake reports availability; callers decide when to try again.
|
|
133
|
+
|
|
134
|
+
`AsyncSandbox` exposes the same methods with `await` and `async with`. Use
|
|
135
|
+
`endpoint.stream(method, path)` for incremental HTTP response reads; `request()` buffers
|
|
136
|
+
the body. Both accept `headers`, `content` and `json`. Paths begin with `/` and remain
|
|
137
|
+
within the endpoint's authority. Credential and Host headers are SDK-owned.
|
|
138
|
+
Native HTTP and WebSocket clients omit browser Origin headers. Authentication
|
|
139
|
+
still uses the current Snowflake credential and TLS verifies the owned authority.
|
|
140
|
+
Origin remains reserved and cannot be supplied by callers.
|
|
141
|
+
HTTP redirects are returned without being followed; WebSocket redirects are rejected.
|
|
142
|
+
TLS verification defaults on, with request/handshake waits bounded by the connection
|
|
143
|
+
configuration. Closing an endpoint closes its streams and sockets. Async cancellation
|
|
144
|
+
also cancels pending operations. The application owns reconnect and protocol state;
|
|
145
|
+
the transport makes one attempt and raises sanitized authentication or transport errors.
|
|
146
|
+
|
|
107
147
|
## CLI
|
|
108
148
|
|
|
109
149
|
Installs as a [`snow sandbox`](https://github.com/snowflakedb/snowflake-cli) plugin:
|
|
@@ -135,3 +175,13 @@ uv run --extra docs mkdocs build --strict
|
|
|
135
175
|
|
|
136
176
|
See `CONTRIBUTING.md` in the source repository for dev setup, how to add new
|
|
137
177
|
methods, exceptions, and CLI commands.
|
|
178
|
+
|
|
179
|
+
### Command execution
|
|
180
|
+
|
|
181
|
+
`exec()` executes the program and its arguments directly. Ordinary commands such
|
|
182
|
+
as `exec(["echo", "hello"])` do not require Python in the sandbox image. For shell
|
|
183
|
+
builtins, use `exec(["sh", "-c", "cd /tmp && pwd"])`; for a script without a shebang,
|
|
184
|
+
use `exec(["sh", "/path/script"])`. Arguments are literal, without shell expansion.
|
|
185
|
+
`python -c` retains its historical alias to `python3 -c`.
|
|
186
|
+
|
|
187
|
+
This requires a server with command-array support. Python jobs, sessions, and directory helpers still require Python in the image.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "snowflake-sandbox-python"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2a6"
|
|
8
8
|
description = "Snowflake sandboxes — Python SDK and CLI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -31,6 +31,8 @@ dependencies = [
|
|
|
31
31
|
# reimplementing the file format, and imports it lazily so `import
|
|
32
32
|
# snowflake.sandbox` stays fast. 3.6+ ships the tomlkit config_manager.
|
|
33
33
|
"snowflake-connector-python>=3.6",
|
|
34
|
+
"websockets>=15,<16",
|
|
35
|
+
"protobuf>=5.29,<6",
|
|
34
36
|
]
|
|
35
37
|
|
|
36
38
|
[project.optional-dependencies]
|
|
@@ -47,6 +49,8 @@ cli = [
|
|
|
47
49
|
connector = []
|
|
48
50
|
dev = [
|
|
49
51
|
"pytest>=9",
|
|
52
|
+
"grpcio-tools==1.71.0",
|
|
53
|
+
"types-protobuf>=5.29",
|
|
50
54
|
# No test timeout meant a hung shell()/create could sit indefinitely; a promotion
|
|
51
55
|
# gate must always return a verdict.
|
|
52
56
|
"pytest-timeout>=2.3",
|
|
@@ -82,10 +86,11 @@ namespaces = true
|
|
|
82
86
|
|
|
83
87
|
[tool.setuptools.package-data]
|
|
84
88
|
# PEP 561: ship the marker so downstream mypy/pyright pick up the SDK's types.
|
|
85
|
-
"snowflake.sandbox" = ["py.typed"]
|
|
89
|
+
"snowflake.sandbox" = ["py.typed", "_proto/*.pyi"]
|
|
86
90
|
"snowflake.cli_sandbox" = ["py.typed"]
|
|
87
91
|
|
|
88
92
|
[tool.ruff]
|
|
93
|
+
extend-exclude = ["src/snowflake/sandbox/_proto"]
|
|
89
94
|
line-length = 100
|
|
90
95
|
target-version = "py311"
|
|
91
96
|
src = ["src", "tests", "prototype"]
|
|
@@ -107,6 +112,7 @@ max-complexity = 15
|
|
|
107
112
|
# Style/complexity sets govern src/; non-shipped code keeps correctness + format.
|
|
108
113
|
[tool.ruff.lint.per-file-ignores]
|
|
109
114
|
"tests/*" = ["B"]
|
|
115
|
+
"src/snowflake/sandbox/_proto/*" = ["ALL"]
|
|
110
116
|
"{tests,tools,examples,prototype}/**" = ["C901", "ASYNC", "SIM", "RUF"]
|
|
111
117
|
|
|
112
118
|
[tool.ruff.lint.flake8-bugbear]
|
|
@@ -142,19 +148,32 @@ ignore_missing_imports = true
|
|
|
142
148
|
asyncio_mode = "auto"
|
|
143
149
|
asyncio_default_fixture_loop_scope = "function"
|
|
144
150
|
testpaths = ["tests"]
|
|
145
|
-
# Skip live tests by default — run with:
|
|
146
|
-
|
|
151
|
+
# Skip live and docs tests by default — run with:
|
|
152
|
+
# pytest -m live --connection <name>
|
|
153
|
+
# pytest -m docs (add --docs-root if the checkout is not a sibling)
|
|
154
|
+
addopts = "-m 'not live and not docs'"
|
|
147
155
|
markers = [
|
|
148
156
|
"live: tests that run against a real sandbox-api deployment",
|
|
157
|
+
# Needs a snowflake-prod-docs checkout, which is a SEPARATE repo. Excluded by
|
|
158
|
+
# default for the same reason `live` is: it reads state this repo does not own,
|
|
159
|
+
# so a plain `pytest` must never depend on it.
|
|
160
|
+
#
|
|
161
|
+
# Specifically, these tests pin the exact fenced-block sequence of every published
|
|
162
|
+
# tutorial. That is the right assertion for catching a broken tutorial, but it also
|
|
163
|
+
# means an ordinary tutorial edit in snowflake-prod-docs would fail THIS repo's
|
|
164
|
+
# suite for anyone who happens to keep a sibling docs checkout — a docs change
|
|
165
|
+
# breaking an SDK test run. CI never checks the docs repo out, so CI was never
|
|
166
|
+
# exposed; this keeps local runs equally unexposed.
|
|
167
|
+
#
|
|
168
|
+
# --docs-root still defaults to ../snowflake-prod-docs, so opting in needs no flag.
|
|
169
|
+
"docs: tests that read a snowflake-prod-docs checkout (see --docs-root)",
|
|
149
170
|
# The promotion gate. A fast subset of `live` that a deployment must pass before it
|
|
150
171
|
# is promoted to the next tier. Chosen so every member fails LOUDLY on an unhealthy
|
|
151
172
|
# deployment: nothing that skips on error, and nothing unbounded. Run with `-m smoke`.
|
|
152
173
|
#
|
|
153
|
-
# Measured on qa6, 2026-
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
# 13-test predecessor's 29.9s. Worth re-measuring rather than trusting this line if the
|
|
157
|
-
# gate's cost ever becomes the question.
|
|
174
|
+
# Measured on qa6-mdrach, 2026-10-05: **30 tests, 83s** (15 sync + 15 async;
|
|
175
|
+
# the 2026-09-24 figure of 15 tests / ~47s was the sync client only). Adding
|
|
176
|
+
# the two egress rows cost +16.6s over the 13-test predecessor's 29.9s.
|
|
158
177
|
#
|
|
159
178
|
# This used to say "no egress tests (they assert 'BLOCKED' and so pass for the wrong
|
|
160
179
|
# reason when networking is dead)". That reasoning was correct and is why egress was
|
|
@@ -192,3 +211,8 @@ filterwarnings = [
|
|
|
192
211
|
# -e '.[cli,dev]'`, used by CI) and uv (`uv sync --extra dev --extra docs`) share
|
|
193
212
|
# a single source of truth. Install dev tooling with `--extra dev`/`--extra docs`
|
|
194
213
|
# rather than `--group`.
|
|
214
|
+
|
|
215
|
+
[[tool.mypy.overrides]]
|
|
216
|
+
# protoc stubs use unparameterized Mapping; generated code is reviewed via drift tests.
|
|
217
|
+
module = ["snowflake.sandbox._proto.*"]
|
|
218
|
+
ignore_errors = true
|
|
@@ -10,14 +10,10 @@ Spellings, chosen to match what the CLI already does elsewhere:
|
|
|
10
10
|
* ``--eai NAME`` — repeatable, like ``--env``. Names an External Access Integration
|
|
11
11
|
whose network rules the platform resolves; that is the only way to reach a host
|
|
12
12
|
outside the built-in baseline.
|
|
13
|
-
* ``--secret ENV_VAR=DB.SCHEMA.NAME@host[,host]`` — repeatable. The env var comes
|
|
13
|
+
* ``--secret ENV_VAR=DB.SCHEMA.NAME[@host[,host]]`` — repeatable. The env var comes
|
|
14
14
|
first because that is the order ``--env KEY=VAL`` established, and it is the half
|
|
15
|
-
you read inside the sandbox.
|
|
16
|
-
|
|
17
|
-
is **always required**: the SDK refuses an unscoped credential rather than let it
|
|
18
|
-
egress anywhere. (Its error text says ``host=`` becomes optional once an EAI supplies
|
|
19
|
-
the hosts — that is not implemented; an EAI does not relax the requirement, verified
|
|
20
|
-
against `compile_egress`.)
|
|
15
|
+
you read inside the sandbox. The ``@host`` scopes where the credential may be sent
|
|
16
|
+
and is required unless an ``--eai`` supplies the host grant.
|
|
21
17
|
* ``--no-default-egress`` — the closed level: Snowflake and its stages only, no
|
|
22
18
|
baseline package-manager hosts. An ``--eai`` still grants on top of it, which is
|
|
23
19
|
why this is not called ``--no-egress``.
|
|
@@ -27,7 +23,7 @@ from __future__ import annotations
|
|
|
27
23
|
|
|
28
24
|
import typer
|
|
29
25
|
|
|
30
|
-
from snowflake.sandbox.egress import Egress, compile_egress
|
|
26
|
+
from snowflake.sandbox.egress import Egress, compile_egress, compile_secrets
|
|
31
27
|
from snowflake.sandbox.secret import Secret
|
|
32
28
|
|
|
33
29
|
__all__ = ["EAI_HELP", "NO_DEFAULT_EGRESS_HELP", "SECRET_HELP", "build_egress"]
|
|
@@ -38,10 +34,17 @@ EAI_HELP = (
|
|
|
38
34
|
"readable or the platform refuses the create."
|
|
39
35
|
)
|
|
40
36
|
|
|
37
|
+
OUTBOUND_WIF_HOST_HELP = (
|
|
38
|
+
"Host that gets a Snowflake workload identity token as Authorization: Bearer, so it "
|
|
39
|
+
"can verify the calling account without a stored secret; repeatable. An exact host "
|
|
40
|
+
"or a leading-dot suffix (.example.com). It does not open egress: the host must also "
|
|
41
|
+
"be reachable, e.g. through --eai."
|
|
42
|
+
)
|
|
43
|
+
|
|
41
44
|
SECRET_HELP = (
|
|
42
|
-
"Snowflake SECRET to inject, as ENV_VAR=DB.SCHEMA.NAME@host[,host]; repeatable. "
|
|
43
|
-
"The @host scopes where the credential may be sent and is required
|
|
44
|
-
"
|
|
45
|
+
"Snowflake SECRET to inject, as ENV_VAR=DB.SCHEMA.NAME[@host[,host]]; repeatable. "
|
|
46
|
+
"The @host scopes where the credential may be sent and is required unless an "
|
|
47
|
+
"--eai supplies the host grant. ENV_VAR= is required."
|
|
45
48
|
)
|
|
46
49
|
|
|
47
50
|
NO_DEFAULT_EGRESS_HELP = (
|
|
@@ -64,6 +67,8 @@ def _parse_secret(raw: str) -> Secret:
|
|
|
64
67
|
env_var, spec = env_var.strip(), spec.strip()
|
|
65
68
|
if not env_var:
|
|
66
69
|
raise typer.BadParameter(f"empty env var in {raw!r} (expected ENV_VAR=DB.SCHEMA.NAME)")
|
|
70
|
+
else:
|
|
71
|
+
raise typer.BadParameter(f"{raw!r}: expected ENV_VAR=DB.SCHEMA.NAME[@host]")
|
|
67
72
|
|
|
68
73
|
hosts: tuple[str, ...] = ()
|
|
69
74
|
if "@" in spec:
|
|
@@ -89,38 +94,42 @@ def build_egress(
|
|
|
89
94
|
eai: list[str] | None,
|
|
90
95
|
secret: list[str] | None,
|
|
91
96
|
no_default_egress: bool,
|
|
97
|
+
outbound_wif_host: list[str] | None = None,
|
|
92
98
|
) -> tuple[Egress | None, tuple[Secret, ...]]:
|
|
93
99
|
"""`(egress, secrets)` for these flags — both empty when none were given.
|
|
94
100
|
|
|
95
101
|
Two values because that is the shape `Sandbox.create()` takes: secrets are authored
|
|
96
|
-
alongside egress
|
|
97
|
-
wire, where `compile_egress` puts them).
|
|
102
|
+
alongside egress and sent as their own top-level create field.
|
|
98
103
|
|
|
99
104
|
`None` rather than an empty `Egress` when nothing was passed, so the create body
|
|
100
105
|
stays byte-identical to today's for every existing caller.
|
|
101
106
|
"""
|
|
102
107
|
integrations = tuple(dict.fromkeys(name.strip() for name in (eai or []) if name.strip()))
|
|
103
108
|
secrets = tuple(_parse_secret(raw) for raw in (secret or []))
|
|
104
|
-
|
|
109
|
+
wif_hosts = tuple(dict.fromkeys(h.strip() for h in (outbound_wif_host or []) if h.strip()))
|
|
110
|
+
if not integrations and not secrets and not no_default_egress and not wif_hosts:
|
|
105
111
|
return None, ()
|
|
106
112
|
|
|
107
|
-
egress = Egress(
|
|
108
|
-
external_access_integrations=integrations,
|
|
109
|
-
# Uses the current parameter name. The CLI FLAG stays `--no-default-egress`: it is
|
|
110
|
-
# released, renaming it would break users' scripts, and that is a separate decision
|
|
111
|
-
# from the library parameter's name. So the flag spelling and the parameter spelling
|
|
112
|
-
# differ here deliberately — the alternative is the CLI depending on a deprecated
|
|
113
|
-
# parameter, which is worse.
|
|
114
|
-
**({"allow_public_package_registries": False} if no_default_egress else {}),
|
|
115
|
-
)
|
|
116
113
|
# Validate now, not at create time. `compile_egress` is pure and is what the client
|
|
117
114
|
# runs anyway -- but it runs *after* connecting, so a mistyped host pair otherwise
|
|
118
115
|
# costs a Snowflake round trip before saying so. These are usage errors; they should
|
|
119
|
-
# cost nothing.
|
|
116
|
+
# cost nothing. Egress() is inside the try too: it rejects a bad --outbound-wif-host.
|
|
120
117
|
from snowflake.sandbox.exceptions import SandboxError
|
|
121
118
|
|
|
122
119
|
try:
|
|
123
|
-
|
|
120
|
+
egress = Egress(
|
|
121
|
+
external_access_integrations=integrations,
|
|
122
|
+
# Uses the current parameter name. The CLI FLAG stays `--no-default-egress`: it
|
|
123
|
+
# is released, renaming it would break users' scripts, and that is a separate
|
|
124
|
+
# decision from the library parameter's name. So the flag spelling and the
|
|
125
|
+
# parameter spelling differ here deliberately — the alternative is the CLI
|
|
126
|
+
# depending on a deprecated parameter, which is worse. None is unset, which
|
|
127
|
+
# sends nothing for the level.
|
|
128
|
+
allow_public_package_registries=False if no_default_egress else None,
|
|
129
|
+
outbound_wif_hosts=wif_hosts,
|
|
130
|
+
)
|
|
131
|
+
compile_egress(egress, ())
|
|
132
|
+
compile_secrets(secrets, has_eai=bool(integrations))
|
|
124
133
|
except SandboxError as exc:
|
|
125
134
|
raise typer.BadParameter(str(exc)) from exc
|
|
126
135
|
return egress, secrets
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""`--git-repo` / `--git-integration`: mount Git repositories into a run's sandbox.
|
|
2
|
+
|
|
3
|
+
* ``--git-repo URL[=MOUNT_PATH]`` — repeatable. The first ``=`` starts the mount path.
|
|
4
|
+
* ``--git-integration NAME`` — the ``EXTERNAL_APPLICATION`` integration every
|
|
5
|
+
``--git-repo`` is cloned through, one per run.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from snowflake.sandbox.git_repo import GitRepo
|
|
16
|
+
|
|
17
|
+
__all__ = ["GIT_INTEGRATION_HELP", "GIT_REPO_HELP", "build_git_repos"]
|
|
18
|
+
|
|
19
|
+
GIT_REPO_HELP = (
|
|
20
|
+
"Git repository to mount, as an https URL with an optional =MOUNT_PATH "
|
|
21
|
+
"(default /mnt/repos/<repo>); repeatable. Needs --git-integration. The default "
|
|
22
|
+
"branch is mounted writable, and git push from the sandbox has no credential."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
GIT_INTEGRATION_HELP = (
|
|
26
|
+
"Snowflake EXTERNAL_APPLICATION integration that every --git-repo is cloned "
|
|
27
|
+
"through, with your own authorization of it."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_git_repos(raw: list[str] | None, integration: str | None) -> tuple[GitRepo, ...]:
|
|
32
|
+
"""The `GitRepo`s for these flags, or a `typer.BadParameter`."""
|
|
33
|
+
from snowflake.sandbox.exceptions import SandboxError
|
|
34
|
+
from snowflake.sandbox.git_repo import GitRepo, validate_git_repos
|
|
35
|
+
|
|
36
|
+
if not raw:
|
|
37
|
+
if integration:
|
|
38
|
+
raise typer.BadParameter("--git-integration needs at least one --git-repo.")
|
|
39
|
+
return ()
|
|
40
|
+
if not integration:
|
|
41
|
+
raise typer.BadParameter(
|
|
42
|
+
"--git-repo needs --git-integration NAME, the EXTERNAL_APPLICATION "
|
|
43
|
+
"integration the repository is cloned through."
|
|
44
|
+
)
|
|
45
|
+
repos: list[GitRepo] = []
|
|
46
|
+
for spec in raw:
|
|
47
|
+
url, _, mount_path = spec.strip().partition("=")
|
|
48
|
+
try:
|
|
49
|
+
repos.append(
|
|
50
|
+
GitRepo(url.strip(), integration=integration, mount_path=mount_path.strip())
|
|
51
|
+
)
|
|
52
|
+
except SandboxError as exc:
|
|
53
|
+
raise typer.BadParameter(f"--git-repo {spec!r}: {exc}") from exc
|
|
54
|
+
try:
|
|
55
|
+
validate_git_repos(repos)
|
|
56
|
+
except SandboxError as exc:
|
|
57
|
+
raise typer.BadParameter(f"--git-repo: {exc}") from exc
|
|
58
|
+
return tuple(repos)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""``snow sandbox suspend`` and ``snow sandbox resume``.
|
|
2
|
+
|
|
3
|
+
The CLI counterparts to ``Sandbox.suspend()`` / ``Sandbox.resume()``. Snowflake
|
|
4
|
+
suspends an idle sandbox on its own timer; these let an operator bank a sandbox's
|
|
5
|
+
state on demand, and wake one without running anything in it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
# From _common, where these are DEFINED. Importing them via _get_command (which
|
|
15
|
+
# re-exports them) is an implicit re-export and mypy rejects it.
|
|
16
|
+
from snowflake.cli_sandbox._common import (
|
|
17
|
+
_CONN_HELP,
|
|
18
|
+
_USE_SNOW_CONN_HELP,
|
|
19
|
+
_apply_connection,
|
|
20
|
+
_resolve_sandbox_id,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _suspend_command(
|
|
25
|
+
sandbox: str = typer.Argument(..., help="Sandbox name (or id) to suspend."),
|
|
26
|
+
use_snow_connection: bool = typer.Option(
|
|
27
|
+
True,
|
|
28
|
+
"--use-snow-connection/--no-snow-connection",
|
|
29
|
+
help=_USE_SNOW_CONN_HELP,
|
|
30
|
+
),
|
|
31
|
+
connection: str | None = typer.Option(
|
|
32
|
+
None,
|
|
33
|
+
"--connection",
|
|
34
|
+
"-c",
|
|
35
|
+
help=_CONN_HELP,
|
|
36
|
+
),
|
|
37
|
+
**options: Any,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Suspend a sandbox: snapshot its filesystem and tear the container down.
|
|
40
|
+
|
|
41
|
+
The sandbox keeps its files; the next thing that touches it brings it back on
|
|
42
|
+
that snapshot with a fresh process tree. Already-suspended is success.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
snow sandbox suspend SANDBOX
|
|
46
|
+
"""
|
|
47
|
+
from snowflake.cli_sandbox._adapter import sdk_connection
|
|
48
|
+
from snowflake.sandbox.exceptions import SandboxError
|
|
49
|
+
from snowflake.sandbox.sync_client import get_sandbox
|
|
50
|
+
|
|
51
|
+
_apply_connection(use_snow_connection, connection)
|
|
52
|
+
try:
|
|
53
|
+
# The SYNC client: AsyncSandbox.connect is itself a coroutine, so a bare
|
|
54
|
+
# asyncio.run(sb.suspend()) on its result would fail on a coroutine object.
|
|
55
|
+
# get_sandbox(), not Sandbox.connect(): _resolve_sandbox_id already turned a
|
|
56
|
+
# name into a cntr_ id, and connect() resolves its argument as a NAME -- so
|
|
57
|
+
# feeding it an id fails with "no sandbox named cntr_...".
|
|
58
|
+
#
|
|
59
|
+
# The SYNC client throughout: AsyncSandbox.connect is itself a coroutine, so a
|
|
60
|
+
# bare asyncio.run(sb.suspend()) on its result fails on a coroutine object.
|
|
61
|
+
get_sandbox(_resolve_sandbox_id(sandbox), connection=sdk_connection()).suspend()
|
|
62
|
+
except SandboxError as exc:
|
|
63
|
+
typer.echo(f"error: {exc}", err=True)
|
|
64
|
+
raise typer.Exit(1) from exc
|
|
65
|
+
typer.echo(f"suspended {sandbox}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _resume_command(
|
|
69
|
+
sandbox: str = typer.Argument(..., help="Sandbox name (or id) to resume."),
|
|
70
|
+
use_snow_connection: bool = typer.Option(
|
|
71
|
+
True,
|
|
72
|
+
"--use-snow-connection/--no-snow-connection",
|
|
73
|
+
help=_USE_SNOW_CONN_HELP,
|
|
74
|
+
),
|
|
75
|
+
connection: str | None = typer.Option(
|
|
76
|
+
None,
|
|
77
|
+
"--connection",
|
|
78
|
+
"-c",
|
|
79
|
+
help=_CONN_HELP,
|
|
80
|
+
),
|
|
81
|
+
**options: Any,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Wake a suspended sandbox without running anything in it.
|
|
84
|
+
|
|
85
|
+
Rarely needed -- anything that touches a sandbox resumes it anyway -- so this
|
|
86
|
+
is for paying the cold start up front. Returns once the resume is under way,
|
|
87
|
+
not once the sandbox is serving.
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
snow sandbox resume SANDBOX
|
|
91
|
+
"""
|
|
92
|
+
from snowflake.cli_sandbox._adapter import sdk_connection
|
|
93
|
+
from snowflake.sandbox.exceptions import SandboxError
|
|
94
|
+
from snowflake.sandbox.sync_client import get_sandbox
|
|
95
|
+
|
|
96
|
+
_apply_connection(use_snow_connection, connection)
|
|
97
|
+
try:
|
|
98
|
+
# get_sandbox(), not Sandbox.connect(): _resolve_sandbox_id already turned a
|
|
99
|
+
# name into a cntr_ id, and connect() resolves its argument as a NAME.
|
|
100
|
+
get_sandbox(_resolve_sandbox_id(sandbox), connection=sdk_connection()).resume()
|
|
101
|
+
except SandboxError as exc:
|
|
102
|
+
typer.echo(f"error: {exc}", err=True)
|
|
103
|
+
raise typer.Exit(1) from exc
|
|
104
|
+
typer.echo(f"resuming {sandbox}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_SUSPEND_HELP = "Suspend a sandbox, snapshotting its filesystem.\n\nExample:\n snow sandbox suspend SANDBOX\n"
|
|
108
|
+
_RESUME_HELP = "Resume a suspended sandbox.\n\nExample:\n snow sandbox resume SANDBOX\n"
|