Skip to content

Commit dfba0d9

Browse files
committed
fix: Don't crash the generator when one of the post-generation hooks is missing [fixes #479]. Thanks @chamini2 and @karolzlot!
1 parent 367487d commit dfba0d9

File tree

3 files changed

+45
-71
lines changed

3 files changed

+45
-71
lines changed

openapi_python_client/__init__.py

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import sys
66
from enum import Enum
77
from pathlib import Path
8-
from typing import Any, Dict, Optional, Sequence, Union
8+
from subprocess import CalledProcessError
9+
from typing import Any, Dict, List, Optional, Sequence, Union
910

1011
import httpcore
1112
import httpx
@@ -16,7 +17,7 @@
1617

1718
from .config import Config
1819
from .parser import GeneratorData, import_string_from_class
19-
from .parser.errors import GeneratorError
20+
from .parser.errors import ErrorLevel, GeneratorError
2021

2122
if sys.version_info.minor < 8: # version did not exist before 3.8, need to use a backport
2223
from importlib_metadata import version
@@ -96,6 +97,7 @@ def __init__(
9697
project_name=self.project_name,
9798
project_dir=self.project_dir,
9899
)
100+
self.errors: List[GeneratorError] = []
99101

100102
def build(self) -> Sequence[GeneratorError]:
101103
"""Create the project from templates"""
@@ -112,7 +114,7 @@ def build(self) -> Sequence[GeneratorError]:
112114
self._build_metadata()
113115
self._build_models()
114116
self._build_api()
115-
self._reformat()
117+
self._run_post_hooks()
116118
return self._get_errors()
117119

118120
def update(self) -> Sequence[GeneratorError]:
@@ -125,35 +127,41 @@ def update(self) -> Sequence[GeneratorError]:
125127
self._create_package()
126128
self._build_models()
127129
self._build_api()
128-
self._reformat()
130+
self._run_post_hooks()
129131
return self._get_errors()
130132

131-
def _reformat(self) -> None:
132-
subprocess.run(
133-
"autoflake -i -r --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports .",
134-
cwd=self.package_dir,
135-
shell=True,
136-
stdout=subprocess.PIPE,
137-
stderr=subprocess.PIPE,
138-
check=True,
139-
)
140-
subprocess.run(
141-
"isort .",
142-
cwd=self.project_dir,
143-
shell=True,
144-
stdout=subprocess.PIPE,
145-
stderr=subprocess.PIPE,
146-
check=True,
147-
)
148-
subprocess.run(
149-
"black .", cwd=self.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
150-
)
133+
def _run_post_hooks(self) -> None:
134+
for command in self.config.post_hooks:
135+
self._run_command(command)
151136

152-
def _get_errors(self) -> Sequence[GeneratorError]:
153-
errors = []
137+
def _run_command(self, cmd: str) -> None:
138+
try:
139+
subprocess.run(
140+
cmd, cwd=self.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
141+
)
142+
except CalledProcessError as err:
143+
cmd_name = cmd.split(" ")[0]
144+
if err.returncode == 127:
145+
self.errors.append(
146+
GeneratorError(
147+
level=ErrorLevel.WARNING, header="Skipping Integration", detail=f"{cmd_name} is not in PATH"
148+
)
149+
)
150+
else:
151+
self.errors.append(
152+
GeneratorError(
153+
level=ErrorLevel.ERROR,
154+
header=f"{cmd_name} failed",
155+
detail=err.stderr.decode() or err.output.decode(),
156+
)
157+
)
158+
159+
def _get_errors(self) -> List[GeneratorError]:
160+
errors: List[GeneratorError] = []
154161
for collection in self.openapi.endpoint_collections_by_tag.values():
155162
errors.extend(collection.parse_errors)
156163
errors.extend(self.openapi.errors)
164+
errors.extend(self.errors)
157165
return errors
158166

159167
def _create_package(self) -> None:

openapi_python_client/config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path
2-
from typing import Dict, Optional
2+
from typing import Dict, List, Optional
33

