Skip to content

Commit cf55cbe

Browse files
Merge branch 'master' into remove-logging-configuration
2 parents 3c6aa14 + efca21b commit cf55cbe

File tree

9 files changed

+82
-14
lines changed

9 files changed

+82
-14
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ $ pip install pydantic-to-typescript
1717
|:----------|:-----------|
1818
|‑‑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.|
1919
|‑‑output|name of the file the typescript definitions should be written to. Ex: './frontend/apiTypes.ts'|
20+
|‑‑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.|
2021
|‑‑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)|
2122
---
2223
### Usage

pydantic2ts/cli/script.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from importlib.util import spec_from_file_location, module_from_spec
99
from tempfile import mkdtemp
1010
from types import ModuleType
11-
from typing import Type, Dict, Any, List
11+
from typing import Type, Dict, Any, List, Tuple
1212
from uuid import uuid4
1313

1414
import click
@@ -139,14 +139,16 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
139139
'[k: string]: any' from being added to every interface. This change is reverted
140140
once the schema has been generated.
141141
"""
142-
model_extras = [getattr(m.Config, 'extra', None) for m in models]
142+
model_extras = [getattr(m.Config, "extra", None) for m in models]
143143

144144
try:
145145
for m in models:
146-
if getattr(m.Config, 'extra', None) != Extra.allow:
146+
if getattr(m.Config, "extra", None) != Extra.allow:
147147
m.Config.extra = Extra.forbid
148148

149-
master_model = create_model("_Master_", **{m.__name__: (m, ...) for m in models})
149+
master_model = create_model(
150+
"_Master_", **{m.__name__: (m, ...) for m in models}
151+
)
150152
master_model.Config.extra = Extra.forbid
151153
master_model.Config.schema_extra = staticmethod(clean_schema)
152154

@@ -164,13 +166,14 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
164166

165167

166168
def generate_typescript_defs(
167-
module: str, output: str, json2ts_cmd: str = "json2ts"
169+
module: str, output: str, exclude: Tuple[str] = (), json2ts_cmd: str = "json2ts"
168170
) -> None:
169171
"""
170172
Convert the pydantic models in a python module into typescript interfaces.
171173
172174
:param module: python module containing pydantic model definitions, ex: my_project.api.schemas
173175
:param output: file that the typescript definitions will be written to
176+
:param exclude: optional, a tuple of names for pydantic models which should be omitted from the typescript output.
174177
:param json2ts_cmd: optional, the command that will execute json2ts. Use this if it's installed in a strange spot.
175178
"""
176179
if not shutil.which(json2ts_cmd):
@@ -183,6 +186,9 @@ def generate_typescript_defs(
183186

184187
models = extract_pydantic_models(import_module(module))
185188

189+
if exclude:
190+
models = [m for m in models if m.__name__ not in exclude]
191+
186192
logger.info("Generating JSON schema from pydantic models...")
187193

188194
schema = generate_json_schema(models)
@@ -214,16 +220,27 @@ def generate_typescript_defs(
214220

215221

216222
@click.command()
217-
@click.option("--module")
218-
@click.option("--output")
223+
@click.option(
224+
"--module",
225+
help="name or filepath of the python module. Discoverable submodules will also be checked",
226+
)
227+
@click.option(
228+
"--output", help="name of the file the typescript definitions should be written to"
229+
)
230+
@click.option(
231+
"--exclude",
232+
multiple=True,
233+
help="name of a pydantic model which should be omitted from the results. This option can be defined multiple times",
234+
)
219235
@click.option("--json2ts-cmd", default="json2ts")
220-
def main(module: str, output: str, json2ts_cmd: str = "json2ts") -> None:
236+
def main(
237+
module: str, output: str, exclude: Tuple[str], json2ts_cmd: str = "json2ts"
238+
) -> None:
221239
"""
222240
CLI entrypoint to run :func:`generate_typescript_defs`
223241
"""
224242
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s")
225-
226-
return generate_typescript_defs(module, output, json2ts_cmd)
243+
return generate_typescript_defs(module, output, exclude, json2ts_cmd)
227244

228245

229246
if __name__ == "__main__":

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def readme():
2424

2525
setup(
2626
name="pydantic-to-typescript",
27-
version="1.0.6",
27+
version="1.0.7",
2828
description="Convert pydantic models to typescript interfaces",
2929
license="MIT",
3030
long_description=readme(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from pydantic import BaseModel
2+
from typing import Optional, List
3+
4+
5+
class LoginCredentials(BaseModel):
6+
username: str
7+
password: str
8+
9+
10+
class Profile(BaseModel):
11+
username: str
12+
age: Optional[int]
13+
hobbies: List[str]
14+
15+
16+
class LoginResponseData(BaseModel):
17+
token: str
18+
profile: Profile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
/**
4+
/* This file was automatically generated from pydantic models by running pydantic2ts.
5+
/* Do not modify it by hand - just update the pydantic models and then re-run the script
6+
*/
7+
8+
export interface Profile {
9+
username: string;
10+
age?: number;
11+
hobbies: string[];
12+
}

tests/expected_results/generics/output.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* tslint:disable */
2+
/* eslint-disable */
23
/**
34
/* This file was automatically generated from pydantic models by running pydantic2ts.
45
/* Do not modify it by hand - just update the pydantic models and then re-run the script

tests/expected_results/single_module/output.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* tslint:disable */
2+
/* eslint-disable */
23
/**
34
/* This file was automatically generated from pydantic models by running pydantic2ts.
45
/* Do not modify it by hand - just update the pydantic models and then re-run the script

tests/expected_results/submodules/output.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* tslint:disable */
2+
/* eslint-disable */
23
/**
34
/* This file was automatically generated from pydantic models by running pydantic2ts.
45
/* Do not modify it by hand - just update the pydantic models and then re-run the script

tests/test_script.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ def get_expected_output(test_name: str) -> str:
1616
return f.read()
1717

1818

19-
def run_test(tmpdir, test_name, *, module_path=None, call_from_python=False):
19+
def run_test(
20+
tmpdir, test_name, *, module_path=None, call_from_python=False, exclude=()
21+
):
2022
"""
2123
Execute pydantic2ts logic for converting pydantic models into tyepscript definitions.
2224
Compare the output with the expected output, verifying it is identical.
@@ -25,9 +27,12 @@ def run_test(tmpdir, test_name, *, module_path=None, call_from_python=False):
2527
output_path = tmpdir.join(f"cli_{test_name}.ts").strpath
2628

