From 3746058c49b95f42e455bb0bbfc06cb57ebbd164 Mon Sep 17 00:00:00 2001 From: AlexCK-STFC <210735915+AlexCK-STFC@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:23:24 +0100 Subject: [PATCH 1/3] ENH: Add IMAGE_OWNER and IMAGE_METADATA --- .../enums/props/image_properties.py | 8 ++++++++ openstackquery/mappings/image_mapping.py | 20 ++++++++++++++++--- openstackquery/mappings/project_mapping.py | 8 +++++++- tests/enums/props/test_image_properties.py | 6 +++++- tests/mappings/test_image_mapping.py | 10 ++++++++-- tests/mappings/test_project_mapping.py | 8 +++++++- 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/openstackquery/enums/props/image_properties.py b/openstackquery/enums/props/image_properties.py index 350e687..3964188 100644 --- a/openstackquery/enums/props/image_properties.py +++ b/openstackquery/enums/props/image_properties.py @@ -1,4 +1,6 @@ +import json from enum import auto + from openstackquery.enums.props.prop_enum import PropEnum from openstackquery.exceptions.query_property_mapping_error import ( QueryPropertyMappingError, @@ -19,6 +21,8 @@ class ImageProperties(PropEnum): IMAGE_NAME = auto() IMAGE_SIZE = auto() IMAGE_STATUS = auto() + IMAGE_OWNER = auto() + IMAGE_METADATA = auto() @staticmethod def _get_aliases(): @@ -35,6 +39,8 @@ def _get_aliases(): ImageProperties.IMAGE_NAME: ["name"], ImageProperties.IMAGE_SIZE: ["size"], ImageProperties.IMAGE_STATUS: ["status"], + ImageProperties.IMAGE_OWNER: ["owner", "tenant"], + ImageProperties.IMAGE_METADATA: ["metadata"], } @staticmethod @@ -55,6 +61,8 @@ def get_prop_mapping(prop): ImageProperties.IMAGE_NAME: lambda a: a["name"], ImageProperties.IMAGE_SIZE: lambda a: a["size"], ImageProperties.IMAGE_STATUS: lambda a: a["status"], + ImageProperties.IMAGE_OWNER: lambda a: a["metadata"]["owner_id"], + ImageProperties.IMAGE_METADATA: lambda a: json.dumps(a["metadata"]), } try: return mapping[prop] diff --git a/openstackquery/mappings/image_mapping.py b/openstackquery/mappings/image_mapping.py index d3b855c..dc6c065 100644 --- a/openstackquery/mappings/image_mapping.py +++ b/openstackquery/mappings/image_mapping.py @@ -2,6 +2,7 @@ from openstackquery.aliases import QueryChainMappings from openstackquery.enums.props.image_properties import ImageProperties +from openstackquery.enums.props.project_properties import ProjectProperties from openstackquery.enums.props.server_properties import ServerProperties from openstackquery.enums.query_presets import QueryPresets from openstackquery.handlers.client_side_handler import ClientSideHandler @@ -23,7 +24,10 @@ def get_chain_mappings() -> QueryChainMappings: Return a dictionary containing property pairs mapped to query mappings. This is used to define how to chain results from this query to other possible queries """ - return {ImageProperties.IMAGE_ID: [ServerProperties.IMAGE_ID]} + return { + ImageProperties.IMAGE_ID: [ServerProperties.IMAGE_ID], + ImageProperties.IMAGE_OWNER: [ProjectProperties.PROJECT_ID], + } @staticmethod def get_runner_mapping() -> Type[ImageRunner]: @@ -54,6 +58,7 @@ def get_server_side_handler() -> ServerSideHandler: QueryPresets.EQUAL_TO: { ImageProperties.IMAGE_NAME: lambda value: {"name": value}, ImageProperties.IMAGE_STATUS: lambda value: {"status": value}, + ImageProperties.IMAGE_OWNER: lambda value: {"owner": value}, }, QueryPresets.ANY_IN: { ImageProperties.IMAGE_NAME: lambda values: [ @@ -62,6 +67,9 @@ def get_server_side_handler() -> ServerSideHandler: ImageProperties.IMAGE_STATUS: lambda values: [ {"status": value} for value in values ], + ImageProperties.IMAGE_OWNER: lambda values: [ + {"owner": value} for value in values + ], }, QueryPresets.OLDER_THAN: { ImageProperties.IMAGE_CREATION_DATE: lambda func=TimeUtils.convert_to_timestamp, **kwargs: { @@ -127,8 +135,14 @@ def get_client_side_handler() -> ClientSideHandler: QueryPresets.NOT_EQUAL_TO: ["*"], QueryPresets.ANY_IN: ["*"], QueryPresets.NOT_ANY_IN: ["*"], - QueryPresets.MATCHES_REGEX: [ImageProperties.IMAGE_NAME], - QueryPresets.NOT_MATCHES_REGEX: [ImageProperties.IMAGE_NAME], + QueryPresets.MATCHES_REGEX: [ + ImageProperties.IMAGE_NAME, + ImageProperties.IMAGE_METADATA, + ], + QueryPresets.NOT_MATCHES_REGEX: [ + ImageProperties.IMAGE_NAME, + ImageProperties.IMAGE_METADATA, + ], QueryPresets.YOUNGER_THAN: date_prop_list, QueryPresets.YOUNGER_THAN_OR_EQUAL_TO: date_prop_list, QueryPresets.OLDER_THAN: date_prop_list, diff --git a/openstackquery/mappings/project_mapping.py b/openstackquery/mappings/project_mapping.py index 4ce582a..1d1417d 100644 --- a/openstackquery/mappings/project_mapping.py +++ b/openstackquery/mappings/project_mapping.py @@ -1,6 +1,7 @@ from typing import Type from openstackquery.aliases import QueryChainMappings +from openstackquery.enums.props.image_properties import ImageProperties from openstackquery.enums.props.project_properties import ProjectProperties from openstackquery.enums.props.server_properties import ServerProperties from openstackquery.enums.query_presets import QueryPresets @@ -22,7 +23,12 @@ def get_chain_mappings() -> QueryChainMappings: Should return a dictionary containing property pairs mapped to query mappings. This is used to define how to chain results from this query to other possible queries """ - return {ProjectProperties.PROJECT_ID: [ServerProperties.PROJECT_ID]} + return { + ProjectProperties.PROJECT_ID: [ + ServerProperties.PROJECT_ID, + ImageProperties.IMAGE_OWNER, + ] + } @staticmethod def get_runner_mapping() -> Type[ProjectRunner]: diff --git a/tests/enums/props/test_image_properties.py b/tests/enums/props/test_image_properties.py index 229017f..2d9c686 100644 --- a/tests/enums/props/test_image_properties.py +++ b/tests/enums/props/test_image_properties.py @@ -1,11 +1,11 @@ from unittest.mock import patch + import pytest from openstackquery.enums.props.image_properties import ImageProperties from openstackquery.exceptions.query_property_mapping_error import ( QueryPropertyMappingError, ) - from tests.mocks.mocked_props import MockProperties @@ -30,6 +30,10 @@ (ImageProperties.IMAGE_NAME, ["image_name", "name"]), (ImageProperties.IMAGE_SIZE, ["image_size", "size"]), (ImageProperties.IMAGE_STATUS, ["image_status", "status"]), + ( + ImageProperties.IMAGE_OWNER, + ["image_owner", "owner", "tenant"], + ), ], ) def test_property_serialization(expected_prop, test_values, property_variant_generator): diff --git a/tests/mappings/test_image_mapping.py b/tests/mappings/test_image_mapping.py index 70c22ac..94b08b0 100644 --- a/tests/mappings/test_image_mapping.py +++ b/tests/mappings/test_image_mapping.py @@ -1,6 +1,7 @@ from unittest.mock import patch from openstackquery.enums.props.image_properties import ImageProperties +from openstackquery.enums.props.project_properties import ProjectProperties from openstackquery.enums.props.server_properties import ServerProperties from openstackquery.enums.query_presets import QueryPresets from openstackquery.handlers.server_side_handler import ServerSideHandler @@ -37,13 +38,14 @@ def test_server_side_handler_mappings_equal_to(server_side_test_mappings): mappings = { ImageProperties.IMAGE_NAME: "name", ImageProperties.IMAGE_STATUS: "status", + ImageProperties.IMAGE_OWNER: "owner", } server_side_test_mappings( ImageMapping, QueryPresets.EQUAL_TO, mappings, - test_case=(True, True), + test_case=(True, True, True), ) @@ -59,11 +61,12 @@ def test_server_side_handler_mappings_any_in(server_side_any_in_mappings): mappings = { ImageProperties.IMAGE_NAME: "name", ImageProperties.IMAGE_STATUS: "status", + ImageProperties.IMAGE_OWNER: "owner", } server_side_any_in_mappings( ImageMapping, mappings, - {"test1": "test1", "test2": "test2"}, + {"test1": "test1", "test2": "test2", "test3": "test3"}, ) @@ -238,9 +241,11 @@ def test_client_side_handler(client_side_test_mappings): QueryPresets.NOT_ANY_IN: ["*"], QueryPresets.MATCHES_REGEX: [ ImageProperties.IMAGE_NAME, + ImageProperties.IMAGE_METADATA, ], QueryPresets.NOT_MATCHES_REGEX: [ ImageProperties.IMAGE_NAME, + ImageProperties.IMAGE_METADATA, ], QueryPresets.OLDER_THAN: date_prop_list, QueryPresets.OLDER_THAN_OR_EQUAL_TO: date_prop_list, @@ -260,6 +265,7 @@ def test_get_chain_mappings(): """ expected_mappings = { ImageProperties.IMAGE_ID: [ServerProperties.IMAGE_ID], + ImageProperties.IMAGE_OWNER: [ProjectProperties.PROJECT_ID], } assert set(ImageMapping.get_chain_mappings()) == set(expected_mappings) diff --git a/tests/mappings/test_project_mapping.py b/tests/mappings/test_project_mapping.py index c4c3e6f..6298728 100644 --- a/tests/mappings/test_project_mapping.py +++ b/tests/mappings/test_project_mapping.py @@ -1,3 +1,4 @@ +from openstackquery.enums.props.image_properties import ImageProperties from openstackquery.enums.props.project_properties import ProjectProperties from openstackquery.enums.props.server_properties import ServerProperties from openstackquery.enums.query_presets import QueryPresets @@ -128,6 +129,11 @@ def test_get_chain_mappings(): """ Tests get_chain_mapping outputs correctly """ - expected_mappings = {ProjectProperties.PROJECT_ID: [ServerProperties.PROJECT_ID]} + expected_mappings = { + ProjectProperties.PROJECT_ID: [ + ServerProperties.PROJECT_ID, + ImageProperties.IMAGE_OWNER, + ] + } assert set(ProjectMapping.get_chain_mappings()) == set(expected_mappings) From 19dc236f74e3cb26f2e9d6454b4215f811e3e3fb Mon Sep 17 00:00:00 2001 From: AlexCK-STFC <210735915+AlexCK-STFC@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:57:34 +0100 Subject: [PATCH 2/3] DOC: Document IMAGE_OWNER and IMAGE_METADATA --- docs/user_docs/query_docs/IMAGES.md | 12 ++++++++---- docs/user_docs/query_docs/PROJECTS.md | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/user_docs/query_docs/IMAGES.md b/docs/user_docs/query_docs/IMAGES.md index 7efa39d..56bc507 100644 --- a/docs/user_docs/query_docs/IMAGES.md +++ b/docs/user_docs/query_docs/IMAGES.md @@ -27,10 +27,12 @@ An `Image` has the following properties: | `string` | "id", "uuid" | ID of the image | | `string` (x) | "updated_at" | The timestamp when this image was last updated | | `int` | "min_ram", "ram" | The minimum disk size in GB that is required to boot the image. | -| `int` | "min_disk", "disk" | The minimum amount of RAM in MB that is required to boot the image. | -| `string` | "name" | Name of the Image | -| `int` | "size" | The size of the image data, in bytes. | -| `string` | "status" | Image status | +| `int` | "min_disk", "disk" | The minimum amount of RAM in MB that is required to boot the image. | +| `string` | "name" | Name of the Image | +| `int` | "size" | The size of the image data, in bytes. | +| `string` | "status" | Image status | +| `string` | "owner", "tenant" | ID of the project which owns the image. Might be None i.e. for public images, which outputs in results as 'Not Found'. | +| `string` | "metadata" | Full metadata dictionary (as JSON-formatted string) | (x) - These are UTC timestamps in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format Any of these properties can be used for any of the API methods that takes a property - like `select`, `where`, `sort_by` etc @@ -55,6 +57,7 @@ The following shared-common properties are listed below (as well as the Query ob | Prop 1 | Prop 2 | Type | Maps | Documentation | |--------|------------|-------------|-------------------------------|--------------------------| | "id" | "image_id" | One-to-Many | `ImageQuery` to `ServerQuery` | [SERVERS.md](SERVERS.md) | +| "owner"| "id" | Many-to-One | `ImageQuery` to `ProjectQuery`| [PROJECTS.md](PROJECTS.md) ## Chaining to @@ -63,6 +66,7 @@ Chaining from other `ImageQuery` requires passing `image_query` or any of the al | From | Prop 1 | Prop 2 | Type | Documentation | |---------------|--------|------------|-------------|--------------------------| | `ServerQuery` | "id" | "image_id" | Many-to-One | [SERVERS.md](SERVERS.md) | +| `ProjectQuery`| "id" | "owner" | One-to-Many | [PROJECTS.md](PROJECTS.md) ## run() meta-parameters diff --git a/docs/user_docs/query_docs/PROJECTS.md b/docs/user_docs/query_docs/PROJECTS.md index f9396a0..ac3085f 100644 --- a/docs/user_docs/query_docs/PROJECTS.md +++ b/docs/user_docs/query_docs/PROJECTS.md @@ -51,6 +51,8 @@ The following shared-common properties are listed below (as well as the Query ob | Prop 1 | Prop 2 | Type | Maps | |--------|--------------|-------------|---------------------------------| | "id" | "project_id" | One-to-Many | `ProjectQuery` to `ServerQuery` | +| "id" | "owner" | One-to-Many | `ProjectQuery` to `ServerQuery` | + ## Chaining to @@ -59,6 +61,7 @@ Chaining from other `ProjectQuery` requires passing `PROJECT_QUERY` or any alias | From | Prop 1 | Prop 2 | Type | Documentation | |---------------|--------------|--------|-------------|--------------------------| | `ServerQuery` | "project_id" | "id" | Many-to-One | [SERVERS.md](SERVERS.md) | +| `ImageQuery` | "owner" | "id" | Many-to-One | [IMAGES.md](IMAGES.md) ## run() meta-parameters From c0edfe5b7006f1bdb7fdd137242d20c8540e7f76 Mon Sep 17 00:00:00 2001 From: AlexCK-STFC <210735915+AlexCK-STFC@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:24:06 +0100 Subject: [PATCH 3/3] MAINT: Minor bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index da8b957..e450df3 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="openstackquery", - version="1.3.0", + version="1.4.0", author="STFC Cloud Team", author_email="", description=DESCRIPTION,