Skip to content

Commit

Permalink
RPC runtime for python #9 (#10)
Browse files Browse the repository at this point in the history
* Implement RPC runtime for python #9

- Implement Service, WsgiApp to create nirum rpc service
  - Add nirum.rpc.WsgiApp, nirum.rpc.Service
  - Return appropriate error message / success response
  - Support /ping/ by default
- Fix deserializers
  - deserialize by class not from given data
  - deserialize generic type(eg. typing.Sequence) as python type
  - deserialize primitive type

* Fix deserialize_tuple_type, reviews

* Divide rpc requirements into service_requires

* Parse datetime by using iso8601

* All name can have behind name

* Reveal behind name in error message
  • Loading branch information
kanghyojun authored Aug 4, 2016
1 parent bc1fb46 commit df4c06c
Show file tree
Hide file tree
Showing 6 changed files with 881 additions and 10 deletions.
137 changes: 130 additions & 7 deletions nirum/deserialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,144 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import datetime
import decimal
import typing
import uuid

from iso8601 import iso8601, parse_date

__all__ = (
'deserialize_boxed_type', 'deserialize_meta',
'deserialize_record_type', 'deserialize_union_type',
'deserialize_abstract_type',
'deserialize_boxed_type',
'deserialize_iterable_abstract_type',
'deserialize_meta',
'deserialize_primitive',
'deserialize_record_type',
'deserialize_tuple_type',
'deserialize_union_type',
'is_support_abstract_type',
)
_NIRUM_PRIMITIVE_TYPE = {
str, float, decimal.Decimal, uuid.UUID, datetime.datetime,
datetime.date, bool, int
}


def deserialize_meta(cls, data):
if not isinstance(data, dict):
def is_support_abstract_type(t):
"""FIXME: 3.5 only"""
if hasattr(t, '__origin__') and t.__origin__:
data_type = t.__origin__
else:
data_type = t
abstract_types = {
typing.Sequence,
typing.List,
typing.Set,
typing.Mapping,
typing.Dict,
}
return any(type_ is data_type for type_ in abstract_types)


def deserialize_iterable_abstract_type(cls, cls_origin_type, data):
abstract_type_map = {
typing.Sequence: list,
typing.List: list,
typing.Set: set,
}
deserialized_data = data
cls_primitive_type = abstract_type_map[cls_origin_type]
if isinstance(cls.__parameters__[0], typing.TypeVar):
deserialized_data = cls_primitive_type(data)
else:
deserialized_data = cls_primitive_type(
deserialize_meta(cls.__parameters__[0], d) for d in data
)
return deserialized_data


def deserialize_abstract_type(cls, data):
abstract_type_map = {
typing.Sequence: list,
typing.List: list,
typing.Mapping: dict,
typing.Dict: dict,
typing.Set: set,
}
cls_origin_type = cls.__origin__
if cls_origin_type is None:
cls_origin_type = cls
iterable_types = {typing.Sequence, typing.List, typing.Tuple, typing.Set}
if cls_origin_type in iterable_types:
return deserialize_iterable_abstract_type(cls, cls_origin_type, data)
else:
return abstract_type_map[cls_origin_type](data)


def deserialize_tuple_type(cls, data):
if not cls.__tuple_params__:
return tuple(data)
if len(cls.__tuple_params__) != len(data):
raise ValueError(
'Expected {}-tuple, not {}-tuple'.format(
len(cls.__tuple_params__), len(data)
)
)
return tuple(
deserialize_meta(t, d)
for t, d in zip(cls.__tuple_params__, data)
)


def deserialize_primitive(cls, data):
if cls is datetime.datetime:
try:
d = parse_date(data)
except iso8601.ParseError:
raise ValueError("'{}' is not a datetime.".format(data))
elif cls is datetime.date:
try:
d = parse_date(data).date()
except iso8601.ParseError:
raise ValueError("'{}' is not a date.".format(data))
elif cls in {int, float, uuid.UUID, bool}:
d = cls(data)
elif cls is decimal.Decimal:
try:
d = cls(data)
except decimal.InvalidOperation:
raise ValueError("'{}' is not a decimal.".format(data))
elif cls is str:
if not isinstance(data, str):
raise ValueError("'{}' is not a string.")
d = cls(data)
elif '_type' in data and '_tag' in data:
else:
raise TypeError(
"'{0.__qualname__}' is not a primitive type.".format(cls)
)
return d


def deserialize_meta(cls, data):
if hasattr(cls, '__nirum_tag__'):
d = deserialize_union_type(cls, data)
elif '_type' in data:
elif hasattr(cls, '__nirum_record_behind_name__'):
d = deserialize_record_type(cls, data)
elif hasattr(cls, '__nirum_boxed_type__'):
d = deserialize_boxed_type(cls, data)
elif type(cls) is typing.TupleMeta:
# typing.Tuple dosen't have either `__origin__` and `__parameters__`
# so it have to be handled special case.
d = deserialize_tuple_type(cls, data)
elif is_support_abstract_type(cls):
d = deserialize_abstract_type(cls, data)
elif callable(cls) and cls in _NIRUM_PRIMITIVE_TYPE:
d = deserialize_primitive(cls, data)
else:
raise TypeError('data is not deserializable: {!r}'.format(data))
raise TypeError('data is not deserializable: {!r} as {!r}'.format(
data, cls
))
return d


Expand Down
36 changes: 36 additions & 0 deletions nirum/exc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
""":mod:nirum.exc`
~~~~~~~~~~~~~~~~~~
"""
__all__ = (
'InvalidNirumServiceMethodNameError',
'InvalidNirumServiceMethodTypeError',
'NirumProcedureArgumentError',
'NirumProcedureArgumentRequiredError',
'NirumProcedureArgumentValueError',
'NirumServiceError',
)


class NirumServiceError(Exception):
"""Base nirum service error"""


class InvalidNirumServiceMethodNameError(ValueError, NirumServiceError):
"""Raised when nirum service has invalid method name."""


class InvalidNirumServiceMethodTypeError(TypeError, NirumServiceError):
"""Raised when nirum service method is not callable."""


class NirumProcedureArgumentError(ValueError, NirumServiceError):
"""WIP"""


class NirumProcedureArgumentRequiredError(NirumProcedureArgumentError):
"""WIP"""


class NirumProcedureArgumentValueError(NirumProcedureArgumentError):
"""WIP"""
Loading

0 comments on commit df4c06c

Please sign in to comment.