9
9
from importlib .util import module_from_spec , spec_from_file_location
10
10
from tempfile import mkdtemp
11
11
from types import ModuleType
12
- from typing import Any , Dict , List , Tuple , Type
12
+ from typing import Any , Dict , List , Tuple , Type , Optional
13
13
from uuid import uuid4
14
14
15
15
from pydantic import BaseModel , Extra , create_model
16
+ from pydantic .fields import ModelField
16
17
17
18
try :
18
19
from pydantic .generics import GenericModel
@@ -141,7 +142,7 @@ def clean_schema(schema: Dict[str, Any]) -> None:
141
142
del schema ["description" ]
142
143
143
144
144
- def generate_json_schema (models : List [Type [BaseModel ]]) -> str :
145
+ def generate_json_schema (models : List [Type [BaseModel ]], readonly_interfaces : bool ) -> str :
145
146
"""
146
147
Create a top-level '_Master_' model with references to each of the actual models.
147
148
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:
152
153
'[k: string]: any' from being added to every interface. This change is reverted
153
154
once the schema has been generated.
154
155
"""
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
+
155
163
model_extras = [getattr (m .Config , "extra" , None ) for m in models ]
156
164
157
165
try :
@@ -170,6 +178,12 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
170
178
for d in schema .get ("definitions" , {}).values ():
171
179
clean_schema (d )
172
180
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
+
173
187
return json .dumps (schema , indent = 2 )
174
188
175
189
finally :
@@ -179,7 +193,7 @@ def generate_json_schema(models: List[Type[BaseModel]]) -> str:
179
193
180
194
181
195
def 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" ,
183
197
) -> None :
184
198
"""
185
199
Convert the pydantic models in a python module into typescript interfaces.
@@ -189,6 +203,8 @@ def generate_typescript_defs(
189
203
:param exclude: optional, a tuple of names for pydantic models which should be omitted from the typescript output.
190
204
:param json2ts_cmd: optional, the command that will execute json2ts. Provide this if the executable is not
191
205
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.
192
208
"""
193
209
if " " not in json2ts_cmd and not shutil .which (json2ts_cmd ):
194
210
raise Exception (
@@ -205,7 +221,7 @@ def generate_typescript_defs(
205
221
206
222
logger .info ("Generating JSON schema from pydantic models..." )
207
223
208
- schema = generate_json_schema (models )
224
+ schema = generate_json_schema (models , readonly_interfaces )
209
225
schema_dir = mkdtemp ()
210
226
schema_file_path = os .path .join (schema_dir , "schema.json" )
211
227
@@ -256,6 +272,14 @@ def parse_cli_args(args: List[str] = None) -> argparse.Namespace:
256
272
"This option can be defined multiple times,\n "
257
273
"ex: `--exclude Foo --exclude Bar` to exclude both the Foo and Bar models from the output." ,
258
274
)
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
+ )
259
283
parser .add_argument (
260
284
"--json2ts-cmd" ,
261
285
dest = "json2ts_cmd" ,
@@ -277,8 +301,9 @@ def main() -> None:
277
301
return generate_typescript_defs (
278
302
args .module ,
279
303
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 ,
282
307
)
283
308
284
309
0 commit comments