morphcloud 0.1.109__tar.gz → 0.1.111__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.
- {morphcloud-0.1.109 → morphcloud-0.1.111}/PKG-INFO +103 -3
- {morphcloud-0.1.109 → morphcloud-0.1.111}/README.md +102 -2
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/api.py +109 -6
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/cli.py +133 -12
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/config.py +12 -0
- morphcloud-0.1.111/morphcloud/volumes/__init__.py +10 -0
- morphcloud-0.1.111/morphcloud/volumes/cli.py +575 -0
- morphcloud-0.1.111/morphcloud/volumes/client.py +536 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/pyproject.toml +1 -1
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_snapshot_operations.py +44 -1
- morphcloud-0.1.111/tests/unit/test_cli_snapshots.py +307 -0
- morphcloud-0.1.111/tests/unit/test_cli_volumes.py +172 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_http_transport_sharding.py +4 -4
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_profiles.py +4 -0
- morphcloud-0.1.111/tests/unit/test_snapshot_ttl.py +258 -0
- morphcloud-0.1.111/tests/unit/test_volumes_client.py +209 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/.github/workflows/publish.yaml +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/.gitignore +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/LICENSE +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/Makefile +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/conf.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/examples/browser_example.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/examples/devbox_example.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/index.rst +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/make.bat +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_asyncify.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_bash_interpreter.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_http_transport.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_llm.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_scramble.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_ssh.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/_utils.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/cli_helpers.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/cli.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/config.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/admin/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/admin/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/admin/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/aliases/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/aliases/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/aliases/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/api_error.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/client_wrapper.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/datetime_utils.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/file.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/force_multipart.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/http_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/http_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/jsonable_encoder.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/pydantic_utilities.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/query_encoder.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/remove_none_from_dict.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/request_options.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/core/serialization.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_actions/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_actions/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_actions/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_core/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_core/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_core/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_lifecycle/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_lifecycle/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/devboxes_lifecycle/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/errors/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/errors/unprocessable_entity_error.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/templates/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/templates/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/templates/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/tmux/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/tmux/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/tmux/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/alias_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/branch_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/delete_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_detail_list_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_list_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_refs.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_spec.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_ssh_credentials_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/devbox_summary_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/http_service.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/http_service_auth_mode.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/http_validation_error.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/networking.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/secret_value_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/snapshot_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/success_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_cache_request.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_cache_start_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_detail.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_list_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_share_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_status.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_step_model.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_step_status.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/template_summary.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/timeline_event.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_debug_info_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_install_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_new_session_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_send_keys_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_session.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_sessions_list.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_snapshot_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/tmux_target.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/ttl.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/upload_response.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/validation_error.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/validation_error_loc_item.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/types/wake_on.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/uploads/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/uploads/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/uploads/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/user_secrets/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/user_secrets/client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/gen/user_secrets/raw_client.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/openapi.json +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/template_local_runner.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/template_runner.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/devbox/terminals.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/experimental/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/experimental/browser.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/morphcloud/py.typed +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/requirements.txt +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/scripts/abuild_smoke.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/scripts/increment_version.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/__init__.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/conftest.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/pytest.ini +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_as_container.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_branching.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_command_execution.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_file_operations.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_from_tag_multiple.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_http_service.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_metadata.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_simple.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_ssh_key_rotation.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_ttl.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/integration/test_user.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_cli_devbox.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_cli_devbox_terminal.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_cli_secrets.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_devbox_terminals.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_ssh_connect_retry.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_template_local_runner.py +0 -0
- {morphcloud-0.1.109 → morphcloud-0.1.111}/tests/unit/test_template_runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: morphcloud
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.111
|
|
4
4
|
Summary: A Python SDK and CLI tool for creating, managing, and interacting with Morph Cloud VMs.
|
|
5
5
|
Author-email: Morph Labs <jobs@morph.so>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -133,7 +133,7 @@ morphcloud image list [--json]
|
|
|
133
133
|
morphcloud snapshot list [--json] [--metadata KEY=VALUE]
|
|
134
134
|
|
|
135
135
|
# Create a new snapshot
|
|
136
|
-
morphcloud snapshot create --image-id <id> --vcpus <n> --memory <mb> --disk-size <mb> [--digest <hash>] [--metadata KEY=VALUE]
|
|
136
|
+
morphcloud snapshot create --image-id <id> --vcpus <n> --memory <mb> --disk-size <mb> [--digest <hash>] [--ttl-seconds <seconds>] [--metadata KEY=VALUE]
|
|
137
137
|
|
|
138
138
|
# Get detailed snapshot information
|
|
139
139
|
morphcloud snapshot get <snapshot-id>
|
|
@@ -143,6 +143,9 @@ morphcloud snapshot delete <snapshot-id>
|
|
|
143
143
|
|
|
144
144
|
# Set metadata on a snapshot
|
|
145
145
|
morphcloud snapshot set-metadata <snapshot-id> KEY1=VALUE1 [KEY2=VALUE2...]
|
|
146
|
+
|
|
147
|
+
# Set or clear snapshot retention
|
|
148
|
+
morphcloud snapshot set-ttl <snapshot-id> --ttl-seconds <seconds|-1>
|
|
146
149
|
```
|
|
147
150
|
|
|
148
151
|
### Instances
|
|
@@ -170,7 +173,7 @@ morphcloud instance stop <instance-id>
|
|
|
170
173
|
morphcloud instance get <instance-id>
|
|
171
174
|
|
|
172
175
|
# Create snapshot from instance
|
|
173
|
-
morphcloud instance snapshot <instance-id> [--digest <hash>] [--json]
|
|
176
|
+
morphcloud instance snapshot <instance-id> [--digest <hash>] [--ttl-seconds <seconds>] [--json]
|
|
174
177
|
|
|
175
178
|
# Create multiple instances from an instance (branching)
|
|
176
179
|
morphcloud instance branch <instance-id> [--count <n>] [--json]
|
|
@@ -213,6 +216,61 @@ morphcloud instance copy inst_123:/remote/file.log ./local_dir/
|
|
|
213
216
|
morphcloud instance copy -r ./local_dir inst_123:/remote/dir
|
|
214
217
|
```
|
|
215
218
|
|
|
219
|
+
### Volumes
|
|
220
|
+
|
|
221
|
+
Morph Volumes exposes an S3-compatible object store. Bucket lifecycle and object operations use your normal `MORPH_API_KEY`.
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import os
|
|
225
|
+
|
|
226
|
+
from morphcloud import MorphCloudClient
|
|
227
|
+
|
|
228
|
+
client = MorphCloudClient(
|
|
229
|
+
api_key=os.environ["MORPH_API_KEY"],
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
client.volumes.create_bucket("my-volume-bucket")
|
|
233
|
+
client.volumes.put_object(
|
|
234
|
+
"my-volume-bucket",
|
|
235
|
+
"builds/artifact.txt",
|
|
236
|
+
b"hello from morph volumes\n",
|
|
237
|
+
content_type="text/plain",
|
|
238
|
+
)
|
|
239
|
+
payload = client.volumes.get_object("my-volume-bucket", "builds/artifact.txt")
|
|
240
|
+
client.volumes.delete_object("my-volume-bucket", "builds/artifact.txt")
|
|
241
|
+
client.volumes.delete_bucket("my-volume-bucket")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# List buckets
|
|
246
|
+
morphcloud volumes ls
|
|
247
|
+
|
|
248
|
+
# Create or remove a bucket
|
|
249
|
+
morphcloud volumes mb my-volume-bucket
|
|
250
|
+
morphcloud volumes rb my-volume-bucket
|
|
251
|
+
|
|
252
|
+
# Browse a bucket or prefix
|
|
253
|
+
morphcloud volumes ls s3://my-volume-bucket/
|
|
254
|
+
morphcloud volumes tree s3://my-volume-bucket/project/
|
|
255
|
+
|
|
256
|
+
# Upload, inspect, and download objects
|
|
257
|
+
morphcloud volumes cp ./artifact.txt s3://my-volume-bucket/builds/artifact.txt
|
|
258
|
+
morphcloud volumes cp -r ./dataset s3://my-volume-bucket/datasets/
|
|
259
|
+
morphcloud volumes cat s3://my-volume-bucket/builds/artifact.txt
|
|
260
|
+
morphcloud volumes cp s3://my-volume-bucket/builds/artifact.txt ./downloads/
|
|
261
|
+
|
|
262
|
+
# Remove objects under a prefix
|
|
263
|
+
morphcloud volumes rm -r s3://my-volume-bucket/datasets/
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
If you use profiles with non-default endpoints, the volumes gateway can be configured explicitly:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
morphcloud profile set prod \
|
|
270
|
+
--api-host cloud.morph.so \
|
|
271
|
+
--volumes-base-url https://volumes.svc.cloud.morph.so
|
|
272
|
+
```
|
|
273
|
+
|
|
216
274
|
### Interactive Tools
|
|
217
275
|
|
|
218
276
|
```bash
|
|
@@ -396,6 +454,48 @@ instance.stop()
|
|
|
396
454
|
print("done\n")
|
|
397
455
|
```
|
|
398
456
|
|
|
457
|
+
### Snapshot TTL
|
|
458
|
+
|
|
459
|
+
`ttl_seconds` must be greater than zero when you set it. Use `None` in Python or `--ttl-seconds -1` in the CLI to remove an existing snapshot TTL.
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
from morphcloud.api import MorphCloudClient
|
|
463
|
+
|
|
464
|
+
client = MorphCloudClient()
|
|
465
|
+
|
|
466
|
+
snapshot = client.snapshots.create(
|
|
467
|
+
image_id="morphvm-minimal",
|
|
468
|
+
vcpus=1,
|
|
469
|
+
memory=512,
|
|
470
|
+
disk_size=1024,
|
|
471
|
+
ttl_seconds=3600, # auto-delete after 1 hour of inactivity
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
print(snapshot.ttl.ttl_seconds)
|
|
475
|
+
print(snapshot.ttl.ttl_expire_at)
|
|
476
|
+
|
|
477
|
+
# Update an existing snapshot TTL
|
|
478
|
+
snapshot.set_ttl(7200)
|
|
479
|
+
|
|
480
|
+
# Clear the snapshot TTL entirely
|
|
481
|
+
snapshot.set_ttl(None)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
CLI equivalents:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
morphcloud snapshot create \
|
|
488
|
+
--image-id morphvm-minimal \
|
|
489
|
+
--vcpus 1 \
|
|
490
|
+
--memory 512 \
|
|
491
|
+
--disk-size 1024 \
|
|
492
|
+
--ttl-seconds 3600
|
|
493
|
+
|
|
494
|
+
morphcloud snapshot set-ttl snapshot_123 --ttl-seconds 7200
|
|
495
|
+
morphcloud snapshot set-ttl snapshot_123 --ttl-seconds -1
|
|
496
|
+
morphcloud instance snapshot instance_123 --ttl-seconds 3600
|
|
497
|
+
```
|
|
498
|
+
|
|
399
499
|
### Working with SSH
|
|
400
500
|
|
|
401
501
|
```python
|
|
@@ -102,7 +102,7 @@ morphcloud image list [--json]
|
|
|
102
102
|
morphcloud snapshot list [--json] [--metadata KEY=VALUE]
|
|
103
103
|
|
|
104
104
|
# Create a new snapshot
|
|
105
|
-
morphcloud snapshot create --image-id <id> --vcpus <n> --memory <mb> --disk-size <mb> [--digest <hash>] [--metadata KEY=VALUE]
|
|
105
|
+
morphcloud snapshot create --image-id <id> --vcpus <n> --memory <mb> --disk-size <mb> [--digest <hash>] [--ttl-seconds <seconds>] [--metadata KEY=VALUE]
|
|
106
106
|
|
|
107
107
|
# Get detailed snapshot information
|
|
108
108
|
morphcloud snapshot get <snapshot-id>
|
|
@@ -112,6 +112,9 @@ morphcloud snapshot delete <snapshot-id>
|
|
|
112
112
|
|
|
113
113
|
# Set metadata on a snapshot
|
|
114
114
|
morphcloud snapshot set-metadata <snapshot-id> KEY1=VALUE1 [KEY2=VALUE2...]
|
|
115
|
+
|
|
116
|
+
# Set or clear snapshot retention
|
|
117
|
+
morphcloud snapshot set-ttl <snapshot-id> --ttl-seconds <seconds|-1>
|
|
115
118
|
```
|
|
116
119
|
|
|
117
120
|
### Instances
|
|
@@ -139,7 +142,7 @@ morphcloud instance stop <instance-id>
|
|
|
139
142
|
morphcloud instance get <instance-id>
|
|
140
143
|
|
|
141
144
|
# Create snapshot from instance
|
|
142
|
-
morphcloud instance snapshot <instance-id> [--digest <hash>] [--json]
|
|
145
|
+
morphcloud instance snapshot <instance-id> [--digest <hash>] [--ttl-seconds <seconds>] [--json]
|
|
143
146
|
|
|
144
147
|
# Create multiple instances from an instance (branching)
|
|
145
148
|
morphcloud instance branch <instance-id> [--count <n>] [--json]
|
|
@@ -182,6 +185,61 @@ morphcloud instance copy inst_123:/remote/file.log ./local_dir/
|
|
|
182
185
|
morphcloud instance copy -r ./local_dir inst_123:/remote/dir
|
|
183
186
|
```
|
|
184
187
|
|
|
188
|
+
### Volumes
|
|
189
|
+
|
|
190
|
+
Morph Volumes exposes an S3-compatible object store. Bucket lifecycle and object operations use your normal `MORPH_API_KEY`.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
import os
|
|
194
|
+
|
|
195
|
+
from morphcloud import MorphCloudClient
|
|
196
|
+
|
|
197
|
+
client = MorphCloudClient(
|
|
198
|
+
api_key=os.environ["MORPH_API_KEY"],
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
client.volumes.create_bucket("my-volume-bucket")
|
|
202
|
+
client.volumes.put_object(
|
|
203
|
+
"my-volume-bucket",
|
|
204
|
+
"builds/artifact.txt",
|
|
205
|
+
b"hello from morph volumes\n",
|
|
206
|
+
content_type="text/plain",
|
|
207
|
+
)
|
|
208
|
+
payload = client.volumes.get_object("my-volume-bucket", "builds/artifact.txt")
|
|
209
|
+
client.volumes.delete_object("my-volume-bucket", "builds/artifact.txt")
|
|
210
|
+
client.volumes.delete_bucket("my-volume-bucket")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# List buckets
|
|
215
|
+
morphcloud volumes ls
|
|
216
|
+
|
|
217
|
+
# Create or remove a bucket
|
|
218
|
+
morphcloud volumes mb my-volume-bucket
|
|
219
|
+
morphcloud volumes rb my-volume-bucket
|
|
220
|
+
|
|
221
|
+
# Browse a bucket or prefix
|
|
222
|
+
morphcloud volumes ls s3://my-volume-bucket/
|
|
223
|
+
morphcloud volumes tree s3://my-volume-bucket/project/
|
|
224
|
+
|
|
225
|
+
# Upload, inspect, and download objects
|
|
226
|
+
morphcloud volumes cp ./artifact.txt s3://my-volume-bucket/builds/artifact.txt
|
|
227
|
+
morphcloud volumes cp -r ./dataset s3://my-volume-bucket/datasets/
|
|
228
|
+
morphcloud volumes cat s3://my-volume-bucket/builds/artifact.txt
|
|
229
|
+
morphcloud volumes cp s3://my-volume-bucket/builds/artifact.txt ./downloads/
|
|
230
|
+
|
|
231
|
+
# Remove objects under a prefix
|
|
232
|
+
morphcloud volumes rm -r s3://my-volume-bucket/datasets/
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
If you use profiles with non-default endpoints, the volumes gateway can be configured explicitly:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
morphcloud profile set prod \
|
|
239
|
+
--api-host cloud.morph.so \
|
|
240
|
+
--volumes-base-url https://volumes.svc.cloud.morph.so
|
|
241
|
+
```
|
|
242
|
+
|
|
185
243
|
### Interactive Tools
|
|
186
244
|
|
|
187
245
|
```bash
|
|
@@ -365,6 +423,48 @@ instance.stop()
|
|
|
365
423
|
print("done\n")
|
|
366
424
|
```
|
|
367
425
|
|
|
426
|
+
### Snapshot TTL
|
|
427
|
+
|
|
428
|
+
`ttl_seconds` must be greater than zero when you set it. Use `None` in Python or `--ttl-seconds -1` in the CLI to remove an existing snapshot TTL.
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
from morphcloud.api import MorphCloudClient
|
|
432
|
+
|
|
433
|
+
client = MorphCloudClient()
|
|
434
|
+
|
|
435
|
+
snapshot = client.snapshots.create(
|
|
436
|
+
image_id="morphvm-minimal",
|
|
437
|
+
vcpus=1,
|
|
438
|
+
memory=512,
|
|
439
|
+
disk_size=1024,
|
|
440
|
+
ttl_seconds=3600, # auto-delete after 1 hour of inactivity
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
print(snapshot.ttl.ttl_seconds)
|
|
444
|
+
print(snapshot.ttl.ttl_expire_at)
|
|
445
|
+
|
|
446
|
+
# Update an existing snapshot TTL
|
|
447
|
+
snapshot.set_ttl(7200)
|
|
448
|
+
|
|
449
|
+
# Clear the snapshot TTL entirely
|
|
450
|
+
snapshot.set_ttl(None)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
CLI equivalents:
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
morphcloud snapshot create \
|
|
457
|
+
--image-id morphvm-minimal \
|
|
458
|
+
--vcpus 1 \
|
|
459
|
+
--memory 512 \
|
|
460
|
+
--disk-size 1024 \
|
|
461
|
+
--ttl-seconds 3600
|
|
462
|
+
|
|
463
|
+
morphcloud snapshot set-ttl snapshot_123 --ttl-seconds 7200
|
|
464
|
+
morphcloud snapshot set-ttl snapshot_123 --ttl-seconds -1
|
|
465
|
+
morphcloud instance snapshot instance_123 --ttl-seconds 3600
|
|
466
|
+
```
|
|
467
|
+
|
|
368
468
|
### Working with SSH
|
|
369
469
|
|
|
370
470
|
```python
|
|
@@ -24,6 +24,7 @@ from morphcloud.config import resolve_settings
|
|
|
24
24
|
|
|
25
25
|
if typing.TYPE_CHECKING:
|
|
26
26
|
from morphcloud.devbox.client import AsyncDevboxClient, DevboxClient
|
|
27
|
+
from morphcloud.volumes.client import VolumesClient
|
|
27
28
|
|
|
28
29
|
# Global console instance
|
|
29
30
|
console = Console()
|
|
@@ -109,6 +110,29 @@ class TTL(BaseModel):
|
|
|
109
110
|
)
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
class SnapshotTTL(BaseModel):
|
|
114
|
+
"""Represents the retention policy for a snapshot."""
|
|
115
|
+
|
|
116
|
+
ttl_seconds: typing.Optional[int] = Field(
|
|
117
|
+
None, description="Time in seconds until the snapshot expires."
|
|
118
|
+
)
|
|
119
|
+
ttl_expire_at: typing.Optional[int] = Field(
|
|
120
|
+
None, description="Unix timestamp when the snapshot is set to expire."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _validate_snapshot_ttl_seconds(
|
|
125
|
+
ttl_seconds: typing.Optional[int], *, allow_none: bool
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Validate snapshot TTL values before sending them to the API."""
|
|
128
|
+
if ttl_seconds is None:
|
|
129
|
+
if allow_none:
|
|
130
|
+
return
|
|
131
|
+
raise ValueError("ttl_seconds must be greater than zero")
|
|
132
|
+
if ttl_seconds <= 0:
|
|
133
|
+
raise ValueError("ttl_seconds must be greater than zero")
|
|
134
|
+
|
|
135
|
+
|
|
112
136
|
class WakeOn(BaseModel):
|
|
113
137
|
"""Represents the wake-on-event configuration for an instance."""
|
|
114
138
|
|
|
@@ -139,6 +163,8 @@ class MorphCloudClient:
|
|
|
139
163
|
profile: typing.Optional[str] = None,
|
|
140
164
|
api_host: typing.Optional[str] = None,
|
|
141
165
|
service_base_url: typing.Optional[str] = None,
|
|
166
|
+
volumes_base_url: typing.Optional[str] = None,
|
|
167
|
+
volumes_service_api_key: typing.Optional[str] = None,
|
|
142
168
|
admin_base_url: typing.Optional[str] = None,
|
|
143
169
|
db_base_url: typing.Optional[str] = None,
|
|
144
170
|
ssh_hostname: typing.Optional[str] = None,
|
|
@@ -152,6 +178,7 @@ class MorphCloudClient:
|
|
|
152
178
|
"base_url": base_url,
|
|
153
179
|
"api_host": api_host,
|
|
154
180
|
"service_base_url": service_base_url,
|
|
181
|
+
"volumes_base_url": volumes_base_url,
|
|
155
182
|
"devbox_base_url": devbox_base_url,
|
|
156
183
|
"admin_base_url": admin_base_url,
|
|
157
184
|
"db_base_url": db_base_url,
|
|
@@ -166,12 +193,17 @@ class MorphCloudClient:
|
|
|
166
193
|
self.ssh_hostname = settings.ssh_hostname
|
|
167
194
|
self.ssh_port = settings.ssh_port
|
|
168
195
|
self.service_base_url = settings.service_base_url
|
|
196
|
+
self.volumes_base_url = settings.volumes_base_url
|
|
197
|
+
self.volumes_service_api_key = volumes_service_api_key or os.environ.get(
|
|
198
|
+
"MORPH_VOLUMES_SERVICE_API_KEY"
|
|
199
|
+
)
|
|
169
200
|
self.devbox_base_url = settings.devbox_base_url
|
|
170
201
|
self.admin_base_url = settings.admin_base_url
|
|
171
202
|
self.db_base_url = settings.db_base_url
|
|
172
203
|
self.profile = settings.profile
|
|
173
204
|
self._devbox_client = None
|
|
174
205
|
self._devbox_async_client = None
|
|
206
|
+
self._volumes_client = None
|
|
175
207
|
if not self.api_key:
|
|
176
208
|
raise ValueError(
|
|
177
209
|
"API key must be provided or set in MORPH_API_KEY environment variable"
|
|
@@ -295,6 +327,18 @@ class MorphCloudClient:
|
|
|
295
327
|
"""Access the API for the current authenticated user."""
|
|
296
328
|
return UserAPI(self)
|
|
297
329
|
|
|
330
|
+
@property
|
|
331
|
+
def volumes(self) -> "VolumesClient":
|
|
332
|
+
if self._volumes_client is None:
|
|
333
|
+
from morphcloud.volumes.client import VolumesClient
|
|
334
|
+
|
|
335
|
+
self._volumes_client = VolumesClient(self)
|
|
336
|
+
return self._volumes_client
|
|
337
|
+
|
|
338
|
+
@volumes.setter
|
|
339
|
+
def volumes(self, value: "VolumesClient") -> None:
|
|
340
|
+
self._volumes_client = value
|
|
341
|
+
|
|
298
342
|
|
|
299
343
|
class BaseAPI:
|
|
300
344
|
def __init__(self, client: MorphCloudClient):
|
|
@@ -645,6 +689,7 @@ class SnapshotAPI:
|
|
|
645
689
|
disk_size: typing.Optional[int] = None,
|
|
646
690
|
digest: typing.Optional[str] = None,
|
|
647
691
|
metadata: typing.Optional[typing.Dict[str, str]] = None,
|
|
692
|
+
ttl_seconds: typing.Optional[int] = None,
|
|
648
693
|
) -> Snapshot:
|
|
649
694
|
"""Create a new snapshot from a base image and a machine configuration.
|
|
650
695
|
|
|
@@ -654,7 +699,11 @@ class SnapshotAPI:
|
|
|
654
699
|
memory: The amount of memory (in MB) for the snapshot.
|
|
655
700
|
disk_size: The size of the snapshot (in MB).
|
|
656
701
|
digest: Optional digest for the snapshot. If provided, it will be used to identify the snapshot. If a snapshot with the same digest already exists, it will be returned instead of creating a new one.
|
|
657
|
-
metadata: Optional metadata to attach to the snapshot.
|
|
702
|
+
metadata: Optional metadata to attach to the snapshot.
|
|
703
|
+
ttl_seconds: Optional retention period in seconds before the snapshot is automatically deleted.
|
|
704
|
+
"""
|
|
705
|
+
if ttl_seconds is not None:
|
|
706
|
+
_validate_snapshot_ttl_seconds(ttl_seconds, allow_none=False)
|
|
658
707
|
body = {}
|
|
659
708
|
if image_id is not None:
|
|
660
709
|
body["image_id"] = image_id
|
|
@@ -668,6 +717,8 @@ class SnapshotAPI:
|
|
|
668
717
|
body["digest"] = digest
|
|
669
718
|
if metadata is not None:
|
|
670
719
|
body["metadata"] = metadata
|
|
720
|
+
if ttl_seconds is not None:
|
|
721
|
+
body["ttl_seconds"] = ttl_seconds
|
|
671
722
|
response = self._client._http_client.post("/snapshot", json=body)
|
|
672
723
|
snap: Snapshot = Snapshot.model_validate(response.json())._set_api(self)
|
|
673
724
|
snap.wait_until_ready()
|
|
@@ -681,6 +732,7 @@ class SnapshotAPI:
|
|
|
681
732
|
disk_size: typing.Optional[int] = None,
|
|
682
733
|
digest: typing.Optional[str] = None,
|
|
683
734
|
metadata: typing.Optional[typing.Dict[str, str]] = None,
|
|
735
|
+
ttl_seconds: typing.Optional[int] = None,
|
|
684
736
|
) -> Snapshot:
|
|
685
737
|
"""Create a new snapshot from a base image and a machine configuration.
|
|
686
738
|
|
|
@@ -690,7 +742,11 @@ class SnapshotAPI:
|
|
|
690
742
|
memory: The amount of memory (in MB) for the snapshot.
|
|
691
743
|
disk_size: The size of the snapshot (in MB).
|
|
692
744
|
digest: Optional digest for the snapshot. If provided, it will be used to identify the snapshot. If a snapshot with the same digest already exists, it will be returned instead of creating a new one.
|
|
693
|
-
metadata: Optional metadata to attach to the snapshot.
|
|
745
|
+
metadata: Optional metadata to attach to the snapshot.
|
|
746
|
+
ttl_seconds: Optional retention period in seconds before the snapshot is automatically deleted.
|
|
747
|
+
"""
|
|
748
|
+
if ttl_seconds is not None:
|
|
749
|
+
_validate_snapshot_ttl_seconds(ttl_seconds, allow_none=False)
|
|
694
750
|
body = {}
|
|
695
751
|
if image_id is not None:
|
|
696
752
|
body["image_id"] = image_id
|
|
@@ -704,6 +760,8 @@ class SnapshotAPI:
|
|
|
704
760
|
body["digest"] = digest
|
|
705
761
|
if metadata is not None:
|
|
706
762
|
body["metadata"] = metadata
|
|
763
|
+
if ttl_seconds is not None:
|
|
764
|
+
body["ttl_seconds"] = ttl_seconds
|
|
707
765
|
response = await self._client._async_http_client.post("/snapshot", json=body)
|
|
708
766
|
snap: Snapshot = Snapshot.model_validate(response.json())._set_api(self)
|
|
709
767
|
await snap.await_until_ready()
|
|
@@ -735,6 +793,7 @@ class Snapshot(BaseModel):
|
|
|
735
793
|
metadata: typing.Dict[str, str] = Field(
|
|
736
794
|
default_factory=dict, description="User provided metadata"
|
|
737
795
|
)
|
|
796
|
+
ttl: SnapshotTTL = Field(default_factory=SnapshotTTL)
|
|
738
797
|
|
|
739
798
|
_api: SnapshotAPI = PrivateAttr()
|
|
740
799
|
|
|
@@ -771,6 +830,28 @@ class Snapshot(BaseModel):
|
|
|
771
830
|
response.raise_for_status()
|
|
772
831
|
await self._refresh_async()
|
|
773
832
|
|
|
833
|
+
def set_ttl(self, ttl_seconds: typing.Optional[int]) -> None:
|
|
834
|
+
"""Set or clear the retention policy for this snapshot.
|
|
835
|
+
|
|
836
|
+
Parameters:
|
|
837
|
+
ttl_seconds: New TTL in seconds. Pass None to remove the snapshot TTL.
|
|
838
|
+
"""
|
|
839
|
+
_validate_snapshot_ttl_seconds(ttl_seconds, allow_none=True)
|
|
840
|
+
response = self._api._client._http_client.post(
|
|
841
|
+
f"/snapshot/{self.id}/ttl", json={"ttl_seconds": ttl_seconds}
|
|
842
|
+
)
|
|
843
|
+
response.raise_for_status()
|
|
844
|
+
self._refresh()
|
|
845
|
+
|
|
846
|
+
async def aset_ttl(self, ttl_seconds: typing.Optional[int]) -> None:
|
|
847
|
+
"""Async version of set_ttl()."""
|
|
848
|
+
_validate_snapshot_ttl_seconds(ttl_seconds, allow_none=True)
|
|
849
|
+
response = await self._api._client._async_http_client.post(
|
|
850
|
+
f"/snapshot/{self.id}/ttl", json={"ttl_seconds": ttl_seconds}
|
|
851
|
+
)
|
|
852
|
+
response.raise_for_status()
|
|
853
|
+
await self._refresh_async()
|
|
854
|
+
|
|
774
855
|
def _refresh(self) -> None:
|
|
775
856
|
refreshed = self._api.get(self.id)
|
|
776
857
|
updated = type(self).model_validate(refreshed.model_dump())
|
|
@@ -2769,13 +2850,27 @@ class Instance(BaseModel):
|
|
|
2769
2850
|
self,
|
|
2770
2851
|
digest: typing.Optional[str] = None,
|
|
2771
2852
|
metadata: typing.Optional[typing.Dict[str, str]] = None,
|
|
2853
|
+
ttl_seconds: typing.Optional[int] = None,
|
|
2772
2854
|
) -> Snapshot:
|
|
2773
|
-
"""Save the instance as a snapshot.
|
|
2855
|
+
"""Save the instance as a snapshot.
|
|
2856
|
+
|
|
2857
|
+
Parameters:
|
|
2858
|
+
digest: Optional digest to associate with the new snapshot.
|
|
2859
|
+
metadata: Optional metadata to attach to the new snapshot.
|
|
2860
|
+
ttl_seconds: Optional retention period in seconds before the snapshot is automatically deleted.
|
|
2861
|
+
"""
|
|
2862
|
+
if ttl_seconds is not None:
|
|
2863
|
+
_validate_snapshot_ttl_seconds(ttl_seconds, allow_none=False)
|
|
2774
2864
|
params = {}
|
|
2775
2865
|
if digest is not None:
|
|
2776
2866
|
params["digest"] = digest
|
|
2867
|
+
body: typing.Dict[str, typing.Any] = {}
|
|
2868
|
+
if metadata is not None:
|
|
2869
|
+
body["metadata"] = metadata
|
|
2870
|
+
if ttl_seconds is not None:
|
|
2871
|
+
body["ttl_seconds"] = ttl_seconds
|
|
2777
2872
|
response = self._api._client._http_client.post(
|
|
2778
|
-
f"/instance/{self.id}/snapshot", params=params, json=
|
|
2873
|
+
f"/instance/{self.id}/snapshot", params=params, json=body
|
|
2779
2874
|
)
|
|
2780
2875
|
return Snapshot.model_validate(response.json())._set_api(
|
|
2781
2876
|
self._api._client.snapshots,
|
|
@@ -2785,13 +2880,21 @@ class Instance(BaseModel):
|
|
|
2785
2880
|
self,
|
|
2786
2881
|
digest: typing.Optional[str] = None,
|
|
2787
2882
|
metadata: typing.Optional[typing.Dict[str, str]] = None,
|
|
2883
|
+
ttl_seconds: typing.Optional[int] = None,
|
|
2788
2884
|
) -> Snapshot:
|
|
2789
|
-
"""
|
|
2885
|
+
"""Async version of snapshot()."""
|
|
2886
|
+
if ttl_seconds is not None:
|
|
2887
|
+
_validate_snapshot_ttl_seconds(ttl_seconds, allow_none=False)
|
|
2790
2888
|
params = {}
|
|
2791
2889
|
if digest is not None:
|
|
2792
2890
|
params = {"digest": digest}
|
|
2891
|
+
body: typing.Dict[str, typing.Any] = {}
|
|
2892
|
+
if metadata is not None:
|
|
2893
|
+
body["metadata"] = metadata
|
|
2894
|
+
if ttl_seconds is not None:
|
|
2895
|
+
body["ttl_seconds"] = ttl_seconds
|
|
2793
2896
|
response = await self._api._client._async_http_client.post(
|
|
2794
|
-
f"/instance/{self.id}/snapshot", params=params, json=
|
|
2897
|
+
f"/instance/{self.id}/snapshot", params=params, json=body
|
|
2795
2898
|
)
|
|
2796
2899
|
return Snapshot.model_validate(response.json())._set_api(
|
|
2797
2900
|
self._api._client.snapshots
|