From cacd68c25ca6b4798201ec1d00112a9c76c484e0 Mon Sep 17 00:00:00 2001 From: eg Date: Wed, 5 Jul 2023 15:35:44 +0200 Subject: [PATCH 1/7] Migrate to Pydantic version 2, initial pass --- pydantic2ts/cli/script.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pydantic2ts/cli/script.py b/pydantic2ts/cli/script.py index 8518395..d53599e 100644 --- a/pydantic2ts/cli/script.py +++ b/pydantic2ts/cli/script.py @@ -152,30 +152,32 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str: '[k: string]: any' from being added to every interface. This change is reverted once the schema has been generated. """ - model_extras = [getattr(m.Config, "extra", None) for m in models] + model_extras = [m.model_config.get("extra", None) for m in models] try: for m in models: - if getattr(m.Config, "extra", None) != Extra.allow: - m.Config.extra = Extra.forbid + if m.model_config.get("extra", None) != "allow": + m.model_config["extra"] = "forbid" master_model = create_model( "_Master_", **{m.__name__: (m, ...) for m in models} ) - master_model.Config.extra = Extra.forbid - master_model.Config.schema_extra = staticmethod(clean_schema) + master_model.model_config["extra"] = "forbid" + master_model.model_config["schema_extra"] = staticmethod(clean_schema) - schema = json.loads(master_model.schema_json()) + schema = master_model.model_json_schema() - for d in schema.get("definitions", {}).values(): + for d in schema.get("$defs", {}).values(): clean_schema(d) + print(json.dumps(schema, indent=2)) + return json.dumps(schema, indent=2) finally: for m, x in zip(models, model_extras): if x is not None: - m.Config.extra = x + m.model_config.extra = x def generate_typescript_defs( From dc1def6a8d4810c54e5b6cb352f9b7160b1f20e7 Mon Sep 17 00:00:00 2001 From: eg Date: Wed, 5 Jul 2023 15:41:34 +0200 Subject: [PATCH 2/7] Fixing tests --- tests/expected_results/generics/input.py | 3 +-- tests/expected_results/generics/output.ts | 24 +++++++++++-------- .../expected_results/single_module/output.ts | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/expected_results/generics/input.py b/tests/expected_results/generics/input.py index a37bc55..61a67a8 100644 --- a/tests/expected_results/generics/input.py +++ b/tests/expected_results/generics/input.py @@ -2,7 +2,6 @@ from typing import Generic, TypeVar, Optional, List, Type, cast, Union from pydantic import BaseModel -from pydantic.generics import GenericModel T = TypeVar("T") @@ -12,7 +11,7 @@ class Error(BaseModel): message: str -class ApiResponse(GenericModel, Generic[T]): +class ApiResponse(BaseModel, Generic[T]): data: Optional[T] error: Optional[Error] diff --git a/tests/expected_results/generics/output.ts b/tests/expected_results/generics/output.ts index 5da2624..79b3b5f 100644 --- a/tests/expected_results/generics/output.ts +++ b/tests/expected_results/generics/output.ts @@ -5,6 +5,14 @@ /* Do not modify it by hand - just update the pydantic models and then re-run the script */ +export interface ApiResponse { + data: unknown; + error: Error | null; +} +export interface Error { + code: number; + message: string; +} export interface Article { author: User; content: string; @@ -14,17 +22,13 @@ export interface User { name: string; email: string; } -export interface Error { - code: number; - message: string; -} export interface ListArticlesResponse { - data?: Article[]; - error?: Error; + data: Article[] | null; + error: Error | null; } export interface ListUsersResponse { - data?: User[]; - error?: Error; + data: User[] | null; + error: Error | null; } export interface UserProfile { name: string; @@ -34,6 +38,6 @@ export interface UserProfile { age: number; } export interface UserProfileResponse { - data?: UserProfile; - error?: Error; + data: UserProfile | null; + error: Error | null; } diff --git a/tests/expected_results/single_module/output.ts b/tests/expected_results/single_module/output.ts index 4defc90..56ea42c 100644 --- a/tests/expected_results/single_module/output.ts +++ b/tests/expected_results/single_module/output.ts @@ -15,6 +15,6 @@ export interface LoginResponseData { } export interface Profile { username: string; - age?: number; + age: number | null; hobbies: string[]; } From d30c26373aa6664fdaec28dc35d8c06b04915671 Mon Sep 17 00:00:00 2001 From: eg Date: Wed, 5 Jul 2023 15:44:01 +0200 Subject: [PATCH 3/7] Fixing tests, update version --- setup.py | 2 +- tests/expected_results/excluding_models/output.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index dff85f4..7777cdc 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def readme(): setup( name="pydantic-to-typescript", - version="1.0.10", + version="2.0.0", description="Convert pydantic models to typescript interfaces", license="MIT", long_description=readme(), diff --git a/tests/expected_results/excluding_models/output.ts b/tests/expected_results/excluding_models/output.ts index 451bb73..af83361 100644 --- a/tests/expected_results/excluding_models/output.ts +++ b/tests/expected_results/excluding_models/output.ts @@ -7,6 +7,6 @@ export interface Profile { username: string; - age?: number; + age: number | null; hobbies: string[]; } From 5bac13237e3b5d00a43a67aa4ce38db3de6eed04 Mon Sep 17 00:00:00 2001 From: eg Date: Wed, 5 Jul 2023 16:03:09 +0200 Subject: [PATCH 4/7] Remove print --- pydantic2ts/cli/script.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pydantic2ts/cli/script.py b/pydantic2ts/cli/script.py index d53599e..826be9e 100644 --- a/pydantic2ts/cli/script.py +++ b/pydantic2ts/cli/script.py @@ -170,8 +170,6 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str: for d in schema.get("$defs", {}).values(): clean_schema(d) - print(json.dumps(schema, indent=2)) - return json.dumps(schema, indent=2) finally: From 86e0ab3084cbe889a5bb899241596880263d5c7c Mon Sep 17 00:00:00 2001 From: eg Date: Mon, 24 Jul 2023 13:14:28 +0200 Subject: [PATCH 5/7] Fix extra attribute => dict --- pydantic2ts/cli/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic2ts/cli/script.py b/pydantic2ts/cli/script.py index 826be9e..a4c4625 100644 --- a/pydantic2ts/cli/script.py +++ b/pydantic2ts/cli/script.py @@ -175,7 +175,7 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str: finally: for m, x in zip(models, model_extras): if x is not None: - m.model_config.extra = x + m.model_config["extra"] = x def generate_typescript_defs( From fde37c63c5660e2de99e14f4f3d2dcc19365b2ee Mon Sep 17 00:00:00 2001 From: eg Date: Mon, 24 Jul 2023 15:01:12 +0200 Subject: [PATCH 6/7] Add a workaround for tuples not producing the correct type --- pydantic2ts/cli/script.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pydantic2ts/cli/script.py b/pydantic2ts/cli/script.py index a4c4625..efa073f 100644 --- a/pydantic2ts/cli/script.py +++ b/pydantic2ts/cli/script.py @@ -137,6 +137,11 @@ def clean_schema(schema: Dict[str, Any]) -> None: for prop in schema.get("properties", {}).values(): prop.pop("title", None) + # Work around to produce Tuples just like Arrays since json2ts doesn't support the + # prefixItems json openAPI spec + if "prefixItems" in prop: + prop["items"] = prop["prefixItems"] + if "enum" in schema and schema.get("description") == "An enumeration.": del schema["description"] @@ -163,10 +168,12 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str: "_Master_", **{m.__name__: (m, ...) for m in models} ) master_model.model_config["extra"] = "forbid" - master_model.model_config["schema_extra"] = staticmethod(clean_schema) + master_model.model_config["json_schema_extra"] = staticmethod(clean_schema) schema = master_model.model_json_schema() + # prefixItems + for d in schema.get("$defs", {}).values(): clean_schema(d) @@ -281,3 +288,4 @@ def main() -> None: if __name__ == "__main__": main() + From 80539a2ff9fdf4216fc7578de4b8b0a6a67b58c4 Mon Sep 17 00:00:00 2001 From: eg Date: Wed, 4 Oct 2023 10:14:24 +0200 Subject: [PATCH 7/7] Fix Generic model warnings --- pydantic2ts/cli/script.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pydantic2ts/cli/script.py b/pydantic2ts/cli/script.py index efa073f..19a341f 100644 --- a/pydantic2ts/cli/script.py +++ b/pydantic2ts/cli/script.py @@ -14,10 +14,6 @@ from pydantic import BaseModel, Extra, create_model -try: - from pydantic.generics import GenericModel -except ImportError: - GenericModel = None logger = logging.getLogger("pydantic2ts") @@ -65,8 +61,9 @@ def is_concrete_pydantic_model(obj) -> bool: return False elif obj is BaseModel: return False - elif GenericModel and issubclass(obj, GenericModel): - return bool(obj.__concrete__) + # Generic model was removed + # elif GenericModel and issubclass(obj, GenericModel): + # return bool(obj.__concrete__) else: return issubclass(obj, BaseModel)