Skip to content

Commit 8e8f8ee

Browse files
committed
Support nullable keyword in properties. Closes #99
1 parent e123b96 commit 8e8f8ee

File tree

6 files changed

+172
-93
lines changed

6 files changed

+172
-93
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## 0.5.4 - Unreleased
99
### Additions
1010
- Added support for octet-stream content type (#116)
11+
- Support for [nullable](https://swagger.io/docs/specification/data-models/data-types/#null) (#99)
1112

1213

1314
## 0.5.3 - 2020-08-13

end_to_end_tests/fastapi_app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import date, datetime
44
from enum import Enum
55
from pathlib import Path
6-
from typing import Any, Dict, List, Union
6+
from typing import Dict, List, Union
77

88
from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
99
from pydantic import BaseModel

openapi_python_client/parser/openapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ def build(*, schemas: Dict[str, Union[oai.Reference, oai.Schema]]) -> Schemas:
273273
required=True,
274274
default=data.default,
275275
values=EnumProperty.values_from_list(data.enum),
276+
nullable=data.nullable,
276277
)
277278
continue
278279
s = Model.from_data(data=data, name=name)

openapi_python_client/parser/properties.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Property:
2727

2828
name: str
2929
required: bool
30+
nullable: bool
3031
default: Optional[Any]
3132

3233
template: ClassVar[Optional[str]] = None
@@ -44,8 +45,13 @@ def _validate_default(self, default: Any) -> Any:
4445
raise ValidationError
4546

4647
def get_type_string(self, no_optional: bool = False) -> str:
47-
""" Get a string representation of type that should be used when declaring this property """
48-
if self.required or no_optional:
48+
"""
49+
Get a string representation of type that should be used when declaring this property
50+
51+
Args:
52+
no_optional: Do not include Optional even if the value is optional (needed for isinstance checks)
53+
"""
54+
if no_optional or (self.required and not self.nullable):
4955
return self._type_string
5056
return f"Optional[{self._type_string}]"
5157

@@ -212,7 +218,7 @@ class ListProperty(Property, Generic[InnerProp]):
212218

213219
def get_type_string(self, no_optional: bool = False) -> str:
214220
""" Get a string representation of type that should be used when declaring this property """
215-
if self.required or no_optional:
221+
if no_optional or (self.required and not self.nullable):
216222
return f"List[{self.inner_property.get_type_string()}]"
217223
return f"Optional[List[{self.inner_property.get_type_string()}]]"
218224

@@ -253,7 +259,7 @@ def get_type_string(self, no_optional: bool = False) -> str:
253259
""" Get a string representation of type that should be used when declaring this property """
254260
inner_types = [p.get_type_string() for p in self.inner_properties]
255261
inner_prop_string = ", ".join(inner_types)
256-
if self.required or no_optional:
262+
if no_optional or (self.required and not self.nullable):
257263
return f"Union[{inner_prop_string}]"
258264
return f"Optional[Union[{inner_prop_string}]]"
259265

@@ -320,7 +326,7 @@ def get_enum(name: str) -> Optional[EnumProperty]:
320326
def get_type_string(self, no_optional: bool = False) -> str:
321327
""" Get a string representation of type that should be used when declaring this property """
322328

323-
if self.required or no_optional:
329+
if no_optional or (self.required and not self.nullable):
324330
return self.reference.class_name
325331
return f"Optional[{self.reference.class_name}]"
326332

@@ -375,7 +381,7 @@ def template(self) -> str: # type: ignore
375381

376382
def get_type_string(self, no_optional: bool = False) -> str:
377383
""" Get a string representation of type that should be used when declaring this property """
378-
if self.required or no_optional:
384+
if no_optional or (self.required and not self.nullable):
379385
return self.reference.class_name
380386
return f"Optional[{self.reference.class_name}]"
381387

@@ -437,13 +443,15 @@ def _string_based_property(
437443
""" Construct a Property from the type "string" """
438444
string_format = data.schema_format
439445
if string_format == "date-time":
440-
return DateTimeProperty(name=name, required=required, default=data.default)
446+
return DateTimeProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
441447
elif string_format == "date":
442-
return DateProperty(name=name, required=required, default=data.default)
448+
return DateProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
443449
elif string_format == "binary":
444-
return FileProperty(name=name, required=required, default=data.default)
450+
return FileProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
445451
else:
446-
return StringProperty(name=name, default=data.default, required=required, pattern=data.pattern)
452+
return StringProperty(
453+
name=name, default=data.default, required=required, pattern=data.pattern, nullable=data.nullable,
454+
)
447455

448456

449457
def _property_from_data(
@@ -452,14 +460,17 @@ def _property_from_data(
452460
""" Generate a Property from the OpenAPI dictionary representation of it """
453461
name = utils.remove_string_escapes(name)
454462
if isinstance(data, oai.Reference):
455-
return RefProperty(name=name, required=required, reference=Reference.from_ref(data.ref), default=None)
463+
return RefProperty(
464+
name=name, required=required, reference=Reference.from_ref(data.ref), default=None, nullable=False,
465+
)
456466
if data.enum:
457467
return EnumProperty(
458468
name=name,
459469
required=required,
460470
values=EnumProperty.values_from_list(data.enum),
461471
title=data.title or name,
462472
default=data.default,
473+
nullable=data.nullable,
463474
)
464475
if data.anyOf:
465476
sub_properties: List[Property] = []
@@ -468,26 +479,30 @@ def _property_from_data(
468479
if isinstance(sub_prop, PropertyError):
469480
return PropertyError(detail=f"Invalid property in union {name}", data=sub_prop_data)
470481
sub_properties.append(sub_prop)
471-
return UnionProperty(name=name, required=required, default=data.default, inner_properties=sub_properties)
482+
return UnionProperty(
483+
name=name, required=required, default=data.default, inner_properties=sub_properties, nullable=data.nullable,
484+
)
472485
if not data.type:
473486
return PropertyError(data=data, detail="Schemas must either have one of enum, anyOf, or type defined.")
474487
if data.type == "string":
475488
return _string_based_property(name=name, required=required, data=data)
476489
elif data.type == "number":
477-
return FloatProperty(name=name, default=data.default, required=required)
490+
return FloatProperty(name=name, default=data.default, required=required, nullable=data.nullable,)
478491
elif data.type == "integer":
479-
return IntProperty(name=name, default=data.default, required=required)
492+
return IntProperty(name=name, default=data.default, required=required, nullable=data.nullable,)
480493
elif data.type == "boolean":
481-
return BooleanProperty(name=name, required=required, default=data.default)
494+
return BooleanProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
482495
elif data.type == "array":
483496
if data.items is None:
484497
return PropertyError(data=data, detail="type array must have items defined")
485498
inner_prop = property_from_data(name=f"{name}_item", required=True, data=data.items)
486499
if isinstance(inner_prop, PropertyError):
487500
return PropertyError(data=inner_prop.data, detail=f"invalid data in items of array {name}")
488-
return ListProperty(name=name, required=required, default=data.default, inner_property=inner_prop,)
501+
return ListProperty(
502+
name=name, required=required, default=data.default, inner_property=inner_prop, nullable=data.nullable,
503+
)
489504
elif data.type == "object":
490-
return DictProperty(name=name, required=required, default=data.default)
505+
return DictProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
491506
return PropertyError(data=data, detail=f"unknown type {data.type}")
492507

493508

openapi_python_client/schema/schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ class Schema(BaseModel):
406406
Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation:
407407
"""
408408

409-
nullable: Optional[bool] = None
409+
nullable: bool = False
410410
"""
411411
A `true` value adds `"null"` to the allowed type specified by the `type` keyword,
412412
only if `type` is explicitly defined within the same Schema Object.

0 commit comments

Comments
 (0)