Skip to content

Commit acccb5e

Browse files
authored
fix:(class-generator) improve generation reliability and code quality (#2493)
1 parent b395e24 commit acccb5e

34 files changed

+275646
-711203
lines changed

.flake8

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,70 +9,3 @@ exclude =
99
Pipfile.*,
1010
docs/*,
1111
.cache/*
12-
13-
fcn_exclude_functions =
14-
update,
15-
getLogger,
16-
loads,
17-
get,
18-
next,
19-
setdefault,
20-
dumps,
21-
request,
22-
list,
23-
warning,
24-
join,
25-
rmtree,
26-
open,
27-
os,
28-
write,
29-
append,
30-
sorted,
31-
compile,
32-
int,
33-
extend,
34-
sleep,
35-
check_output,
36-
sub_resource_level,
37-
func,
38-
LOGGER,
39-
findall,
40-
raises,
41-
contextlib,
42-
re,
43-
requests,
44-
signal,
45-
yaml,
46-
benedict,
47-
logger,
48-
warn,
49-
pytest,
50-
json,
51-
template,
52-
meta,
53-
tmp_path_factory,
54-
tmpdir_factory,
55-
Path,
56-
writelines,
57-
submit,
58-
click,
59-
ast,
60-
filecmp,
61-
datetime,
62-
subprocess,
63-
urlparse,
64-
parse_qs,
65-
self,
66-
defaultdict,
67-
add_row,
68-
get_args,
69-
get_type_hints,
70-
shutil,
71-
asyncio,
72-
__class__,
73-
cls,
74-
validate,
75-
relative_to,
76-
77-
enable-extensions =
78-
FCN,

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,6 @@ CLAUDE.md
141141

142142
# Backup directories
143143
.backups/
144+
145+
# Large schema files (we keep the compressed versions)
146+
class_generator/schema/__resources-mappings.json

class_generator/cli.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from class_generator.core.coverage import analyze_coverage, generate_report
1717
from class_generator.core.discovery import discover_generated_resources
1818
from class_generator.core.generator import class_generator
19-
from class_generator.core.schema import update_kind_schema
19+
from class_generator.core.schema import update_kind_schema, ClusterVersionError
2020
from class_generator.tests.test_generation import generate_class_generator_tests
2121
from ocp_resources.utils.utils import convert_camel_case_to_snake_case
2222

@@ -53,7 +53,11 @@ def handle_schema_update(update_schema: bool, generate_missing: bool) -> bool:
5353
"""
5454
if update_schema:
5555
LOGGER.info("Updating resource schema...")
56-
update_kind_schema()
56+
try:
57+
update_kind_schema()
58+
except (RuntimeError, IOError, ClusterVersionError) as e:
59+
LOGGER.error(f"Failed to update schema: {e}")
60+
sys.exit(1)
5761

5862
# If only updating schema (not generating), exit
5963
if not generate_missing:
@@ -317,13 +321,17 @@ def handle_normal_kind_generation(
317321
# Create backup if file exists
318322
create_backup_if_needed(target_file=target_file, backup_dir=backup_dir)
319323

320-
class_generator(
321-
kind=kind,
322-
overwrite=overwrite,
323-
dry_run=dry_run,
324-
output_file=output_file,
325-
add_tests=add_tests,
326-
)
324+
try:
325+
class_generator(
326+
kind=kind,
327+
overwrite=overwrite,
328+
dry_run=dry_run,
329+
output_file=output_file,
330+
add_tests=add_tests,
331+
)
332+
except Exception as e:
333+
LOGGER.error(f"Failed to generate {kind}: {e}")
334+
sys.exit(1)
327335

328336
if backup_dir and not dry_run:
329337
LOGGER.info(f"Backup files stored in: {backup_dir}")
@@ -339,13 +347,17 @@ def generate_with_backup(kind_to_generate: str) -> list[str]:
339347
# Create backup if file exists
340348
create_backup_if_needed(target_file=target_file, backup_dir=backup_dir)
341349

342-
return class_generator(
343-
kind=kind_to_generate,
344-
overwrite=overwrite,
345-
dry_run=dry_run,
346-
output_file=output_file,
347-
add_tests=add_tests,
348-
)
350+
try:
351+
return class_generator(
352+
kind=kind_to_generate,
353+
overwrite=overwrite,
354+
dry_run=dry_run,
355+
output_file=output_file,
356+
add_tests=add_tests,
357+
)
358+
except Exception as e:
359+
LOGGER.error(f"Failed to generate {kind_to_generate}: {e}")
360+
return []
349361

350362
futures: list[Future] = []
351363
with ThreadPoolExecutor(max_workers=10) as executor:

class_generator/constants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Constants used throughout the class generator."""
1+
"""Constants used throughout the class generator and schema utilities."""
22

33
import keyword
44
from pathlib import Path
@@ -11,6 +11,8 @@
1111
TESTS_MANIFESTS_DIR: Path = Path("class_generator") / "tests" / "manifests"
1212
SCHEMA_DIR: Path = Path("class_generator") / "schema"
1313
RESOURCES_MAPPING_FILE: Path = SCHEMA_DIR / "__resources-mappings.json"
14+
RESOURCES_MAPPING_ARCHIVE: Path = SCHEMA_DIR / "__resources-mappings.json.gz"
15+
DEFINITIONS_FILE: Path = SCHEMA_DIR / "_definitions.json"
1416

1517
# Description constants
1618
MISSING_DESCRIPTION_STR: str = "No field description from API"

class_generator/core/generator.py

Lines changed: 47 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22

33
import filecmp
44
import os
5-
import shlex
6-
import sys
75
from pathlib import Path
86
from typing import Any
97

10-
from pyhelper_utils.shell import run_command
118
from rich.console import Console
129
from rich.syntax import Syntax
1310
from simple_logger.logger import get_logger
@@ -86,9 +83,10 @@ def generate_resource_file_from_dict(
8683
LOGGER.warning(f"Overwriting {_output_file}")
8784

8885
else:
89-
temp_output_file = _output_file.replace(".py", "_TEMP.py")
90-
LOGGER.warning(f"{_output_file} already exists, using {temp_output_file}")
91-
_output_file = temp_output_file
86+
if not dry_run:
87+
temp_output_file = _output_file.replace(".py", "_TEMP.py")
88+
LOGGER.warning(f"{_output_file} already exists, using {temp_output_file}")
89+
_output_file = temp_output_file
9290

9391
if _user_code.strip() or _user_imports.strip():
9492
output += f"{_user_imports}{rendered}{_user_code}"
@@ -114,6 +112,7 @@ def class_generator(
114112
add_tests: bool = False,
115113
called_from_cli: bool = True,
116114
update_schema_executed: bool = False,
115+
called_from_test: bool = False,
117116
) -> list[str]:
118117
"""
119118
Generates a class for a given Kind.
@@ -134,55 +133,50 @@ def class_generator(
134133
LOGGER.info(f"Generating class for {kind}")
135134
kind = kind.lower()
136135
kind_and_namespaced_mappings = read_resources_mapping_file().get(kind)
137-
if not kind_and_namespaced_mappings:
136+
if not called_from_test and not kind_and_namespaced_mappings:
138137
if called_from_cli:
139138
if update_schema_executed:
140-
LOGGER.error(f"{kind} not found in {RESOURCES_MAPPING_FILE} after update-schema executed.")
141-
sys.exit(1)
142-
143-
# Use a loop to handle retries instead of recursion
144-
max_retries = 3
145-
retry_count = 0
146-
147-
while retry_count < max_retries:
148-
# Validate user input with a loop
149-
while True:
150-
run_update_schema = (
151-
input(
152-
f"{kind} not found in {RESOURCES_MAPPING_FILE}, Do you want to run --update-schema and retry? [Y/N]: "
153-
)
154-
.strip()
155-
.lower()
156-
)
157-
158-
if run_update_schema in ["y", "n"]:
159-
break
160-
else:
161-
print("Invalid input. Please enter 'Y' or 'N'.")
162-
163-
if run_update_schema == "n":
164-
sys.exit(1)
165-
166-
# User chose 'y' - update schema and retry
167-
LOGGER.info(f"Updating schema (attempt {retry_count + 1}/{max_retries})...")
168-
update_kind_schema()
139+
error_msg = f"{kind} not found in {RESOURCES_MAPPING_FILE} after update-schema executed."
140+
LOGGER.error(error_msg)
141+
raise RuntimeError(error_msg)
142+
143+
run_update_schema = (
144+
input(
145+
f"{kind} not found in {RESOURCES_MAPPING_FILE}, Do you want to run --update-schema and retry? [Y/N]: "
146+
)
147+
.strip()
148+
.lower()
149+
)
169150

170-
# Re-read the mapping file to check if kind is now available
171-
kind_and_namespaced_mappings = read_resources_mapping_file().get(kind)
172-
if kind_and_namespaced_mappings:
173-
# Kind found, break out of retry loop to continue processing
174-
break
175-
else:
176-
retry_count += 1
177-
if retry_count < max_retries:
178-
LOGGER.warning(
179-
f"{kind} still not found after schema update. Retry {retry_count}/{max_retries}."
180-
)
181-
else:
182-
LOGGER.error(
183-
f"{kind} not found in {RESOURCES_MAPPING_FILE} after {max_retries} update-schema attempts."
184-
)
185-
sys.exit(1)
151+
if run_update_schema != "y":
152+
raise RuntimeError(f"User declined to update schema for {kind}")
153+
154+
# User chose 'y' - update schema and retry
155+
LOGGER.info("Updating schema")
156+
try:
157+
update_kind_schema()
158+
except (RuntimeError, IOError) as e:
159+
error_msg = f"Failed to update schema: {e}"
160+
LOGGER.error(error_msg)
161+
raise RuntimeError(error_msg) from e
162+
163+
# Re-read the mapping file to check if kind is now available
164+
kind_and_namespaced_mappings = read_resources_mapping_file(skip_cache=True).get(kind)
165+
if kind_and_namespaced_mappings:
166+
return class_generator(
167+
kind=kind,
168+
overwrite=overwrite,
169+
dry_run=dry_run,
170+
output_file=output_file,
171+
output_dir=output_dir,
172+
add_tests=add_tests,
173+
called_from_cli=called_from_cli,
174+
update_schema_executed=True,
175+
)
176+
else:
177+
error_msg = f"{kind} not found in {RESOURCES_MAPPING_FILE} after update-schema attempt."
178+
LOGGER.error(error_msg)
179+
raise RuntimeError(error_msg)
186180

187181
else:
188182
LOGGER.error(f"{kind} not found in {RESOURCES_MAPPING_FILE}, Please run --update-schema.")
@@ -215,29 +209,7 @@ def class_generator(
215209
output_dir=output_dir,
216210
)
217211

218-
if not dry_run:
219-
try:
220-
rc, stdout, stderr = run_command(
221-
command=shlex.split(f"uvx pre-commit run --files {generated_py_file}"),
222-
verify_stderr=False,
223-
check=False,
224-
)
225-
# Check if the command failed
226-
if not rc:
227-
LOGGER.warning(
228-
f"Pre-commit hooks failed for {generated_py_file}. "
229-
f"This is non-fatal and generation will continue."
230-
)
231-
if stderr:
232-
LOGGER.debug(f"Pre-commit stderr: {stderr}")
233-
if stdout:
234-
LOGGER.debug(f"Pre-commit stdout: {stdout}")
235-
except Exception as e:
236-
LOGGER.error(
237-
f"Failed to run pre-commit hooks for {generated_py_file}: {e}. "
238-
f"This is non-fatal and generation will continue."
239-
)
240-
212+
if not dry_run and not called_from_test:
241213
if orig_filename != generated_py_file and filecmp.cmp(orig_filename, generated_py_file):
242214
LOGGER.warning(f"File {orig_filename} was not updated, deleting {generated_py_file}")
243215
Path.unlink(Path(generated_py_file))

0 commit comments

Comments
 (0)