99from importlib .util import module_from_spec , spec_from_file_location
1010from tempfile import mkdtemp
1111from types import ModuleType
12- from typing import Any , Dict , List , Tuple , Type
12+ from typing import Any , Dict , List , Tuple , Type , Optional
1313from uuid import uuid4
1414
1515from pydantic import BaseModel , Extra , create_model
16+ from pydantic .fields import ModelField
1617
1718try :
1819 from pydantic .generics import GenericModel
@@ -141,7 +142,7 @@ def clean_schema(schema: Dict[str, Any]) -> None:
141142 del schema ["description" ]
142143
143144
144- def generate_json_schema (models : List [Type [BaseModel ]]) -> str :
145+ def generate_json_schema (models : List [Type [BaseModel ]], readonly_interfaces : bool ) -> str :
145146 """
146147 Create a top-level '_Master_' model with references to each of the actual models.
147148 Generate the schema for this model, which will include the schemas for all the
@@ -152,6 +153,13 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
152153 '[k: string]: any' from being added to every interface. This change is reverted
153154 once the schema has been generated.
154155 """
156+
157+ def find_model (name : str ) -> Optional [Type [BaseModel ]]:
158+ return next ((m for m in models if m .__name__ == name ), None )
159+
160+ def find_field (prop : str , model_ : Type [BaseModel ]) -> ModelField :
161+ return next (f for f in model_ .__fields__ .values () if f .alias == prop )
162+
155163 model_extras = [getattr (m .Config , "extra" , None ) for m in models ]
156164
157165 try :
@@ -170,6 +178,12 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
170178 for d in schema .get ("definitions" , {}).values ():
171179 clean_schema (d )
172180
181+ if readonly_interfaces :
182+ model = find_model (d ["title" ])
183+ if model is not None :
184+ props = d .get ("properties" , {}).keys ()
185+ d ["required" ] = list (prop for prop in props if not find_field (prop , model ).allow_none )
186+
173187 return json .dumps (schema , indent = 2 )
174188
175189 finally :
@@ -179,7 +193,7 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
179193
180194
181195def generate_typescript_defs (
182- module : str , output : str , exclude : Tuple [str ] = (), json2ts_cmd : str = "json2ts"
196+ module : str , output : str , exclude : Tuple [str ] = (), readonly_interfaces : bool = False , json2ts_cmd : str = "json2ts" ,
183197) -> None :
184198 """
185199 Convert the pydantic models in a python module into typescript interfaces.
@@ -189,6 +203,8 @@ def generate_typescript_defs(
189203 :param exclude: optional, a tuple of names for pydantic models which should be omitted from the typescript output.
190204 :param json2ts_cmd: optional, the command that will execute json2ts. Provide this if the executable is not
191205 discoverable or if it's locally installed (ex: 'yarn json2ts').
206+ :param readonly_interfaces: optional, do not mark non-optional properties with default values as optional
207+ in the generated interfaces.
192208 """
193209 if " " not in json2ts_cmd and not shutil .which (json2ts_cmd ):
194210 raise Exception (
@@ -205,7 +221,7 @@ def generate_typescript_defs(
205221
206222 logger .info ("Generating JSON schema from pydantic models..." )
207223
208- schema = generate_json_schema (models )
224+ schema = generate_json_schema (models , readonly_interfaces )
209225 schema_dir = mkdtemp ()
210226 schema_file_path = os .path .join (schema_dir , "schema.json" )
211227
@@ -256,6 +272,14 @@ def parse_cli_args(args: List[str] = None) -> argparse.Namespace:
256272 "This option can be defined multiple times,\n "
257273 "ex: `--exclude Foo --exclude Bar` to exclude both the Foo and Bar models from the output." ,
258274 )
275+ parser .add_argument (
276+ "--readonly-interfaces" ,
277+ dest = "readonly_interfaces" ,
278+ action = "store_true" ,
279+ help = "do not mark non-optional properties with default values as optional in the generated interfaces.\n "
280+ "This is useful if you want an interface for data that is returned by an API (default values are not empty),\n "
281+ "in contrast to an interface for data that is sent to an API (default values may be empty)." ,
282+ )
259283 parser .add_argument (
260284 "--json2ts-cmd" ,
261285 dest = "json2ts_cmd" ,
@@ -277,8 +301,9 @@ def main() -> None:
277301 return generate_typescript_defs (
278302 args .module ,
279303 args .output ,
280- tuple (args .exclude ),
281- args .json2ts_cmd ,
304+ exclude = tuple (args .exclude ),
305+ readonly_interfaces = args .readonly_interfaces ,
306+ json2ts_cmd = args .json2ts_cmd ,
282307 )
283308
284309
0 commit comments