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 CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Version 0.2.0

To be released.

- Made parameters having an optional type possible to be omitted. [`#205`_]
- Added method dispatching by querystring pattern
e.g., ``@http-resource(method="GET", path="/users?from={from}&to={to}")``.
[`#130`_]
Expand All @@ -21,6 +22,7 @@ To be released.
- ``WsgiApp.url_map`` attribute was gone.
- ``/ping/`` resource was gone.

.. _#205: https://github.com/spoqa/nirum/issues/205
.. _#130: https://github.com/spoqa/nirum/issues/130
.. _CORS: https://www.w3.org/TR/cors/

Expand Down
26 changes: 17 additions & 9 deletions nirum_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import re
import typing

from nirum._compat import get_union_types, is_union_type
from nirum.datastructures import List
from nirum.deserialize import deserialize_meta
from nirum.exc import (NirumProcedureArgumentRequiredError,
Expand All @@ -30,7 +31,7 @@
'PathMatch', 'ServiceMethodError',
'UriTemplateMatchResult', 'UriTemplateMatcher',
'WsgiApp',
'match_request', 'parse_json_payload',
'is_optional_type', 'match_request', 'parse_json_payload',
)
MethodDispatch = collections.namedtuple('MethodDispatch', [
'request', 'routed', 'service_method',
Expand All @@ -44,6 +45,20 @@
])


def is_optional_type(type_):
# it have to be removed after nirum._compat.is_optional_type added.
return is_union_type(type_) and type(None) in get_union_types(type_)


def _get_argument_value(payload, key, type_):
if key in payload or is_optional_type(type_):
return payload.get(key)
else:
raise NirumProcedureArgumentRequiredError(
"A argument named '{}' is missing, it is required.".format(key)
)


def match_request(rules, request_method, path_info, querystring):
# Ignore root path.
if path_info == '/':
Expand Down Expand Up @@ -395,14 +410,7 @@ def _parse_procedure_arguments(self, type_hints, request_json):
if version >= 2:
type_ = type_()
behind_name = name_map[argument_name]
try:
data = request_json[behind_name]
except KeyError:
raise NirumProcedureArgumentRequiredError(
"A argument named '{}' is missing, it is required.".format(
behind_name
)
)
data = _get_argument_value(request_json, behind_name, type_=type_)
try:
arguments[argument_name] = deserialize_meta(type_, data)
except ValueError:
Expand Down
6 changes: 6 additions & 0 deletions schema-fixture/fixture.nrm
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ service statistics-service (
path="/statistics/purchases/?from={from}&to={to}&interval={interval}"
)
[int64] purchase-interval (date from, date to, bigint interval),

@http-resource(
method="GET",
path="/statistics/daily-purchases/?ez={exclude}"
)
[int64] daily-purchase (bool? exclude),
);

unboxed token (uuid);
Expand Down
27 changes: 27 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ def purchase_count(self, from_, to):
def purchase_interval(self, from_, to, interval):
return list(range(int(interval)))

def daily_purchase(self, exclude):
if exclude is None:
return [1]
elif exclude:
return [1, 2]
else:
return [1, 2, 3]


@fixture
def fx_music_wsgi():
Expand Down Expand Up @@ -519,3 +527,22 @@ def test_resolve_querystring(qs, expected):
typing.Sequence[int], json.loads(response.get_data(as_text=True))
)
assert return_result == expected


@mark.parametrize('payload, expected', [
({'exclude': False}, [1, 2, 3]),
({'exclude': True}, [1, 2]),
({'exclude': None}, [1]),
({}, [1]),
])
def test_omit_optional_parameter(payload, expected):
app = WsgiApp(StatisticsServiceImpl())
client = Client(app, Response)
response = client.post(
'/?method=daily_purchase',
data=json.dumps(payload),
content_type='application/json'
)
assert response.status_code == 200, response.get_data(as_text=True)
actual = json.loads(response.get_data(as_text=True))
assert actual == expected