1010# License for the specific language governing permissions and limitations
1111# under the License.
1212
13+ from collections .abc import Iterable , Mapping , Sequence
1314import json
1415import logging
1516import os
1617import shlex
1718import subprocess
19+ from typing import Any , Literal , cast , overload
1820
1921from tempest .lib .cli import output_parser
2022from tempest .lib import exceptions
2426LOG = logging .getLogger (__name__ )
2527
2628
27- def execute (cmd , * , fail_ok = False ):
29+ def execute (cmd : str , * , fail_ok : bool = False ) -> str :
2830 """Executes specified command for the given action."""
2931 LOG .debug ('Executing: %s' , cmd )
3032 cmdlist = shlex .split (cmd )
@@ -53,15 +55,37 @@ def execute(cmd, *, fail_ok=False):
5355
5456
5557class TestCase (testtools .TestCase ):
58+ @overload
5659 @classmethod
5760 def openstack (
5861 cls ,
59- cmd ,
62+ cmd : str ,
6063 * ,
61- cloud = ADMIN_CLOUD ,
62- fail_ok = False ,
63- parse_output = False ,
64- ):
64+ cloud : str | None = ADMIN_CLOUD ,
65+ fail_ok : bool = False ,
66+ parse_output : Literal [False ] = False ,
67+ ) -> str : ...
68+
69+ @overload
70+ @classmethod
71+ def openstack (
72+ cls ,
73+ cmd : str ,
74+ * ,
75+ cloud : str | None = ADMIN_CLOUD ,
76+ fail_ok : bool = False ,
77+ parse_output : Literal [True ] = ...,
78+ ) -> Any : ...
79+
80+ @classmethod
81+ def openstack (
82+ cls ,
83+ cmd : str ,
84+ * ,
85+ cloud : str | None = ADMIN_CLOUD ,
86+ fail_ok : bool = False ,
87+ parse_output : bool = False ,
88+ ) -> str | Any :
6589 """Executes openstackclient command for the given action
6690
6791 :param cmd: A string representation of the command to execute.
@@ -106,7 +130,9 @@ def openstack(
106130 return output
107131
108132 @classmethod
109- def is_service_enabled (cls , service , version = None ):
133+ def is_service_enabled (
134+ cls , service : str , version : str | None = None
135+ ) -> bool :
110136 """Ask client cloud if service is available
111137
112138 :param service: The service name or type. This should be either an
@@ -126,7 +152,9 @@ def is_service_enabled(cls, service, version=None):
126152 return bool (ret )
127153
128154 @classmethod
129- def is_extension_enabled (cls , alias , * , service = 'network' ):
155+ def is_extension_enabled (
156+ cls , alias : str , * , service : str = 'network'
157+ ) -> bool :
130158 """Ask client cloud if extension is enabled"""
131159 extensions = cls .openstack (
132160 f'extension list --{ service } ' ,
@@ -135,38 +163,44 @@ def is_extension_enabled(cls, alias, *, service='network'):
135163 return alias in [x ['Alias' ] for x in extensions ]
136164
137165 @classmethod
138- def get_openstack_configuration_value (cls , configuration ) :
166+ def get_openstack_configuration_value (cls , configuration : str ) -> str :
139167 opts = cls .get_opts ([configuration ])
140168 return cls .openstack ('configuration show ' + opts )
141169
142170 @classmethod
143- def get_opts (cls , fields , output_format = 'value' ):
171+ def get_opts (cls , fields : list [ str ] , output_format : str = 'value' ) -> str :
144172 return ' -f {} {}' .format (
145173 output_format , ' ' .join (['-c ' + it for it in fields ])
146174 )
147175
148176 @classmethod
149- def assertOutput (cls , expected , actual ) :
177+ def assertOutput (cls , expected : str , actual : str ) -> None :
150178 if expected != actual :
151179 raise Exception (expected + ' != ' + actual )
152180
153181 @classmethod
154- def assertInOutput (cls , expected , actual ) :
182+ def assertInOutput (cls , expected : str , actual : str ) -> None :
155183 if expected not in actual :
156184 raise Exception (expected + ' not in ' + actual )
157185
158186 @classmethod
159- def assertsOutputNotNone (cls , observed ) :
187+ def assertsOutputNotNone (cls , observed : Any ) -> None :
160188 if observed is None :
161189 raise Exception ('No output observed' )
162190
163- def assert_table_structure (self , items , field_names ):
191+ def assert_table_structure (
192+ self , items : Iterable [Mapping [str , Any ]], field_names : Sequence [str ]
193+ ) -> None :
164194 """Verify that all items have keys listed in field_names."""
165195 for item in items :
166196 for field in field_names :
167197 self .assertIn (field , item )
168198
169- def assert_show_fields (self , show_output , field_names ):
199+ def assert_show_fields (
200+ self ,
201+ show_output : Iterable [Mapping [str , Any ]],
202+ field_names : Sequence [str ],
203+ ) -> None :
170204 """Verify that all items have keys listed in field_names."""
171205
172206 # field_names = ['name', 'description']
@@ -186,17 +220,18 @@ def parse_show_as_object(self, raw_output):
186220 o .update (item )
187221 return o
188222
189- def parse_show (self , raw_output ) :
223+ def parse_show (self , raw_output : str ) -> list [ dict [ str , Any ]] :
190224 """Return list of dicts with item values parsed from cli output."""
191225
192226 items = []
193227 table_ = output_parser .table (raw_output )
194228 for row in table_ ['values' ]:
195229 item = {}
196- item [row [0 ]] = row [1 ]
230+ item [str ( row [0 ]) ] = row [1 ]
197231 items .append (item )
198232 return items
199233
200- def parse_listing (self , raw_output ) :
234+ def parse_listing (self , raw_output : str ) -> list [ dict [ str , Any ]] :
201235 """Return list of dicts with basic item parsed from cli output."""
202- return output_parser .listing (raw_output )
236+ # need to add hints to tempest
237+ return cast (list [dict [str , Any ]], output_parser .listing (raw_output ))
0 commit comments