/bulbs/typesystem.py

https://github.com/HonzaKral/bulbs · Python · 169 lines · 93 code · 16 blank · 60 comment · 20 complexity · 85249e724d18dbf012fe36856b412034 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright 2011 James Thornton (http://jamesthornton.com)
  4. # BSD License (see LICENSE for details)
  5. #
  6. import config
  7. from property import Property, convert_to_rexster, convert_to_python
  8. TYPE_VAR = config.TYPE_VAR
  9. # NOTE: Here the word "Property" in ClassProperty refers to a Python property (lowercase)
  10. # We are creating a custom class b/c the standard version won't work for classmethods
  11. class ClassProperty(property):
  12. def __get__(self, cls, owner):
  13. return self.fget.__get__(None, owner)()
  14. class TypeSystemMeta(type):
  15. def __init__(cls, name, base, namespace):
  16. # store the Property definitions on the class as a dictionary
  17. # mapping the Property name to the Property instance
  18. cls._properties = {}
  19. # loop through the class namespace looking for Property instances
  20. for key, value in namespace.items():
  21. #print key,value
  22. if isinstance(value, Property):
  23. if value.name is None:
  24. # name will be none unless set via kwd param
  25. value.name = key
  26. cls._properties[key] = value
  27. # now that the property reference is stored away,
  28. # initialize its vars to None, the default vale (TODO), or the fget
  29. if value.default:
  30. #TODO: Make this work for scalars too
  31. fget = getattr(cls,value.default)
  32. value = property(fget)
  33. elif value.fget:
  34. # wrapped fset and fdel in str() to make the default None work with getattr
  35. fget = getattr(cls,value.fget)
  36. fset = getattr(cls,str(value.fset),None)
  37. fdel = getattr(cls,str(value.fdel),None)
  38. value = property(fget,fset,fdel)
  39. else:
  40. value = None
  41. setattr(cls,key,value)
  42. class TypeSystem(object):
  43. __metaclass__ = TypeSystemMeta
  44. def _set_property(self,key,value):
  45. if key in self._properties:
  46. datatype = type(value)
  47. try:
  48. if value:
  49. python_type = self._properties[key].datatype.python_type
  50. # TODO: this is a HACK until we clean up 'None' values in DB
  51. if value == 'None':
  52. value = None
  53. value = python_type(value)
  54. #if not self._properties[key].is_valid(value):
  55. # raise TypeError('Invalid datatype for property.')
  56. #if value:
  57. # some types can't be None
  58. super(TypeSystem, self).__setattr__(key, value)
  59. except ValueError:
  60. print "'%s' is not a valid value for %s, must be %s." \
  61. % (value, key, python_type)
  62. raise
  63. except AttributeError:
  64. print "Can't set attribute '%s' to value '%s with type'" % (key,value,datatype)
  65. raise
  66. else:
  67. # set value normally (it's not a database "Property")
  68. # But don't try to set values for get-only attributes, such as eid
  69. #if type(self).__dict__[key].fset is not None:
  70. #try:
  71. # TODO: make this handle properties without fset
  72. super(TypeSystem, self).__setattr__(key, value)
  73. #except:
  74. # pass
  75. def __setattr__(self, key, value):
  76. self._set_property(key,value)
  77. def _constructor(self, kwds):
  78. """A simple constructor that allows initialization from kwargs.
  79. Sets attributes on the constructed instance using the names and
  80. values in ``kwargs``.
  81. Only keys that are present as
  82. attributes of the instance's class are allowed. These could be,
  83. for example, any mapped columns or relationships.
  84. """
  85. cls = type(self)
  86. for k in kwds:
  87. if not hasattr(cls,k):
  88. raise TypeError(
  89. "%r is an invalid keyword argument for %s" %
  90. (k, cls.__name__))
  91. setattr(self, k, kwds[k])
  92. def _set_property_data(self,results):
  93. """
  94. Sets Property data when an element is being initialized, after it is
  95. retrieved from the DB.
  96. This will do type checking and coerce values to the right datatype.
  97. """
  98. for key, property_instance in self._properties.items():
  99. value = results.get(key,None)
  100. #if property_instance.method is None:
  101. # this is a normal attribute, not a derived one so set its value
  102. try:
  103. setattr(self,key,value)
  104. except:
  105. # TODO: log/warn/email regarding type mismatch
  106. setattr(self,key,None)
  107. def _get_property_data(self):
  108. """Returns Property data ready to be saved in the DB."""
  109. data = dict()
  110. # it should default to string in rexster so no need to
  111. # convert_to_rexster()
  112. # should we require element_type on nodes (i.e. Model vertices)?
  113. if hasattr(self,TYPE_VAR):
  114. data[TYPE_VAR] = getattr(self,TYPE_VAR)
  115. for key, value in self._properties.items():
  116. #try:
  117. val = getattr(self,key)
  118. #if isinstance(val,Property):
  119. # data[key] = None
  120. #else:
  121. data[key] = convert_to_rexster(val)
  122. #data[key] = val
  123. #except:
  124. # data[key] = None
  125. return data
  126. def _validate_property_data(self):
  127. """
  128. Validates that Property data is of the right datatype before saving it
  129. to the DB and that the Property has a value if nullable is set to False.
  130. Call this at the top of each save() method.
  131. """
  132. # check that Properties with nullable is set to False
  133. # actually have a value set
  134. for key, property_instance in self._properties.items():
  135. if property_instance.nullable is False:
  136. try:
  137. assert getattr(self,key) is not None
  138. except AssertionError:
  139. print "Cannot set '%s' to %s: '%s' is a Property with nullable set to False" % (key, getattr(self,key), key)
  140. raise
  141. def _set_keyword_attributes(self,kwds):
  142. for key, value in kwds.iteritems():
  143. setattr(self,key,value)