1212from tempfile import gettempdir
1313
1414import textwrap
15- from typing import Any , Dict , List , Tuple
15+ from typing import Any
1616import click
1717import re
1818from concurrent .futures import Future , ThreadPoolExecutor , as_completed
3939
4040
4141def _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
374380def 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 \n from __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
483494def 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
513524def 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
670678def 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 :
0 commit comments