Merge lp://staging/~gary/lazr.enum/devel into lp://staging/~leonardr/lazr.enum/trunk

Proposed by Gary Poster
Status: Merged
Approved by: Francis J. Lacoste
Approved revision: 26
Merge reported by: Robert Collins
Merged at revision: not available
Proposed branch: lp://staging/~gary/lazr.enum/devel
Merge into: lp://staging/~leonardr/lazr.enum/trunk
Diff against target: None lines
To merge this branch: bzr merge lp://staging/~gary/lazr.enum/devel
Reviewer Review Type Date Requested Status
Francis J. Lacoste (community) Approve
LAZR Developers Pending
Review via email: mp+4194@code.staging.launchpad.net
To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Initial checkin of code migrated from Launchpad.

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

Hi Gary,

This is good. I have a couple of small fixes:

- You have a bunch of files missing a newline at the end:

  src/lazr/enum/configure.zcml
  src/lazr/enum/interfaces.py
  src/lazr/enum/tests/test_proxy.py

- There is a comment that needs fixing in

> 656 +# The enumerated_type_registry is a mapping of all enumerated types to the
> 657 +# actual class. There should only ever be one EnumeratedType or
> 658 +# DBEnumerateType with a particular name. This serves two purposes:
> 659 +# * a way to get any enumerated type by its name (used in tales.py)
> 660 +# * a way to iterate over the DBEnumeratedTypes in order to confirm the
> 661 +# values actually stored in the database.
> 662 +enumerated_type_registry = {}

The reference to tales.py isn't pertinennt anymore.

You probably want to keep a __version__ in __init__.py ?

Thanks

review: Approve
lp://staging/~gary/lazr.enum/devel updated
27. By Gary Poster

address review: new lines, comment clean-up

Revision history for this message
Gary Poster (gary) wrote :

Cool, thanks.

I addressed the first two (r27). We discussed the __version__: it is in setup.py because __init__.py imports something and setup.py was trying to use __init__.py for the __version__ so you couldn't build the package.

Gary

lp://staging/~gary/lazr.enum/devel updated
28. By Gary Poster

update top-level README with one-liner description

Revision history for this message
Tim Penhey (thumper) wrote :

Is this branch still valid or shall we reject it?

Revision history for this message
Gary Poster (gary) wrote :

On Apr 1, 2009, at 3:08 PM, Tim Penhey wrote:

> Is this branch still valid or shall we reject it?

Sorry, just saw this. The branch has been merged into the trunk we
care about. It does not need to be merged into leonardr's branch.

I'll try to reject/retract it. Thanks.

Gary

Revision history for this message
Gary Poster (gary) wrote :

I wrote:
> I'll try to reject/retract it. Thanks.

I can see how to delete it, but that deletes the comments too, which are in fact pertinent since they affected the merge into the actual branch.

Should I just delete it, do you suppose, or do you have a better idea, Tim?

Thanks

Gary

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On Tuesday 21 April 2009, Gary Poster wrote:
> I wrote:
> > I'll try to reject/retract it. Thanks.
>
> I can see how to delete it, but that deletes the comments too, which are in
> fact pertinent since they affected the merge into the actual branch.
>
> Should I just delete it, do you suppose, or do you have a better idea, Tim?
>

I think you can just use 'Edit' to set the status of the branch to 'merged'.

The merge proposal could be set to an appropriate status too (Rejected
probably).

