├── .gitignore ├── MANIFEST.in ├── README.md ├── setup.py └── src └── simplenum ├── __init__.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | *.iml 38 | .idea 39 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simple-enum 2 | =========== 3 | 4 | A simpler Enum for Python 3. 5 | 6 | * [Getting Started](#getting-started) 7 | * [The Dictionary Point Of View](#the-dictionary-point-of-view) 8 | * [The Named Tuple Point Of View](#the-named-tuple-point-of-view) 9 | * [That's It, Really](#thats-it-really) 10 | * [Advanced Use](#advanced-use) 11 | * [Retrieving Tuples](#retrieving-tuples) 12 | * [Providing Implicit Values](#providing-implicit-values) 13 | * [Providing Explicit Values](#providing-explicit-values) 14 | * [Aliases](#aliases) 15 | * [Discussion](#discussion) 16 | * [Background](#background) 17 | * [Differences With Enum](#differences-with-enum) 18 | * [The Danger Of Magic](#the-danger-of-magic) 19 | * [Technical Details](#technical-details) 20 | * [TYCDWTSE (TYCDWTSE)](#things-you-can-do-with-the-simpler-enum-that-you-cant-do-with-the-standard-enum) 21 | * [Credits](#credits) 22 | * [Legalities](#legalities) 23 | 24 | Getting Started 25 | --------------- 26 | 27 | Install `simple-enum` from 28 | [PyPI](https://pypi.python.org/pypi/simple-enum): 29 | 30 | ```sh 31 | easy_install simple-enum 32 | ``` 33 | 34 | ### The Dictionary Point Of View 35 | 36 | An Enum is an ordered, immutable dictionary. You only need to provide the 37 | names: 38 | 39 | ```python 40 | >>> from simplenum import Enum 41 | >>> class Colour(Enum): 42 | ... red 43 | ... green 44 | ... blue 45 | ... 46 | >>> list(Colour) 47 | ['red', 'green', 'blue'] 48 | >>> 'red' in Colour 49 | True 50 | ``` 51 | 52 | By default, the dictionary values are the names themselves: 53 | 54 | ```python 55 | >>> Colour['red'] 56 | 'red' 57 | ``` 58 | 59 | but you can change that using `values`: 60 | 61 | ```python 62 | >>> from simplenum import from_one 63 | >>> class Weekday(Enum, values=from_one): 64 | ... monday, tuesday, wednesday, thursday, friday, saturday, sunday 65 | ... 66 | >>> Weekday['monday'] 67 | 1 68 | ``` 69 | 70 | ### The Named Tuple Point Of View 71 | 72 | An Enum is *also* an ordered, immutable set of named tuples: 73 | 74 | ```python 75 | >>> class Colour(Enum): 76 | ... red 77 | ... green 78 | ... blue 79 | ... 80 | >>> Colour.red 81 | Colour(name='red', value='red') 82 | >>> Colour.red.name 83 | 'red' 84 | >>> Colour.red[1] 85 | 'red' 86 | >>> list(Colour.items()) 87 | [Colour(name='red', value='red'), Colour(name='green', value='green'), Colour(name='blue', value='blue')] 88 | >>> isinstance(Colour.red, Colour) 89 | True 90 | ``` 91 | 92 | As before, you can specify the `values`: 93 | 94 | ```python 95 | >>> class Weekday(Enum, values=from_one): 96 | ... monday, tuesday, wednesday, thursday, friday, saturday, sunday 97 | ... 98 | >>> Weekday.monday.value 99 | 1 100 | >>> Weekday.tuesday 101 | Weekday(name='tuesday', value=2) 102 | ``` 103 | 104 | ### That's It, Really 105 | 106 | The two points of view are consistent - you can mix and match as you please. 107 | 108 | OK, so there is a little more below. But in most cases the above should be 109 | all you need. 110 | 111 | Advanced Use 112 | ------------ 113 | 114 | ### Retrieving Tuples 115 | 116 | If you have a name, or value, you can get the tuple by calling the class: 117 | 118 | ```python 119 | >>> Weekday('wednesday') 120 | Weekday(name='wednesday', value=3) 121 | >>> Weekday(value=4).name 122 | thursday 123 | ``` 124 | 125 | ### Providing Implicit Values 126 | 127 | The `values` parameter expects a no-argument function (called once per class 128 | definition), which returns a second function from names to values. 129 | 130 | So, for example, to give random values: 131 | 132 | ```python 133 | >>> from random import random 134 | >>> def random_values(): 135 | ... def value(name): 136 | ... return random() 137 | ... return value 138 | ... 139 | >>> class Random(Enum, values=random_values): 140 | ... a, b, c 141 | ... 142 | >>> list(Random.items()) 143 | [Random(name='a', value=0.49267653329514594), Random(name='b', value=0.5521902021074088), Random(name='c', value=0.5540234367417308)] 144 | ``` 145 | 146 | ### Providing Explicit Values 147 | 148 | If you want to specify the values explicitly, use `implicit=False`: 149 | 150 | ```python 151 | >>> class Favourite(Enum, implicit=False): 152 | ... food = 'bacon' 153 | ... number = 7 154 | ... 155 | >>> Favourite.food.value 156 | bacon 157 | >>> Favourite['number'] 158 | 7 159 | ``` 160 | 161 | You can even go wild and mix things up (here we're using bit-fields via `bits`): 162 | 163 | ```python 164 | >>> class Emphasis(Enum, values=bits, implicit=False): 165 | ... with implicit: 166 | ... underline, italic, bold 167 | ... bold_italic = italic | bold 168 | ... 169 | >>> Emphasis.bold_italic.value 170 | 6 171 | ``` 172 | 173 | ### Aliases 174 | 175 | By default, it is an error to repeat a value: 176 | 177 | ```python 178 | >>> class Error(Enum, implicit=False, values=from_one): 179 | ... with implicit: 180 | ... one 181 | ... another_one = 1 182 | ... 183 | ValueError: Duplicate value (1) for one and another_one 184 | ``` 185 | 186 | but you can disable the safety check with `allow_aliases=True`: 187 | 188 | ```python 189 | >>> class MulitlingualWeekday(Enum, implicit=False, values=from_one, allow_aliases=True): 190 | ... with implicit: 191 | ... monday, tuesday, wednesday, thursday, friday, saturday, sunday 192 | ... lunes, martes, miercoles, jueves, viernes, sabado, domingo = \ 193 | ... monday, tuesday, wednesday, thursday, friday, saturday, sunday 194 | ... 195 | ``` 196 | 197 | This creates *aliases* - they are valid names, but they retrieve the original 198 | tuple: 199 | 200 | ```python 201 | >>> MulitlingualWeekday.lunes 202 | MulitlingualWeekday(name='monday', value=1) 203 | >>> MulitlingualWeekday('martes') 204 | MulitlingualWeekday(name='tuesday', value=2) 205 | >>> MulitlingualWeekday['miercoles'] 206 | 3 207 | ``` 208 | 209 | Discussion 210 | ---------- 211 | 212 | ### Background 213 | 214 | Some time ago I wrote an 215 | [intemperate rant](http://www.acooke.org/cute/Pythonssad0.html) about the 216 | [standard Enum](http://www.python.org/dev/peps/pep-0435/) for Python 3. 217 | 218 | Afterwards, I felt guilty. So, to atone myself, I started to modify the 219 | [code](https://bitbucket.org/stoneleaf/ref435), adding features that I felt 220 | missing from the original. The result was 221 | [bnum](https://github.com/andrewcooke/bnum). 222 | 223 | But, as I worked on bnum, I came to see that I was not producing the 224 | consistent, elegant design that I was 225 | [advocating](#https://github.com/andrewcooke/bnum#why-not-influence-the-official-design). 226 | Instead, I was adding features to an already over-complex project. 227 | 228 | So, after three weeks of work, I stopped. The next day I wrote 229 | [this](https://github.com/andrewcooke/simple-enum/blob/master/src/simplenum/__init__.py). 230 | 231 | ### Differences With Enum 232 | 233 | This project differs from 234 | [PEP-0345](http://www.python.org/dev/peps/pep-0435/)'s Enum in two important 235 | ways. 236 | 237 | 1. The typical end-user will notice that the API defaults to implicit values. 238 | This is because I feel the most common case for an Enum requires nothing more 239 | than a set of names. That case should be as simple as possible. 240 | 241 | 1. The Enum expert will see that I have made no effort to support other types 242 | of enumeration (alternatives to named tuples) through inheritance. 243 | In my career as a software engineer I have made many mistakes. All too often 244 | those mistakes involved inheritance. This design reflects that experience. 245 | 246 | In addition, this code supports alternative implicit values (eg. bit fields), 247 | has no support for the "functional" form, and, by default, flags an error on 248 | duplicates. 249 | 250 | I realise that one day's work (even when born from three weeks of frustration) 251 | is unlikely to have captured all the subtleties of the problem; that some of 252 | the complexity of the standard Enum is justified and will, in time and with bug 253 | fixes, clutter this project. But I hope that I have found something of value 254 | in the balance of features here, and that others will appreciate the view from 255 | this particular local maximum of the design space. 256 | 257 | ### The Danger Of Magic 258 | 259 | One objection to the implicit value approach used here is that it can lead 260 | to confusing errors when global names are shadowed by implicit values. 261 | However, this can be ameliorated in most cases by careful implementation. 262 | 263 | In the case of implicit classes, like: 264 | 265 | ```python 266 | >>> class Error1(Enum): 267 | ... a = sin(b) 268 | ... 269 | ExplicitError: Implicit scope support simple names only - no assignment or evaluation of expressions 270 | ``` 271 | 272 | the values returned are not the implicit values used, but `Explode` 273 | instances which generate the given error on any access. A similar error 274 | is triggered by assignment. 275 | 276 | The approach above, using a modified value, cannot be used for `with` 277 | contexts, where the value might be used later. But `with` contexts provide 278 | a separate mechanism for detecting and modifying errors. So here, for 279 | example, a `TypeError` is detected and replaced: 280 | 281 | ```python 282 | >>> class Error2(Enum, implicit=False): 283 | ... with implicit: 284 | ... a = sin(b) 285 | ... 286 | ExplicitError: Implicit scope support simple names only - no assignment or evaluation of expressions 287 | ``` 288 | 289 | ### Technical Details 290 | 291 | The consistency of the two viewpoints (dict and named tuples) hinges on 292 | the method `dict.items()`, which returns `(name, value)` tuples. These 293 | are both named tuples and the dictionary contents. 294 | 295 | Implicit values are generated by providing a default value for missing 296 | class dictionary contents. This shadows global names so cannot be used 297 | to evaluate expressions - but a simple list of names does not require any 298 | evaluation. 299 | 300 | Most of my Python programming (which I admit may be influenced by functional 301 | languages) uses common, standard data structures. An enumeration - an 302 | immutable set of names, with an optional associated set of values - does not 303 | require anything beyond that. So "what is a good design?" reduces to "how 304 | best can I fit enumerations into existing structures?" A little consideration 305 | gives two ways to associate names and values: in a dictionary, or as pairs. A 306 | little more consideration shows the two can be combined succinctly. Hence the 307 | design. 308 | 309 | ### Things You Can Do With The Simpler Enum (That You Can't Do With The Standard Enum) 310 | 311 | Have a simple list of names in "class" form: 312 | 313 | ```python 314 | >>> class Colour(Enum): 315 | ... red 316 | ... green 317 | ... blue 318 | ``` 319 | 320 | Detect a stupid mistake: 321 | 322 | ```python 323 | >>> class Error(Enum, values=from_one): 324 | ... with implicit: 325 | ... one 326 | ... two 327 | ... three = 2 328 | ... 329 | ValueError: Duplicate value (2) for two and three 330 | ``` 331 | 332 | Define bit fields: 333 | 334 | ```python 335 | >>> class IntEmphasis(Enum, values=bits): 336 | ... underline 337 | ... italic 338 | ... bold 339 | ... 340 | >>> allowed_styles = IntEmphasis.italic.value | IntEmphasis.bold.value 341 | ``` 342 | 343 | ### Credits 344 | 345 | Thanks to Ethan Furman, who graciously shared his code and so educated me on 346 | the subtleties of the Python meta-class protocol. 347 | 348 | [Duncan Booth](http://www.acooke.org/cute/Pythonssad0.html#Fri17May20131519040100) 349 | provided the implicit values hack *and* the motivation to question authority. 350 | 351 | Legalities 352 | ---------- 353 | 354 | (c) 2013 Andrew Cooke, [andrew@acooke.org](mailto://andrew@acooke.org); 355 | released into the public domain for any use, but with absolutely no warranty. 356 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from distutils.core import setup 3 | 4 | setup( 5 | name = 'simple-enum', 6 | url = 'https://github.com/andrewcooke/simple-enum', 7 | packages = ['simplenum'], 8 | package_dir = {'': 'src'}, 9 | version = '0.0.5', 10 | description = 'A simpler Enum for Python 3', 11 | author = 'Andrew Cooke', 12 | author_email = 'andrew@acooke.org', 13 | classifiers = ['Development Status :: 4 - Beta', 14 | 'Intended Audience :: Developers', 15 | 'License :: Public Domain', 16 | 'Programming Language :: Python :: 3', 17 | 'Topic :: Software Development :: Libraries'], 18 | long_description = ''' 19 | What Does Simple Enum Do? 20 | -------------------------- 21 | 22 | Simple Enum provides a simpler way to define Enums in Python 3:: 23 | 24 | class Colour(Enum): 25 | red 26 | green 27 | blue 28 | 29 | You can see the full documentation (and implementation) on 30 | `github `_. 31 | 32 | Why Should I Use Simple Enum? 33 | ------------------------------ 34 | 35 | * It makes the common case as simple as possible. 36 | 37 | * It detects accidental duplicate values. 38 | 39 | * It allows you to easily select alternative values (numbers from one, bit 40 | fields, etc). 41 | 42 | What Else Should I Know? 43 | ------------------------ 44 | 45 | * (c) 2013 Andrew Cooke, andrew@acooke.org; released into the public domain 46 | for any use, but with absolutely no warranty. 47 | ''' 48 | ) 49 | -------------------------------------------------------------------------------- /src/simplenum/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from collections import OrderedDict, namedtuple 3 | from itertools import count 4 | from types import MappingProxyType 5 | 6 | 7 | ''' 8 | A simpler Enum for Python 3. 9 | 10 | (c) 2013 Andrew Cooke, andrew@acooke.org; 11 | released into the public domain for any use, but with absolutely no warranty. 12 | ''' 13 | 14 | 15 | def names(): 16 | '''Provide a value for each enum which is the name itself.''' 17 | def value(name): 18 | return name 19 | return value 20 | 21 | def from_counter(start, step=1): 22 | '''Provide a value for each enum from a counter.''' 23 | def outer(): 24 | counter = count(start, step) 25 | def value(name): 26 | nonlocal counter 27 | return next(counter) 28 | return value 29 | return outer 30 | 31 | from_one = from_counter(1) 32 | from_one.__doc__ = 'Provide a value for each enum that counts from one.' 33 | from_one.__name__ = 'from_one' 34 | 35 | from_zero = from_counter(0) 36 | from_zero.__doc__ = 'Provide a value for each enum that counts from zero.' 37 | from_zero.__name__ = 'from_zero' 38 | 39 | def bits(): 40 | '''Provide a value for each enum that is a distinct bit (1, 2, 4, etc).''' 41 | count = 0 42 | def value(name): 43 | nonlocal count 44 | count += 1 45 | return 2 ** (count - 1) 46 | return value 47 | 48 | 49 | # Used to detect EnumMeta creation in the dict. If Enum is false then we 50 | # disable implicit values. 51 | Enum = None 52 | 53 | def dunder(name): 54 | '''Test for special names.''' 55 | return name[:2] == name[-2:] == '__' 56 | 57 | 58 | class ExplicitError(Exception): pass 59 | ERR_MSG = 'Implicit scope support simple names only - no assignment or evaluation of expressions' 60 | 61 | class Explode(int): 62 | 63 | def __getattribute__(cls, item): 64 | raise ExplicitError(ERR_MSG) 65 | def __call__(self, *args, **kwargs): 66 | raise ExplicitError(ERR_MSG) 67 | 68 | class ClassDict(OrderedDict): 69 | ''' 70 | This is the dictionary used while creating Enum instances. It provides 71 | default values when `implicit` is true. This can either be enabled by 72 | default, or within a `with` context. 73 | ''' 74 | 75 | def __init__(self, implicit=False, values=names): 76 | '''Setting `implicit` will provide default values from `values`.''' 77 | super().__init__() 78 | self.implicit = implicit 79 | self.always_implicit = implicit 80 | self.values = values() 81 | 82 | def __enter__(self): 83 | '''Enable implicit values within a `with` context.''' 84 | self.implicit = True 85 | 86 | def __exit__(self, exc_type, exc_val, exc_tb): 87 | '''Disable implicit values on leaving a `with` context.''' 88 | self.implicit = False 89 | if exc_val: raise ExplicitError(ERR_MSG) from exc_val 90 | 91 | def __getitem__(self, name): 92 | '''Provide an item from the dictionary. Values are created if 93 | `implicit` is currently true.''' 94 | if name not in self: 95 | if self.implicit and Enum and not dunder(name): 96 | super().__setitem__(name, self.values(name)) 97 | if self.always_implicit: return Explode() 98 | elif name == 'implicit': 99 | return self 100 | return super().__getitem__(name) 101 | 102 | def __setitem__(self, name, value): 103 | '''Set a value in the dictionary. Setting is disabled for user 104 | values (not dunders) if `implicit` is true. This helps avoid 105 | confusion from expressions involving shadowed global names.''' 106 | if self.implicit and Enum and not dunder(name): 107 | raise ExplicitError('Cannot use explicit value for %s' % name) 108 | return super().__setitem__(name, value) 109 | 110 | def split(self): 111 | '''Separate the enums from the special values (dunders and 112 | descriptors; we assume the latter are methods).''' 113 | enums, others = OrderedDict(), dict() 114 | for name in self: 115 | value = self[name] 116 | if dunder(name) or hasattr(value, '__get__'): 117 | others[name] = value 118 | else: 119 | enums[name] = value 120 | return enums, others 121 | 122 | 123 | class EnumMeta(type): 124 | ''' 125 | This does three main things: (1) it manages the construction of both 126 | the Enum class and its sub-classes via `__prepare__` and `__new__`; 127 | (2) it delegates the `dict` API to the `cls._enums` member so that 128 | classes look like dictionaries; (3) it provides retrieval of named tuples 129 | via `__call__`. 130 | ''' 131 | 132 | def __init__(metacls, cls, bases, dict, **kargs): 133 | '''Called during class construction. Drop kargs.''' 134 | super().__init__(cls, bases, dict) 135 | 136 | @classmethod 137 | def __prepare__(metacls, name, bases, implicit=True, values=names, **kargs): 138 | '''Provide the class dictionary (which provides implicit values).''' 139 | return ClassDict(implicit=implicit, values=values) 140 | 141 | def __new__(metacls, name, bases, prepared, allow_aliases=False, **kargs): 142 | '''Create the class and then the named tuples, saving the latter in 143 | the former.''' 144 | enums, others = prepared.split() 145 | cls = super().__new__(metacls, name, bases, others) 146 | cls._enums_by_name, cls._enums_by_value = {}, OrderedDict() 147 | for name in enums: 148 | value = enums[name] 149 | enum = cls.__new__(cls, name, value) 150 | # handle aliases 151 | if value in cls._enums_by_value: 152 | if allow_aliases: 153 | cls._enums_by_name[name] = cls._enums_by_value[value] 154 | else: 155 | raise ValueError('Duplicate value (%r) for %s and %s' % 156 | (value, cls._enums_by_value[value].name, name)) 157 | else: 158 | cls._enums_by_value[value] = enum 159 | cls._enums_by_name[name] = enum 160 | # build the delegate from values as that does not include aliases 161 | cls._enums = MappingProxyType( 162 | OrderedDict((enum.name, enum.value) 163 | for enum in cls._enums_by_value.values())) 164 | return cls 165 | 166 | # Needed to avoid calling len() during creation? 167 | def __bool__(cls): return True 168 | 169 | # Delegate dictionary methods. 170 | def __contains__(cls, name): return name in cls._enums_by_name # aliases 171 | def __iter__(cls): return cls._enums.__iter__() 172 | def __getitem__(cls, name): return cls._enums_by_name[name].value # aliases 173 | def __len__(cls): return cls._enums.__len__() 174 | def keys(cls): return cls._enums.keys() 175 | def values(cls): return cls._enums.values() 176 | 177 | def items(cls): 178 | '''This can be seen in two ways. As a dictionary method it returns 179 | `(name, value)` pairs. But it also returns a list of named tuples 180 | that are the enumerations themselves.''' 181 | return iter(cls._enums_by_value[value] for value in cls._enums_by_value) 182 | 183 | def __getattr__(cls, name): 184 | '''Provide access to named tuples.''' 185 | try: return cls._enums_by_name[name] 186 | except KeyError: raise AttributeError(name) 187 | 188 | def __call__(cls, name=None, value=None): 189 | '''Retrieve named tuples by name or value. We also special case 190 | calling with an existing instance.''' 191 | if type(name) is cls: 192 | if value is None or value == name.value: return name 193 | elif value is None: 194 | if name is None: raise ValueError('Give name or value') 195 | if name in cls._enums_by_name: return cls._enums_by_name[name] 196 | raise ValueError('No name %r' % name) 197 | elif name is None: 198 | if value in cls._enums_by_value: return cls._enums_by_value[value] 199 | raise ValueError('No value %r' % value) 200 | elif name in cls._enums_by_name: 201 | enum = cls._enums_by_name[name] 202 | if value in cls._enums_by_value and \ 203 | enum is cls._enums_by_value[value]: return enum 204 | raise ValueError('Inconsistent name (%r) and value (%r)' % 205 | (name, value)) 206 | 207 | def __str__(cls): 208 | return ', '.join(cls.keys()) 209 | 210 | def __repr__(cls): 211 | return '%s(%s)' % (cls.__name__, 212 | ', '.join('%s: %r' % (enum.name, enum.value) 213 | for enum in cls.items())) 214 | 215 | class Enum(namedtuple('Enum', 'name, value'), metaclass=EnumMeta): 216 | ''' 217 | The super class for enumerations. The body of sub-classes should 218 | typically contain a list of enumeration names. 219 | ''' 220 | 221 | def __new__(cls, *args, **kwargs): 222 | '''Called on instance creation and by pickle. We try __call__ first 223 | so that unpickling retrieves an existing instance. If that fails 224 | then we create a new instance.''' 225 | try: return cls.__call__(*args, **kwargs) 226 | except ValueError: return super().__new__(cls, *args, **kwargs) 227 | 228 | def _make(self): raise TypeError('Enum contents cannot be extended') 229 | -------------------------------------------------------------------------------- /src/simplenum/tests.py: -------------------------------------------------------------------------------- 1 | from pickle import loads, dumps 2 | from unittest import TestCase 3 | from simplenum import Enum, from_one, bits, ExplicitError 4 | 5 | 6 | class Examples(TestCase): 7 | 8 | 9 | def test_colour(self): 10 | 11 | class Colour(Enum): 12 | red 13 | green 14 | blue 15 | 16 | assert isinstance(Colour.red, Colour) 17 | assert isinstance(Colour.red, tuple) 18 | assert issubclass(Colour, tuple) 19 | assert Colour.red.name == Colour.red.value == 'red' 20 | assert Colour.red == Colour(Colour.red) == Colour('red') == \ 21 | Colour(name='red') == Colour(value='red') == \ 22 | Colour(name='red', value='red') 23 | assert Colour.red == list(Colour.items())[0] 24 | assert len(Colour) == 3 25 | with self.assertRaises(ValueError): 26 | Colour(value='red', name='blue') 27 | with self.assertRaises(ValueError): 28 | Colour(value='pink') 29 | assert str(Colour.red) == "Colour(name='red', value='red')", str(Colour.red) 30 | assert repr(Colour.red) == "Colour(name='red', value='red')", repr(Colour.red) 31 | assert str(list(Colour)) == "['red', 'green', 'blue']", str(list(Colour)) 32 | assert str(list(Colour.items())) == "[Colour(name='red', value='red'), Colour(name='green', value='green'), Colour(name='blue', value='blue')]", str(list(Colour.items())) 33 | assert str(Colour) == "red, green, blue", str(Colour) 34 | assert repr(Colour) == "Colour(red: 'red', green: 'green', blue: 'blue')", repr(Colour) 35 | 36 | for (name, value) in Colour.items(): 37 | assert name in Colour 38 | assert Colour[name] == value 39 | assert name in set(Colour.keys()) 40 | assert value in set(Colour.values()) 41 | assert Colour.red[0] == 'red' 42 | assert Colour.red[1] == 'red' 43 | 44 | 45 | def test_weekday(self): 46 | 47 | class Weekday(Enum, values=from_one): 48 | monday, tuesday, wednesday, thursday, friday, saturday, sunday 49 | 50 | assert str(list(Weekday)) == "['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']", str(list(Weekday)) 51 | assert Weekday.monday.value == 1 52 | assert Weekday['tuesday'] == 2 53 | assert Weekday.wednesday[1] == 3 54 | with self.assertRaises(TypeError): 55 | Weekday['montag'] = 8 56 | 57 | 58 | def test_emphasis(self): 59 | 60 | class Emphasis(Enum, values=bits, implicit=False): 61 | with implicit: 62 | underline, italic, bold 63 | bold_italic = italic | bold 64 | 65 | assert Emphasis.bold.value == 4 66 | assert Emphasis.bold_italic.value == 6 67 | 68 | 69 | def test_duplicate(self): 70 | 71 | with self.assertRaises(ValueError): 72 | class Error(Enum, implicit=False, values=from_one): 73 | with implicit: one 74 | another_one = 1 75 | 76 | class MulitlingualWeekday(Enum, implicit=False, values=from_one, allow_aliases=True): 77 | with implicit: 78 | monday, tuesday, wednesday, thursday, friday, saturday, sunday 79 | lunes, martes, miercoles, jueves, viernes, sabado, domingo = \ 80 | monday, tuesday, wednesday, thursday, friday, saturday, sunday 81 | 82 | assert str(MulitlingualWeekday.lunes) == "MulitlingualWeekday(name='monday', value=1)", str(MulitlingualWeekdays.lunes) 83 | assert str(MulitlingualWeekday('martes')) == "MulitlingualWeekday(name='tuesday', value=2)", str(MulitlingualWeekdays('lunes')) 84 | assert MulitlingualWeekday['miercoles'] == 3, MulitlingualWeekday['miercoles'] 85 | assert 'jueves' in MulitlingualWeekday 86 | assert len(MulitlingualWeekday) == 7, len(MulitlingualWeekday) 87 | 88 | 89 | class Pickle(Enum): 90 | red, green, blue 91 | 92 | class PickleTest(TestCase): 93 | 94 | def test_pickle(self): 95 | assert Pickle.red is loads(dumps(Pickle.red)) 96 | assert Pickle is loads(dumps(Pickle)) 97 | 98 | 99 | class MethodTest(TestCase): 100 | '''This isn't a particularly good idea.''' 101 | 102 | def test_methods(self): 103 | 104 | class Animal(Enum, implicit=False): 105 | 106 | @property 107 | def legs(self): 108 | return self.value[0] 109 | 110 | @property 111 | def sound(self): 112 | return self.value[1] 113 | 114 | def talk(self): 115 | return self.sound 116 | 117 | def __str__(self): 118 | return '%s has %d legs and says %s' % \ 119 | (self.name, self.legs, self.talk()) 120 | 121 | pig = 4, 'oink' 122 | hen = 2, 'cluck' 123 | 124 | assert str(Animal.pig) == "pig has 4 legs and says oink", str(Animal.pig) 125 | 126 | 127 | class SafetyTest(TestCase): 128 | 129 | def test_explode(self): 130 | 131 | with self.assertRaises(ExplicitError): 132 | class Bad1(Enum): 133 | a = b 134 | 135 | with self.assertRaises(ExplicitError): 136 | class Bad1(Enum): 137 | a = sin(b) + 2 138 | 139 | with self.assertRaises(ExplicitError): 140 | class Bad1(Enum): 141 | a = str(b) 142 | 143 | with self.assertRaises(ExplicitError): 144 | class Bad1(Enum): 145 | a = b * c 146 | 147 | def test_with(self): 148 | 149 | with self.assertRaises(ExplicitError): 150 | class Bad1(Enum, implicit=False): 151 | with implicit: 152 | a = b 153 | 154 | with self.assertRaises(ExplicitError): 155 | class Bad1(Enum, implicit=False): 156 | with implicit: 157 | a = sin(b) 158 | 159 | with self.assertRaises(ExplicitError): 160 | class Bad1(Enum, implicit=False): 161 | with implicit: 162 | a = str(b) 163 | 164 | with self.assertRaises(ExplicitError): 165 | class Bad1(Enum, implicit=False): 166 | with implicit: 167 | a = b * c 168 | 169 | --------------------------------------------------------------------------------