diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3d2ac0b..10f3091 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0" + ".": "0.2.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 93e968f..a7f151b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.2.0](https://github.com/kernel/hypeman-python/compare/v0.1.0...v0.2.0) (2026-08-21) + + +### Features + +* Repair SDK tracking generation ([da303fc](https://github.com/kernel/hypeman-python/commit/da303fcc32f896bcba886a5101a82d8a6ba728cd)) + ## [0.1.0](https://github.com/kernel/hypeman-python/compare/v0.0.1...v0.1.0) (2026-08-17) diff --git a/pyproject.toml b/pyproject.toml index 93006eb..e4edf07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hypeman" -version = "0.1.0" +version = "0.2.0" description = "The official Python library for the hypeman API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/hypeman/_version.py b/src/hypeman/_version.py index 3aa5989..b6e2188 100644 --- a/src/hypeman/_version.py +++ b/src/hypeman/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "hypeman" -__version__ = "0.1.0" # x-release-please-version +__version__ = "0.2.0" # x-release-please-version diff --git a/src/hypeman/resources/instances/instances.py b/src/hypeman/resources/instances/instances.py index e7caa0a..5e5a97a 100644 --- a/src/hypeman/resources/instances/instances.py +++ b/src/hypeman/resources/instances/instances.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Dict, Iterable +from typing import Dict, Union, Iterable +from datetime import datetime from typing_extensions import Literal import httpx @@ -126,6 +127,7 @@ def create( disk_io_bps: str | Omit = omit, entrypoint: SequenceNotStr[str] | Omit = omit, env: Dict[str, str] | Omit = omit, + expires_at: Union[str, datetime] | Omit = omit, gpu: instance_create_params.GPU | Omit = omit, health_check: HealthCheckParam | Omit = omit, hotplug_size: str | Omit = omit, @@ -139,6 +141,7 @@ def create( skip_kernel_headers: bool | Omit = omit, snapshot_policy: SnapshotPolicyParam | Omit = omit, tags: Dict[str, str] | Omit = omit, + ttl: str | Omit = omit, vcpus: int | Omit = omit, volumes: Iterable[VolumeMountParam] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -178,6 +181,9 @@ def create( env: Environment variables + expires_at: Absolute expiration time. Must be in the future and is mutually exclusive with + ttl. + gpu: GPU configuration for the instance health_check: Workload health check policy. Health is reported separately from instance @@ -219,6 +225,10 @@ def create( tags: User-defined key-value tags. + ttl: Relative lifetime from instance creation, in Go duration format. Use "0s" or + omit both expiration fields to disable automatic expiration. Mutually exclusive + with expires_at. + vcpus: Number of virtual CPUs volumes: Volumes to attach to the instance at creation time @@ -244,6 +254,7 @@ def create( "disk_io_bps": disk_io_bps, "entrypoint": entrypoint, "env": env, + "expires_at": expires_at, "gpu": gpu, "health_check": health_check, "hotplug_size": hotplug_size, @@ -257,6 +268,7 @@ def create( "skip_kernel_headers": skip_kernel_headers, "snapshot_policy": snapshot_policy, "tags": tags, + "ttl": ttl, "vcpus": vcpus, "volumes": volumes, }, @@ -274,8 +286,10 @@ def update( *, auto_standby: AutoStandbyPolicyParam | Omit = omit, env: Dict[str, str] | Omit = omit, + expires_at: Union[str, datetime] | Omit = omit, health_check: HealthCheckParam | Omit = omit, restart_policy: RestartPolicyParam | Omit = omit, + ttl: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -283,11 +297,10 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Instance: - """Update mutable properties of a running instance. + """Update mutable instance properties. - Currently supports updating - only the environment variables referenced by existing credential policies, - enabling secret/key rotation without instance restart. + TTL values are relative to when the update + is committed. Expiration updates are rejected after the current deadline passes. Args: auto_standby: Linux-only automatic standby policy based on active inbound TCP connections @@ -297,11 +310,17 @@ def update( the instance's existing credential `source.env` bindings are accepted. Use this to rotate real credential values without restarting the VM. + expires_at: Absolute expiration time. Must be in the future and is mutually exclusive with + ttl. + health_check: Workload health check policy. Health is reported separately from instance lifecycle state. restart_policy: Whole-instance restart supervision policy. + ttl: Relative lifetime from when this update is committed, in Go duration format. Use + "0s" to disable automatic expiration. Mutually exclusive with expires_at. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -318,8 +337,10 @@ def update( { "auto_standby": auto_standby, "env": env, + "expires_at": expires_at, "health_check": health_check, "restart_policy": restart_policy, + "ttl": ttl, }, instance_update_params.InstanceUpdateParams, ), @@ -919,6 +940,7 @@ async def create( disk_io_bps: str | Omit = omit, entrypoint: SequenceNotStr[str] | Omit = omit, env: Dict[str, str] | Omit = omit, + expires_at: Union[str, datetime] | Omit = omit, gpu: instance_create_params.GPU | Omit = omit, health_check: HealthCheckParam | Omit = omit, hotplug_size: str | Omit = omit, @@ -932,6 +954,7 @@ async def create( skip_kernel_headers: bool | Omit = omit, snapshot_policy: SnapshotPolicyParam | Omit = omit, tags: Dict[str, str] | Omit = omit, + ttl: str | Omit = omit, vcpus: int | Omit = omit, volumes: Iterable[VolumeMountParam] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -971,6 +994,9 @@ async def create( env: Environment variables + expires_at: Absolute expiration time. Must be in the future and is mutually exclusive with + ttl. + gpu: GPU configuration for the instance health_check: Workload health check policy. Health is reported separately from instance @@ -1012,6 +1038,10 @@ async def create( tags: User-defined key-value tags. + ttl: Relative lifetime from instance creation, in Go duration format. Use "0s" or + omit both expiration fields to disable automatic expiration. Mutually exclusive + with expires_at. + vcpus: Number of virtual CPUs volumes: Volumes to attach to the instance at creation time @@ -1037,6 +1067,7 @@ async def create( "disk_io_bps": disk_io_bps, "entrypoint": entrypoint, "env": env, + "expires_at": expires_at, "gpu": gpu, "health_check": health_check, "hotplug_size": hotplug_size, @@ -1050,6 +1081,7 @@ async def create( "skip_kernel_headers": skip_kernel_headers, "snapshot_policy": snapshot_policy, "tags": tags, + "ttl": ttl, "vcpus": vcpus, "volumes": volumes, }, @@ -1067,8 +1099,10 @@ async def update( *, auto_standby: AutoStandbyPolicyParam | Omit = omit, env: Dict[str, str] | Omit = omit, + expires_at: Union[str, datetime] | Omit = omit, health_check: HealthCheckParam | Omit = omit, restart_policy: RestartPolicyParam | Omit = omit, + ttl: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1076,11 +1110,10 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Instance: - """Update mutable properties of a running instance. + """Update mutable instance properties. - Currently supports updating - only the environment variables referenced by existing credential policies, - enabling secret/key rotation without instance restart. + TTL values are relative to when the update + is committed. Expiration updates are rejected after the current deadline passes. Args: auto_standby: Linux-only automatic standby policy based on active inbound TCP connections @@ -1090,11 +1123,17 @@ async def update( the instance's existing credential `source.env` bindings are accepted. Use this to rotate real credential values without restarting the VM. + expires_at: Absolute expiration time. Must be in the future and is mutually exclusive with + ttl. + health_check: Workload health check policy. Health is reported separately from instance lifecycle state. restart_policy: Whole-instance restart supervision policy. + ttl: Relative lifetime from when this update is committed, in Go duration format. Use + "0s" to disable automatic expiration. Mutually exclusive with expires_at. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1111,8 +1150,10 @@ async def update( { "auto_standby": auto_standby, "env": env, + "expires_at": expires_at, "health_check": health_check, "restart_policy": restart_policy, + "ttl": ttl, }, instance_update_params.InstanceUpdateParams, ), diff --git a/src/hypeman/types/instance.py b/src/hypeman/types/instance.py index f047835..254db1b 100644 --- a/src/hypeman/types/instance.py +++ b/src/hypeman/types/instance.py @@ -55,6 +55,12 @@ class Instance(BaseModel): created_at: datetime """Creation timestamp (RFC3339)""" + expires_at: Optional[datetime] = None + """Absolute expiration time, or null when automatic expiration is disabled. + + Instance TTL is cleared on fork. + """ + image: str """OCI image reference""" diff --git a/src/hypeman/types/instance_create_params.py b/src/hypeman/types/instance_create_params.py index 51b2195..9938f15 100644 --- a/src/hypeman/types/instance_create_params.py +++ b/src/hypeman/types/instance_create_params.py @@ -2,10 +2,12 @@ from __future__ import annotations -from typing import Dict, Iterable -from typing_extensions import Literal, Required, TypedDict +from typing import Dict, Union, Iterable +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict from .._types import SequenceNotStr +from .._utils import PropertyInfo from .health_check_param import HealthCheckParam from .volume_mount_param import VolumeMountParam from .restart_policy_param import RestartPolicyParam @@ -73,6 +75,12 @@ class InstanceCreateParams(TypedDict, total=False): env: Dict[str, str] """Environment variables""" + expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Absolute expiration time. + + Must be in the future and is mutually exclusive with ttl. + """ + gpu: GPU """GPU configuration for the instance""" @@ -142,6 +150,13 @@ class InstanceCreateParams(TypedDict, total=False): tags: Dict[str, str] """User-defined key-value tags.""" + ttl: str + """Relative lifetime from instance creation, in Go duration format. + + Use "0s" or omit both expiration fields to disable automatic expiration. + Mutually exclusive with expires_at. + """ + vcpus: int """Number of virtual CPUs""" diff --git a/src/hypeman/types/instance_update_params.py b/src/hypeman/types/instance_update_params.py index 1c5ab4f..6d14f3b 100644 --- a/src/hypeman/types/instance_update_params.py +++ b/src/hypeman/types/instance_update_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import Dict -from typing_extensions import TypedDict +from typing import Dict, Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict +from .._utils import PropertyInfo from .health_check_param import HealthCheckParam from .restart_policy_param import RestartPolicyParam from .auto_standby_policy_param import AutoStandbyPolicyParam @@ -26,6 +28,12 @@ class InstanceUpdateParams(TypedDict, total=False): to rotate real credential values without restarting the VM. """ + expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Absolute expiration time. + + Must be in the future and is mutually exclusive with ttl. + """ + health_check: HealthCheckParam """Workload health check policy. @@ -34,3 +42,9 @@ class InstanceUpdateParams(TypedDict, total=False): restart_policy: RestartPolicyParam """Whole-instance restart supervision policy.""" + + ttl: str + """Relative lifetime from when this update is committed, in Go duration format. + + Use "0s" to disable automatic expiration. Mutually exclusive with expires_at. + """ diff --git a/tests/api_resources/test_instances.py b/tests/api_resources/test_instances.py index 84c69ba..8682689 100644 --- a/tests/api_resources/test_instances.py +++ b/tests/api_resources/test_instances.py @@ -16,6 +16,7 @@ InstanceListResponse, WaitForStateResponse, ) +from hypeman._utils import parse_datetime base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -66,6 +67,7 @@ def test_method_create_with_all_params(self, client: Hypeman) -> None: "PORT": "3000", "NODE_ENV": "production", }, + expires_at=parse_datetime("2026-08-21T18:00:00Z"), gpu={"profile": "L40S-1Q"}, health_check={ "exec": { @@ -120,6 +122,7 @@ def test_method_create_with_all_params(self, client: Hypeman) -> None: "team": "backend", "env": "staging", }, + ttl="24h", vcpus=2, volumes=[ { @@ -181,6 +184,7 @@ def test_method_update_with_all_params(self, client: Hypeman) -> None: "ignore_source_cidrs": ["10.0.0.0/8", "192.168.0.0/16"], }, env={"OUTBOUND_OPENAI_KEY": "new-rotated-key-456"}, + expires_at=parse_datetime("2026-08-21T18:00:00Z"), health_check={ "exec": { "command": ["curl", "-f", "http://localhost:4318/"], @@ -206,6 +210,7 @@ def test_method_update_with_all_params(self, client: Hypeman) -> None: "policy": "on_failure", "stable_after": "10m", }, + ttl="6h", ) assert_matches_type(Instance, instance, path=["response"]) @@ -871,6 +876,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncHypeman) - "PORT": "3000", "NODE_ENV": "production", }, + expires_at=parse_datetime("2026-08-21T18:00:00Z"), gpu={"profile": "L40S-1Q"}, health_check={ "exec": { @@ -925,6 +931,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncHypeman) - "team": "backend", "env": "staging", }, + ttl="24h", vcpus=2, volumes=[ { @@ -986,6 +993,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncHypeman) - "ignore_source_cidrs": ["10.0.0.0/8", "192.168.0.0/16"], }, env={"OUTBOUND_OPENAI_KEY": "new-rotated-key-456"}, + expires_at=parse_datetime("2026-08-21T18:00:00Z"), health_check={ "exec": { "command": ["curl", "-f", "http://localhost:4318/"], @@ -1011,6 +1019,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncHypeman) - "policy": "on_failure", "stable_after": "10m", }, + ttl="6h", ) assert_matches_type(Instance, instance, path=["response"])