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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.27.0"
".": "0.28.0"
}
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
configured_endpoints: 62
configured_endpoints: 63
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.28.0](https://github.com/kernel/hypeman-python/compare/v0.27.0...v0.28.0) (2026-09-01)


### Features

* Add local image tagging on shared content storage ([a466b24](https://github.com/kernel/hypeman-python/commit/a466b241a26272ca1e1e1c5ba0d787c3e26ed582))

## [0.27.0](https://github.com/kernel/hypeman-python/compare/v0.2.0...v0.27.0) (2026-08-31)


Expand Down
3 changes: 2 additions & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Methods:
Types:

```python
from hypeman.types import Image, ImageListResponse
from hypeman.types import Image, TagImageRequest, ImageListResponse
```

Methods:
Expand All @@ -50,6 +50,7 @@ Methods:
- <code title="get /images">client.images.<a href="./src/hypeman/resources/images.py">list</a>(\*\*<a href="src/hypeman/types/image_list_params.py">params</a>) -> <a href="./src/hypeman/types/image_list_response.py">ImageListResponse</a></code>
- <code title="delete /images/{name}">client.images.<a href="./src/hypeman/resources/images.py">delete</a>(name) -> None</code>
- <code title="get /images/{name}">client.images.<a href="./src/hypeman/resources/images.py">get</a>(name) -> <a href="./src/hypeman/types/image.py">Image</a></code>
- <code title="post /images/{name}/tag">client.images.<a href="./src/hypeman/resources/images.py">tag</a>(name, \*\*<a href="src/hypeman/types/image_tag_params.py">params</a>) -> <a href="./src/hypeman/types/image.py">Image</a></code>

# Instances

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "hypeman"
version = "0.27.0"
version = "0.28.0"
description = "The official Python library for the hypeman API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/hypeman/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "hypeman"
__version__ = "0.27.0" # x-release-please-version
__version__ = "0.28.0" # x-release-please-version
90 changes: 89 additions & 1 deletion src/hypeman/resources/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import httpx

from ..types import image_list_params, image_create_params
from ..types import image_tag_params, image_list_params, image_create_params
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
Expand Down Expand Up @@ -206,6 +206,44 @@ def get(
cast_to=Image,
)

def tag(
self,
name: str,
*,
target: str,
# 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,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Image:
"""
Create or update a local image tag

Args:
target: Target OCI image reference with a tag. The local tag points to the source image
without pulling it again.

extra_headers: Send extra headers

extra_query: Add additional query parameters to the request

extra_body: Add additional JSON properties to the request

timeout: Override the client-level default timeout for this request, in seconds
"""
if not name:
raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
return self._post(
path_template("/images/{name}/tag", name=name),
body=maybe_transform({"target": target}, image_tag_params.ImageTagParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=Image,
)


class AsyncImagesResource(AsyncAPIResource):
@cached_property
Expand Down Expand Up @@ -388,6 +426,44 @@ async def get(
cast_to=Image,
)

async def tag(
self,
name: str,
*,
target: str,
# 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,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Image:
"""
Create or update a local image tag

Args:
target: Target OCI image reference with a tag. The local tag points to the source image
without pulling it again.

extra_headers: Send extra headers

extra_query: Add additional query parameters to the request

extra_body: Add additional JSON properties to the request

timeout: Override the client-level default timeout for this request, in seconds
"""
if not name:
raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
return await self._post(
path_template("/images/{name}/tag", name=name),
body=await async_maybe_transform({"target": target}, image_tag_params.ImageTagParams),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=Image,
)


class ImagesResourceWithRawResponse:
def __init__(self, images: ImagesResource) -> None:
Expand All @@ -405,6 +481,9 @@ def __init__(self, images: ImagesResource) -> None:
self.get = to_raw_response_wrapper(
images.get,
)
self.tag = to_raw_response_wrapper(
images.tag,
)


class AsyncImagesResourceWithRawResponse:
Expand All @@ -423,6 +502,9 @@ def __init__(self, images: AsyncImagesResource) -> None:
self.get = async_to_raw_response_wrapper(
images.get,
)
self.tag = async_to_raw_response_wrapper(
images.tag,
)


class ImagesResourceWithStreamingResponse:
Expand All @@ -441,6 +523,9 @@ def __init__(self, images: ImagesResource) -> None:
self.get = to_streamed_response_wrapper(
images.get,
)
self.tag = to_streamed_response_wrapper(
images.tag,
)


class AsyncImagesResourceWithStreamingResponse:
Expand All @@ -459,3 +544,6 @@ def __init__(self, images: AsyncImagesResource) -> None:
self.get = async_to_streamed_response_wrapper(
images.get,
)
self.tag = async_to_streamed_response_wrapper(
images.tag,
)
1 change: 1 addition & 0 deletions src/hypeman/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from .available_device import AvailableDevice as AvailableDevice
from .build_provenance import BuildProvenance as BuildProvenance
from .health_check_tcp import HealthCheckTcp as HealthCheckTcp
from .image_tag_params import ImageTagParams as ImageTagParams
from .build_list_params import BuildListParams as BuildListParams
from .capabilities_host import CapabilitiesHost as CapabilitiesHost
from .health_check_exec import HealthCheckExec as HealthCheckExec
Expand Down
15 changes: 15 additions & 0 deletions src/hypeman/types/image_tag_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Required, TypedDict

__all__ = ["ImageTagParams"]


class ImageTagParams(TypedDict, total=False):
target: Required[str]
"""Target OCI image reference with a tag.

The local tag points to the source image without pulling it again.
"""
97 changes: 96 additions & 1 deletion tests/api_resources/test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

from hypeman import Hypeman, AsyncHypeman
from tests.utils import assert_matches_type
from hypeman.types import Image, ImageListResponse
from hypeman.types import (
Image,
ImageListResponse,
)

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")

Expand Down Expand Up @@ -192,6 +195,52 @@ def test_path_params_get(self, client: Hypeman) -> None:
"",
)

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_tag(self, client: Hypeman) -> None:
image = client.images.tag(
name="name",
target="docker.io/library/nginx:stable",
)
assert_matches_type(Image, image, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_tag(self, client: Hypeman) -> None:
response = client.images.with_raw_response.tag(
name="name",
target="docker.io/library/nginx:stable",
)

assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
image = response.parse()
assert_matches_type(Image, image, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_streaming_response_tag(self, client: Hypeman) -> None:
with client.images.with_streaming_response.tag(
name="name",
target="docker.io/library/nginx:stable",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"

image = response.parse()
assert_matches_type(Image, image, path=["response"])

assert cast(Any, response.is_closed) is True

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_path_params_tag(self, client: Hypeman) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
client.images.with_raw_response.tag(
name="",
target="docker.io/library/nginx:stable",
)


class TestAsyncImages:
parametrize = pytest.mark.parametrize(
Expand Down Expand Up @@ -372,3 +421,49 @@ async def test_path_params_get(self, async_client: AsyncHypeman) -> None:
await async_client.images.with_raw_response.get(
"",
)

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_tag(self, async_client: AsyncHypeman) -> None:
image = await async_client.images.tag(
name="name",
target="docker.io/library/nginx:stable",
)
assert_matches_type(Image, image, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_tag(self, async_client: AsyncHypeman) -> None:
response = await async_client.images.with_raw_response.tag(
name="name",
target="docker.io/library/nginx:stable",
)

assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
image = await response.parse()
assert_matches_type(Image, image, path=["response"])

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_streaming_response_tag(self, async_client: AsyncHypeman) -> None:
async with async_client.images.with_streaming_response.tag(
name="name",
target="docker.io/library/nginx:stable",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"

image = await response.parse()
assert_matches_type(Image, image, path=["response"])

assert cast(Any, response.is_closed) is True

@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_path_params_tag(self, async_client: AsyncHypeman) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
await async_client.images.with_raw_response.tag(
name="",
target="docker.io/library/nginx:stable",
)