Skip to content

Commit

Permalink
Python27 support (#36)
Browse files Browse the repository at this point in the history
* Simple py2

* py27 basic support

* py27 support

* Ignore import order when python 27

* tox

* rebase

* Use typing._type_repr()

* Use .get_full_url() always

* Run with python27 on travis-ci

* In py27, have to inherit object

* Add py34 in tox
  • Loading branch information
kanghyojun authored Oct 22, 2016
1 parent 535139a commit 2cbbc7d
Show file tree
Hide file tree
Showing 21 changed files with 1,004 additions and 571 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.pyc
.cache
*.egg-info
.tox
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
language: python
python:
- 2.7
- 3.4
- 3.5
install:
Expand Down
5 changes: 4 additions & 1 deletion lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
set -e

flake8 .
import-order nirum ./nirum

if [[ "$(python -c "import sys;print(sys.version[0])")" != "2" ]]; then
import-order nirum ./nirum
fi
24 changes: 24 additions & 0 deletions nirum/_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import datetime

__all__ = 'utc',


try:
utc = datetime.timezone.utc
except AttributeError:
ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)

class UTC(datetime.tzinfo):
"""UTC"""

def utcoffset(self, dt):
return ZERO

def tzname(self, dt):
return "UTC"

def dst(self, dt):
return ZERO

utc = UTC()
40 changes: 23 additions & 17 deletions nirum/deserialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import uuid

from iso8601 import iso8601, parse_date
from six import text_type

__all__ = (
'deserialize_abstract_type',
Expand All @@ -24,8 +25,8 @@
'is_support_abstract_type',
)
_NIRUM_PRIMITIVE_TYPE = {
str, float, decimal.Decimal, uuid.UUID, datetime.datetime,
datetime.date, bool, int
float, decimal.Decimal, uuid.UUID, datetime.datetime,
datetime.date, bool, int, text_type
}


Expand Down Expand Up @@ -126,13 +127,13 @@ def deserialize_primitive(cls, data):
d = cls(data)
except decimal.InvalidOperation:
raise ValueError("'{}' is not a decimal.".format(data))
elif cls is str:
if not isinstance(data, str):
elif cls is text_type:
if not isinstance(data, text_type):
raise ValueError("'{}' is not a string.".format(data))
d = cls(data)
else:
raise TypeError(
"'{0.__qualname__}' is not a primitive type.".format(cls)
"'{0}' is not a primitive type.".format(typing._type_repr(cls))
)
return d

