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:
Christophe de Vienne 2012-05-22 14:40:36 +02:00
parent f2203742f9
commit 520341238e
5 changed files with 142 additions and 56 deletions

View File

@ -8,6 +8,8 @@ Changes
* String types handling is clearer.
* Supports cross-referenced types.
* Various bugfixes.
* Tests code coverage is now over 95%.

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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_)