[Commits] (pje) Spike: add ``schema.Enumeration`` base type for
defining enumerations. Add
commits at osafoundation.org
commits at osafoundation.org
Mon Feb 21 12:03:54 PST 2005
Commit by: pje
Modified files:
internal/Spike/src/spike/schema.py 1.4 1.5
internal/Spike/src/spike/schema.txt 1.7 1.8
Log message:
Spike: add ``schema.Enumeration`` base type for defining enumerations. Add
``displayName`` and ``doc`` options to ``schema.Role`` types, with
automatic docstring formatting so that entity types read well in pydoc.
Also, add strict attribute checking to ``Role`` types to avoid unintended
misconfiguration or post-initialization changes.
ViewCVS links:
http://cvs.osafoundation.org/index.cgi/internal/Spike/src/spike/schema.py.diff?r1=text&tr1=1.4&r2=text&tr2=1.5
http://cvs.osafoundation.org/index.cgi/internal/Spike/src/spike/schema.txt.diff?r1=text&tr1=1.7&r2=text&tr2=1.8
Index: internal/Spike/src/spike/schema.py
diff -u internal/Spike/src/spike/schema.py:1.4 internal/Spike/src/spike/schema.py:1.5
--- internal/Spike/src/spike/schema.py:1.4 Wed Feb 16 20:27:08 2005
+++ internal/Spike/src/spike/schema.py Mon Feb 21 12:03:53 2005
@@ -2,7 +2,8 @@
__all__ = [
'Entity', 'Role', 'Relationship', 'One', 'Many', 'NullSet', 'LoadEvent',
- 'Activator', 'RelationshipClass', 'ActiveDescriptor',
+ 'Enumeration', 'Activator', 'RelationshipClass', 'EnumerationClass',
+ 'ActiveDescriptor',
]
from models import Set
@@ -85,13 +86,15 @@
types = ()
isReference = False
- name = owner = _inverse = None
+ name = owner = _inverse = _loadMap = None
+ cardinality = "Role"
def __init__(self,types=(),**kw):
self.addTypes(types)
self._loadMap = {}
for k,v in kw.items():
setattr(self,k,v)
+ self.setDoc() # default the doc string
def __setInverse(self,inverse):
if self._inverse is not inverse: # No-op if no change
@@ -101,7 +104,7 @@
try:
inverse.inverse = self
except:
- self._inverse = None # roll back the change
+ self._setattr('_inverse',None) # roll back the change
raise
if self.owner is not None and issubclass(self.owner,Entity):
inverse.addTypes(self.owner)
@@ -122,7 +125,7 @@
raise TypeError("Multiple value types not allowed in one role")
if refs:
self.isReference = True
- self.types += types
+ self._setattr('types',self.types + types)
def activateInClass(self,cls,name):
"""Role was defined/used in class `cls` under name `name`"""
@@ -210,6 +213,58 @@
return load_role, (self.owner, self.name)
return object.__reduce_ex__(self,proto)
+ def __getDoc(self):
+ return self.__dict__.get('doc')
+
+ def __setDoc(self,val):
+ self.__dict__['doc'] = val
+ self.setDoc()
+
+ doc = property(__getDoc,__setDoc)
+
+ def __getDisplayName(self):
+ return self.__dict__.get('displayName')
+
+ def __setDisplayName(self,val):
+ self.__dict__['displayName'] = val
+ self.setDoc()
+
+ displayName = property(__getDisplayName,__setDisplayName)
+
+ def setDoc(self):
+ doc = self.doc
+ name = self.displayName
+ if not name:
+ name = self.docInfo()
+ else:
+ name = "%s -- %s" % (name,self.docInfo())
+ if not doc:
+ doc = name
+ else:
+ doc = "%s\n\n%s" % (name,doc)
+ self._setattr('__doc__',doc)
+
+ def docInfo(self):
+ return ("%s(%s)" %
+ (self.cardinality,
+ '/'.join([typ.__name__ for typ in self.types]) or '()'
+ )
+ )
+
+ def _setattr(self,attr,value):
+ """Private routine allowing bypass of normal setattr constraints"""
+ super(Role,self).__setattr__(attr,value)
+
+ def __setattr__(self,attr,value):
+ if not hasattr(type(self),attr):
+ raise TypeError("%r is not a public attribute of %r objects"
+ % (attr,type(self).__name__))
+ old = self.__dict__.get(attr)
+ if old is not None and old<>value:
+ raise TypeError(
+ "Role objects are immutable; can't change %r once set" % attr
+ )
+ self._setattr(attr,value)
NOT_GIVEN = object()
@@ -217,6 +272,7 @@
"""Single-valued role attribute"""
default = compute = NOT_GIVEN
+ cardinality = "One"
def newSet(self,ob):
"""Return new default linkset for ``ob``
@@ -256,6 +312,8 @@
class Many(Role):
"""Multi-valued role attribute"""
+ cardinality = "Many"
+
def __get__(self,ob,typ):
if ob is None:
return self
@@ -310,6 +368,71 @@
__metaclass__ = RelationshipClass
+class EnumerationClass(Activator):
+ """Metaclass for enumerations"""
+
+ def __init__(cls,name,bases,cdict):
+ super(EnumerationClass,cls).__init__(name,bases,cdict)
+ try:
+ Enumeration
+ except NameError:
+ return # Enumeration isn't defined yet, so nothing to verify
+
+ if bases<>(Enumeration,):
+ raise TypeError("Enumerations cannot be subclassed")
+
+ d = {}
+ for k,v in cls.iteritems():
+ if v in d:
+ v = unicode(v)
+ raise TypeError(
+ "Duplicate definitions: %s=enum(%r) and %s=enum(%r)"
+ % (d[v],v,k,v)
+ )
+ d[v] = k
+
+ def __iter__(cls):
+ for k,v in cls.iteritems():
+ yield v
+
+ def iteritems(cls):
+ """Yield names and values of all enums in this class"""
+ for k in dir(cls):
+ v = getattr(cls,k)
+ if isinstance(v,Enumeration):
+ yield k,v
+
+
+class Enumeration(unicode):
+ """Base class for value types with a fixed set of possible values"""
+
+ __metaclass__ = EnumerationClass
+
+ def __new__(self,*args,**kw):
+ raise TypeError("Enumeration instances cannot be created directly")
+
+ @property
+ def name(self):
+ cls = self.__class__
+ for k,v in cls.iteritems():
+ if v==self:
+ return k
+ raise AssertionError("Invalid instance",unicode(self))
+
+ def __repr__(self):
+ return ".".join([self.__class__.__name__,self.name])
+
+
+class enum(ActiveDescriptor):
+ """schema.enum(displayName) -- define an enumeration value"""
+
+ def __init__(self,displayName):
+ self.displayName = displayName
+
+ def activateInClass(self,cls,name):
+ setattr(cls,name,unicode.__new__(cls,self.displayName))
+
+
class LoaderWrapper(ActiveDescriptor):
"""Registration wrapper for loader methods declared ``@aRole.loader``"""
Index: internal/Spike/src/spike/schema.txt
diff -u internal/Spike/src/spike/schema.txt:1.7 internal/Spike/src/spike/schema.txt:1.8
--- internal/Spike/src/spike/schema.txt:1.7 Wed Feb 16 20:27:08 2005
+++ internal/Spike/src/spike/schema.txt Mon Feb 21 12:03:53 2005
@@ -566,6 +566,39 @@
>>> Kind.superkinds.types
(<class 'Kind'>,)
+``displayName`` and ``doc``
+ The name and description of this role, if any. They will be used to
+ collectively form a ``__doc__`` string, so that ``help()`` is informative
+ for entity types::
+
+ >>> Kind.subkinds.doc = "Sub-kinds of this kind"
+ >>> Kind.superkinds.doc = "Super-kinds of this kind"
+ >>> Kind.name.doc = "This kind's name"
+ >>> Kind.name.displayName = "Kind Name"
+
+ >>> help(Kind) # doctest: +NORMALIZE_WHITESPACE
+ Help on class Kind ...
+ ...
+ class Kind(spike.schema.Entity)
+ | ...
+ | Data and other attributes defined here:
+ |
+ | name = <Role name of <class 'Kind'>>
+ | Kind Name -- One(str)
+ |
+ | This kind's name
+ |
+ | subkinds = <Role subkinds of <class 'Kind'>>
+ | Many(Kind)
+ |
+ | Sub-kinds of this kind
+ |
+ | superkinds = <Role superkinds of <class 'Kind'>>
+ | Many(Kind)
+ |
+ | Super-kinds of this kind
+ | ...
+
``of(ob)``
Return the linkset for this role and `ob`. If the role has an ``inverse``,
@@ -671,6 +704,22 @@
>>> print CalcDemo.alias.getLoader(Dummy())
<function dummyLoader at ...>
+To avoid silent definition errors, ``Role`` classes will not allow setting
+attributes that are not defined by their class::
+
+ >>> r = schema.Role(foo="bar")
+ Traceback (most recent call last):
+ ...
+ TypeError: 'foo' is not a public attribute of 'Role' objects
+
+And, to avoid unintentional alterations, Role attributes can be set only once::
+
+ >>> Kind.superkinds.doc = "testing"
+ Traceback (most recent call last):
+ ...
+ TypeError: Role objects are immutable; can't change 'doc' once set
+
+
Creating Role Subclasses
========================
@@ -790,9 +839,9 @@
Set([Test], type=Kind)
---------------------------
-Entities and Relationships
---------------------------
+-----------
+Other Types
+-----------
Standalone Relationships
========================
@@ -843,6 +892,82 @@
TypeError: Relationships cannot be subclassed
+Enumerations
+============
+
+An enumeration is a value type with a fixed set of values, defined using
+the ``schema.Enumeration`` base class, and the ``schema.enum()`` descriptor::
+
+ >>> class Importance(schema.Enumeration):
+ ... important = schema.enum("Important")
+ ... normal = schema.enum("Normal")
+ ... fyi = schema.enum("FYI")
+
+You cannot create enumeration instances directly::
+
+ >>> Importance("x")
+ Traceback (most recent call last):
+ ...
+ TypeError: Enumeration instances cannot be created directly
+
+Instead, you must access instances via the class' attributes::
+
+ >>> Importance.normal
+ Importance.normal
+
+ >>> getattr(Importance,'normal')
+ Importance.normal
+
+Instances hash and compare equal to the string (or unicode) values assigned in
+the class definition, and when converted to a string or unicode, they use that
+same value::
+
+ >>> print Importance.important
+ Important
+
+ >>> unicode(Importance.important)
+ u'Important'
+
+ >>> str(Importance.important)
+ 'Important'
+
+ >>> Importance.important == "Important"
+ True
+
+ >>> hash(Importance.important) == hash("Important")
+ True
+
+Note, however, that this means each enumeration value's display name must be
+unique within that enumeration type, and enumeration subclasses are not
+allowed::
+
+ >>> class BadEnum(schema.Enumeration):
+ ... foo = schema.enum("Spam")
+ ... bar = schema.enum("Spam")
+ Traceback (most recent call last):
+ ...
+ File "<doctest schema.txt[...]>", line 1, in ?
+ class BadEnum(schema.Enumeration):
+ ...
+ TypeError: Duplicate definitions: bar=enum(u'Spam') and foo=enum(u'Spam')
+
+ >>> class MoreImportant(Importance):
+ ... super_urgent = schema.enum("Emergency!!!")
+ Traceback (most recent call last):
+ ...
+ File "<doctest schema.txt[...]>", line 1, in ?
+ class MoreImportant(Importance):
+ ...
+ TypeError: Enumerations cannot be subclassed
+
+Finally, note that enumeration types are iterable, and they yield their
+instances when iterated over, so that you can use this information
+programmatically::
+
+ >>> sorted(Importance)
+ [Importance.fyi, Importance.important, Importance.normal]
+
+
---------
Internals
---------
@@ -966,7 +1091,7 @@
>>> schema.Role().__reduce_ex__() # doctest: +NORMALIZE_WHITESPACE
(<...>, (<class '...schema.Role'>, <type 'object'>, None),
- {'types': (), '_loadMap': {}})
+ {'__doc__': 'Role(())', 'types': (), '_loadMap': {}})
Type Flattening
@@ -1014,9 +1139,6 @@
* Role knows its requiredness, and other metadata (policies?)
-* Role.__setattr__ doesn't allow setting unless current value is None/unset, or
- new value equals old value (addType should bypass using __set__)
-
Entity Classes
More information about the Commits
mailing list