Skip to content

Commit 06e8007

Browse files
authored
Merge pull request #79 from grktsh/string-format
Support unmarshaling string with format keyword
2 parents 2487d0e + d4f65a2 commit 06e8007

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-3
lines changed

openapi_core/schema/schemas/models.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
from six import iteritems
77

88
from openapi_core.extensions.models.factories import ModelFactory
9-
from openapi_core.schema.schemas.enums import SchemaType
9+
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
1010
from openapi_core.schema.schemas.exceptions import (
1111
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
1212
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
1313
)
14-
from openapi_core.schema.schemas.util import forcebool
14+
from openapi_core.schema.schemas.util import forcebool, format_date
1515

1616
log = logging.getLogger(__name__)
1717

@@ -25,6 +25,10 @@ class Schema(object):
2525
SchemaType.BOOLEAN: forcebool,
2626
}
2727

28+
FORMAT_CALLABLE_GETTER = defaultdict(lambda: lambda x: x, {
29+
SchemaFormat.DATE.value: format_date,
30+
})
31+
2832
def __init__(
2933
self, schema_type=None, model=None, properties=None, items=None,
3034
schema_format=None, required=None, default=None, nullable=False,
@@ -92,6 +96,7 @@ def get_all_required_properties_names(self):
9296
def get_cast_mapping(self):
9397
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
9498
mapping.update({
99+
SchemaType.STRING: self._unmarshal_string,
95100
SchemaType.ARRAY: self._unmarshal_collection,
96101
SchemaType.OBJECT: self._unmarshal_object,
97102
})
@@ -110,7 +115,7 @@ def cast(self, value):
110115

111116
cast_mapping = self.get_cast_mapping()
112117

113-
if self.type in cast_mapping and value == '':
118+
if self.type is not SchemaType.STRING and value == '':
114119
return None
115120

116121
cast_callable = cast_mapping[self.type]
@@ -139,6 +144,16 @@ def unmarshal(self, value):
139144

140145
return casted
141146

147+
def _unmarshal_string(self, value):
148+
formatter = self.FORMAT_CALLABLE_GETTER[self.format]
149+
try:
150+
return formatter(value)
151+
except ValueError:
152+
raise InvalidSchemaValue(
153+
"Failed to format value of {0} to {1}".format(
154+
value, self.format)
155+
)
156+
142157
def _unmarshal_collection(self, value):
143158
return list(map(self.items.unmarshal, value))
144159

openapi_core/schema/schemas/util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""OpenAPI core schemas util module"""
2+
import datetime
23
from distutils.util import strtobool
34
from json import dumps
45
from six import string_types
@@ -13,3 +14,7 @@ def forcebool(val):
1314

1415
def dicthash(d):
1516
return hash(dumps(d, sort_keys=True))
17+
18+
19+
def format_date(value):
20+
return datetime.datetime.strptime(value, '%Y-%m-%d').date()

tests/unit/schema/test_schemas.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import datetime
2+
13
import mock
24
import pytest
35

@@ -64,6 +66,49 @@ def test_string_default_nullable(self):
6466

6567
assert result == default_value
6668

69+
def test_string_format_date(self):
70+
schema = Schema('string', schema_format='date')
71+
value = '2018-01-02'
72+
73+
result = schema.unmarshal(value)
74+
75+
assert result == datetime.date(2018, 1, 2)
76+
77+
def test_string_format_custom(self):
78+
custom_format = 'custom'
79+
schema = Schema('string', schema_format=custom_format)
80+
value = 'x'
81+
82+
with mock.patch.dict(
83+
Schema.FORMAT_CALLABLE_GETTER,
84+
{custom_format: lambda x: x + '-custom'},
85+
):
86+
result = schema.unmarshal(value)
87+
88+
assert result == 'x-custom'
89+
90+
def test_string_format_unknown(self):
91+
unknown_format = 'unknown'
92+
schema = Schema('string', schema_format=unknown_format)
93+
value = 'x'
94+
95+
result = schema.unmarshal(value)
96+
97+
assert result == 'x'
98+
99+
def test_string_format_invalid_value(self):
100+
custom_format = 'custom'
101+
schema = Schema('string', schema_format=custom_format)
102+
value = 'x'
103+
104+
with mock.patch.dict(
105+
Schema.FORMAT_CALLABLE_GETTER,
106+
{custom_format: mock.Mock(side_effect=ValueError())},
107+
), pytest.raises(
108+
InvalidSchemaValue, message='Failed to format value'
109+
):
110+
schema.unmarshal(value)
111+
67112
def test_integer_valid(self):
68113
schema = Schema('integer')
69114
value = '123'

0 commit comments

Comments
 (0)