136 lines
3.9 KiB
Python
136 lines
3.9 KiB
Python
|
""" Utility functions for package."""
|
||
|
from abc import ABCMeta, abstractmethod
|
||
|
import datetime
|
||
|
import decimal
|
||
|
import inspect
|
||
|
import json
|
||
|
import sys
|
||
|
|
||
|
from . import six
|
||
|
|
||
|
|
||
|
class JSONSerializable(six.with_metaclass(ABCMeta, object)):
|
||
|
|
||
|
""" Common functionality for json serializable objects."""
|
||
|
|
||
|
serialize = staticmethod(json.dumps)
|
||
|
deserialize = staticmethod(json.loads)
|
||
|
|
||
|
@abstractmethod
|
||
|
def json(self):
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
@classmethod
|
||
|
def from_json(cls, json_str):
|
||
|
data = cls.deserialize(json_str)
|
||
|
|
||
|
if not isinstance(data, dict):
|
||
|
raise ValueError("data should be dict")
|
||
|
|
||
|
return cls(**data)
|
||
|
|
||
|
|
||
|
class DatetimeDecimalEncoder(json.JSONEncoder):
|
||
|
|
||
|
""" Encoder for datetime and decimal serialization.
|
||
|
|
||
|
Usage: json.dumps(object, cls=DatetimeDecimalEncoder)
|
||
|
NOTE: _iterencode does not work
|
||
|
|
||
|
"""
|
||
|
|
||
|
def default(self, o):
|
||
|
""" Encode JSON.
|
||
|
|
||
|
:return str: A JSON encoded string
|
||
|
|
||
|
"""
|
||
|
if isinstance(o, decimal.Decimal):
|
||
|
return float(o)
|
||
|
|
||
|
if isinstance(o, (datetime.datetime, datetime.date)):
|
||
|
return o.isoformat()
|
||
|
|
||
|
return json.JSONEncoder.default(self, o)
|
||
|
|
||
|
|
||
|
def is_invalid_params_py2(func, *args, **kwargs):
|
||
|
""" Check, whether function 'func' accepts parameters 'args', 'kwargs'.
|
||
|
|
||
|
NOTE: Method is called after funct(*args, **kwargs) generated TypeError,
|
||
|
it is aimed to destinguish TypeError because of invalid parameters from
|
||
|
TypeError from inside the function.
|
||
|
|
||
|
.. versionadded: 1.9.0
|
||
|
|
||
|
"""
|
||
|
funcargs, varargs, varkwargs, defaults = inspect.getargspec(func)
|
||
|
|
||
|
unexpected = set(kwargs.keys()) - set(funcargs)
|
||
|
if len(unexpected) > 0:
|
||
|
return True
|
||
|
|
||
|
params = [funcarg for funcarg in funcargs if funcarg not in kwargs]
|
||
|
funcargs_required = funcargs[:-len(defaults)] \
|
||
|
if defaults is not None \
|
||
|
else funcargs
|
||
|
params_required = [
|
||
|
funcarg for funcarg in funcargs_required
|
||
|
if funcarg not in kwargs
|
||
|
]
|
||
|
|
||
|
return not (len(params_required) <= len(args) <= len(params))
|
||
|
|
||
|
|
||
|
def is_invalid_params_py3(func, *args, **kwargs):
|
||
|
"""
|
||
|
Use inspect.signature instead of inspect.getargspec or
|
||
|
inspect.getfullargspec (based on inspect.signature itself) as it provides
|
||
|
more information about function parameters.
|
||
|
|
||
|
.. versionadded: 1.11.2
|
||
|
|
||
|
"""
|
||
|
signature = inspect.signature(func)
|
||
|
parameters = signature.parameters
|
||
|
|
||
|
unexpected = set(kwargs.keys()) - set(parameters.keys())
|
||
|
if len(unexpected) > 0:
|
||
|
return True
|
||
|
|
||
|
params = [
|
||
|
parameter for name, parameter in parameters.items()
|
||
|
if name not in kwargs
|
||
|
]
|
||
|
params_required = [
|
||
|
param for param in params
|
||
|
if param.default is param.empty
|
||
|
]
|
||
|
|
||
|
return not (len(params_required) <= len(args) <= len(params))
|
||
|
|
||
|
|
||
|
def is_invalid_params(func, *args, **kwargs):
|
||
|
"""
|
||
|
Method:
|
||
|
Validate pre-defined criteria, if any is True - function is invalid
|
||
|
0. func should be callable
|
||
|
1. kwargs should not have unexpected keywords
|
||
|
2. remove kwargs.keys from func.parameters
|
||
|
3. number of args should be <= remaining func.parameters
|
||
|
4. number of args should be >= remaining func.parameters less default
|
||
|
"""
|
||
|
# For builtin functions inspect.getargspec(funct) return error. If builtin
|
||
|
# function generates TypeError, it is because of wrong parameters.
|
||
|
if not inspect.isfunction(func):
|
||
|
return True
|
||
|
|
||
|
if sys.version_info >= (3, 3):
|
||
|
return is_invalid_params_py3(func, *args, **kwargs)
|
||
|
else:
|
||
|
# NOTE: use Python2 method for Python 3.2 as well. Starting from Python
|
||
|
# 3.3 it is recommended to use inspect.signature instead.
|
||
|
# In Python 3.0 - 3.2 inspect.getfullargspec is preferred but these
|
||
|
# versions are almost not supported. Users should consider upgrading.
|
||
|
return is_invalid_params_py2(func, *args, **kwargs)
|