Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Support for querying OS images by instance type via `verda.images.get(instance_type=...)`

### Changed

- Refactored `Image` model to use `@dataclass` and `@dataclass_json` for consistency with `Instance` and `Volume`

## [1.24.0] - 2026-03-30

### Added
Expand Down
43 changes: 33 additions & 10 deletions tests/unit_tests/images/test_images.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import responses # https://github.com/getsentry/responses
from responses import matchers

from verda.images import Image, ImagesService

IMAGE_RESPONSE = {
'id': '0888da25-bb0d-41cc-a191-dccae45d96fd',
'name': 'Ubuntu 20.04 + CUDA 11.0',
'details': ['Ubuntu 20.04', 'CUDA 11.0'],
'image_type': 'ubuntu-20.04-cuda-11.0',
}


def test_images(http_client):
# arrange - add response mock
# arrange
responses.add(
responses.GET,
http_client._base_url + '/images',
json=[
{
'id': '0888da25-bb0d-41cc-a191-dccae45d96fd',
'name': 'Ubuntu 20.04 + CUDA 11.0',
'details': ['Ubuntu 20.04', 'CUDA 11.0'],
'image_type': 'ubuntu-20.04-cuda-11.0',
}
],
json=[IMAGE_RESPONSE],
status=200,
)

Expand All @@ -34,4 +35,26 @@ def test_images(http_client):
assert isinstance(images[0].details, list)
assert images[0].details[0] == 'Ubuntu 20.04'
assert images[0].details[1] == 'CUDA 11.0'
assert isinstance(images[0].__str__(), str)


def test_images_filter_by_instance_type(http_client):
# arrange
responses.add(
responses.GET,
http_client._base_url + '/images',
match=[matchers.query_param_matcher({'instance_type': '1A100.22V'})],
json=[IMAGE_RESPONSE],
status=200,
)

image_service = ImagesService(http_client)

# act
images = image_service.get(instance_type='1A100.22V')

# assert
assert isinstance(images, list)
assert len(images) == 1
assert isinstance(images[0], Image)
assert images[0].id == '0888da25-bb0d-41cc-a191-dccae45d96fd'
assert images[0].image_type == 'ubuntu-20.04-cuda-11.0'
96 changes: 26 additions & 70 deletions verda/images/_images.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,26 @@
from verda.helpers import stringify_class_object_properties
from dataclasses import dataclass

from dataclasses_json import Undefined, dataclass_json

IMAGES_ENDPOINT = '/images'


@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class Image:
"""An image model class."""

def __init__(self, id: str, name: str, image_type: str, details: list[str]) -> None:
"""Initialize an image object.

:param id: image id
:type id: str
:param name: image name
:type name: str
:param image_type: image type, e.g. 'ubuntu-20.04-cuda-11.0'
:type image_type: str
:param details: image details
:type details: list[str]
"""
self._id = id
self._name = name
self._image_type = image_type
self._details = details

@property
def id(self) -> str:
"""Get the image id.

:return: image id
:rtype: str
"""
return self._id

@property
def name(self) -> str:
"""Get the image name.

:return: image name
:rtype: str
"""
return self._name

@property
def image_type(self) -> str:
"""Get the image type.
"""Represents an OS image available for instances.

Comment thread
tamirse marked this conversation as resolved.
:return: image type
:rtype: str
"""
return self._image_type
Attributes:
id: Unique identifier for the image.
name: Human-readable name of the image.
image_type: Image type identifier, e.g. 'ubuntu-20.04-cuda-11.0'.
details: List of additional image details.
"""

@property
def details(self) -> list[str]:
"""Get the image details.

:return: image details
:rtype: list[str]
"""
return self._details

def __str__(self) -> str:
"""Returns a string of the json representation of the image.

:return: json representation of the image
:rtype: str
"""
return stringify_class_object_properties(self)
id: str
name: str
image_type: str
details: list[str]


class ImagesService:
Expand All @@ -74,15 +29,16 @@ class ImagesService:
def __init__(self, http_client) -> None:
self._http_client = http_client

def get(self) -> list[Image]:
def get(self, instance_type: str | None = None) -> list[Image]:
"""Get the available instance images.

:return: list of images objects
:rtype: list[Image]
Args:
instance_type: Filter OS images by instance type, e.g. '1A100.22V'.
Default is all instance images.

Returns:
List of Image objects.
"""
images = self._http_client.get(IMAGES_ENDPOINT).json()
image_objects = [
Image(image['id'], image['name'], image['image_type'], image['details'])
for image in images
]
return image_objects
params = {'instance_type': instance_type} if instance_type else None
Comment thread
tamirse marked this conversation as resolved.
images = self._http_client.get(IMAGES_ENDPOINT, params=params).json()
return [Image.from_dict(image) for image in images]
Loading