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 <anandkarancompsci@gmail.com>
This commit is contained in:
Karan Anand
2026-03-21 16:15:18 -04:00
parent fedddff7be
commit c03e9b215b
4 changed files with 114 additions and 38 deletions

View File

@@ -4,3 +4,4 @@ repos:
hooks:
- id: mypy
args: [--config-file=pyproject.toml]
additional_dependencies: [types-PyYAML]

View File

@@ -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

View File

@@ -34,4 +34,5 @@ files = [
"ironicclient/v1/allocation.py",
"ironicclient/v1/chassis.py",
"ironicclient/v1/resource_fields.py",
"ironicclient/v1/create_resources.py",
]

View File

@@ -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}