Skip to content

Commit ec7f330

Browse files
authored
Use | for typing instead of Optional, Fix imports for existing files. (#2238)
It's recommended to use `|` for typing instead of `Optional`. When we import existing code from existing file we do not included `imports`
1 parent 74f8ebb commit ec7f330

File tree

21 files changed

+348
-307
lines changed

21 files changed

+348
-307
lines changed

class_generator/class_generator.py

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from tempfile import gettempdir
1313

1414
import textwrap
15-
from typing import Any, Dict, List, Tuple
15+
from typing import Any
1616
import click
1717
import re
1818
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
@@ -39,8 +39,8 @@
3939

4040

4141
def _is_kind_and_namespaced(
42-
client: str, _key: str, _data: Dict[str, Any], kind: str, group: str, version: str
43-
) -> Dict[str, Any]:
42+
client: str, _key: str, _data: dict[str, Any], kind: str, group: str, version: str
43+
) -> dict[str, Any]:
4444
_group_and_version = f"{group}/{version}" if group else version
4545
not_resource_dict = {"is_kind": False, "kind": _key}
4646

@@ -86,7 +86,7 @@ def map_kind_to_namespaced(client: str, newer_cluster_version: bool, schema_defi
8686
with open(schema_definition_file) as fd:
8787
_definitions_json_data = json.load(fd)
8888

89-
_kind_data_futures: List[Future] = []
89+
_kind_data_futures: list[Future] = []
9090
with ThreadPoolExecutor() as executor:
9191
for _key, _data in _definitions_json_data["definitions"].items():
9292
if not _data.get("x-kubernetes-group-version-kind"):
@@ -116,7 +116,7 @@ def map_kind_to_namespaced(client: str, newer_cluster_version: bool, schema_defi
116116
)
117117
)
118118

