Parameter with required and allow empty value support

This commit is contained in:
Artur Maciag 2017-10-18 14:42:10 +01:00
parent 279ace9ed0
commit a86c49d1be
7 changed files with 88 additions and 20 deletions

View file

@ -33,5 +33,9 @@ class InvalidValue(OpenAPIMappingError):
pass pass
class EmptyValue(OpenAPIMappingError):
pass
class UndefinedSchemaProperty(OpenAPIMappingError): class UndefinedSchemaProperty(OpenAPIMappingError):
pass pass

View file

@ -1,5 +1,8 @@
"""OpenAPI core parameters module""" """OpenAPI core parameters module"""
import logging import logging
import warnings
from openapi_core.exceptions import EmptyValue
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -8,20 +11,32 @@ class Parameter(object):
"""Represents an OpenAPI operation Parameter.""" """Represents an OpenAPI operation Parameter."""
def __init__( def __init__(
self, name, location, schema=None, default=None, self, name, location, schema=None, required=False,
required=False, deprecated=False, allow_empty_value=False, deprecated=False, allow_empty_value=False,
items=None, collection_format=None): items=None, collection_format=None):
self.name = name self.name = name
self.location = location self.location = location
self.schema = schema self.schema = schema
self.default = default self.required = True if self.location == "path" else required
self.required = required
self.deprecated = deprecated self.deprecated = deprecated
self.allow_empty_value = allow_empty_value self.allow_empty_value = (
allow_empty_value if self.location == "query" else False
)
self.items = items self.items = items
self.collection_format = collection_format self.collection_format = collection_format
def unmarshal(self, value): def unmarshal(self, value):
if self.deprecated:
warnings.warn(
"{0} parameter is deprecated".format(self.name),
DeprecationWarning,
)
if (self.location == "query" and value == "" and
not self.allow_empty_value):
raise EmptyValue(
"Value of {0} parameter cannot be empty.".format(self.name))
if not self.schema: if not self.schema:
return value return value
@ -38,7 +53,7 @@ class ParametersGenerator(object):
for parameter in paramters: for parameter in paramters:
parameter_deref = self.dereferencer.dereference(parameter) parameter_deref = self.dereferencer.dereference(parameter)
default = parameter_deref.get('default') allow_empty_value = parameter_deref.get('allowEmptyValue')
required = parameter_deref.get('required', False) required = parameter_deref.get('required', False)
schema_spec = parameter_deref.get('schema', None) schema_spec = parameter_deref.get('schema', None)
@ -50,6 +65,7 @@ class ParametersGenerator(object):
parameter_deref['name'], parameter_deref['name'],
Parameter( Parameter(
parameter_deref['name'], parameter_deref['in'], parameter_deref['name'], parameter_deref['in'],
schema=schema, default=default, required=required, schema=schema, required=required,
allow_empty_value=allow_empty_value,
), ),
) )

View file

@ -149,6 +149,7 @@ class SchemaFactory(object):
schema_type = schema_deref['type'] schema_type = schema_deref['type']
model = schema_deref.get('x-model', None) model = schema_deref.get('x-model', None)
required = schema_deref.get('required', False) required = schema_deref.get('required', False)
default = schema_deref.get('default', None)
properties_spec = schema_deref.get('properties', None) properties_spec = schema_deref.get('properties', None)
items_spec = schema_deref.get('items', None) items_spec = schema_deref.get('items', None)
nullable = schema_deref.get('nullable', False) nullable = schema_deref.get('nullable', False)
@ -165,7 +166,7 @@ class SchemaFactory(object):
return Schema( return Schema(
schema_type, model=model, properties=properties, items=items, schema_type, model=model, properties=properties, items=items,
required=required, nullable=nullable, enum=enum, required=required, default=default, nullable=nullable, enum=enum,
deprecated=deprecated, deprecated=deprecated,
) )

View file

@ -64,27 +64,30 @@ class RequestParametersFactory(BaseRequestFactory):
params = RequestParameters() params = RequestParameters()
for param_name, param in iteritems(operation.parameters): for param_name, param in iteritems(operation.parameters):
try: try:
value = self._unmarshal_param(request, param) raw_value = self._get_raw_value(request, param)
except MissingParameterError: except MissingParameterError:
if param.required: if param.required:
raise raise
continue
if not param.schema or not param.schema.default:
continue
raw_value = param.schema.default
value = param.unmarshal(raw_value)
params[param.location][param_name] = value params[param.location][param_name] = value
return params return params
def _unmarshal_param(self, request, param): def _get_raw_value(self, request, param):
request_location = self.attr_mapping[param.location] request_location = self.attr_mapping[param.location]
request_attr = getattr(request, request_location) request_attr = getattr(request, request_location)
try: try:
raw_value = request_attr[param.name] return request_attr[param.name]
except KeyError: except KeyError:
raise MissingParameterError( raise MissingParameterError(
"Missing required `{0}` parameter".format(param.name)) "Missing required `{0}` parameter".format(param.name))
return param.unmarshal(raw_value)
class RequestBodyFactory(BaseRequestFactory): class RequestBodyFactory(BaseRequestFactory):

View file

@ -20,6 +20,12 @@ paths:
tags: tags:
- pets - pets
parameters: parameters:
- name: page
in: query
schema:
type: integer
format: int32
default: 1
- name: limit - name: limit
in: query in: query
description: How many items to return at one time (max 100) description: How many items to return at one time (max 100)

View file

@ -5,6 +5,7 @@ from six import iteritems
from openapi_core.exceptions import ( from openapi_core.exceptions import (
MissingParameterError, InvalidContentTypeError, InvalidServerError, MissingParameterError, InvalidContentTypeError, InvalidServerError,
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError, InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
EmptyValue,
) )
from openapi_core.media_types import MediaType from openapi_core.media_types import MediaType
from openapi_core.operations import Operation from openapi_core.operations import Operation
@ -135,6 +136,7 @@ class TestPetstore(object):
assert parameters == { assert parameters == {
'query': { 'query': {
'limit': 20, 'limit': 20,
'page': 1,
'ids': [12, 13], 'ids': [12, 13],
} }
} }
@ -186,14 +188,10 @@ class TestPetstore(object):
path_pattern=path_pattern, args=query_params, path_pattern=path_pattern, args=query_params,
) )
parameters = request.get_parameters(spec) with pytest.raises(EmptyValue):
request.get_parameters(spec)
body = request.get_body(spec) body = request.get_body(spec)
assert parameters == {
'query': {
'limit': None,
}
}
assert body is None assert body is None
def test_get_pets_none_value(self, spec): def test_get_pets_none_value(self, spec):
@ -213,6 +211,7 @@ class TestPetstore(object):
assert parameters == { assert parameters == {
'query': { 'query': {
'limit': None, 'limit': None,
'page': 1,
} }
} }

View file

@ -0,0 +1,39 @@
import pytest
from openapi_core.exceptions import EmptyValue
from openapi_core.parameters import Parameter
class TestParameterUnmarshal(object):
def test_deprecated(self):
param = Parameter('param', 'query', deprecated=True)
value = 'test'
with pytest.warns(DeprecationWarning):
result = param.unmarshal(value)
assert result == value
def test_query_valid(self):
param = Parameter('param', 'query')
value = 'test'
result = param.unmarshal(value)
assert result == value
def test_query_empty(self):
param = Parameter('param', 'query')
value = ''
with pytest.raises(EmptyValue):
param.unmarshal(value)
def test_query_allow_empty_value(self):
param = Parameter('param', 'query', allow_empty_value=True)
value = ''
result = param.unmarshal(value)
assert result == value