Skip to content

Commit

Permalink
Implement RPC runtime for python #9
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
kanghyojun committed Aug 4, 2016
1 parent bc1fb46 commit df60dac
Show file tree
Hide file tree
Showing 6 changed files with 768 additions and 7 deletions.
123 changes: 118 additions & 5 deletions nirum/deserialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,134 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import datetime
import decimal
import typing
import uuid

__all__ = (
'deserialize_boxed_type', 'deserialize_meta',
'deserialize_record_type', 'deserialize_union_type',
)
_NIRUM_PRIMITIVE_TYPE = {
str, float, decimal.Decimal, uuid.UUID, datetime.datetime,
datetime.date, bool
}


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,
}
for type_ in abstract_types:
if type_ is data_type:
return True
else:
return False


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(cls)
if len(cls.__tuple_params__) != len(data):
raise ValueError(
'Expected length of tuple is {}, but given length of tuple'
' is {}'.format(
len(cls.__tuple_params__), len(data)
)
)
return (
deserialize_meta(t, d)
for t, d in zip(cls.__tuple_params__, data)
)


def deserialize_primitive(cls, data):
if cls is datetime.datetime:
d = datetime.datetime.strptime(data, '%Y-%m-%dT%H:%M:%S.%f')
elif cls is datetime.date:
d = datetime.datetime.strptime(data, '%Y-%m-%d').date()
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 df60dac

Please sign in to comment.