Skip to content

Commit c0d5c66

Browse files
authored
Add typing (#334)
* add typing * Add typing * add missing return value * add missing return value
1 parent 056e331 commit c0d5c66

File tree

12 files changed

+534
-224
lines changed

12 files changed

+534
-224
lines changed

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ fcn_exclude_functions =
2525
Version,
2626
find_all,
2727
BeautifulSoup,
28+
warn,

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,9 @@ repos:
5151
rev: v8.18.2
5252
hooks:
5353
- id: gitleaks
54+
55+
- repo: https://github.com/pre-commit/mirrors-mypy
56+
rev: v1.10.0
57+
hooks:
58+
- id: mypy
59+
additional_dependencies: [types-all]

ocp_utilities/cluster_versions.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from __future__ import annotations
12
import functools
23
import re
4+
from typing import Dict, List
35

46
import requests
5-
from bs4 import BeautifulSoup
7+
from bs4 import BeautifulSoup, ResultSet
8+
from kubernetes.dynamic import DynamicClient
69
from ocp_resources.cluster_version import ClusterVersion
710
from simple_logger.logger import get_logger
811
from semver import Version
@@ -13,7 +16,7 @@
1316

1417

1518
@functools.cache
16-
def parse_openshift_release_url():
19+
def parse_openshift_release_url() -> ResultSet:
1720
url = "https://openshift-release.apps.ci.l2s4.p1.openshiftapps.com"
1821
LOGGER.info(f"Parsing {url}")
1922
req = requests.get(url, headers={"Cache-Control": "no-cache"})
@@ -22,7 +25,7 @@ def parse_openshift_release_url():
2225

2326

2427
@functools.cache
25-
def get_accepted_cluster_versions():
28+
def get_accepted_cluster_versions() -> Dict[str, Dict[str, List[str]]]:
2629
"""
2730
Get all accepted cluster versions from https://openshift-release.apps.ci.l2s4.p1.openshiftapps.com
2831
@@ -45,7 +48,7 @@ def get_accepted_cluster_versions():
4548
'rc': {'4.15': ['4.15.0-0.rc-2022-05-25-113430']},
4649
'fc': {'4.15': ['4.15.0-0.fc-2022-05-25-113430']}}
4750
"""
48-
_accepted_version_dict = {}
51+
_accepted_version_dict: Dict[str, Dict[str, List[str]]] = {}
4952
for tr in parse_openshift_release_url():
5053
version, status = [_tr for _tr in tr.text.splitlines() if _tr][:2]
5154
if status == "Accepted":
@@ -67,7 +70,7 @@ def get_accepted_cluster_versions():
6770
return _accepted_version_dict
6871

6972

70-
def get_cluster_version(client=None):
73+
def get_cluster_version(client: DynamicClient = None) -> Version:
7174
"""
7275
Get cluster version
7376
@@ -82,12 +85,11 @@ def get_cluster_version(client=None):
8285
condition_type=cluster_version.Condition.AVAILABLE,
8386
condition_status=cluster_version.Condition.Status.TRUE,
8487
):
85-
try:
86-
ocp_version = re.search(r"([\d.]+)", cluster_version_message).group()
88+
if ocp_version_match := re.search(r"([\d.]+)", cluster_version_message):
89+
ocp_version = ocp_version_match.group()
8790
LOGGER.info(f"Cluster version: {ocp_version}")
8891
return Version.parse(ocp_version)
8992

90-
except (AttributeError, IndexError) as ex:
91-
raise ClusterVersionNotFoundError(f"Cluster version not found: {cluster_version_message}, exception: {ex}")
93+
raise ClusterVersionNotFoundError(f"Cluster version not found: {cluster_version_message}")
9294

9395
raise ClusterVersionNotFoundError("`ClusterVersion` message not found")

ocp_utilities/debugger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ class WebDebugger(WebPdb):
1515
pytest --pdbcls=ocp_utilities.debugger:WebDebugger --pdb
1616
"""
1717

18-
def __init__(self):
18+
def __init__(self) -> None:
1919
super().__init__(host="0.0.0.0", port=int(os.environ.get("PYTHON_REMOTE_DEBUG_PORT", 1212)))

ocp_utilities/exceptions.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from __future__ import annotations
2+
3+
14
class NodeNotReadyError(Exception):
25
pass
36

@@ -15,11 +18,11 @@ class NodesNotHealthyConditionError(Exception):
1518

1619

1720
class CommandExecFailed(Exception):
18-
def __init__(self, name, err=None):
21+
def __init__(self, name: str, err: str = "") -> None:
1922
self.name = name
2023
self.err = f"Error: {err}" if err else ""
2124

22-
def __str__(self):
25+
def __str__(self) -> str:
2326
return f"Command: {self.name} - exec failed. {self.err}"
2427

2528

ocp_utilities/infra.py

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
from __future__ import annotations
12
import base64
23
import importlib
34
import json
45
import os
56
import shlex
7+
from typing import Any, Dict, List, Optional
68

79
import kubernetes
810
import urllib3
11+
from kubernetes.dynamic import DynamicClient
912
from ocp_resources.image_content_source_policy import ImageContentSourcePolicy
1013
from ocp_resources.node import Node
14+
from ocp_resources.pod import Pod
1115
from ocp_resources.resource import ResourceEditor
1216
from ocp_resources.secret import Secret
1317
from ocp_wrapper_data_collector.data_collector import (
1418
get_data_collector_base_dir,
1519
get_data_collector_dict,
1620
)
21+
from pyhelper_utils.shell import run_command
1722
from simple_logger.logger import get_logger
1823
from urllib3.exceptions import MaxRetryError
1924

@@ -23,7 +28,6 @@
2328
NodeUnschedulableError,
2429
PodsFailedOrPendingError,
2530
)
26-
from ocp_utilities.utils import run_command
2731

