Skip to content

Commit 83ec9e9

Browse files
The --json2ts-cmd option should not be validated via 'shutil.which' in all cases (phillipdupuis#26)
* The --json2ts-cmd option should not be validated via 'shutil.which' if it contains spaces (ex: 'yarn json2ts'). In those cases we will just attempt to run it, and if the command fails we will raise a RuntimeError noting that.
1 parent c2b7473 commit 83ec9e9

File tree

4 files changed

+50
-22
lines changed

4 files changed

+50
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ $ pip install pydantic-to-typescript
2323
| ‑‑module | name or filepath of the python module you would like to convert. All the pydantic models within it will be converted to typescript interfaces. Discoverable submodules will also be checked. |
2424
| ‑‑output | name of the file the typescript definitions should be written to. Ex: './frontend/apiTypes.ts' |
2525
| ‑‑exclude | name of a pydantic model which should be omitted from the resulting typescript definitions. This option can be defined multiple times, ex: `--exclude Foo --exclude Bar` to exclude both the Foo and Bar models from the output. |
26-
| ‑‑json2ts‑cmd | optional, the command used to invoke json2ts. The default is 'json2ts'. Specify this if you have it installed in a strange location and need to provide the exact path (ex: /myproject/node_modules/bin/json2ts) |
26+
| ‑‑json2ts‑cmd | optional, the command used to invoke json2ts. The default is 'json2ts'. Specify this if you have it installed locally (ex: 'yarn json2ts') or if the exact path to the executable is required (ex: /myproject/node_modules/bin/json2ts) |
2727

2828
---
2929

pydantic2ts/cli/script.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,16 @@ def extract_pydantic_models(module: ModuleType) -> List[Type[BaseModel]]:
8989
return models
9090

9191

92-
def remove_master_model_from_output(output: str) -> None:
92+
def clean_output_file(output_filename: str) -> None:
9393
"""
94-
A faux 'master model' with references to all the pydantic models is necessary for generating
95-
clean typescript definitions without any duplicates, but we don't actually want it in the
96-
output. This function handles removing it from the generated typescript file.
94+
Clean up the output file typescript definitions were written to by:
95+
1. Removing the 'master model'.
96+
This is a faux pydantic model with references to all the *actual* models necessary for generating
97+
clean typescript definitions without any duplicates. We don't actually want it in the output, so
98+
this function removes it from the generated typescript file.
99+
2. Adding a banner comment with clear instructions for how to regenerate the typescript definitions.
97100
"""
98-
with open(output, "r") as f:
101+
with open(output_filename, "r") as f:
99102
lines = f.readlines()
100103

101104
start, end = None, None
@@ -117,7 +120,7 @@ def remove_master_model_from_output(output: str) -> None:
117120

118121
new_lines = banner_comment_lines + lines[:start] + lines[(end + 1) :]
119122

120-
with open(output, "w") as f:
123+
with open(output_filename, "w") as f:
121124
f.writelines(new_lines)
122125

123126

@@ -184,9 +187,10 @@ def generate_typescript_defs(
184187
:param module: python module containing pydantic model definitions, ex: my_project.api.schemas
185188
:param output: file that the typescript definitions will be written to
186189
:param exclude: optional, a tuple of names for pydantic models which should be omitted from the typescript output.
187-
:param json2ts_cmd: optional, the command that will execute json2ts. Use this if it's installed in a strange spot.
190+
:param json2ts_cmd: optional, the command that will execute json2ts. Provide this if the executable is not
191+
discoverable or if it's locally installed (ex: 'yarn json2ts').
188192
"""
189-
if not shutil.which(json2ts_cmd):
193+
if " " not in json2ts_cmd and not shutil.which(json2ts_cmd):
190194
raise Exception(
191195
"json2ts must be installed. Instructions can be found here: "
192196
"https://www.npmjs.com/package/json-schema-to-typescript"
@@ -214,13 +218,15 @@ def generate_typescript_defs(
214218
f'{json2ts_cmd} -i {schema_file_path} -o {output} --bannerComment ""'
215219
)
216220

221+
shutil.rmtree(schema_dir)
222+
217223
if json2ts_exit_code == 0:
218-
remove_master_model_from_output(output)
224+
clean_output_file(output)
219225
logger.info(f"Saved typescript definitions to {output}.")
220226
else:
221-
logger.error(f"{json2ts_cmd} failed with exit code {json2ts_exit_code}.")
222-
223-
shutil.rmtree(schema_dir)
227+
raise RuntimeError(
228+
f'"{json2ts_cmd}" failed with exit code {json2ts_exit_code}.'
229+
)
224230

225231

226232
def parse_cli_args(args: List[str] = None) -> argparse.Namespace:
@@ -252,7 +258,9 @@ def parse_cli_args(args: List[str] = None) -> argparse.Namespace:
252258
"--json2ts-cmd",
253259
dest="json2ts_cmd",
254260
default="json2ts",
255-
help="path to the json-schema-to-typescript executable.\n" "(default: json2ts)",
261+
help="path to the json-schema-to-typescript executable.\n"
262+
"Provide this if it's not discoverable or if it's only installed locally (example: 'yarn json2ts').\n"
263+
"(default: json2ts)",
256264
)
257265
return parser.parse_args(args)
258266

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def readme():
77

88

99
classifiers = [
10-
"Development Status :: 4 - Beta",
10+
"Development Status :: 5 - Production/Stable",
1111
"License :: OSI Approved :: MIT License",
1212
"Programming Language :: Python",
1313
"Programming Language :: Python :: 3",
@@ -25,7 +25,7 @@ def readme():
2525

2626
setup(
2727
name="pydantic-to-typescript",
28-
version="1.0.9",
28+
version="1.0.10",
2929
description="Convert pydantic models to typescript interfaces",
3030
license="MIT",
3131
long_description=readme(),

tests/test_script.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,36 @@ def test_calling_from_python(tmpdir):
101101

102102

103103
def test_error_if_json2ts_not_installed(tmpdir):
104-
with pytest.raises(Exception) as exc:
105-
module_path = get_input_module("single_module")
106-
output_path = tmpdir.join(f"cli_single_module.ts").strpath
107-
json2ts_cmd = "someCommandWhichDefinitelyDoesNotExist"
108-
generate_typescript_defs(module_path, output_path, json2ts_cmd=json2ts_cmd)
104+
module_path = get_input_module("single_module")
105+
output_path = tmpdir.join(f"cli_single_module.ts").strpath
106+
107+
# If the json2ts command has no spaces and the executable cannot be found,
108+
# that means the user either hasn't installed json-schema-to-typescript or they made a typo.
109+
# We should raise a descriptive error with installation instructions.
110+
invalid_global_cmd = "someCommandWhichDefinitelyDoesNotExist"
111+
with pytest.raises(Exception) as exc1:
112+
generate_typescript_defs(
113+
module_path,
114+
output_path,
115+
json2ts_cmd=invalid_global_cmd,
116+
)
109117
assert (
110-
str(exc.value)
118+
str(exc1.value)
111119
== "json2ts must be installed. Instructions can be found here: https://www.npmjs.com/package/json-schema-to-typescript"
112120
)
113121

122+
# But if the command DOES contain spaces (ex: "yarn json2ts") they're likely using a locally installed CLI.
123+
# We should not be validating the command in this case.
124+
# Instead we should just be *trying* to run it and checking the exit code.
125+
invalid_local_cmd = "yaaaarn json2tsbutwithatypo"
126+
with pytest.raises(RuntimeError) as exc2:
127+
generate_typescript_defs(
128+
module_path,
129+
output_path,
130+
json2ts_cmd=invalid_local_cmd,
131+
)
132+
assert str(exc2.value).startswith(f'"{invalid_local_cmd}" failed with exit code ')
133+
114134

115135
def test_error_if_invalid_module_path(tmpdir):
116136
with pytest.raises(ModuleNotFoundError):

0 commit comments

Comments
 (0)