Add a type Registry.
It mainly allow to have easy cross-referenced types by setting the attribute datatype to the class name instead of the class instance. It may allow to have separate registry for different apis in the future
This commit is contained in:
parent
f2203742f9
commit
520341238e
@ -8,6 +8,8 @@ Changes
|
||||
|
||||
* String types handling is clearer.
|
||||
|
||||
* Supports cross-referenced types.
|
||||
|
||||
* Various bugfixes.
|
||||
|
||||
* Tests code coverage is now over 95%.
|
||||
|
@ -220,3 +220,14 @@ A few things you should know about complex types:
|
||||
|
||||
For input values, it allows the code to know if the values were, or not,
|
||||
sent by the caller.
|
||||
|
||||
- When 2 complex types refers to each other, their names can be
|
||||
used as datatypes to avoid adding attributes afterwards:
|
||||
|
||||
::
|
||||
|
||||
class A(object):
|
||||
b = wsattr('B')
|
||||
|
||||
class B(object):
|
||||
a = wsattr(A)
|
||||
|
@ -11,6 +11,7 @@ import webob
|
||||
from wsme import exc
|
||||
from wsme.protocols import getprotocol
|
||||
from wsme.api import scan_api
|
||||
import wsme.types
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -74,6 +75,8 @@ class WSRoot(object):
|
||||
module will be imported and used.
|
||||
|
||||
"""
|
||||
__registry__ = wsme.types.registry
|
||||
|
||||
def __init__(self, protocols=[], webpath='', transaction=None):
|
||||
self._debug = True
|
||||
self._webpath = webpath
|
||||
@ -114,6 +117,7 @@ class WSRoot(object):
|
||||
:rtype: list of (path, :class:`FunctionDefinition`)
|
||||
"""
|
||||
if self._api is None:
|
||||
self.__registry__.resolve_references()
|
||||
self._api = [i for i in scan_api(self)]
|
||||
return self._api
|
||||
|
||||
|
@ -12,6 +12,9 @@ def gen_class():
|
||||
|
||||
|
||||
class TestTypes(unittest.TestCase):
|
||||
def setUp(self):
|
||||
types.registry = types.Registry()
|
||||
|
||||
def test_default_usertype(self):
|
||||
class MyType(types.UserType):
|
||||
basetype = str
|
||||
@ -261,12 +264,29 @@ class TestTypes(unittest.TestCase):
|
||||
self.assertRaises(ValueError, types.register_type,
|
||||
{types.Unset: str})
|
||||
|
||||
def test_list_attribute_auto_register(self):
|
||||
def test_list_attribute_no_auto_register(self):
|
||||
class MyType(object):
|
||||
aint = int
|
||||
|
||||
assert not hasattr(MyType, '_wsme_attributes')
|
||||
|
||||
types.list_attributes(MyType)
|
||||
try:
|
||||
types.list_attributes(MyType)
|
||||
assert False, "TypeError was not raised"
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
assert hasattr(MyType, '_wsme_attributes')
|
||||
assert not hasattr(MyType, '_wsme_attributes')
|
||||
|
||||
def test_cross_referenced_types(self):
|
||||
class A(object):
|
||||
b = types.wsattr('B')
|
||||
|
||||
class B(object):
|
||||
a = A
|
||||
|
||||
types.register_type(A)
|
||||
types.register_type(B)
|
||||
types.registry.resolve_references()
|
||||
|
||||
assert A.b.datatype is B
|
||||
|
155
wsme/types.py
155
wsme/types.py
@ -81,16 +81,6 @@ class Enum(UserType):
|
||||
def frombasetype(self, value):
|
||||
return value
|
||||
|
||||
pod_types = six.integer_types + (
|
||||
bytes, text, float, bool)
|
||||
dt_types = (datetime.date, datetime.time, datetime.datetime)
|
||||
extra_types = (binary, decimal.Decimal)
|
||||
native_types = pod_types + dt_types + extra_types
|
||||
|
||||
complex_types = []
|
||||
array_types = []
|
||||
dict_types = []
|
||||
|
||||
|
||||
class UnsetType(object):
|
||||
if sys.version < '3':
|
||||
@ -103,6 +93,12 @@ class UnsetType(object):
|
||||
Unset = UnsetType()
|
||||
|
||||
|
||||
pod_types = six.integer_types + (
|
||||
bytes, text, float, bool)
|
||||
dt_types = (datetime.date, datetime.time, datetime.datetime)
|
||||
extra_types = (binary, decimal.Decimal)
|
||||
native_types = pod_types + dt_types + extra_types
|
||||
|
||||
def iscomplex(datatype):
|
||||
return inspect.isclass(datatype) \
|
||||
and '_wsme_attributes' in datatype.__dict__
|
||||
@ -207,8 +203,7 @@ class wsattr(object):
|
||||
#: The attribute name on the public of the api.
|
||||
#: Defaults to :attr:`key`
|
||||
self.name = name
|
||||
#: attribute data type
|
||||
self.datatype = datatype
|
||||
self._datatype = datatype
|
||||
#: True if the attribute is mandatory
|
||||
self.mandatory = mandatory
|
||||
#: Default value. The attribute will return this instead
|
||||
@ -235,6 +230,19 @@ class wsattr(object):
|
||||
def __delete__(self, instance):
|
||||
self.__set__(instance, Unset)
|
||||
|
||||
def _get_datatype(self):
|
||||
if isinstance(self._datatype, weakref.ref):
|
||||
return self._datatype()
|
||||
return self._datatype
|
||||
|
||||
def _set_datatype(self, datatype):
|
||||
self._datatype = datatype
|
||||
|
||||
#: attribute data type. Can be either an actual type,
|
||||
#: or a type name, in which case the actual type will be
|
||||
#: determined when needed (generaly just before scaning the api).
|
||||
datatype = property(_get_datatype, _set_datatype)
|
||||
|
||||
|
||||
def iswsattr(attr):
|
||||
if inspect.isfunction(attr) or inspect.ismethod(attr):
|
||||
@ -319,49 +327,90 @@ def inspect_class(class_):
|
||||
return attributes
|
||||
|
||||
|
||||
def register_type(class_):
|
||||
"""
|
||||
Make sure a type is registered.
|
||||
|
||||
It is automatically called by :class:`expose() <wsme.expose>`
|
||||
and :class:`validate() <wsme.validate>`.
|
||||
Unless you want to control when the class inspection is done there
|
||||
is no need to call it.
|
||||
"""
|
||||
if class_ is None or \
|
||||
class_ in native_types or \
|
||||
isusertype(class_) or iscomplex(class_):
|
||||
return
|
||||
|
||||
if isinstance(class_, list):
|
||||
if len(class_) != 1:
|
||||
raise ValueError("Cannot register type %s" % repr(class_))
|
||||
register_type(class_[0])
|
||||
if class_[0] not in array_types:
|
||||
array_types.append(class_[0])
|
||||
return
|
||||
|
||||
if isinstance(class_, dict):
|
||||
if len(class_) != 1:
|
||||
raise ValueError("Cannot register type %s" % repr(class_))
|
||||
key_type, value_type = list(class_.items())[0]
|
||||
if key_type not in pod_types:
|
||||
raise ValueError("Dictionnaries key can only be a pod type")
|
||||
register_type(value_type)
|
||||
if (key_type, value_type) not in dict_types:
|
||||
dict_types.append((key_type, value_type))
|
||||
return
|
||||
|
||||
class_._wsme_attributes = None
|
||||
class_._wsme_attributes = inspect_class(class_)
|
||||
|
||||
complex_types.append(weakref.ref(class_))
|
||||
|
||||
|
||||
def list_attributes(class_):
|
||||
"""
|
||||
Returns a list of a complex type attributes.
|
||||
"""
|
||||
if not hasattr(class_, '_wsme_attributes'):
|
||||
register_type(class_)
|
||||
if not iscomplex(class_):
|
||||
raise TypeError("%s is not a registered type")
|
||||
return class_._wsme_attributes
|
||||
|
||||
|
||||
class Registry(object):
|
||||
def __init__(self):
|
||||
self.complex_types = []
|
||||
self.array_types = []
|
||||
self.dict_types = []
|
||||
|
||||
def register(self, class_):
|
||||
"""
|
||||
Make sure a type is registered.
|
||||
|
||||
It is automatically called by :class:`expose() <wsme.expose>`
|
||||
and :class:`validate() <wsme.validate>`.
|
||||
Unless you want to control when the class inspection is done there
|
||||
is no need to call it.
|
||||
"""
|
||||
if class_ is None or \
|
||||
class_ in native_types or \
|
||||
isusertype(class_) or iscomplex(class_):
|
||||
return
|
||||
|
||||
if isinstance(class_, list):
|
||||
if len(class_) != 1:
|
||||
raise ValueError("Cannot register type %s" % repr(class_))
|
||||
register_type(class_[0])
|
||||
if class_[0] not in self.array_types:
|
||||
self.array_types.append(class_[0])
|
||||
return
|
||||
|
||||
if isinstance(class_, dict):
|
||||
if len(class_) != 1:
|
||||
raise ValueError("Cannot register type %s" % repr(class_))
|
||||
key_type, value_type = list(class_.items())[0]
|
||||
if key_type not in pod_types:
|
||||
raise ValueError("Dictionnaries key can only be a pod type")
|
||||
register_type(value_type)
|
||||
if (key_type, value_type) not in self.dict_types:
|
||||
self.dict_types.append((key_type, value_type))
|
||||
return
|
||||
|
||||
class_._wsme_attributes = None
|
||||
class_._wsme_attributes = inspect_class(class_)
|
||||
|
||||
self.complex_types.append(weakref.ref(class_))
|
||||
|
||||
def lookup(self, typename):
|
||||
modname = None
|
||||
if '.' in typename:
|
||||
modname, typename = typename.rsplit('.', 1)
|
||||
for ct in self.complex_types:
|
||||
ct = ct()
|
||||
if typename == ct.__name__ and (
|
||||
modname is None or modname == ct.__module__):
|
||||
return weakref.ref(ct)
|
||||
|
||||
def resolve_type(self, type_):
|
||||
if isinstance(type_, six.string_types):
|
||||
return self.lookup(type_)
|
||||
if isinstance(type_, list):
|
||||
return [self.resolve_type(type_[0])]
|
||||
if isinstance(type_, dict):
|
||||
key_type, value_type = list(type_.items())[0]
|
||||
return {
|
||||
key_type: self.resolve_type(value_type)
|
||||
}
|
||||
return type_
|
||||
|
||||
def resolve_references(self):
|
||||
for ct in self.complex_types:
|
||||
ct = ct()
|
||||
for attr in list_attributes(ct):
|
||||
attr.datatype = self.resolve_type(attr.datatype)
|
||||
|
||||
# Default type registry
|
||||
registry = Registry()
|
||||
|
||||
|
||||
def register_type(class_):
|
||||
return registry.register(class_)
|
||||
|
Loading…
x
Reference in New Issue
Block a user