44
import yaml
55
from pydantic import BaseModel
@@ -25,6 +25,11 @@ class Config(BaseModel):
2525
project_name_override: Optional[str]
2626
package_name_override: Optional[str]
2727
package_version_override: Optional[str]
28+
post_hooks: List[str] = [
29+
"autoflake -i -r --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports .",
30+
"isort .",
31+
"black .",
32+
]
2833
field_prefix: str = "field_"
2934

3035
@staticmethod

tests/test___init__.py

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def test_build(self, mocker):
303303
project._build_models = mocker.MagicMock()
304304
project._build_api = mocker.MagicMock()
305305
project._create_package = mocker.MagicMock()
306-
project._reformat = mocker.MagicMock()
306+
project._run_post_hooks = mocker.MagicMock()
307307
project._get_errors = mocker.MagicMock()
308308

309309
result = project.build()
@@ -313,7 +313,7 @@ def test_build(self, mocker):
313313
project._build_metadata.assert_called_once()
314314
project._build_models.assert_called_once()
315315
project._build_api.assert_called_once()
316-
project._reformat.assert_called_once()
316+
project._run_post_hooks.assert_called_once()
317317
project._get_errors.assert_called_once()
318318
assert result == project._get_errors.return_value
319319

@@ -327,7 +327,7 @@ def test_build_no_meta(self, mocker):
327327
project._build_models = mocker.MagicMock()
328328
project._build_api = mocker.MagicMock()
329329
project._create_package = mocker.MagicMock()
330-
project._reformat = mocker.MagicMock()
330+
project._run_post_hooks = mocker.MagicMock()
331331
project._get_errors = mocker.MagicMock()
332332

333333
project.build()
@@ -354,7 +354,7 @@ def test_update(self, mocker):
354354
project._build_models = mocker.MagicMock()
355355
project._build_api = mocker.MagicMock()
356356
project._create_package = mocker.MagicMock()
357-
project._reformat = mocker.MagicMock()
357+
project._run_post_hooks = mocker.MagicMock()
358358
project._get_errors = mocker.MagicMock()
359359

360360
result = project.update()
@@ -363,7 +363,7 @@ def test_update(self, mocker):
363363
project._create_package.assert_called_once()
364364
project._build_models.assert_called_once()
365365
project._build_api.assert_called_once()
366-
project._reformat.assert_called_once()
366+
project._run_post_hooks.assert_called_once()
367367
project._get_errors.assert_called_once()
368368
assert result == project._get_errors.return_value
369369

@@ -502,45 +502,6 @@ def test__build_setup_py(self, mocker):
502502
setup_path.write_text.assert_called_once_with(setup_template.render(), encoding="utf-8")
503503

504504

505-
def test__reformat(mocker):
506-
import subprocess
507-
508-
sub_run = mocker.patch("subprocess.run")
509-
project = make_project()
510-
project.project_dir = mocker.MagicMock(autospec=pathlib.Path)
511-
512-
project._reformat()
513-
514-
sub_run.assert_has_calls(
515-
[
516-
mocker.call(
517-
"autoflake -i -r --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports .",
518-
cwd=project.package_dir,
519-
shell=True,
520-
stdout=subprocess.PIPE,
521-
stderr=subprocess.PIPE,
522-
check=True,
523-
),
524-
mocker.call(
525-
"isort .",
526-
cwd=project.project_dir,
527-
shell=True,
528-
stdout=subprocess.PIPE,
529-
stderr=subprocess.PIPE,
530-
check=True,
531-
),
532-
mocker.call(
533-
"black .",
534-
cwd=project.project_dir,
535-
shell=True,
536-
stdout=subprocess.PIPE,
537-
stderr=subprocess.PIPE,
538-
check=True,
539-
),
540-
]
541-
)
542-
543-
544505
def test__get_errors(mocker):
545506
from openapi_python_client import GeneratorData, MetaType, Project
546507
from openapi_python_client.parser.openapi import EndpointCollection

0 commit comments

Comments
 (0)