Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ Release tags use the `python-vX.Y.Z` prefix.

## [Unreleased]

## [0.1.5] - 2026-07-10

### Added
- **`spawn.launch()` now exposes the remaining launch parameters** (#6):
`ami` (custom AMI), `key_name` (SSH key pair), `pre_stop` (pre-stop hook), and
`completion_file` (path spored watches). These were accepted by the REST launch
endpoint but not surfaced by the SDK. With this, the SDK covers the full launch
body. Additive; unset params are omitted.

## [0.1.4] - 2026-07-10

### Added
Expand Down Expand Up @@ -62,7 +71,8 @@ Baseline. Earlier history is in the

---

[Unreleased]: https://github.com/spore-host/python-sdk/compare/python-v0.1.4...HEAD
[Unreleased]: https://github.com/spore-host/python-sdk/compare/python-v0.1.5...HEAD
[0.1.5]: https://github.com/spore-host/python-sdk/compare/python-v0.1.4...python-v0.1.5
[0.1.4]: https://github.com/spore-host/python-sdk/compare/python-v0.1.3...python-v0.1.4
[0.1.3]: https://github.com/spore-host/python-sdk/compare/python-v0.1.2...python-v0.1.3
[0.1.2]: https://github.com/spore-host/python-sdk/releases/tag/python-v0.1.2
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "spore-host"
version = "0.1.4"
version = "0.1.5"
description = "Python SDK for spore.host — ephemeral EC2 compute for researchers"
readme = "README.md"
requires-python = ">=3.9"
Expand Down
2 changes: 1 addition & 1 deletion spore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __repr__(self) -> str:
spawn = _LazySubClient("spawn")
notifications = _LazySubClient("notifications")

__version__ = "0.1.4"
__version__ = "0.1.5"
__all__ = [
"Client",
"truffle",
Expand Down
17 changes: 17 additions & 0 deletions spore/_spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ def launch(
on_complete: str = "terminate",
slack_workspace: Optional[str] = None,
active_processes: Optional[List[str]] = None,
ami: Optional[str] = None,
key_name: Optional[str] = None,
pre_stop: Optional[str] = None,
completion_file: Optional[str] = None,
wait: bool = False,
) -> Instance:
"""
Expand All @@ -166,6 +170,11 @@ def launch(
on_complete: Action on SPAWN_COMPLETE: "terminate", "stop", "hibernate".
slack_workspace: Slack workspace ID for lifecycle notifications.
active_processes: Process names that indicate active work (e.g. ["rsession"]).
ami: Custom AMI id (default: spore.host's recommended AMI).
key_name: EC2 key pair name for SSH access.
pre_stop: Shell command run on the instance before stop/terminate.
completion_file: Path spored watches for the completion signal
(default: /tmp/SPAWN_COMPLETE).
wait: If True, block until instance is running.

For SMS notifications, register your number separately via
Expand Down Expand Up @@ -199,6 +208,14 @@ def launch(
body["slack_workspace"] = slack_workspace
if active_processes:
body["active_processes"] = ",".join(active_processes)
if ami:
body["ami"] = ami
if key_name:
body["key_name"] = key_name
if pre_stop:
body["pre_stop"] = pre_stop
if completion_file:
body["completion_file"] = completion_file

data = self._c.post("/v1/instances", body)
# Build via _parse (single source of truth for API→Instance mapping). The
Expand Down
26 changes: 26 additions & 0 deletions tests/test_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,32 @@ def test_spawn_launch_returns_instance_with_fields():
assert body["ttl"] == "4h"


def test_spawn_launch_forwards_advanced_params():
# #6: ami/key_name/pre_stop/completion_file must reach the request body with
# the API's real keys (instances.go launch body).
fake = FakeClient(post_return=_launch_response())
SpawnClient(fake).launch(
"c7i.2xlarge",
ami="ami-0abc",
key_name="my-key",
pre_stop="aws s3 sync /out s3://bucket/",
completion_file="/tmp/DONE",
)
_, body = fake.post_calls[0]
assert body["ami"] == "ami-0abc"
assert body["key_name"] == "my-key"
assert body["pre_stop"] == "aws s3 sync /out s3://bucket/"
assert body["completion_file"] == "/tmp/DONE"


def test_spawn_launch_omits_unset_advanced_params():
fake = FakeClient(post_return=_launch_response())
SpawnClient(fake).launch("c7i.2xlarge")
_, body = fake.post_calls[0]
for k in ("ami", "key_name", "pre_stop", "completion_file"):
assert k not in body


# ── spawn status/list parsing against real instances.go keys ────────────────

def test_spawn_status_parses_instance():
Expand Down
Loading