From c03e9b215bf850d996637ea1f1cbfa77a4c44760 Mon Sep 17 00:00:00 2001 From: Karan Anand Date: Sat, 21 Mar 2026 16:15:18 -0400 Subject: [PATCH] Add type annotations to `ironicclient/v1/create_resources.py` Changes: Add type annotations following code conventions. Added file to the migrated files list. Change-Id: I27b32b7d32cb6f9529c041239a29fa8d13c13aad Signed-off-by: Karan Anand --- .pre-commit-config.yaml | 1 + ironicclient/v1/create_resources.py | 149 +++++++++++++++++++++------- pyproject.toml | 1 + tox.ini | 1 + 4 files changed, 114 insertions(+), 38 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bdfcdcf02..c403ba988 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,3 +4,4 @@ repos: hooks: - id: mypy args: [--config-file=pyproject.toml] + additional_dependencies: [types-PyYAML] diff --git a/ironicclient/v1/create_resources.py b/ironicclient/v1/create_resources.py index 07a011941..1b245d5c9 100644 --- a/ironicclient/v1/create_resources.py +++ b/ironicclient/v1/create_resources.py @@ -10,15 +10,21 @@ # License for the specific language governing permissions and limitations # under the License. +from __future__ import annotations + +from collections.abc import Callable import functools import json +from typing import Any +from typing import cast import jsonschema import yaml from ironicclient import exc +from ironicclient.v1 import client as v1_client -_CREATE_SCHEMA = { +_CREATE_SCHEMA: dict[str, object] = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for ironic resources file", "type": "object", @@ -40,7 +46,10 @@ _CREATE_SCHEMA = { } -def create_resources(client, filenames): +def create_resources( + client: v1_client.Client, + filenames: list[str], +) -> None: """Create resources using their JSON or YAML descriptions. :param client: an instance of ironic client; @@ -49,8 +58,8 @@ def create_resources(client, filenames): :raises: ClientException if any operation during files processing/resource creation fails. """ - errors = [] - resources = [] + errors: list[Exception] = [] + resources: list[dict[str, object]] = [] for resource_file in filenames: try: resource = load_from_file(resource_file) @@ -63,15 +72,19 @@ def create_resources(client, filenames): ' following error(s) were encountered:\n%s' % '\n'.join(str(e) for e in errors)) for r in resources: - errors.extend(create_chassis(client, r.get('chassis', []))) - errors.extend(create_nodes(client, r.get('nodes', []))) + errors.extend(create_chassis( + client, + cast(list[dict[str, object]], r.get('chassis', [])))) + errors.extend(create_nodes( + client, + cast(list[dict[str, object]], r.get('nodes', [])))) if errors: raise exc.ClientException('During resources creation, the following ' 'error(s) were encountered:\n%s' % '\n'.join(str(e) for e in errors)) -def load_from_file(filename): +def load_from_file(filename: str) -> dict[str, object]: """Deserialize JSON or YAML from file. :param filename: name of the file containing JSON or YAML. @@ -82,9 +95,9 @@ def load_from_file(filename): try: with open(filename) as f: if filename.endswith('.yaml'): - return yaml.safe_load(f) + return cast(dict[str, object], yaml.safe_load(f)) elif filename.endswith('.json'): - return json.load(f) + return cast(dict[str, object], json.load(f)) else: # The file is neither .json, nor .yaml, raise an exception raise exc.ClientException( @@ -100,7 +113,12 @@ def load_from_file(filename): '%(err)s' % {'err': e, 'file': filename}) -def create_single_handler(resource_type): +def create_single_handler( + resource_type: str, +) -> Callable[ + [Callable[..., str]], + Callable[..., tuple[str | None, Exception | None]], +]: """Catch errors of the creation of a single resource. This decorator appends an error (which is an instance of some client @@ -112,11 +130,16 @@ def create_single_handler(resource_type): e.g. 'node', used purely for exception messages. """ - def outer_wrapper(create_method): + def outer_wrapper( + create_method: Callable[..., str], + ) -> Callable[..., tuple[str | None, Exception | None]]: @functools.wraps(create_method) - def wrapper(client, **params): - uuid = None - error = None + def wrapper( + client: v1_client.Client, + **params: object, + ) -> tuple[str | None, Exception | None]: + uuid: str | None = None + error: Exception | None = None try: uuid = create_method(client, **params) except exc.InvalidAttribute as e: @@ -136,7 +159,9 @@ def create_single_handler(resource_type): @create_single_handler('node') -def create_single_node(client, **params): +def create_single_node( + client: v1_client.Client, **params: Any, +) -> str: """Call the client to create a node. :param client: ironic client instance. @@ -152,13 +177,19 @@ def create_single_node(client, **params): params.pop('portgroups', None) traits = params.pop('traits', None) ret = client.node.create(**params) + if ret is None: + raise exc.ClientException( + 'Unable to create the node: ' + 'the API returned an empty response') if traits: - client.node.set_traits(ret.uuid, traits) - return ret.uuid + client.node.set_traits(ret.uuid, cast(list[str], traits)) + return cast(str, ret.uuid) @create_single_handler('port') -def create_single_port(client, **params): +def create_single_port( + client: v1_client.Client, **params: Any, +) -> str: """Call the client to create a port. :param client: ironic client instance. @@ -170,11 +201,17 @@ def create_single_port(client, **params): :raises: ClientException, if the creation of the port fails. """ ret = client.port.create(**params) - return ret.uuid + if ret is None: + raise exc.ClientException( + 'Unable to create the port: ' + 'the API returned an empty response') + return cast(str, ret.uuid) @create_single_handler('port group') -def create_single_portgroup(client, **params): +def create_single_portgroup( + client: v1_client.Client, **params: Any, +) -> str: """Call the client to create a port group. :param client: ironic client instance. @@ -188,11 +225,17 @@ def create_single_portgroup(client, **params): """ params.pop('ports', None) ret = client.portgroup.create(**params) - return ret.uuid + if ret is None: + raise exc.ClientException( + 'Unable to create the port group: ' + 'the API returned an empty response') + return cast(str, ret.uuid) @create_single_handler('chassis') -def create_single_chassis(client, **params): +def create_single_chassis( + client: v1_client.Client, **params: Any, +) -> str: """Call the client to create a chassis. :param client: ironic client instance. @@ -206,10 +249,19 @@ def create_single_chassis(client, **params): """ params.pop('nodes', None) ret = client.chassis.create(**params) - return ret.uuid + if ret is None: + raise exc.ClientException( + 'Unable to create the chassis: ' + 'the API returned an empty response') + return cast(str, ret.uuid) -def create_ports(client, port_list, node_uuid, portgroup_uuid=None): +def create_ports( + client: v1_client.Client, + port_list: list[dict[str, object]], + node_uuid: str, + portgroup_uuid: str | None = None, +) -> list[Exception]: """Create ports from dictionaries. :param client: ironic client instance. @@ -220,7 +272,7 @@ def create_ports(client, port_list, node_uuid, portgroup_uuid=None): with, if they are its members. :returns: array of exceptions encountered during creation. """ - errors = [] + errors: list[Exception] = [] for port in port_list: port_node_uuid = port.get('node_uuid') if port_node_uuid and port_node_uuid != node_uuid: @@ -249,7 +301,11 @@ def create_ports(client, port_list, node_uuid, portgroup_uuid=None): return errors -def create_portgroups(client, portgroup_list, node_uuid): +def create_portgroups( + client: v1_client.Client, + portgroup_list: list[dict[str, object]], + node_uuid: str, +) -> list[Exception]: """Create port groups from dictionaries. :param client: ironic client instance. @@ -259,7 +315,7 @@ def create_portgroups(client, portgroup_list, node_uuid): :param node_uuid: UUID of a node the port groups should be associated with. :returns: array of exceptions encountered during creation. """ - errors = [] + errors: list[Exception] = [] for portgroup in portgroup_list: portgroup_node_uuid = portgroup.get('node_uuid') if portgroup_node_uuid and portgroup_node_uuid != node_uuid: @@ -278,12 +334,19 @@ def create_portgroups(client, portgroup_list, node_uuid): # Port group UUID == None means that port group creation failed, don't # create the ports inside it if ports is not None and portgroup_uuid is not None: - errors.extend(create_ports(client, ports, node_uuid, - portgroup_uuid=portgroup_uuid)) + errors.extend(create_ports( + client, + cast(list[dict[str, object]], ports), + node_uuid, + portgroup_uuid=portgroup_uuid)) return errors -def create_nodes(client, node_list, chassis_uuid=None): +def create_nodes( + client: v1_client.Client, + node_list: list[dict[str, object]], + chassis_uuid: str | None = None, +) -> list[Exception]: """Create nodes from dictionaries. :param client: ironic client instance. @@ -293,7 +356,7 @@ def create_nodes(client, node_list, chassis_uuid=None): :param chassis_uuid: UUID of a chassis the nodes should be associated with. :returns: array of exceptions encountered during creation. """ - errors = [] + errors: list[Exception] = [] for node in node_list: if chassis_uuid is not None: node_chassis_uuid = node.get('chassis_uuid') @@ -315,14 +378,22 @@ def create_nodes(client, node_list, chassis_uuid=None): # create the port(group)s inside it if node_uuid is not None: if portgroups is not None: - errors.extend( - create_portgroups(client, portgroups, node_uuid)) + errors.extend(create_portgroups( + client, + cast(list[dict[str, object]], portgroups), + node_uuid)) if ports is not None: - errors.extend(create_ports(client, ports, node_uuid)) + errors.extend(create_ports( + client, + cast(list[dict[str, object]], ports), + node_uuid)) return errors -def create_chassis(client, chassis_list): +def create_chassis( + client: v1_client.Client, + chassis_list: list[dict[str, object]], +) -> list[Exception]: """Create chassis from dictionaries. :param client: ironic client instance. @@ -331,7 +402,7 @@ def create_chassis(client, chassis_list): separately to /nodes endpoint. :returns: array of exceptions encountered during creation. """ - errors = [] + errors: list[Exception] = [] for chassis in chassis_list: chassis_uuid, error = create_single_chassis(client, **chassis) if error: @@ -340,6 +411,8 @@ def create_chassis(client, chassis_list): # Chassis UUID == None means that chassis creation failed, don't # create the nodes inside it if nodes is not None and chassis_uuid is not None: - errors.extend(create_nodes(client, nodes, - chassis_uuid=chassis_uuid)) + errors.extend(create_nodes( + client, + cast(list[dict[str, object]], nodes), + chassis_uuid=chassis_uuid)) return errors diff --git a/pyproject.toml b/pyproject.toml index 27fd7fcba..eb63ed21d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,4 +34,5 @@ files = [ "ironicclient/v1/allocation.py", "ironicclient/v1/chassis.py", "ironicclient/v1/resource_fields.py", + "ironicclient/v1/create_resources.py", ] \ No newline at end of file diff --git a/tox.ini b/tox.ini index 96a1a6a3f..37d9cf325 100644 --- a/tox.ini +++ b/tox.ini @@ -100,4 +100,5 @@ deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt + types-PyYAML commands = mypy {posargs} \ No newline at end of file