--
Francis J. Lacoste
<email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'buildout.cfg'
2--- buildout.cfg 2009-03-04 14:22:39 +0000
3+++ buildout.cfg 2009-03-05 05:27:41 +0000
4@@ -2,6 +2,7 @@
5 parts =
6 interpreter
7 test
8+ test_proxy
9 tags
10 unzip = true
11
12@@ -10,7 +11,12 @@
13 [test]
14 recipe = zc.recipe.testrunner
15 eggs = lazr.enum
16-defaults = '--tests-pattern ^tests --exit-with-status'.split()
17+defaults = '--tests-pattern ^test_documentation --exit-with-status'.split()
18+
19+[test_proxy]
20+recipe = zc.recipe.testrunner
21+eggs = lazr.enum[proxy]
22+defaults = '--tests-pattern ^test_proxy --exit-with-status'.split()
23
24 [interpreter]
25 recipe = zc.recipe.egg
26
27=== modified file 'setup.py'
28--- setup.py 2009-03-04 14:22:39 +0000
29+++ setup.py 2009-03-05 05:27:41 +0000
30@@ -34,7 +34,7 @@
31
32
33 sys.path.insert(0, 'src')
34-from lazr.enum import __version__
35+__version__ = '0.1'
36
37 setup(
38 name='lazr.enum',
39@@ -54,7 +54,11 @@
40 install_requires=[
41 'setuptools',
42 'zope.interface',
43+ 'zope.schema',
44 ],
45+ extras_require={
46+ 'proxy': ['zope.security']
47+ },
48 url='https://launchpad.net/lazr.enum',
49 classifiers=[
50 "Development Status :: 5 - Production/Stable",
51
52=== modified file 'src/lazr/enum/README.txt'
53--- src/lazr/enum/README.txt 2009-03-04 14:22:39 +0000
54+++ src/lazr/enum/README.txt 2009-03-04 23:07:36 +0000
55@@ -14,9 +14,366 @@
56 You should have received a copy of the GNU Lesser General Public License
57 along with lazr.enum. If not, see <http://www.gnu.org/licenses/>.
58
59-LAZR enum
60-***********
61-
62-This is a pure template for new lazr namespace packages. Whenever you want to
63-make a new lazr subpackage, just branch this code and change all occurrences
64-of 'enum' with whatever your subpackage's name is.
65+Enumerated Types
66+****************
67+
68+Enumerated types are used primarily in two distinct places in the Launchpad
69+code: selector types; and database types.
70+
71+Simple enumerated types do not have values, whereas database enumerated
72+types are a mapping from an integer value to something meaningful in the
73+code.
74+
75+ >>> from lazr.enum import (
76+ ... EnumeratedType, DBEnumeratedType, Item, DBItem, use_template)
77+
78+The `enum` values of EnumeratedTypes are instances of Item.
79+
80+ >>> class Fruit(EnumeratedType):
81+ ... "A choice of fruit."
82+ ... APPLE = Item('Apple')
83+ ... PEAR = Item('Pear')
84+ ... ORANGE = Item('Orange')
85+
86+===================
87+IVocabulary support
88+===================
89+
90+Enumerated types support IVocabularyTokenized.
91+
92+ >>> from zope.interface.verify import verifyObject
93+ >>> from zope.schema.interfaces import (
94+ ... ITitledTokenizedTerm, IVocabularyTokenized)
95+ >>> verifyObject(IVocabularyTokenized, Fruit)
96+ True
97+
98+The items themselves do not support any interface. Items returned
99+by the methods for vocabularies return wrapped items that support
100+the ITitledTokenizedTerm interface.
101+
102+The token used to identify terms in the vocabulary is the name of the
103+Item variable.
104+
105+ >>> item = Fruit.getTermByToken('APPLE')
106+ >>> type(item)
107+ <class 'lazr.enum...TokenizedItem'>
108+ >>> verifyObject(ITitledTokenizedTerm, item)
109+ True
110+
111+TokenizedItems have three attributes (in order to support
112+ITitledTokenizedTerm):
113+
114+ >>> item.value
115+ <Item Fruit.APPLE, Apple>
116+ >>> item.token
117+ 'APPLE'
118+ >>> item.title
119+ 'Apple'
120+
121+The length of an EnumeratedType returns the number of items it has.
122+
123+ >>> print len(Fruit)
124+ 3
125+
126+===========================
127+The EnumeratedType registry
128+===========================
129+
130+All enumerated types that are created are added to the
131+enumerated_type_registry.
132+
133+ >>> from lazr.enum import enumerated_type_registry
134+
135+The enumerated_type_registry maps the name of the enumerated type to the type
136+itself.
137+
138+ >>> 'Fruit' in enumerated_type_registry
139+ True
140+ >>> enumerated_type_registry['Fruit']
141+ <EnumeratedType 'Fruit'>
142+
143+You cannot redefine an existing enumerated type, nor create another enumerated
144+type with the same name as an existing type.
145+
146+ >>> class BranchType(EnumeratedType):
147+ ... BAR = Item('Bar')
148+ ...
149+ >>> BranchType.name = 'AltBranchType'
150+ >>> class BranchType(EnumeratedType):
151+ ... FOO = Item('Foo')
152+ Traceback (most recent call last):
153+ ...
154+ TypeError: An enumerated type already exists with the name BranchType
155+ (...AltBranchType).
156+
157+======================
158+Enumerated Type basics
159+======================
160+
161+An EnumeratedType has a name and a description. The name is the same as the
162+class name, and the description is the docstring for the class.
163+
164+ >>> print Fruit.name
165+ Fruit
166+ >>> print Fruit.description
167+ A choice of fruit.
168+
169+If you do not specify an explicit sort_order for the items of the
170+EnumeratedType one is created for you. This is tuple of the tokens.
171+
172+ >>> print Fruit.sort_order
173+ ('APPLE', 'PEAR', 'ORANGE')
174+
175+The items of an enumerated type can be iterated over. However the type that
176+is returned by the iteration is the TokenizedItem, not the item itself.
177+
178+ >>> for item in Fruit:
179+ ... print item.token, item.title
180+ APPLE Apple
181+ PEAR Pear
182+ ORANGE Orange
183+
184+Items in an enumerator support comparison and equality checks. Comparison
185+is based on the sort order of the items.
186+
187+ >>> apple = Fruit.APPLE
188+ >>> pear = Fruit.PEAR
189+ >>> orange = Fruit.ORANGE
190+ >>> apple < pear
191+ True
192+ >>> apple == pear
193+ False
194+ >>> apple == apple
195+ True
196+ >>> apple != pear
197+ True
198+ >>> apple > pear
199+ False
200+ >>> pear < orange
201+ True
202+ >>> apple < orange
203+ True
204+
205+The string representation of an Item is the title, and the representation
206+also shows the enumeration that the Item is from.
207+
208+ >>> print apple
209+ Apple
210+ >>> print repr(apple)
211+ <Item Fruit.APPLE, Apple>
212+
213+The `items` attribute of an enumerated type is not a list, but a class that
214+provides iteration over the items, and access to the Item attributes through
215+either the name of the Item, or the database value if there is one.
216+
217+The primary use of this is to provide a backwards compatable accessor for items,
218+but it also provides a suitable alternative to getattr
219+
220+ >>> name = 'APPLE'
221+ >>> Fruit.items[name]
222+ <Item Fruit.APPLE, Apple>
223+ >>> getattr(Fruit, name)
224+ <Item Fruit.APPLE, Apple>
225+
226+=========================
227+Database Enumerated Types
228+=========================
229+
230+A very common use of enumerated types are to give semantic meaning to integer
231+values stored in database columns. EnumeratedType Items themselves don't have
232+any integer values.
233+
234+The DBEnumeratedType provides the semantic framework for a type that is used to
235+map integer values to python enumerated values.
236+
237+ >>> # Remove the existing reference to BranchType from the registry
238+ >>> del enumerated_type_registry['BranchType']
239+ >>> class BranchType(DBEnumeratedType):
240+ ... HOSTED = DBItem(1, """
241+ ... Hosted
242+ ...
243+ ... Hosted braches use the supermirror as the main repository
244+ ... for the branch.""")
245+ ...
246+ ... MIRRORED = DBItem(2, """
247+ ... Mirrored
248+ ...
249+ ... Mirrored branches are "pulled" from a remote location.""")
250+ ...
251+ ... IMPORTED = DBItem(3, """
252+ ... Imported
253+ ...
254+ ... Imported branches are natively maintained in CVS or SVN""")
255+
256+Note carefully that the value of a DBItem is the integer represenation. But the
257+value of the TokenizedItem is the DBItem itself.
258+
259+ >>> hosted = BranchType.HOSTED
260+ >>> hosted.value
261+ 1
262+ >>> hosted == BranchType.HOSTED
263+ True
264+ >>> tokenized_item = BranchType.getTermByToken('HOSTED')
265+ >>> tokenized_item.value
266+ <DBItem BranchType.HOSTED, (1) Hosted>
267+
268+DBEnumeratedTypes also support IVocabularyTokenized
269+
270+ >>> verifyObject(IVocabularyTokenized, BranchType)
271+ True
272+
273+The items attribute of DBEnumeratedTypes provide a mapping from the database
274+values to the DBItems.
275+
276+ >>> BranchType.items[3]
277+ <DBItem BranchType.IMPORTED, (3) Imported>
278+
279+Items in a DBEnumeratedType class must be of type DBItem.
280+
281+ >>> class BadItemType(DBEnumeratedType):
282+ ... TESTING = Item("Testing")
283+ Traceback (most recent call last):
284+ ...
285+ TypeError: Items must be of the appropriate type for the DBEnumeratedType,
286+ __builtin__.BadItemType.TESTING
287+
288+You are not able to define a DBEnumeratedType that has two different
289+DBItems that map to the same numeric value.
290+
291+ >>> class TwoMapping(DBEnumeratedType):
292+ ... FIRST = DBItem(42, 'First')
293+ ... SECOND = DBItem(42, 'Second')
294+ Traceback (most recent call last):
295+ ...
296+ TypeError: Two DBItems with the same value 42 (FIRST, SECOND)
297+
298+=========================
299+Overriding the sort order
300+=========================
301+
302+By default the sort order of the items in an enumerated type is defined by the
303+order in which the Items are declared. This my be overridden by specifying
304+a sort_order attribute in the class.
305+
306+If a sort_order is specified, it has to specify every item in the enumeration.
307+
308+ >>> class AnimalClassification(EnumeratedType):
309+ ... sort_order = "REPTILE", "INSECT", "MAMMAL"
310+ ... INSECT = Item("Insect")
311+ ... MAMMAL = Item("Mammal")
312+ ... FISH = Item("Fish")
313+ ... REPTILE = Item("Reptile")
314+ Traceback (most recent call last):
315+ ...
316+ TypeError: sort_order for EnumeratedType must contain all and only Item instances ...
317+
318+The sort_order may also appear anywhere in the definition of the class, although
319+convention has it that it appears first, before the Item instances.
320+
321+ >>> class AnimalClassification(EnumeratedType):
322+ ... sort_order = "REPTILE", "FISH", "INSECT", "MAMMAL"
323+ ... INSECT = Item("Insect")
324+ ... MAMMAL = Item("Mammal")
325+ ... FISH = Item("Fish")
326+ ... REPTILE = Item("Reptile")
327+
328+The items attribute of the enumerated type is ordered based on the sort_order.
329+The items attribute is also used to control iteration using __iter__.
330+
331+ >>> for item in AnimalClassification.items:
332+ ... print item.title
333+ Reptile
334+ Fish
335+ Insect
336+ Mammal
337+
338+The sort order also drives the comparison operations.
339+
340+ >>> reptile, fish, insect, mammal = AnimalClassification.items
341+ >>> reptile < fish < insect < mammal
342+ True
343+
344+==========================
345+Extending enumerated types
346+==========================
347+
348+The simplest way to extend a class is to derive from it.
349+
350+ >>> class AnimalClassificationExtended(AnimalClassification):
351+ ... INVERTEBRATE = Item("Invertebrate")
352+
353+ >>> for item in AnimalClassificationExtended:
354+ ... print item.title
355+ Reptile
356+ Fish
357+ Insect
358+ Mammal
359+ Invertebrate
360+
361+The use_template function inserts the items from the specified enumerated type
362+into the new enumerated type. The default case is to take all the enumerated
363+items.
364+
365+ >>> class UIBranchType(EnumeratedType):
366+ ... use_template(BranchType)
367+ >>> for item in UIBranchType:
368+ ... print item.title
369+ Hosted
370+ Mirrored
371+ Imported
372+
373+You can also specify items to be excluded by referring to the attribute name
374+in the exclude parameter. This can be either a string referring to one name
375+or a tuple or list that refers to multiple attribute names.
376+
377+ >>> class UIBranchType2(EnumeratedType):
378+ ... use_template(BranchType, exclude='IMPORTED')
379+ >>> for item in UIBranchType2:
380+ ... print item.title
381+ Hosted
382+ Mirrored
383+
384+Or limit the items to those specified:
385+
386+ >>> class UIBranchType3(EnumeratedType):
387+ ... use_template(BranchType, include=('HOSTED', 'MIRRORED'))
388+ >>> for item in UIBranchType3:
389+ ... print item.title
390+ Hosted
391+ Mirrored
392+
393+================================================
394+Getting from an item back to the enumerated type
395+================================================
396+
397+Each Item in an EnumeratedType has a reference back to the EnumeratedType.
398+
399+ >>> print repr(apple)
400+ <Item Fruit.APPLE, Apple>
401+ >>> print repr(apple.enum)
402+ <EnumeratedType 'Fruit'>
403+ >>> for item in apple.enum:
404+ ... print item.title
405+ Apple
406+ Pear
407+ Orange
408+
409+============
410+Item.sortkey
411+============
412+
413+The sortkey attribute of the Items are defined by the sort_order that is
414+defined for the enumerated type. The value is often used as a hidden value
415+in columns in order to ensure appropriate sorting.
416+
417+ >>> for item in Fruit.items:
418+ ... print item.title, item.sortkey
419+ Apple 0
420+ Pear 1
421+ Orange 2
422+
423+ >>> for item in BranchType.items:
424+ ... print item.title, item.sortkey
425+ Hosted 0
426+ Mirrored 1
427+ Imported 2
428
429=== modified file 'src/lazr/enum/__init__.py'
430--- src/lazr/enum/__init__.py 2009-03-04 14:22:39 +0000
431+++ src/lazr/enum/__init__.py 2009-03-05 05:27:41 +0000
432@@ -1,4 +1,4 @@
433-# Copyright 2009 Canonical Ltd. All rights reserved.
434+# Copyright 2004-2009 Canonical Ltd. All rights reserved.
435 #
436 # This file is part of lazr.enum
437 #
438@@ -15,4 +15,521 @@
439 # You should have received a copy of the GNU Lesser General Public License
440 # along with lazr.enum. If not, see <http://www.gnu.org/licenses/>.
441
442-__version__ = '0.1'
443+__metaclass__ = type
444+
445+import itertools
446+import operator
447+import sys
448+import warnings
449+
450+from zope.interface import implements
451+from zope.schema.interfaces import ITitledTokenizedTerm, IVocabularyTokenized
452+try:
453+ from zope.proxy import removeAllProxies
454+except ImportError:
455+ removeAllProxies = lambda obj: obj # no-op
456+
457+from lazr.enum.interfaces import IEnumeratedType
458+
459+__all__ = [
460+ 'BaseItem',
461+ 'DBEnumeratedType',
462+ 'DBItem',
463+ 'EnumeratedType',
464+ 'IEnumeratedType',
465+ 'Item',
466+ 'TokenizedItem',
467+ 'enumerated_type_registry',
468+ 'use_template',
469+ ]
470+
471+def proxy_isinstance(obj, cls):
472+ """Test whether an object is an instance of a type.
473+
474+ This works even if the object is proxied by zope.proxy, if that package
475+ is available.
476+ """
477+ return isinstance(removeAllProxies(obj), cls)
478+
479+def docstring_to_title_descr(string):
480+ """When given a classically formatted docstring, returns a tuple
481+ (title, description).
482+
483+ >>> class Foo:
484+ ... '''
485+ ... Title of foo
486+ ...
487+ ... Description of foo starts here. It may
488+ ... spill onto multiple lines. It may also have
489+ ... indented examples:
490+ ...
491+ ... Foo
492+ ... Bar
493+ ...
494+ ... like the above.
495+ ... '''
496+ ...
497+ >>> title, descr = docstring_to_title_descr(Foo.__doc__)
498+ >>> print title
499+ Title of foo
500+ >>> for num, line in enumerate(descr.splitlines()):
501+ ... print "%d.%s" % (num, line)
502+ ...
503+ 0.Description of foo starts here. It may
504+ 1.spill onto multiple lines. It may also have
505+ 2.indented examples:
506+ 3.
507+ 4. Foo
508+ 5. Bar
509+ 6.
510+ 7.like the above.
511+
512+ """
513+ lines = string.splitlines()
514+ # title is the first non-blank line
515+ for num, line in enumerate(lines):
516+ line = line.strip()
517+ if line:
518+ title = line
519+ break
520+ else:
521+ raise ValueError
522+ assert not lines[num+1].strip()
523+ descrlines = lines[num+2:]
524+ descr1 = descrlines[0]
525+ indent = len(descr1) - len(descr1.lstrip())
526+ descr = '\n'.join([line[indent:] for line in descrlines])
527+ return title, descr
528+
529+
530+class BaseItem:
531+ """Items are the primary elements of the enumerated types.
532+
533+ `BaseItem` is the base class for both `Item` and `DBItem`.
534+
535+ The enum attribute is a reference to the enumerated type that the
536+ Item is a member of.
537+
538+ The token attribute is the name assigned to the item.
539+
540+ The value is the short text string used to identify the item.
541+ """
542+
543+ sortkey = 0
544+ name = None
545+ description = None
546+ title = None
547+
548+ def __init__(self, title, description=None):
549+ """Items are the main elements of the EnumeratedType.
550+
551+ Where the title is passed in without a description,
552+ and the title looks like a docstring (has embedded carriage returns),
553+ the title is the first line, and the description is the rest.
554+ """
555+
556+ self.sortkey = BaseItem.sortkey
557+ BaseItem.sortkey += 1
558+ self.title = title
559+ # The enum attribute is set duing the class constructor of the
560+ # containing enumerated type.
561+
562+ self.description = description
563+
564+ if self.description is None:
565+ # check value
566+ if self.title.find('\n') != -1:
567+ self.title, self.description = docstring_to_title_descr(
568+ self.title)
569+
570+ def __int__(self):
571+ raise TypeError("Cannot cast Item to int.")
572+
573+ def __cmp__(self, other):
574+ if proxy_isinstance(other, BaseItem):
575+ return cmp(self.sortkey, other.sortkey)
576+ else:
577+ raise TypeError(
578+ 'Comparisons of Items are only valid with other Items')
579+
580+ def __eq__(self, other, stacklevel=2):
581+ if isinstance(other, int):
582+ warnings.warn('comparison of Item to an int: %r' % self,
583+ stacklevel=stacklevel)
584+ return False
585+ elif proxy_isinstance(other, BaseItem):
586+ return (self.name == other.name and
587+ self.enum == other.enum)
588+ else:
589+ return False
590+
591+ def __ne__(self, other):
592+ return not self.__eq__(other, stacklevel=3)
593+
594+ def __hash__(self):
595+ return hash(self.title)
596+
597+ def __str__(self):
598+ return str(self.title)
599+
600+
601+class Item(BaseItem):
602+ """The `Item` is an element of an `EnumeratedType`."""
603+ @staticmethod
604+ def construct(other_item):
605+ """Create an Item based on the other_item."""
606+ item = Item(other_item.title, other_item.description)
607+ item.sortkey = other_item.sortkey
608+ return item
609+
610+ def __repr__(self):
611+ return "<Item %s.%s, %s>" % (
612+ self.enum.name, self.name, self.title)
613+
614+
615+class DBItem(BaseItem):
616+ """The `DBItem` refers to an enumerated item that is used in the database.
617+
618+ Database enumerations are stored in the database using integer columns.
619+ """
620+
621+ @staticmethod
622+ def construct(other_item):
623+ """Create an Item based on the other_item."""
624+ item = DBItem(
625+ other_item.value, other_item.title, other_item.description)
626+ item.sortkey = other_item.sortkey
627+ return item
628+
629+ def __init__(self, value, title, description=None):
630+ BaseItem.__init__(self, title, description)
631+ self.value = value
632+
633+ def __hash__(self):
634+ return self.value
635+
636+ def __repr__(self):
637+ return "<DBItem %s.%s, (%d) %s>" % (
638+ self.enum.name, self.name, self.value, self.title)
639+
640+ # XXX this is probably going away.
641+ def __sqlrepr__(self, dbname):
642+ return repr(self.value)
643+
644+
645+class TokenizedItem:
646+ """Wraps an `Item` or `DBItem` to provide `ITitledTokenizedTerm`."""
647+
648+ implements(ITitledTokenizedTerm)
649+
650+ def __init__(self, item):
651+ self.value = item
652+ self.token = item.name
653+ self.title = item.title
654+
655+
656+# The enumerated_type_registry is a mapping of all enumerated types to the
657+# actual class. There should only ever be one EnumeratedType or
658+# DBEnumerateType with a particular name. This serves two purposes:
659+# * a way to get any enumerated type by its name (used in tales.py)
660+# * a way to iterate over the DBEnumeratedTypes in order to confirm the
661+# values actually stored in the database.
662+enumerated_type_registry = {}
663+
664+
665+class EnumItems:
666+ """Allow access to Items of an enumerated type using names or db values.
667+
668+ Access can be made to the items using the name of the Item.
669+
670+ If the enumerated type has DBItems then the mapping includes a mapping of
671+ the database integer values to the DBItems.
672+ """
673+ def __init__(self, items, mapping):
674+ self.items = items
675+ self.mapping = mapping
676+ def __getitem__(self, key):
677+ if key in self.mapping:
678+ return self.mapping[key]
679+ else:
680+ raise KeyError(key)
681+ def __iter__(self):
682+ return self.items.__iter__()
683+ def __len__(self):
684+ return len(self.items)
685+
686+
687+class BaseMetaEnum(type):
688+ """The metaclass functionality for `EnumeratedType` and `DBEnumeratedType`.
689+
690+ This metaclass defines methods that allow the enumerated types to implement
691+ the IVocabularyTokenized interface.
692+
693+ The metaclass also enforces "correct" definitions of enumerated types by
694+ enforcing capitalisation of Item variable names and defining an appropriate
695+ ordering.
696+ """
697+ implements(IEnumeratedType, IVocabularyTokenized)
698+
699+ @classmethod
700+ def _enforceSingleInheritance(cls, classname, bases, classdict):
701+ """Only one base class is allowed for enumerated types."""
702+ if len(bases) > 1:
703+ raise TypeError(
704+ 'Multiple inheritance is not allowed with '
705+ '%s, %s.%s' % (
706+ cls.enum_name, classdict['__module__'], classname))
707+
708+ @classmethod
709+ def _updateClassDictWithBaseItems(cls, bases, classdict):
710+ """Copy each of the items from the base class that hasn't been
711+ explicitly defined in the new class."""
712+ if bases:
713+ base_class = bases[0]
714+ if hasattr(base_class, 'items'):
715+ for item in base_class.items:
716+ if item.name not in classdict:
717+ new_item = cls.item_type.construct(item)
718+ classdict[item.name] = new_item
719+
720+ @classmethod
721+ def _updateClassDictWithTemplateItems(cls, classdict):
722+ """If constructed through use_template, we need to construct
723+ the appropriate type of items based on our item_type of our class."""
724+ if 'template_items' in classdict:
725+ for item in classdict['template_items']:
726+ classdict[item.name] = cls.item_type.construct(item)
727+ # The template_items key is not wanted or needed in the new type.
728+ del classdict['template_items']
729+
730+ @classmethod
731+ def _enforceItemClassAndName(cls, items, classname, module_name):
732+ """All items must be of the appropriate type for the enumeration type.
733+
734+ All item variable names must be capitalised.
735+ """
736+ for item_name, item in items:
737+ if not isinstance(item, cls.item_type):
738+ raise TypeError(
739+ 'Items must be of the appropriate type for the '
740+ '%s, %s.%s.%s' % (
741+ cls.enum_name, module_name, classname, item_name))
742+
743+ if item_name.upper() != item_name:
744+ raise TypeError(
745+ 'Item instance variable names must be capitalised.'
746+ ' %s.%s.%s' % (module_name, classname, item_name))
747+
748+ item.name = item_name
749+
750+ @classmethod
751+ def _generateItemMapping(cls, items):
752+ """Each enumerated type has a mapping of the item names to the item
753+ instances."""
754+ return dict(items)
755+
756+ @classmethod
757+ def _enforceSortOrder(cls, classname, classdict, items):
758+ """ Override item's default sort order if sort_order is defined.
759+
760+ :return: A list of items ordered appropriately.
761+ """
762+ items = dict(items)
763+ if 'sort_order' in classdict:
764+ sort_order = classdict['sort_order']
765+ item_names = sorted(items.keys())
766+ if item_names != sorted(sort_order):
767+ raise TypeError(
768+ 'sort_order for %s must contain all and '
769+ 'only Item instances %s.%s' % (
770+ cls.enum_name, classdict['__module__'], classname))
771+ else:
772+ # Sort the items by the automatically generated
773+ # sortkey.
774+ sort_order = [
775+ item.name for item in sorted(
776+ items.values(), key=operator.attrgetter('sortkey'))]
777+ classdict['sort_order'] = tuple(sort_order)
778+ # Assign new sortkey values from zero.
779+ sorted_items = []
780+ for sort_id, item_name in enumerate(sort_order):
781+ item = classdict[item_name]
782+ item.sortkey = sort_id
783+ sorted_items.append(item)
784+ return sorted_items
785+
786+ def __new__(cls, classname, bases, classdict):
787+ """Called when defining a new class."""
788+
789+ cls._enforceSingleInheritance(classname, bases, classdict)
790+ cls._updateClassDictWithBaseItems(bases, classdict)
791+ cls._updateClassDictWithTemplateItems(classdict)
792+
793+ items = [(key, value) for key, value in classdict.iteritems()
794+ if isinstance(value, BaseItem)]
795+
796+ cls._enforceItemClassAndName(items, classname, classdict['__module__'])
797+
798+ mapping = cls._generateItemMapping(items)
799+ sorted_items = cls._enforceSortOrder(classname, classdict, items)
800+
801+ classdict['items'] = EnumItems(sorted_items, mapping)
802+ classdict['name'] = classname
803+ classdict['description'] = classdict.get('__doc__', None)
804+
805+ global enumerated_type_registry
806+ if classname in enumerated_type_registry:
807+ other = enumerated_type_registry[classname]
808+ raise TypeError(
809+ 'An enumerated type already exists with the name %s (%s.%s).'
810+ % (classname, other.__module__, other.name))
811+
812+ instance = type.__new__(cls, classname, bases, classdict)
813+
814+ # Add a reference to the enumerated type to each item.
815+ for item in instance.items:
816+ item.enum = instance
817+
818+ # Add the enumerated type to the registry.
819+ enumerated_type_registry[classname] = instance
820+
821+ return instance
822+
823+ def __contains__(self, value):
824+ """See `ISource`."""
825+ return value in self.items
826+
827+ def __iter__(self):
828+ """See `IIterableVocabulary`."""
829+ return itertools.imap(TokenizedItem, self.items)
830+
831+ def __len__(self):
832+ """See `IIterableVocabulary`."""
833+ return len(self.items)
834+
835+ def getTerm(self, value):
836+ """See `IBaseVocabulary`."""
837+ if value in self.items:
838+ return TokenizedItem(value)
839+ raise LookupError(value)
840+
841+ def getTermByToken(self, token):
842+ """See `IVocabularyTokenized`."""
843+ # The sort_order of the enumerated type lists all the items.
844+ if token in self.sort_order:
845+ return TokenizedItem(getattr(self, token))
846+ else:
847+ # If the token is not specified in the sort order then check
848+ # the titles of the items. This is to support the transition
849+ # of accessing items by their titles. To continue support
850+ # of old URLs et al, this will probably stay for some time.
851+ for item in self.items:
852+ if item.title == token:
853+ return TokenizedItem(item)
854+ # The token was not in the sort_order (and hence the name of a
855+ # variable), nor was the token the title of one of the items.
856+ raise LookupError(token)
857+
858+
859+class MetaEnum(BaseMetaEnum):
860+ """The metaclass for `EnumeratedType`."""
861+ item_type = Item
862+ enum_name = 'EnumeratedType'
863+
864+ def __repr__(self):
865+ return "<EnumeratedType '%s'>" % self.name
866+
867+
868+class MetaDBEnum(BaseMetaEnum):
869+ """The meta class for `DBEnumeratedType`.
870+
871+ Provides a method for getting the item based on the database identifier in
872+ addition to all the normal enumerated type methods.
873+ """
874+ item_type = DBItem
875+ enum_name = 'DBEnumeratedType'
876+
877+ @classmethod
878+ def _generateItemMapping(cls, items):
879+ """DBEnumeratedTypes also map the database value of the DBItem to the
880+ item instance."""
881+ mapping = BaseMetaEnum._generateItemMapping(items)
882+ for item_name, item in items:
883+ # If the value is already in the mapping then we have two
884+ # different items attempting to map the same number.
885+ if item.value in mapping:
886+ # We really want to provide the names in alphabetical order.
887+ args = [item.value] + sorted(
888+ [item_name, mapping[item.value].name])
889+ raise TypeError(
890+ 'Two DBItems with the same value %s (%s, %s)'
891+ % tuple(args))
892+ else:
893+ mapping[item.value] = item
894+ return mapping
895+
896+ def __repr__(self):
897+ return "<DBEnumeratedType '%s'>" % self.name
898+
899+
900+class EnumeratedType:
901+ """An enumeration of items.
902+
903+ The items of the enumeration must be instances of the class `Item`.
904+ These items are accessible through a class attribute `items`. The ordering
905+ of the items attribute is the same order that the items are defined in the
906+ class.
907+
908+ A `sort_order` attribute can be defined to override the default ordering.
909+ The sort_order should contain the names of the all the items in the
910+ ordering that is desired.
911+ """
912+ __metaclass__ = MetaEnum
913+
914+
915+class DBEnumeratedType:
916+ """An enumeration with additional mapping from an integer to `Item`.
917+
918+ The items of a class inheriting from DBEnumeratedType must be of type
919+ `DBItem`.
920+ """
921+ __metaclass__ = MetaDBEnum
922+
923+
924+def use_template(enum_type, include=None, exclude=None):
925+ """An alternative way to extend an enumerated type other than inheritance.
926+
927+ The parameters include and exclude should either be the name values of the
928+ items (the parameter names), or a list or tuple that contains string
929+ values.
930+ """
931+ frame = sys._getframe(1)
932+ locals = frame.f_locals
933+
934+ # Try to make sure we were called from a class def.
935+ if (locals is frame.f_globals) or ('__module__' not in locals):
936+ raise TypeError(
937+ "use_template can be used only from a class definition.")
938+
939+ # You can specify either includes or excludes, not both.
940+ if include and exclude:
941+ raise ValueError("You can specify includes or excludes not both.")
942+
943+ if include is None:
944+ items = enum_type.items
945+ else:
946+ if isinstance(include, str):
947+ include = [include]
948+ items = [item for item in enum_type.items if item.name in include]
949+
950+ if exclude is None:
951+ exclude = []
952+ elif isinstance(exclude, str):
953+ exclude = [exclude]
954+
955+ template_items = []
956+ for item in items:
957+ if item.name not in exclude:
958+ template_items.append(item)
959+
960+ locals['template_items'] = template_items
961
962=== added file 'src/lazr/enum/configure.zcml'
963--- src/lazr/enum/configure.zcml 1970-01-01 00:00:00 +0000
964+++ src/lazr/enum/configure.zcml 2009-03-05 05:27:41 +0000
965@@ -0,0 +1,23 @@
966+<configure
967+ xmlns="http://namespaces.zope.org/zope"
968+ xmlns:browser="http://namespaces.zope.org/browser"
969+ i18n_domain="lazr.enum">
970+
971+ <!-- Enumerated types and items -->
972+ <class class="lazr.enum.MetaEnum">
973+ <allow interface="zope.schema.interfaces.IVocabularyTokenized" />
974+ <allow interface="lazr.enum.IEnumeratedType" />
975+ </class>
976+ <class class="lazr.enum.Item">
977+ <allow attributes="name title description enum sortkey __sqlrepr__" />
978+ </class>
979+
980+ <class class="lazr.enum.MetaDBEnum">
981+ <allow interface="zope.schema.interfaces.IVocabularyTokenized" />
982+ <allow interface="lazr.enum.IEnumeratedType" />
983+ </class>
984+ <class class="lazr.enum.DBItem">
985+ <allow attributes="name title value description sortkey enum __sqlrepr__" />
986+ </class>
987+
988+</configure>
989\ No newline at end of file
990
991=== removed directory 'src/lazr/enum/docs'
992=== removed file 'src/lazr/enum/docs/__init__.py'
993=== removed file 'src/lazr/enum/docs/basic.txt'
994--- src/lazr/enum/docs/basic.txt 2009-03-04 14:22:39 +0000
995+++ src/lazr/enum/docs/basic.txt 1970-01-01 00:00:00 +0000
996@@ -1,8 +0,0 @@
997-Importable
998-==========
999-
1000-The lazr.enum package is importable, and has a version number.
1001-
1002- >>> import lazr.enum
1003- >>> print 'VERSION:', lazr.enum.__version__
1004- VERSION: ...
1005
1006=== added file 'src/lazr/enum/interfaces.py'
1007--- src/lazr/enum/interfaces.py 1970-01-01 00:00:00 +0000
1008+++ src/lazr/enum/interfaces.py 2009-03-04 23:07:36 +0000
1009@@ -0,0 +1,36 @@
1010+# Copyright 2004-2009 Canonical Ltd. All rights reserved.
1011+#
1012+# This file is part of lazr.enum
1013+#
1014+# lazr.enum is free software: you can redistribute it and/or modify it
1015+# under the terms of the GNU Lesser General Public License as published by
1016+# the Free Software Foundation, either version 3 of the License, or (at your
1017+# option) any later version.
1018+#
1019+# lazr.enum is distributed in the hope that it will be useful, but WITHOUT
1020+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1021+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1022+# License for more details.
1023+#
1024+# You should have received a copy of the GNU Lesser General Public License
1025+# along with lazr.enum. If not, see <http://www.gnu.org/licenses/>.
1026+
1027+__all__ = [
1028+ 'IEnumeratedType',
1029+ ]
1030+
1031+from zope.interface import Attribute, Interface
1032+
1033+class IEnumeratedType(Interface):
1034+ """Defines the attributes that EnumeratedTypes have."""
1035+ name = Attribute(
1036+ "The name of the EnumeratedType is the same as the name of the class.")
1037+ description = Attribute(
1038+ "The description is the docstring of the EnumeratedType class.")
1039+ sort_order = Attribute(
1040+ "A tuple of Item names that is used to determine the ordering of the "
1041+ "Items.")
1042+ items = Attribute(
1043+ "An instance of `EnumItems` which allows access to the enumerated "
1044+ "types items by either name of database value if the items are "
1045+ "DBItems.")
1046\ No newline at end of file
1047
1048=== modified file 'src/lazr/enum/tests/test_documentation.py'
1049--- src/lazr/enum/tests/test_documentation.py 2009-03-03 16:14:37 +0000
1050+++ src/lazr/enum/tests/test_documentation.py 2009-03-05 05:27:41 +0000
1051@@ -11,21 +11,20 @@
1052 import doctest
1053 import unittest
1054
1055+import lazr.enum
1056+
1057 DOCTEST_FLAGS = (
1058 doctest.ELLIPSIS |
1059 doctest.NORMALIZE_WHITESPACE |
1060 doctest.REPORT_NDIFF)
1061
1062-
1063 def test_suite():
1064- # This breaks the zip-safe flag.
1065- docs_directory = os.path.normpath(os.path.join(__file__, '../../docs'))
1066- doctest_files = ['../docs/%s' % filename
1067- for filename in os.listdir(docs_directory)
1068- if filename.endswith('.txt')]
1069-
1070- kwargs = dict(
1071- module_relative=True,
1072- optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
1073+ readme = os.path.normpath(
1074+ os.path.abspath(
1075+ os.path.join(os.path.dirname(__file__),
1076+ os.path.pardir,
1077+ 'README.txt')))
1078 return unittest.TestSuite((
1079- doctest.DocFileSuite(*doctest_files, **kwargs)))
1080+ doctest.DocFileSuite(
1081+ readme, module_relative=False, optionflags=DOCTEST_FLAGS),
1082+ doctest.DocTestSuite(lazr.enum)))
1083
1084=== added file 'src/lazr/enum/tests/test_proxy.py'
1085--- src/lazr/enum/tests/test_proxy.py 1970-01-01 00:00:00 +0000
1086+++ src/lazr/enum/tests/test_proxy.py 2009-03-05 05:27:41 +0000
1087@@ -0,0 +1,44 @@
1088+# Copyright 2009 Canonical Ltd. All rights reserved.
1089+
1090+"""Test harness."""
1091+
1092+__all__ = [
1093+ 'test_suite',
1094+ ]
1095+
1096+import doctest
1097+import unittest
1098+
1099+def test_proxy_isinstance():
1100+ """
1101+ Proxies such as the zope.security proxy can mask whether an object is an
1102+ instance of a class. ``proxy_isinstance`` allows you to ask whether a
1103+ proxied object is an instance of another class.
1104+
1105+ >>> from lazr.enum import proxy_isinstance
1106+
1107+ >>> class C1(object):
1108+ ... pass
1109+
1110+ >>> c = C1()
1111+ >>> proxy_isinstance(c, C1)
1112+ True
1113+
1114+ >>> from zope.security.checker import ProxyFactory
1115+ >>> isinstance(ProxyFactory(c), C1)
1116+ False
1117+ >>> proxy_isinstance(ProxyFactory(c), C1)
1118+ True
1119+
1120+ >>> class C2(C1):
1121+ ... pass
1122+
1123+ >>> c = C2()
1124+ >>> proxy_isinstance(ProxyFactory(c), C1)
1125+ True
1126+ """
1127+
1128+
1129+def test_suite():
1130+ return unittest.TestSuite((
1131+ doctest.DocTestSuite()))
1132\ No newline at end of file

Subscribers

People subscribed via source and target branches