2832

2933
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -32,15 +36,17 @@
3236
LOGGER = get_logger(name=__name__)
3337

3438

35-
def get_client(config_file=None, config_dict=None, context=None, **kwargs):
39+
def get_client(**kwargs: Any) -> DynamicClient:
3640
"""
3741
Get a kubernetes client.
3842
3943
Pass either config_file or config_dict.
4044
If none of them are passed, client will be created from default OS kubeconfig
4145
(environment variable or .kube folder).
46+
All kwargs, except config_file, config_dict and context will be passed to kubernetes.config.new_client_from_config_dict.
4247
43-
Args:
48+
49+
kwargs:
4450
config_file (str): path to a kubeconfig file.
4551
config_dict (dict): dict with kubeconfig configuration.
4652
context (str): name of the context to use.
@@ -49,6 +55,10 @@ def get_client(config_file=None, config_dict=None, context=None, **kwargs):
4955
DynamicClient: a kubernetes client.
5056
"""
5157
# Ref: https://github.com/kubernetes-client/python/blob/v26.1.0/kubernetes/base/config/kube_config.py
58+
config_file = kwargs.pop("config_file", None)
59+
config_dict = kwargs.pop("config_dict", None)
60+
context = kwargs.pop("context", None)
61+
5262
if config_dict:
5363
return kubernetes.dynamic.DynamicClient(
5464
client=kubernetes.config.new_client_from_config_dict(config_dict=config_dict, context=context, **kwargs)
@@ -75,7 +85,7 @@ def get_client(config_file=None, config_dict=None, context=None, **kwargs):
7585
)
7686

7787

78-
def assert_nodes_ready(nodes):
88+
def assert_nodes_ready(nodes: List[Node]) -> None:
7989
"""
8090
Validates all nodes are in ready
8191
@@ -91,7 +101,7 @@ def assert_nodes_ready(nodes):
91101
raise NodeNotReadyError(f"Following nodes are not in ready state: {not_ready_nodes}")
92102

93103

94-
def assert_nodes_schedulable(nodes):
104+
def assert_nodes_schedulable(nodes: List[Node]) -> None:
95105
"""
96106
Validates all nodes are in schedulable state
97107
@@ -107,7 +117,7 @@ def assert_nodes_schedulable(nodes):
107117
raise NodeUnschedulableError(f"Following nodes are in unscheduled state: {unschedulable_nodes}")
108118

109119