119-
_temp_resources_mappings: Dict[Any, Any] = {}
119+
_temp_resources_mappings: dict[Any, Any] = {}
120120
for res in as_completed(_kind_data_futures):
121121
_res = res.result()
122122
# _res["kind"] is group.version.kind, set only kind as key in the final dict
@@ -137,7 +137,7 @@ def map_kind_to_namespaced(client: str, newer_cluster_version: bool, schema_defi
137137
fd.writelines("\n".join(not_kind_list))
138138

139139

140-
def read_resources_mapping_file() -> Dict[Any, Any]:
140+
def read_resources_mapping_file() -> dict[Any, Any]:
141141
try:
142142
with open(RESOURCES_MAPPING_FILE) as fd:
143143
return json.load(fd)
@@ -339,7 +339,7 @@ def convert_camel_case_to_snake_case(string_: str) -> str:
339339
return formatted_str
340340

341341

342-
def render_jinja_template(template_dict: Dict[Any, Any], template_dir: str, template_name: str) -> str:
342+
def render_jinja_template(template_dict: dict[Any, Any], template_dir: str, template_name: str) -> str:
343343
env = Environment(
344344
loader=FileSystemLoader(template_dir),
345345
trim_blocks=True,
@@ -358,28 +358,34 @@ def render_jinja_template(template_dict: Dict[Any, Any], template_dir: str, temp
358358
return rendered
359359

360360

361-
def parse_user_code_from_file(file_path: str) -> str:
361+
def parse_user_code_from_file(file_path: str) -> tuple[str, str]:
362362
with open(file_path) as fd:
363363
data = fd.read()
364364

365-
line = " # End of generated code"
366-
if line in data:
367-
_end_of_generated_code_index = data.index(line)
368-
_user_code = data[_end_of_generated_code_index + len(line) :]
369-
return _user_code
365+
end_of_generated_code_line = " # End of generated code"
366+
user_code: str = ""
367+
user_imports: str = ""
370368

371-
return ""
369+
if end_of_generated_code_line in data:
370+
_end_of_generated_code_index = data.index(end_of_generated_code_line)
371+
user_code = data[_end_of_generated_code_index + len(end_of_generated_code_line) :]
372+
373+
for _line in data.splitlines():
374+
if _line.startswith("import") or _line.startswith("from"):
375+
user_imports += f"{_line}\n"
376+
377+
return user_code, user_imports
372378

373379

374380
def generate_resource_file_from_dict(
375-
resource_dict: Dict[str, Any],
381+
resource_dict: dict[str, Any],
376382
overwrite: bool = False,
377383
dry_run: bool = False,
378384
output_file: str = "",
379385
add_tests: bool = False,
380386
output_file_suffix: str = "",
381387
output_dir: str = "",
382-
) -> Tuple[str, str]:
388+
) -> tuple[str, str]:
383389
base_dir = output_dir or "ocp_resources"
384390
if not os.path.exists(base_dir):
385391
os.makedirs(base_dir)
@@ -390,6 +396,7 @@ def generate_resource_file_from_dict(
390396
template_name="class_generator_template.j2",
391397
)
392398

399+
output = "# Generated using https://github.com/RedHatQE/openshift-python-wrapper/blob/main/scripts/resource/README.md\n\nfrom __future__ import annotations\n"
393400
formatted_kind_str = convert_camel_case_to_snake_case(string_=resource_dict["kind"])
394401
_file_suffix: str = f"{'_' + output_file_suffix if output_file_suffix else ''}"
395402

@@ -409,9 +416,10 @@ def generate_resource_file_from_dict(
409416

410417
_output_file_exists: bool = os.path.exists(_output_file)
411418
_user_code: str = ""
419+
_user_imports: str = ""
412420

413-
if _output_file_exists:
414-
_user_code = parse_user_code_from_file(file_path=_output_file)
421+
if _output_file_exists and not add_tests:
422+
_user_code, _user_imports = parse_user_code_from_file(file_path=_output_file)
415423

416424
orig_filename = _output_file
417425
if _output_file_exists:
@@ -423,31 +431,34 @@ def generate_resource_file_from_dict(
423431
LOGGER.warning(f"{_output_file} already exists, using {temp_output_file}")
424432
_output_file = temp_output_file
425433

434+
if _user_code or _user_imports:
435+
output += f"{_user_imports}{rendered}{_user_code}"
436+
else:
437+
output += rendered
438+
426439
if dry_run:
427-
if _user_code:
428-
rendered += _user_code
429-
_code = Syntax(code=rendered, lexer="python", line_numbers=True)
440+
_code = Syntax(code=output, lexer="python", line_numbers=True)
430441
Console().print(_code)
431442

432443
else:
433-
write_and_format_rendered(filepath=_output_file, data=rendered, user_code=_user_code)
444+
write_and_format_rendered(filepath=_output_file, output=output)
434445

435446
return orig_filename, _output_file
436447

437448

438-
def types_generator(key_dict: Dict[str, Any]) -> Dict[str, str]:
449+
def types_generator(key_dict: dict[str, Any]) -> dict[str, str]:
439450
type_for_docstring: str = "Any"
440451
type_from_dict_for_init: str = ""
441452
# A resource field may be defined with `x-kubernetes-preserve-unknown-fields`. In this case, `type` is not provided.
442453
resource_type = key_dict.get("type")
443454

444455
# All fields must be set with Optional since resource can have yaml_file to cover all args.
445456
if resource_type == "array":
446-
type_for_docstring = "List[Any]"
457+
type_for_docstring = "list[Any]"
447458

448459
elif resource_type == "string":
449460
type_for_docstring = "str"
450-
type_from_dict_for_init = f'Optional[{type_for_docstring}] = ""'
461+
type_from_dict_for_init = f"{type_for_docstring} | None = None"
451462

452463
elif resource_type == "boolean":
453464
type_for_docstring = "bool"
@@ -456,15 +467,15 @@ def types_generator(key_dict: Dict[str, Any]) -> Dict[str, str]:
456467
type_for_docstring = "int"
457468

458469
elif resource_type == "object":
459-
type_for_docstring = "Dict[str, Any]"
470+
type_for_docstring = "dict[str, Any]"
460471

461472
if not type_from_dict_for_init:
462-
type_from_dict_for_init = f"Optional[{type_for_docstring}] = None"
473+
type_from_dict_for_init = f"{type_for_docstring} | None = None"
463474

464475
return {"type-for-init": type_from_dict_for_init, "type-for-doc": type_for_docstring}
465476

466477

467-
def get_property_schema(property_: Dict[str, Any]) -> Dict[str, Any]:
478+
def get_property_schema(property_: dict[str, Any]) -> dict[str, Any]:
468479
if _ref := property_.get("$ref"):
469480
with open(f"{SCHEMA_DIR}/{_ref.rsplit('.')[-1].lower()}.json") as fd:
470481
return json.load(fd)
@@ -481,12 +492,12 @@ def format_description(description: str) -> str:
481492

482493

483494
def prepare_property_dict(
484-
schema: Dict[str, Any],
485-
required: List[str],
486-
resource_dict: Dict[str, Any],
495+
schema: dict[str, Any],
496+
required: list[str],
497+
resource_dict: dict[str, Any],
487498
dict_key: str,
488-
) -> Dict[str, Any]:
489-
keys_to_ignore: List[str] = ["kind", "apiVersion", "status", SPEC_STR.lower()]
499+
) -> dict[str, Any]:
500+
keys_to_ignore: list[str] = ["kind", "apiVersion", "status", SPEC_STR.lower()]
490501
keys_to_rename: set[str] = {"annotations", "labels"}
491502
if dict_key != SPEC_STR.lower():
492503
keys_to_ignore.append("metadata")
@@ -512,21 +523,21 @@ def prepare_property_dict(
512523

513524
def parse_explain(
514525
kind: str,
515-
) -> List[Dict[str, Any]]:
526+
) -> list[dict[str, Any]]:
516527
_schema_definition = read_resources_mapping_file()
517-
_resources: List[Dict[str, Any]] = []
528+
_resources: list[dict[str, Any]] = []
518529

519530
_kinds_schema = _schema_definition[kind.lower()]
520531
for _kind_schema in _kinds_schema:
521532
namespaced = _kind_schema["namespaced"]
522-
resource_dict: Dict[str, Any] = {
533+
resource_dict: dict[str, Any] = {
523534
"base_class": "NamespacedResource" if namespaced else "Resource",
524535
"description": _kind_schema.get("description", MISSING_DESCRIPTION_STR),
525536
"fields": [],
526537
"spec": [],
527538
}
528539

529-
schema_properties: Dict[str, Any] = _kind_schema.get("properties", {})
540+
schema_properties: dict[str, Any] = _kind_schema.get("properties", {})
530541
fields_required = _kind_schema.get("required", [])
531542

532543
resource_dict.update(extract_group_kind_version(_kind_schema=_kind_schema))
@@ -585,8 +596,8 @@ def parse_explain(
585596
return _resources
586597

587598

588-
def extract_group_kind_version(_kind_schema: Dict[str, Any]) -> Dict[str, str]:
589-
group_kind_versions: List[Dict[str, str]] = _kind_schema["x-kubernetes-group-version-kind"]
599+
def extract_group_kind_version(_kind_schema: dict[str, Any]) -> dict[str, str]:
600+
group_kind_versions: list[dict[str, str]] = _kind_schema["x-kubernetes-group-version-kind"]
590601
group_kind_version = group_kind_versions[0]
591602

592603
for group_kind_version in group_kind_versions:
@@ -604,7 +615,7 @@ def class_generator(
604615
output_dir: str = "",
605616
add_tests: bool = False,
606617
called_from_cli: bool = True,
607-
) -> List[str]:
618+
) -> list[str]:
608619
"""
609620
Generates a class for a given Kind.
610621
"""
@@ -622,7 +633,7 @@ def class_generator(
622633
resources = parse_explain(kind=kind)
623634

624635
use_output_file_suffix: bool = len(resources) > 1
625-
generated_files: List[str] = []
636+
generated_files: list[str] = []
626637
for resource_dict in resources:
627638
output_file_suffix = resource_dict["group"].lower() if use_output_file_suffix else ""
628639

@@ -652,12 +663,9 @@ def class_generator(
652663
return generated_files
653664

654665

655-
def write_and_format_rendered(filepath: str, data: str, user_code: str = "") -> None:
666+
def write_and_format_rendered(filepath: str, output: str) -> None:
656667
with open(filepath, "w") as fd:
657-
fd.write(data)
658-
659-
if user_code:
660-
fd.write(user_code)
668+
fd.write(output)
661669

662670
for op in ("format", "check"):
663671
run_command(
@@ -668,8 +676,8 @@ def write_and_format_rendered(filepath: str, data: str, user_code: str = "") ->
668676

669677

670678
def generate_class_generator_tests() -> None:
671-
tests_info: Dict[str, List[Dict[str, str]]] = {"template": []}
672-
dirs_to_ignore: List[str] = ["__pycache__"]
679+
tests_info: dict[str, list[dict[str, str]]] = {"template": []}
680+
dirs_to_ignore: list[str] = ["__pycache__"]
673681

674682
for _dir in os.listdir(TESTS_MANIFESTS_DIR):
675683
if _dir in dirs_to_ignore:
@@ -693,7 +701,7 @@ def generate_class_generator_tests() -> None:
693701

694702
write_and_format_rendered(
695703
filepath=os.path.join(Path(TESTS_MANIFESTS_DIR).parent, "test_class_generator.py"),
696-
data=rendered,
704+
output=rendered,
697705
)
698706

699707

@@ -756,15 +764,15 @@ def main(
756764
if update_schema:
757765
return update_kind_schema()
758766

759-
_kwargs: Dict[str, Any] = {
767+
_kwargs: dict[str, Any] = {
760768
"overwrite": overwrite,
761769
"dry_run": dry_run,
762770
"output_file": output_file,
763771
"add_tests": add_tests,
764772
}
765773

766-
kinds: List[str] = kind.split(",")
767-
futures: List[Future] = []
774+
kinds: list[str] = kind.split(",")
775+
futures: list[Future] = []
768776

769777
with ThreadPoolExecutor() as executor:
770778
for _kind in kinds:

class_generator/manifests/class_generator_template.j2

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
{% set _ = all_names_types_for_docstring.append(arg["name-for-class-arg"] + " (" + arg["type-for-docstring"] + "): " + arg["description"]|indent(10)|safe ) %}
77
{% set _ = all_required_args.append(arg["name-for-class-arg"]) if arg["required"] == True %}
88
{% endfor %}
9-
# Generated using https://github.com/RedHatQE/openshift-python-wrapper/blob/main/scripts/resource/README.md
109

11-
from typing import Any, Dict, List, Optional
10+
from typing import Any
1211
from ocp_resources.resource import {{ base_class }}, MissingRequiredArgumentError
1312

1413

class_generator/tests/manifests/APIServer/api_server.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Generated using https://github.com/RedHatQE/openshift-python-wrapper/blob/main/scripts/resource/README.md
22

3-
from typing import Any, Dict, List, Optional
3+
from __future__ import annotations
4+
5+
from typing import Any
46
from ocp_resources.resource import Resource
57

68

@@ -17,27 +19,27 @@ class APIServer(Resource):
1719

1820
def __init__(
1921
self,
20-
additional_cors_allowed_origins: Optional[List[Any]] = None,
21-
audit: Optional[Dict[str, Any]] = None,
22-
client_ca: Optional[Dict[str, Any]] = None,
23-
encryption: Optional[Dict[str, Any]] = None,
24-
serving_certs: Optional[Dict[str, Any]] = None,
25-
tls_security_profile: Optional[Dict[str, Any]] = None,
22+
additional_cors_allowed_origins: list[Any] | None = None,
23+
audit: dict[str, Any] | None = None,
24+
client_ca: dict[str, Any] | None = None,
25+
encryption: dict[str, Any] | None = None,
26+
serving_certs: dict[str, Any] | None = None,
27+
tls_security_profile: dict[str, Any] | None = None,
2628
**kwargs: Any,
2729
) -> None:
2830
"""
2931
Args:
30-
additional_cors_allowed_origins (List[Any]): additionalCORSAllowedOrigins lists additional, user-defined regular
32+
additional_cors_allowed_origins (list[Any]): additionalCORSAllowedOrigins lists additional, user-defined regular
3133
expressions describing hosts for which the API server allows
3234
access using the CORS headers. This may be needed to access the
3335
API and the integrated OAuth server from JavaScript applications.
3436
The values are regular expressions that correspond to the Golang
3537
regular expression language.
3638
37-
audit (Dict[str, Any]): audit specifies the settings for audit configuration to be applied to
39+
audit (dict[str, Any]): audit specifies the settings for audit configuration to be applied to
3840
all OpenShift-provided API servers in the cluster.
3941
40-
client_ca (Dict[str, Any]): clientCA references a ConfigMap containing a certificate bundle for
42+
client_ca (dict[str, Any]): clientCA references a ConfigMap containing a certificate bundle for
4143
the signers that will be recognized for incoming client
4244
certificates in addition to the operator managed signers. If this
4345
is empty, then only operator managed signers are valid. You
@@ -46,14 +48,14 @@ def __init__(
4648
openshift-config namespace and contain the following required
4749
fields: - ConfigMap.Data["ca-bundle.crt"] - CA bundle.
4850
49-
encryption (Dict[str, Any]): encryption allows the configuration of encryption of resources at the
51+
encryption (dict[str, Any]): encryption allows the configuration of encryption of resources at the
5052
datastore layer.
5153
52-
serving_certs (Dict[str, Any]): servingCert is the TLS cert info for serving secure traffic. If not
54+
serving_certs (dict[str, Any]): servingCert is the TLS cert info for serving secure traffic. If not
5355
specified, operator managed certificates will be used for serving
5456
secure traffic.
5557
56-
tls_security_profile (Dict[str, Any]): tlsSecurityProfile specifies settings for TLS connections for
58+
tls_security_profile (dict[str, Any]): tlsSecurityProfile specifies settings for TLS connections for
5759
externally exposed servers. If unset, a default (which may change
5860
between releases) is chosen. Note that only Old, Intermediate and
5961
Custom profiles are currently supported, and the maximum available

class_generator/tests/manifests/ClusterOperator/cluster_operator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Generated using https://github.com/RedHatQE/openshift-python-wrapper/blob/main/scripts/resource/README.md
22

3+
from __future__ import annotations
4+
35
from typing import Any
46
from ocp_resources.resource import Resource
57

0 commit comments

Comments
 (0)