Expand Down Expand Up @@ -207,9 +208,13 @@ def deserialize_record_type(cls, value):
if '_type' not in value:
raise ValueError('"_type" field is missing.')
if not cls.__nirum_record_behind_name__ == value['_type']:
raise ValueError('{0.__class__.__name__} expect "_type" equal to'
' "{0.__nirum_record_behind_name__}"'
', but found {1}.'.format(cls, value['_type']))
raise ValueError(
'{0} expect "_type" equal to "{1.__nirum_record_behind_name__}"'
', but found {2}.'.format(
typing._type_repr(cls),
cls, value['_type']
)
)
args = {}
behind_names = cls.__nirum_field_names__.behind_names
for attribute_name, item in value.items():
Expand All @@ -235,19 +240,20 @@ def deserialize_union_type(cls, value):
break
else:
raise ValueError(
'{0!r} is not deserialzable tag of '
'`{1.__name__}`.'.format(
value, cls
'{0!r} is not deserialzable tag of `{1}`.'.format(
value, typing._type_repr(cls)
)
)
if not cls.__nirum_union_behind_name__ == value['_type']:
raise ValueError('{0.__class__.__name__} expect "_type" equal to'
' "{0.__nirum_union_behind_name__}"'
', but found {1}.'.format(cls, value['_type']))
raise ValueError('{0} expect "_type" equal to'
' "{1.__nirum_union_behind_name__}"'
', but found {2}.'.format(typing._type_repr(cls), cls,
value['_type']))
if not cls.__nirum_tag__.value == value['_tag']:
raise ValueError('{0.__class__.__name__} expect "_tag" equal to'
' "{0.__nirum_tag__.value}"'
', but found {1}.'.format(cls, value['_tag']))
raise ValueError('{0} expect "_tag" equal to'
' "{1.__nirum_tag__.value}"'
', but found {1}.'.format(typing._type_repr(cls),
cls, value['_tag']))
args = {}
behind_names = cls.__nirum_tag_names__.behind_names
for attribute_name, item in value.items():
Expand Down
4 changes: 2 additions & 2 deletions nirum/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
~~~~~~~~~~~~~~~~~~
"""
import urllib.error
from six.moves import urllib

__all__ = (
'InvalidNirumServiceMethodNameError',
Expand Down Expand Up @@ -48,7 +48,7 @@ class NirumHttpError(urllib.error.HTTPError, NirumServiceError):
class NirumUrlError(urllib.error.URLError, NirumServiceError):
"""TODO"""

def __init__(self, exc: urllib.error.URLError):
def __init__(self, exc):
self.text = exc.read()
super().__init__(exc.reason)

Expand Down
4 changes: 2 additions & 2 deletions nirum/func.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import urllib.parse
from six.moves import urllib

__all__ = 'url_endswith_slash',


def url_endswith_slash(url: str) -> str:
def url_endswith_slash(url):
scheme, netloc, path, _, _ = urllib.parse.urlsplit(url)
if not (scheme and netloc):
raise ValueError("{} isn't URL.".format(url))
Expand Down
78 changes: 29 additions & 49 deletions nirum/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
"""
import json
import typing
import urllib.parse
import urllib.request

from six.moves import urllib
from werkzeug.exceptions import HTTPException
from werkzeug.http import HTTP_STATUS_CODES
from werkzeug.routing import Map, Rule
Expand Down Expand Up @@ -42,14 +41,14 @@ def __init__(self):
method = getattr(self, method_name)
except AttributeError:
raise InvalidNirumServiceMethodNameError(
"{0.__class__.__qualname__}.{1} inexist.".format(
self, method_name
"{0}.{1} inexist.".format(
typing._type_repr(self.__class__), method_name
)
)
if not callable(method):
raise InvalidNirumServiceMethodTypeError(
"{0.__class__.__qualname__}.{1} isn't callable".format(
self, method_name
"{0}.{1} isn't callable".format(
typing._type_repr(self.__class__), method_name
)
)

Expand All @@ -67,11 +66,10 @@ class WsgiApp:
Rule('/ping/', endpoint='ping'),
])

def __init__(self, service: Service):
def __init__(self, service):
self.service = service

def __call__(self, environ: typing.Mapping[str, object],
start_response: typing.Callable) -> WsgiResponse:
def __call__(self, environ, start_response):
"""
:param environ:
Expand All @@ -80,8 +78,7 @@ def __call__(self, environ: typing.Mapping[str, object],
"""
return self.route(environ, start_response)

def route(self, environ: typing.Mapping[str, object],
start_response: typing.Callable) -> WsgiResponse:
def route(self, environ, start_response):
"""Route
:param environ:
Expand All @@ -99,14 +96,14 @@ def route(self, environ: typing.Mapping[str, object],
response = procedure(request, args)
return response(environ, start_response)

def ping(self, request: WsgiRequest, args: typing.Any) -> WsgiResponse:
def ping(self, request, args):
return WsgiResponse(
'"Ok"',
200,
content_type='application/json'
)

def rpc(self, request: WsgiRequest, args: typing.Any) -> WsgiResponse:
def rpc(self, request, args):
"""RPC
:param request:
Expand Down Expand Up @@ -145,8 +142,7 @@ def rpc(self, request: WsgiRequest, args: typing.Any) -> WsgiResponse:
)
if not callable(service_method):
return self.error(
400,
request,
400, request,
message="Remote procedure '{}' is not callable.".format(
request_method
)
Expand All @@ -173,19 +169,17 @@ def rpc(self, request: WsgiRequest, args: typing.Any) -> WsgiResponse:
return self.error(
400,
request,
message="Incorrect return type '{0.__class__.__qualname__}' "
"for '{1}'. expected '{2.__qualname__}'.".format(
result, request_method, type_hints['_return']
message="Incorrect return type '{0}' "
"for '{1}'. expected '{2}'.".format(
typing._type_repr(result.__class__),
request_method,
typing._type_repr(type_hints['_return'])
)
)
else:
return self._json_response(200, serialize_meta(result))

def _parse_procedure_arguments(
self,
type_hints: typing.Mapping[str, typing.Union[type, NameDict]],
request_json: JSONType
) -> typing.Mapping[str, typing.Union[str, float, int, bool, object]]:
def _parse_procedure_arguments(self, type_hints, request_json):
arguments = {}
name_map = type_hints['_names']
for argument_name, type_ in type_hints.items():
Expand All @@ -204,27 +198,23 @@ def _parse_procedure_arguments(
arguments[argument_name] = deserialize_meta(type_, data)
except ValueError:
raise NirumProcedureArgumentValueError(
"Incorrect type '{0.__class__.__name__}' for '{1}'. "
"expected '{2.__name__}'.".format(
data, behind_name, type_
"Incorrect type '{0}' for '{1}'. "
"expected '{2}'.".format(
typing._type_repr(data.__class__), behind_name,
typing._type_repr(type_)
)
)
return arguments

def _check_return_type(
self,
type_hint: type, procedure_result: typing.Any
) -> bool:
def _check_return_type(self, type_hint, procedure_result):
try:
deserialize_meta(type_hint, serialize_meta(procedure_result))
except ValueError:
return False
else:
return True

def _make_error_response(
self, error_type: str, message: typing.Optional[str]=None
) -> typing.Mapping[str, str]:
def _make_error_response(self, error_type, message=None):
"""Create error response json temporary.
.. code-block:: nirum
Expand All @@ -242,10 +232,7 @@ def _make_error_response(
'message': message,
}

def error(
self, status_code: int, request: WsgiRequest,
*, message: typing.Optional[str]=None
) -> WsgiResponse:
def error(self, status_code, request, message=None):
"""Handle error response.
:param int status_code:
Expand Down Expand Up @@ -278,9 +265,7 @@ def error(
)
)

def _json_response(
self, status_code: int, response_json: JSONType, **kwargs
) -> WsgiResponse:
def _json_response(self, status_code, response_json, **kwargs):
return WsgiResponse(
json.dumps(response_json),
status_code,
Expand All @@ -291,10 +276,7 @@ def _json_response(

class Client:

def __init__(
self, url: str,
opener: urllib.request.OpenerDirector=urllib.request.build_opener()
):
def __init__(self, url, opener=urllib.request.build_opener()):
self.url = url_endswith_slash(url)
self.opener = opener

Expand All @@ -306,9 +288,7 @@ def ping(self):
)
return self.make_request(req)

def remote_call(self,
method_name: str,
payload: JSONType={}) -> JSONType:
def remote_call(self, method_name, payload={}):
qs = urllib.parse.urlencode({'method': method_name})
scheme, netloc, path, _, _ = urllib.parse.urlsplit(self.url)
request_url = urllib.parse.urlunsplit((
Expand All @@ -321,9 +301,9 @@ def remote_call(self,
)
return self.make_request(req)

def make_request(self, request: urllib.request.Request) -> JSONType:
def make_request(self, request):
try:
response = self.opener.open(request)
response = self.opener.open(request, None)
except urllib.error.URLError as e:
raise NirumUrlError(e)
except urllib.error.HTTPError as e:
Expand Down
Loading

0 comments on commit 2cbbc7d

Please sign in to comment.