Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 0.5.4 - Unreleased
### Additions
- Added support for octet-stream content type (#116)
- Support for [nullable](https://swagger.io/docs/specification/data-models/data-types/#null) (#99)
- Union properties defined using oneOf (#98)


## 0.5.3 - 2020-08-13
### Security
- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)
Expand Down
2 changes: 1 addition & 1 deletion end_to_end_tests/fastapi_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import date, datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Union
from typing import Dict, List, Union

from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
from pydantic import BaseModel
Expand Down
1 change: 1 addition & 0 deletions openapi_python_client/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ def build(*, schemas: Dict[str, Union[oai.Reference, oai.Schema]]) -> Schemas:
required=True,
default=data.default,
values=EnumProperty.values_from_list(data.enum),
nullable=data.nullable,
)
continue
s = Model.from_data(data=data, name=name)
Expand Down
49 changes: 32 additions & 17 deletions openapi_python_client/parser/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Property:

name: str
required: bool
nullable: bool
default: Optional[Any]

template: ClassVar[Optional[str]] = None
Expand All @@ -45,8 +46,13 @@ def _validate_default(self, default: Any) -> Any:
raise ValidationError

def get_type_string(self, no_optional: bool = False) -> str:
""" Get a string representation of type that should be used when declaring this property """
if self.required or no_optional:
"""
Get a string representation of type that should be used when declaring this property

Args:
no_optional: Do not include Optional even if the value is optional (needed for isinstance checks)
"""
if no_optional or (self.required and not self.nullable):
return self._type_string
return f"Optional[{self._type_string}]"

Expand Down Expand Up @@ -213,7 +219,7 @@ class ListProperty(Property, Generic[InnerProp]):

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

Expand Down Expand Up @@ -254,7 +260,7 @@ def get_type_string(self, no_optional: bool = False) -> str:
""" Get a string representation of type that should be used when declaring this property """
inner_types = [p.get_type_string() for p in self.inner_properties]
inner_prop_string = ", ".join(inner_types)
if self.required or no_optional:
if no_optional or (self.required and not self.nullable):
return f"Union[{inner_prop_string}]"
return f"Optional[Union[{inner_prop_string}]]"

Expand Down Expand Up @@ -321,7 +327,7 @@ def get_enum(name: str) -> Optional[EnumProperty]:
def get_type_string(self, no_optional: bool = False) -> str:
""" Get a string representation of type that should be used when declaring this property """

if self.required or no_optional:
if no_optional or (self.required and not self.nullable):
return self.reference.class_name
return f"Optional[{self.reference.class_name}]"

Expand Down Expand Up @@ -376,7 +382,7 @@ def template(self) -> str: # type: ignore

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

Expand Down Expand Up @@ -438,13 +444,15 @@ def _string_based_property(
""" Construct a Property from the type "string" """
string_format = data.schema_format
if string_format == "date-time":
return DateTimeProperty(name=name, required=required, default=data.default)
return DateTimeProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
elif string_format == "date":
return DateProperty(name=name, required=required, default=data.default)
return DateProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
elif string_format == "binary":
return FileProperty(name=name, required=required, default=data.default)
return FileProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
else:
return StringProperty(name=name, default=data.default, required=required, pattern=data.pattern)
return StringProperty(
name=name, default=data.default, required=required, pattern=data.pattern, nullable=data.nullable,
)


def _property_from_data(
Expand All @@ -453,14 +461,17 @@ def _property_from_data(
""" Generate a Property from the OpenAPI dictionary representation of it """
name = utils.remove_string_escapes(name)
if isinstance(data, oai.Reference):
return RefProperty(name=name, required=required, reference=Reference.from_ref(data.ref), default=None)
return RefProperty(
name=name, required=required, reference=Reference.from_ref(data.ref), default=None, nullable=False,
)
if data.enum:
return EnumProperty(
name=name,
required=required,
values=EnumProperty.values_from_list(data.enum),
title=data.title or name,
default=data.default,
nullable=data.nullable,
)
if data.anyOf or data.oneOf:
sub_properties: List[Property] = []
Expand All @@ -469,26 +480,30 @@ def _property_from_data(
if isinstance(sub_prop, PropertyError):
return PropertyError(detail=f"Invalid property in union {name}", data=sub_prop_data)
sub_properties.append(sub_prop)
return UnionProperty(name=name, required=required, default=data.default, inner_properties=sub_properties)
return UnionProperty(
name=name, required=required, default=data.default, inner_properties=sub_properties, nullable=data.nullable,
)
if not data.type:
return PropertyError(data=data, detail="Schemas must either have one of enum, anyOf, or type defined.")
if data.type == "string":
return _string_based_property(name=name, required=required, data=data)
elif data.type == "number":
return FloatProperty(name=name, default=data.default, required=required)
return FloatProperty(name=name, default=data.default, required=required, nullable=data.nullable,)
elif data.type == "integer":
return IntProperty(name=name, default=data.default, required=required)
return IntProperty(name=name, default=data.default, required=required, nullable=data.nullable,)
elif data.type == "boolean":
return BooleanProperty(name=name, required=required, default=data.default)
return BooleanProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
elif data.type == "array":
if data.items is None:
return PropertyError(data=data, detail="type array must have items defined")
inner_prop = property_from_data(name=f"{name}_item", required=True, data=data.items)
if isinstance(inner_prop, PropertyError):
return PropertyError(data=inner_prop.data, detail=f"invalid data in items of array {name}")
return ListProperty(name=name, required=required, default=data.default, inner_property=inner_prop,)
return ListProperty(
name=name, required=required, default=data.default, inner_property=inner_prop, nullable=data.nullable,
)
elif data.type == "object":
return DictProperty(name=name, required=required, default=data.default)
return DictProperty(name=name, required=required, default=data.default, nullable=data.nullable,)
return PropertyError(data=data, detail=f"unknown type {data.type}")


Expand Down
2 changes: 1 addition & 1 deletion openapi_python_client/schema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ class Schema(BaseModel):
Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation:
"""

nullable: Optional[bool] = None
nullable: bool = False
"""
A `true` value adds `"null"` to the allowed type specified by the `type` keyword,
only if `type` is explicitly defined within the same Schema Object.
Expand Down
Loading