110-
def assert_pods_failed_or_pending(pods):
120+
def assert_pods_failed_or_pending(pods: List[Pod]) -> None:
111121
"""
112122
Validates all pods are not in failed nor pending phase
113123
@@ -134,9 +144,9 @@ def assert_pods_failed_or_pending(pods):
134144

135145

136146
def assert_nodes_in_healthy_condition(
137-
nodes,
138-
healthy_node_condition_type=None,
139-
):
147+
nodes: List[Node],
148+
healthy_node_condition_type: Optional[Dict[str, str]],
149+
) -> None:
140150
"""
141151
Validates nodes are in a healthy condition.
142152
Nodes Ready condition is True and the following node conditions are False:
@@ -155,7 +165,7 @@ def assert_nodes_in_healthy_condition(
155165
156166
Raises:
157167
NodesNotHealthyConditionError: if any nodes DiskPressure MemoryPressure,
158-
PIDPressure, NetworkUnavailable, etc condition is True
168+
PIDPressure, NetworkUnavailable, etc. condition is True
159169
"""
160170
LOGGER.info("Verify all nodes are in a healthy condition.")
161171

@@ -199,29 +209,29 @@ class DynamicClassCreator:
199209
Taken from https://stackoverflow.com/a/66815839
200210
"""
201211

202-
def __init__(self):
203-
self.created_classes = {}
212+
def __init__(self) -> None:
213+
self.created_classes: Dict[Any, Any] = {}
204214

205-
def __call__(self, base_class):
215+
def __call__(self, base_class: Any) -> Any: # TODO: return `BaseResource` class
206216
if base_class in self.created_classes:
207217
return self.created_classes[base_class]
208218

209219
class BaseResource(base_class):
210-
def __init__(self, *args, **kwargs):
220+
def __init__(self, *args: Any, **kwargs: Any) -> None:
211221
super().__init__(*args, **kwargs)
212222

213-
def _set_dynamic_class_creator_label(self):
223+
def _set_dynamic_class_creator_label(self) -> None:
214224
self.res.setdefault("metadata", {}).setdefault("labels", {}).update({
215225
"created-by-dynamic-class-creator": "Yes"
216226
})
217227

218-
def to_dict(self):
228+
def to_dict(self) -> None:
219229
if not self.res:
220230
super().to_dict()
221231

222232
self._set_dynamic_class_creator_label()
223233

224-
def clean_up(self):
234+
def clean_up(self) -> None:
225235
try:
226236
data_collector_dict = get_data_collector_dict()
227237
if data_collector_dict:
@@ -248,7 +258,7 @@ def clean_up(self):
248258
return BaseResource
249259

250260

251-
def cluster_resource(base_class):
261+
def cluster_resource(base_class: Any) -> Any:
252262
"""
253263
Base class for all resources in order to override clean_up() method to collect resource data.
254264
data_collect_yaml dict can be set via py_config pytest plugin or via
@@ -282,7 +292,13 @@ def cluster_resource(base_class):
282292
return creator(base_class=base_class)
283293

284294

285-
def create_icsp_command(image, source_url, folder_name, pull_secret=None, filter_options=""):
295+
def create_icsp_command(
296+
image: str,
297+
source_url: str,
298+
folder_name: str,
299+
pull_secret: str = "",
300+
filter_options: str = "",
301+
) -> str:
286302
"""
287303
Create ImageContentSourcePolicy command.
288304
@@ -304,7 +320,13 @@ def create_icsp_command(image, source_url, folder_name, pull_secret=None, filter
304320
return base_command
305321

306322

307-
def generate_icsp_file(folder_name, image, source_url, pull_secret=None, filter_options=""):
323+
def generate_icsp_file(
324+
folder_name: str,
325+
image: str,
326+
source_url: str,
327+
pull_secret: str = "",
328+
filter_options: str = "",
329+
) -> str:
308330
base_command = create_icsp_command(
309331
image=image,
310332
source_url=source_url,
@@ -323,19 +345,19 @@ def generate_icsp_file(folder_name, image, source_url, pull_secret=None, filter_
323345
return icsp_file_path
324346

325347

326-
def create_icsp_from_file(icsp_file_path):
348+
def create_icsp_from_file(icsp_file_path: str) -> ImageContentSourcePolicy:
327349
icsp = ImageContentSourcePolicy(yaml_file=icsp_file_path)
328350
icsp.deploy()
329351
return icsp
330352

331353

332-
def create_icsp(icsp_name, repository_digest_mirrors):
354+
def create_icsp(icsp_name: str, repository_digest_mirrors: List[Dict[str, Any]]) -> ImageContentSourcePolicy:
333355
icsp = ImageContentSourcePolicy(name=icsp_name, repository_digest_mirrors=repository_digest_mirrors)
334356
icsp.deploy()
335357
return icsp
336358

337359

338-
def dict_base64_encode(_dict):
360+
def dict_base64_encode(_dict: Dict[Any, Any]) -> str:
339361
"""
340362
Encoding dict in base64
341363
@@ -345,10 +367,15 @@ def dict_base64_encode(_dict):
345367
Returns:
346368
str: given _dict encoded in base64
347369
"""
348-
return base64.b64encode(json.dumps(_dict).encode("ascii")).decode("utf-8")
370+
return base64.b64encode(json.dumps(_dict).encode("ascii")).decode()
349371

350372

351-
def create_update_secret(secret_data_dict, name, namespace, admin_client=None):
373+
def create_update_secret(
374+
secret_data_dict: Dict[str, Dict[str, Dict[str, str]]],
375+
name: str,
376+
namespace: str,
377+
admin_client: DynamicClient = None,
378+
) -> Secret:
352379
"""
353380
Update existing secret or create a new secret; secret type - dockerconfigjson
354381

0 commit comments

Comments
 (0)