2729
if call_from_python:
28-
generate_typescript_defs(module_path, output_path)
30+
generate_typescript_defs(module_path, output_path, exclude)
2931
else:
30-
os.system(f"pydantic2ts --module {module_path} --output {output_path}")
32+
cmd = f"pydantic2ts --module {module_path} --output {output_path}"
33+
for model_to_exclude in exclude:
34+
cmd += f" --exclude {model_to_exclude}"
35+
os.system(cmd)
3136

3237
with open(output_path, "r") as f:
3338
output = f.read()
@@ -46,6 +51,12 @@ def test_generics(tmpdir):
4651
run_test(tmpdir, "generics")
4752

4853

54+
def test_excluding_models(tmpdir):
55+
run_test(
56+
tmpdir, "excluding_models", exclude=("LoginCredentials", "LoginResponseData")
57+
)
58+
59+
4960
def test_relative_filepath(tmpdir):
5061
test_name = "single_module"
5162
relative_path = os.path.join(".", "expected_results", test_name, "input.py")
@@ -58,3 +69,9 @@ def test_calling_from_python(tmpdir):
5869
run_test(tmpdir, "single_module", call_from_python=True)
5970
run_test(tmpdir, "submodules", call_from_python=True)
6071
run_test(tmpdir, "generics", call_from_python=True)
72+
run_test(
73+
tmpdir,
74+
"excluding_models",
75+
call_from_python=True,
76+
exclude=("LoginCredentials", "LoginResponseData"),
77+
)

0 commit comments

Comments
 (0)