Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions pydantic2ts/cli/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def clean_schema(schema: Dict[str, Any]) -> None:
del schema["description"]


def generate_json_schema(models: List[Type[BaseModel]]) -> str:
def generate_json_schema(models: List[Type[BaseModel]], by_alias: bool) -> str:
"""
Create a top-level '_Master_' model with references to each of the actual models.
Generate the schema for this model, which will include the schemas for all the
Expand All @@ -152,7 +152,7 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
master_model.Config.extra = Extra.forbid
master_model.Config.schema_extra = staticmethod(clean_schema)

schema = json.loads(master_model.schema_json())
schema = json.loads(master_model.schema_json(by_alias=by_alias))

for d in schema.get("definitions", {}).values():
clean_schema(d)
Expand All @@ -166,7 +166,8 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:


def generate_typescript_defs(
module: str, output: str, exclude: Tuple[str] = (), json2ts_cmd: str = "json2ts"
module: str, output: str, exclude: Tuple[str] = (), json2ts_cmd: str
= "json2ts", by_alias: bool = False
) -> None:
"""
Convert the pydantic models in a python module into typescript interfaces.
Expand All @@ -191,7 +192,7 @@ def generate_typescript_defs(

logger.info("Generating JSON schema from pydantic models...")

schema = generate_json_schema(models)
schema = generate_json_schema(models, by_alias)
schema_dir = mkdtemp()
schema_file_path = os.path.join(schema_dir, "schema.json")

Expand Down Expand Up @@ -227,20 +228,27 @@ def generate_typescript_defs(
@click.option(
"--output", help="name of the file the typescript definitions should be written to"
)
@click.option(
"--by-alias",
help="Use the backing field name (True) or the model field name (False)",
default=False
)
@click.option(
"--exclude",
multiple=True,
help="name of a pydantic model which should be omitted from the results. This option can be defined multiple times",
)
@click.option("--json2ts-cmd", default="json2ts")
def main(
module: str, output: str, exclude: Tuple[str], json2ts_cmd: str = "json2ts"
module: str, output: str, exclude: Tuple[str], json2ts_cmd: str =
"json2ts", by_alias: bool = False
) -> None:
"""
CLI entrypoint to run :func:`generate_typescript_defs`
"""
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s")
return generate_typescript_defs(module, output, exclude, json2ts_cmd)
return generate_typescript_defs(module, output, exclude,
json2ts_cmd, by_alias)


if __name__ == "__main__":
Expand Down
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/expected_results/single_module_alias_id/input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pydantic import BaseModel, Field
from typing import Optional, List


class LoginCredentials(BaseModel):
username: str
password: str


class Profile(BaseModel):
id: int = Field(..., alias='_id',
description="Appears in model without the underscore, but stored with it.")
username: str
age: Optional[int]
hobbies: List[str]


class LoginResponseData(BaseModel):
token: str
profile: Profile
24 changes: 24 additions & 0 deletions tests/expected_results/single_module_alias_id/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* tslint:disable */
/* eslint-disable */
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/

export interface LoginCredentials {
username: string;
password: string;
}
export interface LoginResponseData {
token: string;
profile: Profile;
}
export interface Profile {
/**
* Appears in model without the underscore, but stored with it.
*/
_id: number;
username: string;
age?: number;
hobbies: string[];
}
13 changes: 11 additions & 2 deletions tests/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def get_expected_output(test_name: str) -> str:


def run_test(
tmpdir, test_name, *, module_path=None, call_from_python=False, exclude=()
tmpdir, test_name, *, module_path=None, call_from_python=False,
exclude=(), by_alias: bool = False
):
"""
Execute pydantic2ts logic for converting pydantic models into tyepscript definitions.
Expand All @@ -27,11 +28,13 @@ def run_test(
output_path = tmpdir.join(f"cli_{test_name}.ts").strpath

if call_from_python:
generate_typescript_defs(module_path, output_path, exclude)
generate_typescript_defs(module_path, output_path, exclude,
by_alias)
else:
cmd = f"pydantic2ts --module {module_path} --output {output_path}"
for model_to_exclude in exclude:
cmd += f" --exclude {model_to_exclude}"
cmd += f" --by-alias {by_alias}"
os.system(cmd)

with open(output_path, "r") as f:
Expand All @@ -42,6 +45,12 @@ def run_test(
def test_single_module(tmpdir):
run_test(tmpdir, "single_module")

def test_single_module_alias_id(tmpdir):
run_test(tmpdir, "single_module_alias_id",
call_from_python=True, by_alias=True)

def test_single_module_alias_id_cmd(tmpdir):
run_test(tmpdir, "single_module_alias_id", by_alias=True)

def test_submodules(tmpdir):
run_test(tmpdir, "submodules")
Expand Down