├── .gitignore ├── .hgignore ├── MANIFEST.in ├── README.md ├── aenum ├── CHANGES ├── LICENSE ├── __init__.py ├── _common.py ├── _constant.py ├── _enum.py ├── _py2.py ├── _py3.py ├── _tuple.py ├── doc │ └── aenum.rst ├── test.py ├── test_stdlib_tests.py ├── test_v3.py └── test_v37.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build/ 3 | *.sw* 4 | .hgignore 5 | *.pyc 6 | *.pyo 7 | __pycache__/ 8 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | .pyc 4 | __pycache__/ 5 | py2 6 | py3 7 | code/ 8 | .swp 9 | ~ 10 | .pdf 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude aenum/* 2 | include setup.py 3 | include README.md 4 | include aenum/__init__.py 5 | include aenum/_common.py 6 | include aenum/_constant.py 7 | include aenum/_enum.py 8 | include aenum/_tuple.py 9 | include aenum/_py2.py 10 | include aenum/_py3.py 11 | include aenum/test.py 12 | include aenum/test_v3.py 13 | include aenum/LICENSE 14 | include aenum/CHANGES 15 | include aenum/README.md 16 | include aenum/doc/aenum.pdf 17 | include aenum/doc/aenum.rst 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | aenum --- support for advanced enumerations, namedtuples, and constants 2 | ======================================================================= 3 | 4 | Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, 5 | and NamedConstants 6 | 7 | aenum includes a Python stdlib Enum-compatible data type, as well as a 8 | metaclass-based NamedTuple implementation and a NamedConstant class. 9 | 10 | An Enum is a set of symbolic names (members) bound to unique, constant 11 | values. Within an enumeration, the members can be compared by identity, and 12 | the enumeration itself can be iterated over. If using Python 3 there is 13 | built-in support for unique values, multiple values, auto-numbering, and 14 | suspension of aliasing (members with the same value are not identical), plus 15 | the ability to have values automatically bound to attributes. 16 | 17 | A NamedTuple is a class-based, fixed-length tuple with a name for each 18 | possible position accessible using attribute-access notation as well as the 19 | standard index notation. 20 | 21 | A NamedConstant is a class whose members cannot be rebound; it lacks all other 22 | Enum capabilities, however; consequently, it can have duplicate values. 23 | 24 | 25 | Module Contents 26 | =============== 27 | 28 | NamedTuple 29 | ------------ 30 | 31 | Base class for `creating NamedTuples`, either by subclassing or via it's 32 | functional API. 33 | 34 | Constant 35 | -------- 36 | 37 | Constant class for creating groups of constants. These names cannot be rebound 38 | to other values. 39 | 40 | Enum 41 | ---- 42 | 43 | Base class for creating enumerated constants. See section `Enum Functional API` 44 | for an alternate construction syntax. 45 | 46 | IntEnum 47 | ------- 48 | 49 | Base class for creating enumerated constants that are also subclasses of `int`. 50 | 51 | StrEnum 52 | ------- 53 | 54 | Base class for creating enumerated constants that are also subclasses of `str`. 55 | 56 | AutoNumberEnum 57 | -------------- 58 | 59 | Derived class that automatically assigns an `int` value to each member. 60 | 61 | OrderedEnum 62 | ----------- 63 | 64 | Derived class that adds `<`, `<=`, `>=`, and `>` methods to an `Enum`. 65 | 66 | UniqueEnum 67 | ---------- 68 | 69 | Derived class that ensures only one name is bound to any one value. 70 | 71 | Flag 72 | ---- 73 | 74 | Base class for creating enumerated constants that can be combined using 75 | the bitwise operations without losing their `Flag` membership. 76 | 77 | IntFlag 78 | ------- 79 | 80 | Base class for creating enumerated constants that can be combined using 81 | the bitwise operators without losing their `IntFlag` membership. 82 | `IntFlag` members are also subclasses of `int`. 83 | 84 | unique 85 | ------ 86 | 87 | Enum class decorator that ensures only one name is bound to any one value. 88 | 89 | constant 90 | -------- 91 | 92 | Descriptor to add constant values to an `Enum` 93 | 94 | convert 95 | ------- 96 | 97 | Helper to transform target global variables into an `Enum`. 98 | 99 | enum 100 | ---- 101 | 102 | Helper for specifying keyword arguments when creating `Enum` members. 103 | 104 | export 105 | ------ 106 | 107 | Helper for inserting `Enum` members into a namespace (usually `globals()`). 108 | 109 | extend_enum 110 | ----------- 111 | 112 | Helper for adding new `Enum` members after creation. 113 | 114 | module 115 | ------ 116 | 117 | Function to take a `Constant` or `Enum` class and insert it into 118 | `sys.modules` with the effect of a module whose top-level constant and 119 | member names cannot be rebound. 120 | 121 | member 122 | ------ 123 | 124 | Decorator to force a member in an `Enum` or `Constant`. 125 | 126 | nonmember 127 | --------- 128 | 129 | Decorator to force a normal (non-`Enum` member) attribute in an `Enum` 130 | or `Constant`. 131 | 132 | 133 | Creating an Enum 134 | ================ 135 | 136 | Enumerations can be created using the `class` syntax, which makes them 137 | easy to read and write. To define an enumeration, subclass `Enum` as 138 | follows: 139 | 140 | >>> from aenum import Enum 141 | >>> class Color(Enum): 142 | ... RED = 1 143 | ... GREEN = 2 144 | ... BLUE = 3 145 | 146 | The `Enum` class is also callable, providing the following functional API: 147 | 148 | >>> Animal = Enum('Animal', 'ANT BEE CAT DOG') 149 | >>> Animal 150 | 151 | >>> Animal.ANT 152 | 153 | >>> Animal.ANT.value 154 | 1 155 | >>> list(Animal) 156 | [, , , ] 157 | 158 | Note that `Enum` members are boolean `True` unless the `__nonzero__` 159 | (Python 2) or `__bool__` (Python 3) method is overridden to provide 160 | different semantics. 161 | 162 | 163 | Creating a Flag 164 | =============== 165 | 166 | `Flag` (and `IntFlag`) has members that can be combined with each other 167 | using the bitwise operators (&, \|, ^, ~). `IntFlag` members can be combined 168 | with `int` and other `IntFlag` members. While it is possible to specify 169 | the values directly it is recommended to use `auto` as the value and let 170 | `(Int)Flag` select an appropriate value: 171 | 172 | >>> from enum import Flag 173 | >>> class Color(Flag): 174 | ... RED = auto() 175 | ... BLUE = auto() 176 | ... GREEN = auto() 177 | ... 178 | >>> Color.RED & Color.GREEN 179 | 180 | >>> bool(Color.RED & Color.GREEN) 181 | False 182 | >>> Color.RED | Color.BLUE 183 | 184 | 185 | If you want to name the empty flag, or various combinations of flags, you may: 186 | 187 | >>> class Color(Flag): 188 | ... BLACK = 0 189 | ... RED = auto() 190 | ... BLUE = auto() 191 | ... GREEN = auto() 192 | ... WHITE = RED | BLUE | GREEN 193 | ... 194 | >>> Color.BLACK 195 | 196 | >>> Color.WHITE 197 | 198 | 199 | Note that `(Int)Flag` zero-value members have the usual boolean value of 200 | `False`. 201 | 202 | 203 | Creating NamedTuples 204 | ==================== 205 | 206 | Simple 207 | ------ 208 | 209 | The most common way to create a new NamedTuple will be via the functional API: 210 | 211 | >>> from aenum import NamedTuple 212 | >>> Book = NamedTuple('Book', 'title author genre', module=__name__) 213 | 214 | Advanced 215 | -------- 216 | 217 | The simple method of creating `NamedTuples` requires always specifying all 218 | possible arguments when creating instances; failure to do so will raise 219 | exceptions. 220 | 221 | However, it is possible to specify both docstrings and default values when 222 | creating a `NamedTuple` using the class method: 223 | 224 | >>> class Point(NamedTuple): 225 | ... x = 0, 'horizontal coordinate', 0 226 | ... y = 1, 'vertical coordinate', 0 227 | ... 228 | >>> Point() 229 | Point(x=0, y=0) 230 | 231 | 232 | Creating Constants 233 | ================== 234 | 235 | `Constant` is similar to `Enum`, but does not support the `Enum` 236 | protocols, and have no restrictions on duplications: 237 | 238 | >>> class K(Constant): 239 | ... PI = 3.141596 240 | ... TAU = 2 * PI 241 | ... 242 | >>> K.TAU 243 | 6.283192 244 | 245 | More Information 246 | ================ 247 | 248 | Detailed documentation can be found at ``_ 249 | -------------------------------------------------------------------------------- /aenum/CHANGES: -------------------------------------------------------------------------------- 1 | 3.1.13 2 | ====== 3 | 4 | - remove Python 2.6 code 5 | - add Python 3.12 enhancements 6 | - split source code into separate files 7 | - Enum and Flag inherit from stdlib versions 8 | 9 | 10 | 3.1.12 11 | ====== 12 | 13 | support inheriting from empty NamedTuples 14 | 15 | 16 | 3.1.10 17 | ====== 18 | 19 | prevent test_v3.py from being run as main 20 | 21 | 22 | 3.1.9 23 | ===== 24 | 25 | Move Py2/3 specific code to dedicated files 26 | 27 | 28 | 3.1.8 29 | ===== 30 | 31 | recalculate bits used after all flags created (sometimes needed when a custom 32 | `__new__` is in place. 33 | 34 | 35 | 3.1.7 36 | ===== 37 | 38 | update flag creation to (possibly) add bitwise operator methods to newly 39 | created flags 40 | 41 | update extend_enum() to work with 3.11 flags 42 | 43 | 44 | 3.1.6 45 | ===== 46 | 47 | Update `dir()` on mixed enums to include mixed data type methods and 48 | attributes. 49 | 50 | Rename `enum_property` to `property` to match stdlib. Recommended usage is 51 | `aenum.property` (prefix with module name). 52 | 53 | Remove quadritic creation behavior. 54 | 55 | 56 | BREAKING CHANGE BUG FIX that won't affect most people 57 | 58 | Enums with a custom `__new__` that: 59 | 60 | - use the enum machinery to generate the values; AND 61 | - have keyword arguments set to a default (like `None`) 62 | 63 | will fail to generate a missing value. To fix: remove the default value and 64 | instead specify it on the member creation line. 65 | 66 | BREAKING CHANGE 67 | 68 | In Python 3.11 the `str()` of mixed enums will now match its `format()` which 69 | will be the normal `str()` of the data type -- so for an IntEnum you'll see 70 | `5` instead of `Perm.R|X`. This affects IntEnum, StrEnum, and IntFlag. 71 | 72 | 73 | 3.1.5 74 | ===== 75 | 76 | fix support of `auto()` kwds 77 | 78 | 79 | 3.1.3 80 | ===== 81 | 82 | rename `aenum.property` to `aenum.enum_property` 83 | 84 | fix `enum_property` to work with `_init_` attributes 85 | 86 | 87 | 3.1.2 88 | ===== 89 | 90 | fix `extend_enum()` for unhashable values 91 | 92 | 93 | 3.1.1 94 | ===== 95 | 96 | fix `extend_enum()` for most cases 97 | 98 | 99 | 3.1.0 100 | ===== 101 | 102 | AddValue is similar to the old AutoNumber: it will always activate, but 103 | uses _generate_next_value_ to get the next value (so the user has some 104 | control over the return data type instead of always getting an int). 105 | 106 | 107 | BREAKING CHANGES 108 | 109 | AutoValue is gone. It was superflous and its removal simplified the code. 110 | Simply put the fields needed in an `_init_` and `_generate_next_value_` 111 | will be called to supply the missing values (this is probably already what 112 | is happening). 113 | 114 | 115 | 116 | 3.0.0 117 | ===== 118 | 119 | standard Enum usage is unchanged 120 | 121 | BREAKING CHANGES 122 | 123 | Enum 124 | - the more esoteric method of creating Enums have been modified or removed 125 | - AutoNumber setting is gone, inherit from AutoNumberEnum instead 126 | - creating members without specifying anything is removed (if you don't 127 | know what this means, you weren't doing it) 128 | 129 | Flag 130 | - unique flags are canonical (i.e. flags with powers of two values such as 131 | 1, 2, 4, 8, 16, etc.) 132 | - non-unique flags are aliases (i.e. values such as 3 or 7) 133 | - iteration of Flag and flag members only uses canonical flags 134 | 135 | 136 | ENHANCEMENTS 137 | 138 | Member creation has been redone to match Python 3.10's methods. This also 139 | allows all supported Pythons (2.7, 3.3+) to use the __set_name__ and 140 | __init_subclass__ protocols (more robustly than in aenum 2.2.5) 141 | 142 | 143 | CHANGES 144 | 145 | enum_property() has been renamed to property() (old name still available, but 146 | deprecated). 147 | 148 | bin() replacement shows negative integers in twos-complement 149 | 150 | 151 | 152 | 153 | 2.2.5 154 | ===== 155 | 156 | call __init_subclass__ after members have been added, and in Pythons < 3.6 157 | call __set_name__ in Pythons < 3.6 158 | do not convert/disallow private names 159 | add iteration/len support to NamedConstant 160 | 161 | 162 | 2.2.4 163 | ===== 164 | 165 | add support to Constant to retrieve members by value 166 | 167 | --> class K(Constant): 168 | ... one = 1 169 | ... two = 2 170 | 171 | --> K.one 172 | 173 | 174 | --> K(1) 175 | 176 | 177 | add pickle/deepcopy support to Constant 178 | 179 | add support for Constant to use other Constant values 180 | (resulting members /are not/ the same) 181 | 182 | --> class C(Constant) 183 | ... one = K.one 184 | ... three = 3 185 | 186 | --> C.one == K.one 187 | True 188 | 189 | --> C.one is K.one 190 | False 191 | 192 | AutoNumber and auto() now work together 193 | 194 | Enum members are now added to the class as enum_property, which supports 195 | unshadowing of parent class attributes when called on an Enum member: 196 | 197 | --> class StrEnum(str, Enum): 198 | ... lower = 'lower' 199 | ... upper = 'upper' 200 | ... mixed = 'mixed' 201 | 202 | --> StrEnum.lower 203 | 204 | 205 | --> StrEnum.lower.upper() 206 | 'LOWER' 207 | 208 | --> StrEnum.upper 209 | 210 | 211 | --> StrEnum.upper.upper() 212 | 'UPPER' 213 | 214 | 215 | 2.2.3 216 | ===== 217 | 218 | use members' type's methods __str__, __repr__, __format__, and 219 | __reduce_ex__ if directly assigned in Enum class body; i.e.: 220 | 221 | --> class Color(str, Enum): 222 | ... red = 'red' 223 | ... green = 'green' 224 | ... blue = 'blue' 225 | ... __str__ = str.__str__ 226 | 227 | --> print(repr(Color.green)) 228 | 229 | 230 | --> print(Color.green) 231 | green 232 | 233 | 234 | 2.2.2 235 | ===== 236 | 237 | replace _RouteClassAttributeToGetattr with enum_property (it is still 238 | available as an alias) 239 | 240 | support constant() and auto() being used together: 241 | 242 | --> class Fruit(Flag): 243 | ... _order_ = 'apple banana lemon orange' 244 | ... apple = auto() 245 | ... banana = auto() 246 | ... lemon = auto() 247 | ... orange = auto() 248 | ... CitrusTypes = constant(lemon | orange) 249 | 250 | --> list(Fruit) 251 | [Fruit.apple, Fruit.banana, Fruit.lemon, Fruit.orange] 252 | 253 | --> list(Fruit.CitrusTypes) 254 | [Fruit.orange, Fruit.lemon] 255 | 256 | --> Fruit.orange in Fruit.CitrusTypes 257 | True 258 | 259 | 260 | 2.2.1 261 | ===== 262 | 263 | allow Enums to be called without a value 264 | 265 | class Color(Enum): 266 | black = 0 267 | red = 1 268 | green = 2 269 | blue = 3 270 | # 271 | @classmethod 272 | def _missing_value_(cls, value): 273 | if value is no_arg: 274 | return cls.black 275 | 276 | >>> Color() 277 | 278 | 279 | allow Enum name use while constructing Enum (Python 3.4+ only) 280 | 281 | --> class Color(Enum): 282 | ... _order_ = 'BLACK WHITE' 283 | ... BLACK = Color('black', '#000') 284 | ... WHITE = Color('white', '#fff') 285 | ... # 286 | ... def __init__(self, label, hex): 287 | ... self.label = label 288 | ... self.hex = hex 289 | 290 | 291 | 2.2.0 292 | ===== 293 | 294 | BREAKING CHANGE 295 | --------------- 296 | In Python 3+ classes defined inside an Enum no longer become members by 297 | default; in Python 2 they still become members, but see below. 298 | 299 | For cross-compatibility and full control two decorators are provided: 300 | 301 | - @member --> forces item to become a member 302 | - @nonmember --> excludes item from becoming a member 303 | 304 | So to have an Enum that behaves the same in Python 2 and 3, use the 305 | decorators (and other compatibility shims): 306 | 307 | class Color(Enum): 308 | 309 | _order_ = 'red green blue' 310 | 311 | red = 1 312 | green = 2 313 | blue = 3 314 | 315 | @nonmember 316 | class Shades(Enum): 317 | 318 | _order_ = 'light medium dark' 319 | 320 | light = 1 321 | medium = 2 322 | dark = 3 323 | 324 | 325 | 2.1.4 326 | ===== 327 | 328 | EnumMeta: 329 | - change __member_new__ to __new_member__ (as the stdlib enum does) 330 | - assign member name to enum() instances (an Enum helper for defining members) 331 | - handle empty iterables when using functional API 332 | - make auto() work with previous enum members 333 | - keep searching mixins until base class is found 334 | 335 | Enum: 336 | - fix bug in Flag checks (ensure it is a Flag before checking the name) 337 | - add multiple mixin support 338 | - do not allow blank names (functional API) 339 | - raise TypeError if _missing_* returns wrong type 340 | - fix __format__ to honor custom __str__ 341 | 342 | extend_enum: 343 | - support stdlib Enums 344 | - use _generate_next_value_ if value not provided 345 | 346 | general: 347 | - standardize exception formatting 348 | - use getfullargspec() in Python 3 (avoids deprecation warnings) 349 | 350 | 351 | 2.1.2 352 | ===== 353 | 354 | when order is callable, save it for subclass use 355 | 356 | 357 | 2.1.1 358 | ===== 359 | 360 | correctly raise TypeError for non-Enum containment checks 361 | support combining names with | for Flag key access 362 | support _order_ being a callable 363 | 364 | 365 | 2.1.0 366 | ===== 367 | 368 | support Flags being combined with other data types: 369 | - add _create_pseudo_member_values_ 370 | - add default __new__ and temporary _init_ 371 | 372 | 373 | 2.0.10 374 | ====== 375 | 376 | ensure _ignore_ is set when _settings_ specified in body which includes 377 | AutoValue 378 | 379 | make Flag members iterable 380 | 381 | 382 | 2.0.9 383 | ===== 384 | 385 | fix missing comma in __all__ 386 | fix extend_enum with custom __new__ methods 387 | fix MultiValue with AutoNumber without _init_ 388 | 389 | 390 | 2.0.8 391 | ===== 392 | 393 | extend_enum now handles aliases and multivalues correctly 394 | 395 | 396 | 2.0.7 397 | ===== 398 | 399 | support mixin types with extend_enum 400 | init and AutoNumber can now work together 401 | add test for new Enum using EnumMeta 402 | add tests for variations of multivalue and init 403 | prevent deletion of NamedConstant.constant 404 | 405 | 406 | 2.0.6 407 | ===== 408 | 409 | constants cannot be deleted (they already couldn't be changed) 410 | constants can be used to define other constants 411 | 412 | 413 | 2.0.5 414 | ===== 415 | 416 | _init_ and MultiValue can now work together 417 | 418 | 419 | 2.0.4 420 | ===== 421 | 422 | _init_ and AutoValue (and _generate_next_value_) can now work together to 423 | supply missing values even when some of the required values per member are 424 | absent 425 | 426 | 427 | 2.0.3 428 | ===== 429 | 430 | add _missing_value_ and _missing_name_ methods, deprecate _missing_ 431 | make enum instances comparable 432 | 433 | 434 | 2.0.2 435 | ===== 436 | 437 | both EnumMeta.__getattr__ and Enum.__new__ fall back to _missing_ 438 | 439 | 440 | 2.0.1 441 | ===== 442 | 443 | auto() now works with other data types 444 | AutoNumber supports legacy Enums (fixed regression) 445 | 446 | 447 | 2.0.0 448 | ===== 449 | 450 | Flag and IntFlag added. 451 | 452 | 453 | 1.4.7 454 | ===== 455 | 456 | fix %-interpolation bug 457 | defined SqlLiteEnum only if sqlite exists 458 | support pyflakes 459 | 460 | 461 | 1.4.6 462 | ===== 463 | 464 | version numbering error 465 | 466 | 467 | 1.4.5 468 | ===== 469 | 470 | revert AutoNumberEnum to custom __new__ instead of AutoNumber 471 | use _ignore_ to shield against AutoNumber magic 472 | inherit start and init settings from base Enums 473 | 474 | 475 | 1.4.4 476 | ===== 477 | 478 | enabled export as a decorator 479 | enabled _order_ to replace __order__ 480 | enabled python2 support for settings, init, and start 481 | 482 | 483 | 1.4.3 484 | ===== 485 | 486 | support _ignore_ for dynamically creating class bodies 487 | 488 | 489 | 1.4.2 490 | ===== 491 | 492 | MultiValue, NoAlias, Unique, and init now work with Python 2 493 | 494 | 495 | 1.4.1 496 | ===== 497 | 498 | Py3: added Enum creation flags: Auto, MultiValue, NoAlias, Unique 499 | 500 | fixed extend_enum to honor Enum flags 501 | 502 | 503 | 1.4.0 504 | ===== 505 | 506 | When possible aenum inherits from Python's own enum. 507 | 508 | Breaking change: enum members now default to evaluating as True to maintain 509 | compatibility with the stdlib. 510 | 511 | Add your own __bool__ (__nonzero__ in Python 2) if need this behavior: 512 | 513 | def __bool__(self): 514 | return bool(self.value) 515 | __nonzero__ = __bool__ 516 | 517 | -------------------------------------------------------------------------------- /aenum/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, 2016, 2017, 2018 Ethan Furman. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | Redistributions of source code must retain the above 9 | copyright notice, this list of conditions and the 10 | following disclaimer. 11 | 12 | Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials 15 | provided with the distribution. 16 | 17 | Neither the name Ethan Furman nor the names of any 18 | contributors may be used to endorse or promote products 19 | derived from this software without specific prior written 20 | permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 26 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /aenum/__init__.py: -------------------------------------------------------------------------------- 1 | """Python Advanced Enumerations & NameTuples""" 2 | from __future__ import print_function 3 | 4 | version = 3, 1, 16, 1 5 | 6 | # imports 7 | from ._common import * 8 | from ._constant import * 9 | from ._tuple import * 10 | from ._enum import * 11 | 12 | 13 | __all__ = [ 14 | 'NamedConstant', 'Constant', 'constant', 'skip', 'nonmember', 'member', 'no_arg', 15 | 'Member', 'NonMember', 'bin', 16 | 'Enum', 'IntEnum', 'AutoNumberEnum', 'OrderedEnum', 'UniqueEnum', 17 | 'StrEnum', 'UpperStrEnum', 'LowerStrEnum', 'ReprEnum', 18 | 'Flag', 'IntFlag', 'enum_property', 19 | 'AddValue', 'MagicValue', 'MultiValue', 'NoAlias', 'Unique', 20 | 'AddValueEnum', 'MultiValueEnum', 'NoAliasEnum', 21 | 'enum', 'extend_enum', 'unique', 'property', 22 | 'NamedTuple', 'SqliteEnum', '_reduce_ex_by_name', 23 | 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', 24 | 'add_stdlib_integration', 'remove_stdlib_integration' 25 | ] 26 | 27 | if sqlite3 is None: 28 | __all__.remove('SqliteEnum') 29 | 30 | 31 | if PY2: 32 | from . import _py2 33 | __all__.extend(_py2.__all__) 34 | else: 35 | from . import _py3 36 | __all__.extend(_py3.__all__) 37 | __all__.append('AutoEnum') 38 | 39 | 40 | 41 | # helpers 42 | 43 | -------------------------------------------------------------------------------- /aenum/_common.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | __all__ = [ 4 | 'pyver', 'PY2', 'PY2_6', 'PY3', 'PY3_3', 'PY3_4', 'PY3_5', 'PY3_6', 'PY3_7', 'PY3_11', 5 | '_or_', '_and_', '_xor_', '_inv_', '_abs_', '_add_', '_floordiv_', '_lshift_', 6 | '_rshift_', '_mod_', '_mul_', '_neg_', '_pos_', '_pow_', '_truediv_', '_sub_', 7 | 'unicode', 'basestring', 'baseinteger', 'long', 'NoneType', '_Addendum', 8 | 'is_descriptor', 'is_dunder', 'is_sunder', 'is_internal_class', 'is_private_name', 9 | 'get_attr_from_chain', '_value', 'constant', 'undefined', 10 | 'make_class_unpicklable', 'bltin_property', 11 | 'skip', 'nonmember', 'member', 'Member', 'NonMember', 'OrderedDict', 12 | ] 13 | 14 | 15 | # imports 16 | import sys as _sys 17 | pyver = _sys.version_info[:2] 18 | PY2 = pyver < (3, ) 19 | PY3 = pyver >= (3, ) 20 | PY2_6 = (2, 6) 21 | PY3_3 = (3, 3) 22 | PY3_4 = (3, 4) 23 | PY3_5 = (3, 5) 24 | PY3_6 = (3, 6) 25 | PY3_7 = (3, 7) 26 | PY3_11 = (3, 11) 27 | 28 | import re 29 | 30 | from operator import or_ as _or_, and_ as _and_, xor as _xor_, inv as _inv_ 31 | from operator import abs as _abs_, add as _add_, floordiv as _floordiv_ 32 | from operator import lshift as _lshift_, rshift as _rshift_, mod as _mod_ 33 | from operator import mul as _mul_, neg as _neg_, pos as _pos_, pow as _pow_ 34 | from operator import truediv as _truediv_, sub as _sub_ 35 | 36 | if PY2: 37 | from . import _py2 38 | from ._py2 import * 39 | __all__.extend(_py2.__all__) 40 | if PY3: 41 | from . import _py3 42 | from ._py3 import * 43 | __all__.extend(_py3.__all__) 44 | 45 | bltin_property = property 46 | 47 | # shims 48 | 49 | try: 50 | from collections import OrderedDict 51 | except ImportError: 52 | OrderedDict = dict 53 | 54 | try: 55 | unicode 56 | unicode = unicode 57 | except NameError: 58 | # In Python 3 unicode no longer exists (it's just str) 59 | unicode = str 60 | 61 | try: 62 | basestring 63 | basestring = bytes, unicode 64 | except NameError: 65 | # In Python 2 basestring is the ancestor of both str and unicode 66 | # in Python 3 it's just str, but was missing in 3.1 67 | basestring = str, 68 | 69 | try: 70 | baseinteger = int, long 71 | long = long 72 | except NameError: 73 | baseinteger = int, 74 | long = int 75 | # deprecated 76 | baseint = baseinteger 77 | 78 | try: 79 | NoneType 80 | except NameError: 81 | NoneType = type(None) 82 | 83 | class undefined(object): 84 | def __repr__(self): 85 | return 'undefined' 86 | def __bool__(self): 87 | return False 88 | __nonzero__ = __bool__ 89 | undefined = undefined() 90 | 91 | class _Addendum(object): 92 | def __init__(self, dict, doc, ns): 93 | # dict is the dict to update with functions 94 | # doc is the docstring to put in the dict 95 | # ns is the namespace to remove the function names from 96 | self.dict = dict 97 | self.ns = ns 98 | self.added = set() 99 | def __call__(self, func): 100 | if isinstance(func, (staticmethod, classmethod)): 101 | name = func.__func__.__name__ 102 | elif isinstance(func, (property, bltin_property)): 103 | name = (func.fget or func.fset or func.fdel).__name__ 104 | else: 105 | name = func.__name__ 106 | self.dict[name] = func 107 | self.added.add(name) 108 | return func 109 | def __getitem__(self, name): 110 | return self.dict[name] 111 | def __setitem__(self, name, value): 112 | self.dict[name] = value 113 | def resolve(self): 114 | ns = self.ns 115 | for name in self.added: 116 | del ns[name] 117 | return self.dict 118 | 119 | def is_descriptor(obj): 120 | """Returns True if obj is a descriptor, False otherwise.""" 121 | return ( 122 | hasattr(obj, '__get__') or 123 | hasattr(obj, '__set__') or 124 | hasattr(obj, '__delete__')) 125 | 126 | 127 | def is_dunder(name): 128 | """Returns True if a __dunder__ name, False otherwise.""" 129 | return (len(name) > 4 and 130 | name[:2] == name[-2:] == '__' and 131 | name[2] != '_' and 132 | name[-3] != '_') 133 | 134 | 135 | def is_sunder(name): 136 | """Returns True if a _sunder_ name, False otherwise.""" 137 | return (len(name) > 2 and 138 | name[0] == name[-1] == '_' and 139 | name[1] != '_' and 140 | name[-2] != '_') 141 | 142 | def is_internal_class(cls_name, obj): 143 | # only 3.3 and up, always return False in 3.2 and below 144 | if pyver < PY3_3: 145 | return False 146 | else: 147 | qualname = getattr(obj, '__qualname__', False) 148 | return not is_descriptor(obj) and qualname and re.search(r"\.?%s\.\w+$" % cls_name, qualname) 149 | 150 | def is_private_name(cls_name, name): 151 | pattern = r'^_%s__\w+[^_]_?$' % (cls_name, ) 152 | return re.search(pattern, name) 153 | 154 | def get_attr_from_chain(cls, attr): 155 | sentinel = object() 156 | for basecls in cls.mro(): 157 | obj = basecls.__dict__.get(attr, sentinel) 158 | if obj is not sentinel: 159 | return obj 160 | 161 | def _value(obj): 162 | if isinstance(obj, (auto, constant)): 163 | return obj.value 164 | else: 165 | return obj 166 | 167 | class constant(object): 168 | ''' 169 | Simple constant descriptor for NamedConstant and Enum use. 170 | ''' 171 | def __init__(self, value, doc=None): 172 | self.value = value 173 | self.__doc__ = doc 174 | def __get__(self, *args): 175 | return self.value 176 | def __repr__(self): 177 | return '%s(%r)' % (self.__class__.__name__, self.value) 178 | def __and__(self, other): 179 | return _and_(self.value, _value(other)) 180 | def __rand__(self, other): 181 | return _and_(_value(other), self.value) 182 | def __invert__(self): 183 | return _inv_(self.value) 184 | def __or__(self, other): 185 | return _or_(self.value, _value(other)) 186 | def __ror__(self, other): 187 | return _or_(_value(other), self.value) 188 | def __xor__(self, other): 189 | return _xor_(self.value, _value(other)) 190 | def __rxor__(self, other): 191 | return _xor_(_value(other), self.value) 192 | def __abs__(self): 193 | return _abs_(self.value) 194 | def __add__(self, other): 195 | return _add_(self.value, _value(other)) 196 | def __radd__(self, other): 197 | return _add_(_value(other), self.value) 198 | def __neg__(self): 199 | return _neg_(self.value) 200 | def __pos__(self): 201 | return _pos_(self.value) 202 | if PY2: 203 | def __div__(self, other): 204 | return _div_(self.value, _value(other)) 205 | def __rdiv__(self, other): 206 | return _div_(_value(other), (self.value)) 207 | def __floordiv__(self, other): 208 | return _floordiv_(self.value, _value(other)) 209 | def __rfloordiv__(self, other): 210 | return _floordiv_(_value(other), self.value) 211 | def __truediv__(self, other): 212 | return _truediv_(self.value, _value(other)) 213 | def __rtruediv__(self, other): 214 | return _truediv_(_value(other), self.value) 215 | def __lshift__(self, other): 216 | return _lshift_(self.value, _value(other)) 217 | def __rlshift__(self, other): 218 | return _lshift_(_value(other), self.value) 219 | def __rshift__(self, other): 220 | return _rshift_(self.value, _value(other)) 221 | def __rrshift__(self, other): 222 | return _rshift_(_value(other), self.value) 223 | def __mod__(self, other): 224 | return _mod_(self.value, _value(other)) 225 | def __rmod__(self, other): 226 | return _mod_(_value(other), self.value) 227 | def __mul__(self, other): 228 | return _mul_(self.value, _value(other)) 229 | def __rmul__(self, other): 230 | return _mul_(_value(other), self.value) 231 | def __pow__(self, other): 232 | return _pow_(self.value, _value(other)) 233 | def __rpow__(self, other): 234 | return _pow_(_value(other), self.value) 235 | def __sub__(self, other): 236 | return _sub_(self.value, _value(other)) 237 | def __rsub__(self, other): 238 | return _sub_(_value(other), self.value) 239 | def __set_name__(self, ownerclass, name): 240 | self.name = name 241 | self.clsname = ownerclass.__name__ 242 | 243 | def make_class_unpicklable(obj): 244 | """ 245 | Make the given obj un-picklable. 246 | 247 | obj should be either a dictionary, on an Enum 248 | """ 249 | def _break_on_call_reduce(self, proto): 250 | raise TypeError('%r cannot be pickled' % self) 251 | if isinstance(obj, dict): 252 | obj['__reduce_ex__'] = _break_on_call_reduce 253 | obj['__module__'] = '' 254 | else: 255 | setattr(obj, '__reduce_ex__', _break_on_call_reduce) 256 | setattr(obj, '__module__', '') 257 | 258 | class NonMember(object): 259 | """ 260 | Protects item from becaming an Enum member during class creation. 261 | """ 262 | def __init__(self, value): 263 | self.value = value 264 | 265 | def __get__(self, instance, ownerclass=None): 266 | return self.value 267 | skip = nonmember = NonMember 268 | 269 | class Member(object): 270 | """ 271 | Forces item to became an Enum member during class creation. 272 | """ 273 | def __init__(self, value): 274 | self.value = value 275 | member = Member 276 | 277 | 278 | -------------------------------------------------------------------------------- /aenum/_constant.py: -------------------------------------------------------------------------------- 1 | from ._common import * 2 | 3 | __all__ = [ 4 | 'NamedConstant', 'Constant', 5 | ] 6 | 7 | # NamedConstant 8 | 9 | NamedConstant = None 10 | 11 | class NamedConstantDict(dict): 12 | """Track constant order and ensure names are not reused. 13 | 14 | NamedConstantMeta will use the names found in self._names as the 15 | Constant names. 16 | """ 17 | def __init__(self): 18 | super(NamedConstantDict, self).__init__() 19 | self._names = [] 20 | 21 | def __setitem__(self, key, value): 22 | """Changes anything not dundered or not a constant descriptor. 23 | 24 | If an constant name is used twice, an error is raised; duplicate 25 | values are not checked for. 26 | 27 | Single underscore (sunder) names are reserved. 28 | """ 29 | if is_sunder(key): 30 | raise ValueError( 31 | '_sunder_ names, such as %r, are reserved for future NamedConstant use' 32 | % (key, ) 33 | ) 34 | elif is_dunder(key): 35 | pass 36 | elif key in self._names: 37 | # overwriting an existing constant? 38 | raise TypeError('attempt to reuse name: %r' % (key, )) 39 | elif isinstance(value, constant) or not is_descriptor(value): 40 | if key in self: 41 | # overwriting a descriptor? 42 | raise TypeError('%s already defined as: %r' % (key, self[key])) 43 | self._names.append(key) 44 | super(NamedConstantDict, self).__setitem__(key, value) 45 | 46 | 47 | class NamedConstantMeta(type): 48 | """ 49 | Block attempts to reassign NamedConstant attributes. 50 | """ 51 | 52 | @classmethod 53 | def __prepare__(metacls, cls, bases, **kwds): 54 | return NamedConstantDict() 55 | 56 | def __new__(metacls, cls, bases, clsdict): 57 | if type(clsdict) is dict: 58 | original_dict = clsdict 59 | clsdict = NamedConstantDict() 60 | for k, v in original_dict.items(): 61 | clsdict[k] = v 62 | newdict = {} 63 | constants = {} 64 | for name, obj in clsdict.items(): 65 | if name in clsdict._names: 66 | constants[name] = obj 67 | continue 68 | elif isinstance(obj, nonmember): 69 | obj = obj.value 70 | newdict[name] = obj 71 | newcls = super(NamedConstantMeta, metacls).__new__(metacls, cls, bases, newdict) 72 | newcls._named_constant_cache_ = {} 73 | newcls._members_ = {} 74 | for name, obj in constants.items(): 75 | new_k = newcls.__new__(newcls, name, obj) 76 | newcls._members_[name] = new_k 77 | return newcls 78 | 79 | def __bool__(cls): 80 | return True 81 | 82 | def __delattr__(cls, attr): 83 | cur_obj = cls.__dict__.get(attr) 84 | if NamedConstant is not None and isinstance(cur_obj, NamedConstant): 85 | raise AttributeError('cannot delete constant <%s.%s>' % (cur_obj.__class__.__name__, cur_obj._name_)) 86 | super(NamedConstantMeta, cls).__delattr__(attr) 87 | 88 | def __iter__(cls): 89 | return (k for k in cls._members_.values()) 90 | 91 | def __reversed__(cls): 92 | return (k for k in reversed(cls._members_.values())) 93 | 94 | def __len__(cls): 95 | return len(cls._members_) 96 | 97 | __nonzero__ = __bool__ 98 | 99 | def __setattr__(cls, name, value): 100 | """Block attempts to reassign NamedConstants. 101 | """ 102 | cur_obj = cls.__dict__.get(name) 103 | if NamedConstant is not None and isinstance(cur_obj, NamedConstant): 104 | raise AttributeError('cannot rebind constant <%s.%s>' % (cur_obj.__class__.__name__, cur_obj._name_)) 105 | super(NamedConstantMeta, cls).__setattr__(name, value) 106 | 107 | constant_dict = _Addendum( 108 | dict=NamedConstantMeta.__prepare__('NamedConstant', (object, )), 109 | doc="NamedConstants protection.\n\n Derive from this class to lock NamedConstants.\n\n", 110 | ns=globals(), 111 | ) 112 | 113 | @constant_dict 114 | def __new__(cls, name, value=None, doc=None): 115 | if value is None: 116 | # lookup, name is value 117 | value = name 118 | for name, obj in cls.__dict__.items(): 119 | if isinstance(obj, cls) and obj._value_ == value: 120 | return obj 121 | else: 122 | raise ValueError('%r does not exist in %r' % (value, cls.__name__)) 123 | cur_obj = cls.__dict__.get(name) 124 | if isinstance(cur_obj, NamedConstant): 125 | raise AttributeError('cannot rebind constant <%s.%s>' % (cur_obj.__class__.__name__, cur_obj._name_)) 126 | elif isinstance(value, constant): 127 | doc = doc or value.__doc__ 128 | value = value.value 129 | metacls = cls.__class__ 130 | if isinstance(value, NamedConstant): 131 | # constants from other classes are reduced to their actual value 132 | value = value._value_ 133 | actual_type = type(value) 134 | value_type = cls._named_constant_cache_.get(actual_type) 135 | if value_type is None: 136 | value_type = type(cls.__name__, (cls, type(value)), {}) 137 | cls._named_constant_cache_[type(value)] = value_type 138 | obj = actual_type.__new__(value_type, value) 139 | obj._name_ = name 140 | obj._value_ = value 141 | obj.__doc__ = doc 142 | cls._members_[name] = obj 143 | metacls.__setattr__(cls, name, obj) 144 | return obj 145 | 146 | @constant_dict 147 | def __repr__(self): 148 | return "<%s.%s: %r>" % ( 149 | self.__class__.__name__, self._name_, self._value_) 150 | 151 | @constant_dict 152 | def __reduce_ex__(self, proto): 153 | return getattr, (self.__class__, self._name_) 154 | 155 | NamedConstant = NamedConstantMeta('NamedConstant', (object, ), constant_dict.resolve()) 156 | Constant = NamedConstant 157 | del constant_dict 158 | 159 | 160 | -------------------------------------------------------------------------------- /aenum/_py2.py: -------------------------------------------------------------------------------- 1 | from operator import div as _div_ 2 | from inspect import getargspec 3 | 4 | def raise_with_traceback(exc, tb): 5 | raise exc, None, tb 6 | 7 | __all__ = ['_div_', 'getargspec', 'raise_with_traceback'] 8 | -------------------------------------------------------------------------------- /aenum/_py3.py: -------------------------------------------------------------------------------- 1 | from inspect import getfullargspec as _getfullargspec 2 | 3 | __all__ = [ 4 | 'getargspec', 'raise_with_traceback', 'raise_from_none', 5 | ] 6 | 7 | def getargspec(method): 8 | args, varargs, keywords, defaults, _, _, _ = _getfullargspec(method) 9 | return args, varargs, keywords, defaults 10 | 11 | def raise_with_traceback(exc, tb): 12 | raise exc.with_traceback(tb) 13 | 14 | def raise_from_none(exc): 15 | raise exc from None 16 | 17 | -------------------------------------------------------------------------------- /aenum/_tuple.py: -------------------------------------------------------------------------------- 1 | from ._common import * 2 | from ._constant import NamedConstant 3 | import sys as _sys 4 | 5 | __all__ = [ 6 | 'TupleSize', 'NamedTuple', 7 | ] 8 | 9 | # NamedTuple 10 | 11 | class NamedTupleDict(OrderedDict): 12 | """Track field order and ensure field names are not reused. 13 | 14 | NamedTupleMeta will use the names found in self._field_names to translate 15 | to indices. 16 | """ 17 | def __init__(self, *args, **kwds): 18 | self._field_names = [] 19 | super(NamedTupleDict, self).__init__(*args, **kwds) 20 | 21 | def __setitem__(self, key, value): 22 | """Records anything not dundered or not a descriptor. 23 | 24 | If a field name is used twice, an error is raised. 25 | 26 | Single underscore (sunder) names are reserved. 27 | """ 28 | if is_sunder(key): 29 | if key not in ('_size_', '_order_', '_fields_', '_review_'): 30 | raise ValueError( 31 | '_sunder_ names, such as %r, are reserved for future NamedTuple use' 32 | % (key, ) 33 | ) 34 | elif is_dunder(key): 35 | if key == '__order__': 36 | key = '_order_' 37 | elif key in self._field_names: 38 | # overwriting a field? 39 | raise TypeError('attempt to reuse field name: %r' % (key, )) 40 | elif not is_descriptor(value): 41 | if key in self: 42 | # field overwriting a descriptor? 43 | raise TypeError('%s already defined as: %r' % (key, self[key])) 44 | self._field_names.append(key) 45 | super(NamedTupleDict, self).__setitem__(key, value) 46 | 47 | 48 | class _TupleAttributeAtIndex(object): 49 | 50 | def __init__(self, name, index, doc, default): 51 | self.name = name 52 | self.index = index 53 | if doc is undefined: 54 | doc = None 55 | self.__doc__ = doc 56 | self.default = default 57 | 58 | def __get__(self, instance, owner): 59 | if instance is None: 60 | return self 61 | if len(instance) <= self.index: 62 | raise AttributeError('%s instance has no value for %s' % (instance.__class__.__name__, self.name)) 63 | return instance[self.index] 64 | 65 | def __repr__(self): 66 | return '%s(%d)' % (self.__class__.__name__, self.index) 67 | 68 | 69 | 70 | 71 | class TupleSize(NamedConstant): 72 | fixed = constant('fixed', 'tuple length is static') 73 | minimum = constant('minimum', 'tuple must be at least x long (x is calculated during creation') 74 | variable = constant('variable', 'tuple length can be anything') 75 | 76 | class NamedTupleMeta(type): 77 | "Metaclass for NamedTuple" 78 | 79 | @classmethod 80 | def __prepare__(metacls, cls, bases, size=undefined, **kwds): 81 | return NamedTupleDict() 82 | 83 | def __init__(cls, *args , **kwds): 84 | super(NamedTupleMeta, cls).__init__(*args) 85 | 86 | def __new__(metacls, cls, bases, clsdict, size=undefined, **kwds): 87 | if bases == (object, ): 88 | bases = (tuple, object) 89 | elif tuple not in bases: 90 | if object in bases: 91 | index = bases.index(object) 92 | bases = bases[:index] + (tuple, ) + bases[index:] 93 | else: 94 | bases = bases + (tuple, ) 95 | # include any fields from base classes 96 | base_dict = NamedTupleDict() 97 | namedtuple_bases = [] 98 | for base in bases: 99 | if isinstance(base, NamedTupleMeta): 100 | namedtuple_bases.append(base) 101 | i = 0 102 | if namedtuple_bases: 103 | for name, index, doc, default in metacls._convert_fields(*namedtuple_bases): 104 | base_dict[name] = index, doc, default 105 | i = max(i, index) 106 | # construct properly ordered dict with normalized indexes 107 | for k, v in clsdict.items(): 108 | base_dict[k] = v 109 | original_dict = base_dict 110 | if size is not undefined and '_size_' in original_dict: 111 | raise TypeError('_size_ cannot be set if "size" is passed in header') 112 | add_order = isinstance(clsdict, NamedTupleDict) 113 | clsdict = NamedTupleDict() 114 | clsdict.setdefault('_size_', size or TupleSize.fixed) 115 | unnumbered = OrderedDict() 116 | numbered = OrderedDict() 117 | _order_ = original_dict.pop('_order_', []) 118 | if _order_ : 119 | _order_ = _order_.replace(',',' ').split() 120 | add_order = False 121 | # and process this class 122 | for k, v in original_dict.items(): 123 | if k not in original_dict._field_names: 124 | clsdict[k] = v 125 | else: 126 | # TODO:normalize v here 127 | if isinstance(v, baseinteger): 128 | # assume an offset 129 | v = v, undefined, undefined 130 | i = v[0] + 1 131 | target = numbered 132 | elif isinstance(v, basestring): 133 | # assume a docstring 134 | if add_order: 135 | v = i, v, undefined 136 | i += 1 137 | target = numbered 138 | else: 139 | v = undefined, v, undefined 140 | target = unnumbered 141 | elif isinstance(v, tuple) and len(v) in (2, 3) and isinstance(v[0], baseinteger) and isinstance(v[1], (basestring, NoneType)): 142 | # assume an offset, a docstring, and (maybe) a default 143 | if len(v) == 2: 144 | v = v + (undefined, ) 145 | v = v 146 | i = v[0] + 1 147 | target = numbered 148 | elif isinstance(v, tuple) and len(v) in (1, 2) and isinstance(v[0], (basestring, NoneType)): 149 | # assume a docstring, and (maybe) a default 150 | if len(v) == 1: 151 | v = v + (undefined, ) 152 | if add_order: 153 | v = (i, ) + v 154 | i += 1 155 | target = numbered 156 | else: 157 | v = (undefined, ) + v 158 | target = unnumbered 159 | else: 160 | # refuse to guess further 161 | raise ValueError('not sure what to do with %s=%r (should be OFFSET [, DOC [, DEFAULT]])' % (k, v)) 162 | target[k] = v 163 | # all index values have been normalized 164 | # deal with _order_ (or lack thereof) 165 | fields = [] 166 | aliases = [] 167 | seen = set() 168 | max_len = 0 169 | if not _order_: 170 | if unnumbered: 171 | raise ValueError("_order_ not specified and OFFSETs not declared for %r" % (unnumbered.keys(), )) 172 | for name, (index, doc, default) in sorted(numbered.items(), key=lambda nv: (nv[1][0], nv[0])): 173 | if index in seen: 174 | aliases.append(name) 175 | else: 176 | fields.append(name) 177 | seen.add(index) 178 | max_len = max(max_len, index + 1) 179 | offsets = numbered 180 | else: 181 | # check if any unnumbered not in _order_ 182 | missing = set(unnumbered) - set(_order_) 183 | if missing: 184 | raise ValueError("unable to order fields: %s (use _order_ or specify OFFSET" % missing) 185 | offsets = OrderedDict() 186 | # if any unnumbered, number them from their position in _order_ 187 | i = 0 188 | for k in _order_: 189 | try: 190 | index, doc, default = unnumbered.pop(k, None) or numbered.pop(k) 191 | except IndexError: 192 | raise ValueError('%s (from _order_) not found in %s' % (k, cls)) 193 | if index is not undefined: 194 | i = index 195 | if i in seen: 196 | aliases.append(k) 197 | else: 198 | fields.append(k) 199 | seen.add(i) 200 | offsets[k] = i, doc, default 201 | i += 1 202 | max_len = max(max_len, i) 203 | # now handle anything in numbered 204 | for k, (index, doc, default) in sorted(numbered.items(), key=lambda nv: (nv[1][0], nv[0])): 205 | if index in seen: 206 | aliases.append(k) 207 | else: 208 | fields.append(k) 209 | seen.add(index) 210 | offsets[k] = index, doc, default 211 | max_len = max(max_len, index+1) 212 | 213 | # at this point fields and aliases should be ordered lists, offsets should be an 214 | # OrdededDict with each value an int, str or None or undefined, default or None or undefined 215 | assert len(fields) + len(aliases) == len(offsets), "number of fields + aliases != number of offsets" 216 | assert set(fields) & set(offsets) == set(fields), "some fields are not in offsets: %s" % set(fields) & set(offsets) 217 | assert set(aliases) & set(offsets) == set(aliases), "some aliases are not in offsets: %s" % set(aliases) & set(offsets) 218 | for name, (index, doc, default) in offsets.items(): 219 | assert isinstance(index, baseinteger), "index for %s is not an int (%s:%r)" % (name, type(index), index) 220 | assert isinstance(doc, (basestring, NoneType)) or doc is undefined, "doc is not a str, None, nor undefined (%s:%r)" % (name, type(doc), doc) 221 | 222 | # create descriptors for fields 223 | for name, (index, doc, default) in offsets.items(): 224 | clsdict[name] = _TupleAttributeAtIndex(name, index, doc, default) 225 | clsdict['__slots__'] = () 226 | 227 | # create our new NamedTuple type 228 | namedtuple_class = super(NamedTupleMeta, metacls).__new__(metacls, cls, bases, clsdict) 229 | namedtuple_class._fields_ = fields 230 | namedtuple_class._aliases_ = aliases 231 | namedtuple_class._defined_len_ = max_len 232 | return namedtuple_class 233 | 234 | @staticmethod 235 | def _convert_fields(*namedtuples): 236 | "create list of index, doc, default triplets for cls in namedtuples" 237 | all_fields = [] 238 | for cls in namedtuples: 239 | base = len(all_fields) 240 | for field in cls._fields_: 241 | desc = getattr(cls, field) 242 | all_fields.append((field, base+desc.index, desc.__doc__, desc.default)) 243 | return all_fields 244 | 245 | def __add__(cls, other): 246 | "A new NamedTuple is created by concatenating the _fields_ and adjusting the descriptors" 247 | if not isinstance(other, NamedTupleMeta): 248 | return NotImplemented 249 | return NamedTupleMeta('%s%s' % (cls.__name__, other.__name__), (cls, other), {}) 250 | 251 | def __call__(cls, *args, **kwds): 252 | """Creates a new NamedTuple class or an instance of a NamedTuple subclass. 253 | 254 | NamedTuple should have args of (class_name, names, module) 255 | 256 | `names` can be: 257 | 258 | * A string containing member names, separated either with spaces or 259 | commas. Values are auto-numbered from 1. 260 | * An iterable of member names. Values are auto-numbered from 1. 261 | * An iterable of (member name, value) pairs. 262 | * A mapping of member name -> value. 263 | 264 | `module`, if set, will be stored in the new class' __module__ attribute; 265 | 266 | Note: if `module` is not set this routine will attempt to discover the 267 | calling module by walking the frame stack; if this is unsuccessful 268 | the resulting class will not be pickleable. 269 | 270 | subclass should have whatever arguments and/or keywords will be used to create an 271 | instance of the subclass 272 | """ 273 | if cls is NamedTuple or cls._defined_len_ == 0: 274 | original_args = args 275 | original_kwds = kwds.copy() 276 | # create a new subclass 277 | try: 278 | if 'class_name' in kwds: 279 | class_name = kwds.pop('class_name') 280 | else: 281 | class_name, args = args[0], args[1:] 282 | if 'names' in kwds: 283 | names = kwds.pop('names') 284 | else: 285 | names, args = args[0], args[1:] 286 | if 'module' in kwds: 287 | module = kwds.pop('module') 288 | elif args: 289 | module, args = args[0], args[1:] 290 | else: 291 | module = None 292 | if 'type' in kwds: 293 | type = kwds.pop('type') 294 | elif args: 295 | type, args = args[0], args[1:] 296 | else: 297 | type = None 298 | 299 | except IndexError: 300 | raise TypeError('too few arguments to NamedTuple: %s, %s' % (original_args, original_kwds)) 301 | if args or kwds: 302 | raise TypeError('too many arguments to NamedTuple: %s, %s' % (original_args, original_kwds)) 303 | if PY2: 304 | # if class_name is unicode, attempt a conversion to ASCII 305 | if isinstance(class_name, unicode): 306 | try: 307 | class_name = class_name.encode('ascii') 308 | except UnicodeEncodeError: 309 | raise TypeError('%r is not representable in ASCII' % (class_name, )) 310 | # quick exit if names is a NamedTuple 311 | if isinstance(names, NamedTupleMeta): 312 | names.__name__ = class_name 313 | if type is not None and type not in names.__bases__: 314 | names.__bases__ = (type, ) + names.__bases__ 315 | return names 316 | 317 | metacls = cls.__class__ 318 | bases = (cls, ) 319 | clsdict = metacls.__prepare__(class_name, bases) 320 | 321 | # special processing needed for names? 322 | if isinstance(names, basestring): 323 | names = names.replace(',', ' ').split() 324 | if isinstance(names, (tuple, list)) and isinstance(names[0], basestring): 325 | names = [(e, i) for (i, e) in enumerate(names)] 326 | # Here, names is either an iterable of (name, index) or (name, index, doc, default) or a mapping. 327 | item = None # in case names is empty 328 | for item in names: 329 | if isinstance(item, basestring): 330 | # mapping 331 | field_name, field_index = item, names[item] 332 | else: 333 | # non-mapping 334 | if len(item) == 2: 335 | field_name, field_index = item 336 | else: 337 | field_name, field_index = item[0], item[1:] 338 | clsdict[field_name] = field_index 339 | if type is not None: 340 | if not isinstance(type, tuple): 341 | type = (type, ) 342 | bases = type + bases 343 | namedtuple_class = metacls.__new__(metacls, class_name, bases, clsdict) 344 | 345 | # TODO: replace the frame hack if a blessed way to know the calling 346 | # module is ever developed 347 | if module is None: 348 | try: 349 | module = _sys._getframe(1).f_globals['__name__'] 350 | except (AttributeError, ValueError, KeyError): 351 | pass 352 | if module is None: 353 | make_class_unpicklable(namedtuple_class) 354 | else: 355 | namedtuple_class.__module__ = module 356 | 357 | return namedtuple_class 358 | else: 359 | # instantiate a subclass 360 | namedtuple_instance = cls.__new__(cls, *args, **kwds) 361 | if isinstance(namedtuple_instance, cls): 362 | namedtuple_instance.__init__(*args, **kwds) 363 | return namedtuple_instance 364 | 365 | @bltin_property 366 | def __fields__(cls): 367 | return list(cls._fields_) 368 | # collections.namedtuple compatibility 369 | _fields = __fields__ 370 | 371 | @bltin_property 372 | def __aliases__(cls): 373 | return list(cls._aliases_) 374 | 375 | def __repr__(cls): 376 | return "" % (cls.__name__, ) 377 | 378 | namedtuple_dict = _Addendum( 379 | dict=NamedTupleMeta.__prepare__('NamedTuple', (object, )), 380 | doc="NamedTuple base class.\n\n Derive from this class to define new NamedTuples.\n\n", 381 | ns=globals(), 382 | ) 383 | 384 | @namedtuple_dict 385 | def __new__(cls, *args, **kwds): 386 | if cls._size_ is TupleSize.fixed and len(args) > cls._defined_len_: 387 | raise TypeError('%d fields expected, %d received' % (cls._defined_len_, len(args))) 388 | unknown = set(kwds) - set(cls._fields_) - set(cls._aliases_) 389 | if unknown: 390 | raise TypeError('unknown fields: %r' % (unknown, )) 391 | final_args = list(args) + [undefined] * (len(cls.__fields__) - len(args)) 392 | for field, value in kwds.items(): 393 | index = getattr(cls, field).index 394 | if final_args[index] != undefined: 395 | raise TypeError('field %s specified more than once' % field) 396 | final_args[index] = value 397 | cls._review_(final_args) 398 | missing = [] 399 | for index, value in enumerate(final_args): 400 | if value is undefined: 401 | # look for default values 402 | name = cls.__fields__[index] 403 | default = getattr(cls, name).default 404 | if default is undefined: 405 | missing.append(name) 406 | else: 407 | final_args[index] = default 408 | if missing: 409 | if cls._size_ in (TupleSize.fixed, TupleSize.minimum): 410 | raise TypeError('values not provided for field(s): %s' % ', '.join(missing)) 411 | while final_args and final_args[-1] is undefined: 412 | final_args.pop() 413 | missing.pop() 414 | if cls._size_ is not TupleSize.variable or undefined in final_args: 415 | raise TypeError('values not provided for field(s): %s' % ', '.join(missing)) 416 | return tuple.__new__(cls, tuple(final_args)) 417 | 418 | @namedtuple_dict 419 | def __reduce_ex__(self, proto): 420 | return self.__class__, tuple(getattr(self, f) for f in self._fields_) 421 | 422 | @namedtuple_dict 423 | def __repr__(self): 424 | if len(self) == len(self._fields_): 425 | return "%s(%s)" % ( 426 | self.__class__.__name__, ', '.join(['%s=%r' % (f, o) for f, o in zip(self._fields_, self)]) 427 | ) 428 | else: 429 | return '%s(%s)' % (self.__class__.__name__, ', '.join([repr(o) for o in self])) 430 | 431 | @namedtuple_dict 432 | def __str__(self): 433 | return "%s(%s)" % ( 434 | self.__class__.__name__, ', '.join(['%r' % (getattr(self, f), ) for f in self._fields_]) 435 | ) 436 | 437 | @namedtuple_dict 438 | @bltin_property 439 | def _fields_(self): 440 | return list(self.__class__._fields_) 441 | 442 | # compatibility methods with stdlib namedtuple 443 | @namedtuple_dict 444 | @bltin_property 445 | def __aliases__(self): 446 | return list(self.__class__._aliases_) 447 | 448 | @namedtuple_dict 449 | @bltin_property 450 | def _fields(self): 451 | return list(self.__class__._fields_) 452 | 453 | @namedtuple_dict 454 | @classmethod 455 | def _make(cls, iterable, new=None, len=None): 456 | return cls.__new__(cls, *iterable) 457 | 458 | @namedtuple_dict 459 | def _asdict(self): 460 | return OrderedDict(zip(self._fields_, self)) 461 | 462 | @namedtuple_dict 463 | def _replace(self, **kwds): 464 | current = self._asdict() 465 | current.update(kwds) 466 | return self.__class__(**current) 467 | 468 | @namedtuple_dict 469 | @classmethod 470 | def _review_(cls, final_args): 471 | pass 472 | 473 | NamedTuple = NamedTupleMeta('NamedTuple', (object, ), namedtuple_dict.resolve()) 474 | del namedtuple_dict 475 | 476 | 477 | 478 | -------------------------------------------------------------------------------- /aenum/doc/aenum.rst: -------------------------------------------------------------------------------- 1 | ``aenum`` --- support for advanced enumerations, namedtuples, and constants 2 | =========================================================================== 3 | 4 | .. :synopsis:: enumerations are sets of symbolic names bound to unique, 5 | constant values; namedtuples are fixed- or variable-length 6 | tuples with the positions addressable by field name as well as by index; 7 | constants are classes of named constants that cannot be rebound. 8 | .. :moduleauthor:: Ethan Furman 9 | 10 | ---------------- 11 | 12 | An ``Enum`` is a set of symbolic names (members) bound to unique, constant 13 | values. Within an enumeration, the members can be compared by identity, and 14 | the enumeration itself can be iterated over. 15 | 16 | A ``NamedTuple`` is a class-based, fixed-length tuple with a name for each 17 | possible position accessible using attribute-access notation. 18 | 19 | A ``NamedConstant`` is a class whose members cannot be rebound; it lacks all 20 | other ``Enum`` capabilities, however; consequently, it can have duplicate 21 | values. There is also a ``module`` function that can insert the 22 | ``NamedConstant`` class into ``sys.modules`` where it will appear to be a 23 | module whose top-level names cannot be rebound. 24 | 25 | .. note:: 26 | ``constant`` refers to names not being rebound; mutable objects can be 27 | mutated. 28 | 29 | 30 | Module Contents 31 | --------------- 32 | 33 | This module defines five enumeration classes that can be used to define unique 34 | sets of names and values, one ``Enum`` class decorator, one ``NamedTuple`` 35 | class, one ``NamedConstant`` class, and several helpers. 36 | 37 | ``NamedConstant`` 38 | 39 | NamedConstant class for creating groups of constants. These names cannot be 40 | rebound to other values. 41 | 42 | ``Enum`` 43 | 44 | Base class for creating enumerated constants. See section `Enum Functional API`_ 45 | for an alternate construction syntax. 46 | 47 | ``AddValue`` 48 | 49 | Flag specifying that ``_generate_next_value_`` should always be called to 50 | provide the initial value for an enum member. 51 | 52 | ``MultiValue`` 53 | 54 | Flag specifying that each item of tuple value is a separate value for that 55 | member; the first tuple item is the canonical one. 56 | 57 | ``NoAlias`` 58 | 59 | Flag specifying that duplicate valued members are distinct and not aliases; 60 | by-value lookups are disabled. 61 | 62 | ``Unique`` 63 | 64 | Flag specifying that duplicate valued members are not allowed. 65 | 66 | .. note:: 67 | The flags are inherited by the enumeration's subclasses. To use them in 68 | Python 2 assign to ``_settings_`` in the class body. 69 | 70 | ``IntEnum`` 71 | 72 | Base class for creating enumerated constants that are also subclasses of ``int``. 73 | 74 | ``AutoNumberEnum`` 75 | 76 | Derived class that automatically assigns an ``int`` value to each member. 77 | 78 | ``OrderedEnum`` 79 | 80 | Derived class that adds ``<``, ``<=``, ``>=``, and ``>`` methods to an ``Enum``. 81 | 82 | ``UniqueEnum`` 83 | 84 | Derived class that ensures only one name is bound to any one value. 85 | 86 | ``unique`` 87 | 88 | Enum class decorator that ensures only one name is bound to any one value. 89 | 90 | .. note:: 91 | 92 | the ``UniqueEnum`` class, the ``unique`` decorator, and the Unique 93 | flag all do the same thing; you do not need to use more than one of 94 | them at the same time. 95 | 96 | ``NamedTuple`` 97 | 98 | Base class for `creating NamedTuples`_, either by subclassing or via it's 99 | functional API. 100 | 101 | ``constant`` 102 | 103 | Descriptor to add constant values to an ``Enum``, or advanced constants to 104 | ``NamedConstant``. 105 | 106 | ``convert`` 107 | 108 | Helper to transform target global variables into an ``Enum``. 109 | 110 | ``enum`` 111 | 112 | Helper for specifying keyword arguments when creating ``Enum`` members. 113 | 114 | ``export`` 115 | 116 | Helper for inserting ``Enum`` members and ``NamedConstant`` constants into a 117 | namespace (usually ``globals()``. 118 | 119 | ``extend_enum`` 120 | 121 | Helper for adding new ``Enum`` members, both stdlib and aenum. 122 | 123 | ``module`` 124 | 125 | Function to take a ``NamedConstant`` or ``Enum`` class and insert it into 126 | ``sys.modules`` with the affect of a module whose top-level constant and 127 | member names cannot be rebound. 128 | 129 | ``skip`` 130 | 131 | Descriptor to add a normal (non-``Enum`` member) attribute to an ``Enum`` 132 | or ``NamedConstant``. 133 | 134 | 135 | Creating an Enum 136 | ---------------- 137 | 138 | Enumerations are created using the ``class`` syntax, which makes them 139 | easy to read and write. An alternative creation method is described in 140 | `Enum Functional API`_. To define an enumeration, subclass ``Enum`` as 141 | follows:: 142 | 143 | >>> from aenum import Enum 144 | >>> class Color(Enum): 145 | ... red = 1 146 | ... green = 2 147 | ... blue = 3 148 | 149 | *Nomenclature* 150 | 151 | - The class ``Color`` is an *enumeration* (or *enum*) 152 | - The attributes ``Color.red``, ``Color.green``, etc., are 153 | *enumeration members* (or *enum members*). 154 | - The enum members have *names* and *values* (the name of 155 | ``Color.red`` is ``red``, the value of ``Color.blue`` is 156 | ``3``, etc.) 157 | 158 | .. note:: 159 | 160 | Even though we use the ``class`` syntax to create Enums, Enums 161 | are not normal Python classes. See `How are Enums different?`_ for 162 | more details. 163 | 164 | Enumeration members have human readable string representations:: 165 | 166 | >>> print(Color.red) 167 | Color.red 168 | 169 | ...while their ``repr`` has more information:: 170 | 171 | >>> print(repr(Color.red)) 172 | 173 | 174 | The *type* of an enumeration member is the enumeration it belongs to:: 175 | 176 | >>> type(Color.red) 177 | 178 | >>> isinstance(Color.green, Color) 179 | True 180 | 181 | Enumerations support iteration. In Python 3.x definition order is used; in 182 | Python 2.x the definition order is not available, but class attribute 183 | ``_order_`` is supported; otherwise, value order is used if posible, 184 | otherwise alphabetical name order is used:: 185 | 186 | >>> class Shake(Enum): 187 | ... _order_ = 'vanilla chocolate cookies mint' # only needed in 2.x 188 | ... vanilla = 7 189 | ... chocolate = 4 190 | ... cookies = 9 191 | ... mint = 3 192 | ... 193 | >>> for shake in Shake: 194 | ... print(shake) 195 | ... 196 | Shake.vanilla 197 | Shake.chocolate 198 | Shake.cookies 199 | Shake.mint 200 | 201 | The ``_order_`` attribute is always removed, but in 3.x it is also used to 202 | verify that definition order is the same (useful for py2&3 code bases); 203 | however, in the stdlib version it will be ignored and not removed. 204 | 205 | .. note:: 206 | 207 | To maintain compatibility with Python 3.4 and 3.5, use __order__ 208 | instead (double leading and trailing underscores). 209 | 210 | Enumeration members are hashable, so they can be used in dictionaries and sets:: 211 | 212 | >>> apples = {} 213 | >>> apples[Color.red] = 'red delicious' 214 | >>> apples[Color.green] = 'granny smith' 215 | >>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'} 216 | True 217 | 218 | In Python 3 the class syntax has a few extra advancements:: 219 | 220 | --> class Color( 221 | ... Enum, 222 | ... settings=(AddValue, MultiValue, NoAlias, Unique), 223 | ... init='field_name1 field_name2 ...', 224 | ... start=7, 225 | ... ) 226 | ... 227 | 228 | ``start`` is used to specify the starting value for the first member:: 229 | 230 | --> class Count(Enum, start=11): 231 | ... eleven 232 | ... twelve 233 | ... 234 | --> Count.twelve.value == 12 235 | True 236 | 237 | ``init`` specifies the attribute names to store creation values to:: 238 | 239 | --> class Planet(Enum, init='mass radius'): 240 | ... MERCURY = (3.303e+23, 2.4397e6) 241 | ... EARTH = (5.976e+24, 6.37814e6) 242 | ... 243 | --> Planet.EARTH.value 244 | (5.976e+24, 6378140.0) 245 | --> Planet.EARTH.radius 246 | 2.4397e6 247 | 248 | The various settings enable special behavior: 249 | 250 | - ``AddValue`` calls a user supplied ``_generate_next_value_`` to provide 251 | the initial value 252 | - ``MultiValue`` allows multiple values per member instead of the usual 1 253 | - ``NoAlias`` allows different members to have the same value 254 | - ``Unique`` disallows different members to have the same value 255 | 256 | .. note:: 257 | 258 | To use these features in Python 2 use the _sundered_ versions of 259 | the names in the class body: ``_start_``, ``_init_``, ``_settings_``. 260 | 261 | 262 | Programmatic access to enumeration members and their attributes 263 | --------------------------------------------------------------- 264 | 265 | Sometimes it's useful to access members in enumerations programmatically (i.e. 266 | situations where ``Color.red`` won't do because the exact color is not known 267 | at program-writing time). ``Enum`` allows such access:: 268 | 269 | >>> Color(1) 270 | 271 | >>> Color(3) 272 | 273 | 274 | If you want to access enum members by *name*, use item access:: 275 | 276 | >>> Color['red'] 277 | 278 | >>> Color['green'] 279 | 280 | 281 | If have an enum member and need its ``name`` or ``value``:: 282 | 283 | >>> member = Color.red 284 | >>> member.name 285 | 'red' 286 | >>> member.value 287 | 1 288 | 289 | 290 | Duplicating enum members and values 291 | ----------------------------------- 292 | 293 | Having two enum members (or any other attribute) with the same name is invalid; 294 | in Python 3.x this would raise an error, but in Python 2.x the second member 295 | simply overwrites the first:: 296 | 297 | # python 2.x 298 | --> class Shape(Enum): 299 | ... square = 2 300 | ... square = 3 301 | ... 302 | --> Shape.square 303 | 304 | 305 | # python 3.x 306 | --> class Shape(Enum): 307 | ... square = 2 308 | ... square = 3 309 | Traceback (most recent call last): 310 | ... 311 | TypeError: Attempted to reuse key: 'square' 312 | 313 | However, two enum members are allowed to have the same value. Given two members 314 | A and B with the same value (and A defined first), B is an alias to A. By-value 315 | lookup of the value of A and B will return A. By-name lookup of B will also 316 | return A:: 317 | 318 | >>> class Shape(Enum): 319 | ... _order_ = 'square diamond circle' # needed in 2.x 320 | ... square = 2 321 | ... diamond = 1 322 | ... circle = 3 323 | ... alias_for_square = 2 324 | ... 325 | >>> Shape.square 326 | 327 | >>> Shape.alias_for_square 328 | 329 | >>> Shape(2) 330 | 331 | 332 | 333 | Allowing aliases is not always desirable. ``unique`` can be used to ensure 334 | that none exist in a particular enumeration:: 335 | 336 | >>> from aenum import unique 337 | >>> @unique 338 | ... class Mistake(Enum): 339 | ... _order_ = 'one two three' # only needed in 2.x 340 | ... one = 1 341 | ... two = 2 342 | ... three = 3 343 | ... four = 3 344 | Traceback (most recent call last): 345 | ... 346 | ValueError: duplicate names found in : four -> three 347 | 348 | Iterating over the members of an enum does not provide the aliases:: 349 | 350 | >>> list(Shape) 351 | [, , ] 352 | 353 | The special attribute ``__members__`` is a dictionary mapping names to members. 354 | It includes all names defined in the enumeration, including the aliases:: 355 | 356 | >>> for name, member in sorted(Shape.__members__.items()): 357 | ... name, member 358 | ... 359 | ('alias_for_square', ) 360 | ('circle', ) 361 | ('diamond', ) 362 | ('square', ) 363 | 364 | The ``__members__`` attribute can be used for detailed programmatic access to 365 | the enumeration members. For example, finding all the aliases:: 366 | 367 | >>> [n for n, mbr in Shape.__members__.items() if mbr.name != n] 368 | ['alias_for_square'] 369 | 370 | Comparisons 371 | ----------- 372 | 373 | Enumeration members are compared by identity:: 374 | 375 | >>> Color.red is Color.red 376 | True 377 | >>> Color.red is Color.blue 378 | False 379 | >>> Color.red is not Color.blue 380 | True 381 | 382 | Ordered comparisons between enumeration values are *not* supported. Enum 383 | members are not integers (but see `IntEnum`_ below):: 384 | 385 | >>> Color.red < Color.blue 386 | Traceback (most recent call last): 387 | File "", line 1, in 388 | TypeError: unorderable types: Color() < Color() 389 | 390 | .. warning:: 391 | 392 | In Python 2 *everything* is ordered, even though the ordering may not 393 | make sense. If you want your enumerations to have a sensible ordering 394 | consider using an `OrderedEnum`_. 395 | 396 | 397 | Equality comparisons are defined though:: 398 | 399 | >>> Color.blue == Color.red 400 | False 401 | >>> Color.blue != Color.red 402 | True 403 | >>> Color.blue == Color.blue 404 | True 405 | 406 | Comparisons against non-enumeration values will always compare not equal 407 | (again, ``IntEnum`` was explicitly designed to behave differently, see 408 | below):: 409 | 410 | >>> Color.blue == 2 411 | False 412 | 413 | 414 | Allowed members and attributes of enumerations 415 | ---------------------------------------------- 416 | 417 | The examples above use integers for enumeration values. Using integers is 418 | short and handy (and provided by default by the `Enum Functional API`_), but not 419 | strictly enforced. In the vast majority of use-cases, one doesn't care what 420 | the actual value of an enumeration is. But if the value *is* important, 421 | enumerations can have arbitrary values. 422 | 423 | Enumerations are Python classes, and can have methods and special methods as 424 | usual. If we have this enumeration:: 425 | 426 | >>> class Mood(Enum): 427 | ... funky = 1 428 | ... happy = 3 429 | ... 430 | ... def describe(self): 431 | ... # self is the member here 432 | ... return self.name, self.value 433 | ... 434 | ... def __str__(self): 435 | ... return 'my custom str! {0}'.format(self.value) 436 | ... 437 | ... @classmethod 438 | ... def favorite_mood(cls): 439 | ... # cls here is the enumeration 440 | ... return cls.happy 441 | 442 | Then:: 443 | 444 | >>> Mood.favorite_mood() 445 | 446 | >>> Mood.happy.describe() 447 | ('happy', 3) 448 | >>> str(Mood.funky) 449 | 'my custom str! 1' 450 | 451 | The rules for what is allowed are as follows: _sunder_ names (starting and 452 | ending with a single underscore) are reserved by enum and cannot be used; 453 | all other attributes defined within an enumeration will become members of this 454 | enumeration, with the exception of *__dunder__* names and descriptors (methods 455 | are also descriptors). 456 | 457 | .. note:: 458 | 459 | If your enumeration defines ``__new__`` and/or ``__init__`` then 460 | whatever value(s) were given to the enum member will be passed into 461 | those methods. See `Planet`_ for an example. 462 | 463 | 464 | Restricted Enum subclassing 465 | --------------------------- 466 | 467 | A new `Enum` class must have one base Enum class, up to one concrete 468 | data type, and as many `object`-based mixin classes as needed. The 469 | order of these base classes is:: 470 | 471 | def EnumName([mix-in, ...,] [data-type,] base-enum): 472 | pass 473 | 474 | Also, subclassing an enumeration is allowed only if the enumeration does not define 475 | 476 | any members. So this is forbidden:: 477 | 478 | >>> class MoreColor(Color): 479 | ... pink = 17 480 | Traceback (most recent call last): 481 | ... 482 | TypeError: cannot extend 483 | 484 | But this is allowed:: 485 | 486 | >>> class Foo(Enum): 487 | ... def some_behavior(self): 488 | ... pass 489 | ... 490 | >>> class Bar(Foo): 491 | ... happy = 1 492 | ... sad = 2 493 | ... 494 | 495 | Allowing subclassing of enums that define members would lead to a violation of 496 | some important invariants of types and instances. On the other hand, it makes 497 | sense to allow sharing some common behavior between a group of enumerations. 498 | (See `OrderedEnum`_ for an example.) 499 | 500 | 501 | Pickling 502 | -------- 503 | 504 | Enumerations can be pickled and unpickled:: 505 | 506 | >>> from aenum.test import Fruit 507 | >>> from pickle import dumps, loads 508 | >>> Fruit.tomato is loads(dumps(Fruit.tomato, 2)) 509 | True 510 | 511 | The usual restrictions for pickling apply: picklable enums must be defined in 512 | the top level of a module, since unpickling requires them to be importable 513 | from that module. 514 | 515 | .. note:: 516 | 517 | With pickle protocol version 4 (introduced in Python 3.4) it is possible 518 | to easily pickle enums nested in other classes. 519 | 520 | 521 | 522 | Enum Functional API 523 | ------------------- 524 | 525 | The ``Enum`` class is callable, providing the following functional API:: 526 | 527 | >>> Animal = Enum('Animal', 'ant bee cat dog') 528 | >>> Animal 529 | 530 | >>> Animal.ant 531 | 532 | >>> Animal.ant.value 533 | 1 534 | >>> list(Animal) 535 | [, , , ] 536 | 537 | The semantics of this API resemble ``namedtuple``. The first argument 538 | of the call to ``Enum`` is the name of the enumeration. 539 | 540 | The second argument is the *source* of enumeration member names. It can be a 541 | whitespace-separated string of names, a sequence of names, a sequence of 542 | 2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to 543 | values. The last two options enable assigning arbitrary values to 544 | enumerations; the others auto-assign increasing integers starting with 1. A 545 | new class derived from ``Enum`` is returned. In other words, the above 546 | assignment to ``Animal`` is equivalent to:: 547 | 548 | >>> class Animals(Enum): 549 | ... ant = 1 550 | ... bee = 2 551 | ... cat = 3 552 | ... dog = 4 553 | 554 | Pickling enums created with the functional API can be tricky as frame stack 555 | implementation details are used to try and figure out which module the 556 | enumeration is being created in (e.g. it will fail if you use a utility 557 | function in separate module, and also may not work on IronPython or Jython). 558 | The solution is to specify the module name explicitly as follows:: 559 | 560 | >>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__) 561 | 562 | Derived Enumerations 563 | -------------------- 564 | 565 | IntEnum 566 | ^^^^^^^ 567 | 568 | A variation of ``Enum`` is provided which is also a subclass of 569 | ``int``. Members of an ``IntEnum`` can be compared to integers; 570 | by extension, integer enumerations of different types can also be compared 571 | to each other:: 572 | 573 | >>> from aenum import IntEnum 574 | >>> class Shape(IntEnum): 575 | ... circle = 1 576 | ... square = 2 577 | ... 578 | >>> class Request(IntEnum): 579 | ... post = 1 580 | ... get = 2 581 | ... 582 | >>> Shape == 1 583 | False 584 | >>> Shape.circle == 1 585 | True 586 | >>> Shape.circle == Request.post 587 | True 588 | 589 | However, they still can't be compared to standard ``Enum`` enumerations:: 590 | 591 | >>> class Shape(IntEnum): 592 | ... circle = 1 593 | ... square = 2 594 | ... 595 | >>> class Color(Enum): 596 | ... red = 1 597 | ... green = 2 598 | ... 599 | >>> Shape.circle == Color.red 600 | False 601 | 602 | ``IntEnum`` values behave like integers in other ways you'd expect:: 603 | 604 | >>> int(Shape.circle) 605 | 1 606 | >>> ['a', 'b', 'c'][Shape.circle] 607 | 'b' 608 | >>> [i for i in range(Shape.square)] 609 | [0, 1] 610 | 611 | For the vast majority of code, ``Enum`` is strongly recommended, 612 | since ``IntEnum`` breaks some semantic promises of an enumeration (by 613 | being comparable to integers, and thus by transitivity to other 614 | unrelated enumerations). It should be used only in special cases where 615 | there's no other choice; for example, when integer constants are 616 | replaced with enumerations and backwards compatibility is required with code 617 | that still expects integers. 618 | 619 | 620 | IntFlag 621 | ^^^^^^^ 622 | 623 | The next variation of ``Enum`` provided, ``IntFlag``, is also based 624 | on ``int``. The difference being ``IntFlag`` members can be combined 625 | using the bitwise operators (&, \|, ^, ~) and the result is still an 626 | ``IntFlag`` member. However, as the name implies, ``IntFlag`` 627 | members also subclass ``int`` and can be used wherever an ``int`` is 628 | used. Any operation on an ``IntFlag`` member besides the bit-wise 629 | operations will lose the ``IntFlag`` membership. 630 | 631 | Sample ``IntFlag`` class:: 632 | 633 | >>> from aenum import IntFlag 634 | >>> class Perm(IntFlag): 635 | ... _order_ = 'R W X' 636 | ... R = 4 637 | ... W = 2 638 | ... X = 1 639 | ... 640 | >>> Perm.R | Perm.W 641 | 642 | >>> Perm.R + Perm.W 643 | 6 644 | >>> RW = Perm.R | Perm.W 645 | >>> Perm.R in RW 646 | True 647 | 648 | It is also possible to name the combinations:: 649 | 650 | >>> class Perm(IntFlag): 651 | ... _order_ = 'R W X' 652 | ... R = 4 653 | ... W = 2 654 | ... X = 1 655 | ... RWX = 7 656 | >>> Perm.RWX 657 | 658 | >>> ~Perm.RWX 659 | 660 | 661 | Another important difference between ``IntFlag`` and ``Enum`` is that 662 | if no flags are set (the value is 0), its boolean evaluation is ``False``:: 663 | 664 | >>> Perm.R & Perm.X 665 | 666 | >>> bool(Perm.R & Perm.X) 667 | False 668 | 669 | Because ``IntFlag`` members are also subclasses of ``int`` they can 670 | be combined with them:: 671 | 672 | >>> Perm.X | 4 673 | 674 | 675 | If the result is not a ``Flag`` then, depending on the ``_boundary_`` setting, 676 | an exception is raised (``STRICT``), the extra bits are lost (``CONFORM``), or 677 | it reverts to an int (``EJECT``): 678 | 679 | >>> from aenum import STRICT, CONFORM, EJECT 680 | >>> Perm._boundary_ = STRICT 681 | >>> Perm.X | 8 682 | Traceback (most recent call last): 683 | ... 684 | ValueError: invalid value 9 685 | given 0b0 1001 686 | allowed 0b0 0111 687 | 688 | >>> Perm._boundary_ = EJECT 689 | >>> Perm.X | 8 690 | 9 691 | 692 | >>> Perm._boundary_ = CONFORM 693 | >>> Perm.X | 8 694 | 695 | 696 | 697 | Flag 698 | ^^^^ 699 | 700 | The last variation is ``Flag``. Like ``IntFlag``, ``Flag`` 701 | members can be combined using the bitwise operators (&, \|, ^, ~). Unlike 702 | ``IntFlag``, they cannot be combined with, nor compared against, any 703 | other ``Flag`` enumeration, nor ``int``. While it is possible to 704 | specify the values directly it is recommended to use ``auto`` as the 705 | value and let ``Flag`` select an appropriate value. 706 | 707 | Like ``IntFlag``, if a combination of ``Flag`` members results in no 708 | flags being set, the boolean evaluation is ``False``:: 709 | 710 | >>> from aenum import Flag, auto 711 | >>> class Color(Flag): 712 | ... RED = auto() 713 | ... BLUE = auto() 714 | ... GREEN = auto() 715 | ... 716 | >>> Color.RED & Color.GREEN 717 | 718 | >>> bool(Color.RED & Color.GREEN) 719 | False 720 | 721 | Individual flags should have values that are powers of two (1, 2, 4, 8, ...), 722 | while combinations of flags won't:: 723 | 724 | --> class Color(Flag): 725 | ... RED = auto() 726 | ... BLUE = auto() 727 | ... GREEN = auto() 728 | ... WHITE = RED | BLUE | GREEN 729 | ... 730 | --> Color.WHITE 731 | 732 | 733 | Giving a name to the "no flags set" condition does not change its boolean 734 | value:: 735 | 736 | >>> class Color(Flag): 737 | ... BLACK = 0 738 | ... RED = auto() 739 | ... BLUE = auto() 740 | ... GREEN = auto() 741 | ... 742 | >>> Color.BLACK 743 | 744 | >>> bool(Color.BLACK) 745 | False 746 | 747 | Flags can be iterated over to retrieve the individual truthy flags in the value:: 748 | 749 | >>> class Color(Flag): 750 | ... _order_ = 'BLACK RED BLUE GREEN WHITE' 751 | ... BLACK = 0 752 | ... RED = auto() 753 | ... BLUE = auto() 754 | ... GREEN = auto() 755 | ... WHITE = RED | BLUE | GREEN 756 | ... 757 | >>> list(Color.GREEN) 758 | [] 759 | >>> list(Color.WHITE) 760 | [, , ] 761 | 762 | .. note:: 763 | 764 | For the majority of new code, ``Enum`` and ``Flag`` are strongly 765 | recommended, since ``IntEnum`` and ``IntFlag`` break some 766 | semantic promises of an enumeration (by being comparable to integers, and 767 | thus by transitivity to other unrelated enumerations). ``IntEnum`` 768 | and ``IntFlag`` should be used only in cases where ``Enum`` and 769 | ``Flag`` will not do; for example, when integer constants are replaced 770 | with enumerations, or for interoperability with other systems. 771 | 772 | 773 | Others 774 | ^^^^^^ 775 | 776 | While ``IntEnum`` is part of the ``aenum`` module, it would be very 777 | simple to implement independently:: 778 | 779 | class MyIntEnum(int, Enum): 780 | pass 781 | 782 | This demonstrates how similar derived enumerations can be defined; for example 783 | a ``MyStrEnum`` that mixes in ``str`` instead of ``int``. 784 | 785 | Some rules: 786 | 787 | 1. When subclassing ``Enum``, mix-in types must appear before 788 | ``Enum`` itself in the sequence of bases, as in the ``MyIntEnum`` 789 | example above. 790 | 2. While ``Enum`` can have members of any type, once you mix in an 791 | additional type, all the members must have values of that type or be 792 | convertible into that type. This restriction does not apply to mix-ins 793 | which only add methods and don't specify another data type. 794 | 3. When another data type is mixed in, the ``value`` attribute is *not the 795 | same* as the enum member itself, although it is equivalant and will compare 796 | equal. 797 | 4. %-style formatting: ``%s`` and ``%r`` call ``Enum``'s ``__str__`` and 798 | ``__repr__`` respectively; other codes (such as ``%i`` or ``%h`` for 799 | MyIntEnum) treat the enum member as its mixed-in type. 800 | 5. ``str.__format__`` (or ``format``) will use the mixed-in 801 | type's ``__format__``. If the ``Enum``'s ``str`` or ``repr`` is desired 802 | use the ``!s`` or ``!r`` ``str`` format codes. 803 | 804 | .. note:: 805 | 806 | If you override the ``__str__`` method, then it will be used to provide the 807 | string portion of the ``format()`` call. 808 | 809 | .. note:: 810 | 811 | Prior to Python 3.4 there is a bug in ``str``'s %-formatting: ``int`` 812 | subclasses are printed as strings and not numbers when the ``%d``, ``%i``, 813 | or ``%u`` codes are used. 814 | 815 | 816 | Extra Goodies 817 | ------------- 818 | 819 | aenum supports a few extra techniques not found in the stdlib version. 820 | 821 | enum 822 | ^^^^ 823 | 824 | If you have several items to initialize your ``Enum`` members with and 825 | would like to use keyword arguments, the ``enum`` helper is for you:: 826 | 827 | >>> from aenum import enum 828 | >>> class Presidents(Enum): 829 | ... Washington = enum('George Washington', circa=1776, death=1797) 830 | ... Jackson = enum('Andrew Jackson', circa=1830, death=1837) 831 | ... Lincoln = enum('Abraham Lincoln', circa=1860, death=1865) 832 | ... 833 | >>> Presidents.Lincoln 834 | 835 | 836 | extend_enum 837 | ^^^^^^^^^^^ 838 | 839 | For those rare cases when you need to create your ``Enum`` in pieces, you 840 | can use ``extend_enum`` to add new members after the initial creation 841 | (the new member is returned):: 842 | 843 | >>> from aenum import extend_enum 844 | >>> class Color(Enum): 845 | ... red = 1 846 | ... green = 2 847 | ... blue = 3 848 | ... 849 | >>> list(Color) 850 | [, , ] 851 | >>> extend_enum(Color, 'opacity', 4) 852 | 853 | >>> list(Color) 854 | [, , , ] 855 | >>> Color.opacity in Color 856 | True 857 | >>> Color.opacity.name == 'opacity' 858 | True 859 | >>> Color.opacity.value == 4 860 | True 861 | >>> Color(4) 862 | 863 | >>> Color['opacity'] 864 | 865 | 866 | --> Color.__members__ 867 | OrderedDict([ 868 | ('red', ), 869 | ('green', ), 870 | ('blue', ), 871 | ('opacity', ) 872 | ]) 873 | 874 | constant 875 | ^^^^^^^^ 876 | 877 | If you need to have some constant value in your ``Enum`` that isn't a member, 878 | use ``constant``:: 879 | 880 | >>> from aenum import constant 881 | >>> class Planet(Enum): 882 | ... MERCURY = (3.303e+23, 2.4397e6) 883 | ... EARTH = (5.976e+24, 6.37814e6) 884 | ... JUPITER = (1.9e+27, 7.1492e7) 885 | ... URANUS = (8.686e+25, 2.5559e7) 886 | ... G = constant(6.67300E-11) 887 | ... def __init__(self, mass, radius): 888 | ... self.mass = mass # in kilograms 889 | ... self.radius = radius # in meters 890 | ... @property 891 | ... def surface_gravity(self): 892 | ... # universal gravitational constant (m3 kg-1 s-2) 893 | ... return self.G * self.mass / (self.radius * self.radius) 894 | ... 895 | >>> Planet.EARTH.value 896 | (5.976e+24, 6378140.0) 897 | >>> Planet.EARTH.surface_gravity 898 | 9.802652743337129 899 | >>> Planet.G 900 | 6.673e-11 901 | >>> Planet.G = 9 902 | Traceback (most recent call last): 903 | ... 904 | AttributeError: Planet: cannot rebind constant 'G' 905 | 906 | skip 907 | ^^^^ 908 | 909 | If you need a standard attribute that is not converted into an ``Enum`` 910 | member, use ``skip``:: 911 | 912 | >>> from aenum import skip 913 | >>> class Color(Enum): 914 | ... red = 1 915 | ... green = 2 916 | ... blue = 3 917 | ... opacity = skip(0.45) 918 | ... 919 | >>> Color.opacity 920 | 0.45 921 | >>> Color.opacity = 0.77 922 | >>> Color.opacity 923 | 0.77 924 | 925 | start 926 | ^^^^^ 927 | 928 | ``start`` can be used to turn on auto-numbering (useful for when you don't 929 | care which numbers are assigned as long as they are consistent and in order) 930 | The Python 3 version can look like this:: 931 | 932 | >>> class Color(Enum, start=1): # doctest: +SKIP 933 | ... red, green, blue 934 | ... 935 | >>> Color.blue 936 | 937 | 938 | This can also be done in Python 2, albeit not as elegantly (this also works in 939 | Python 3):: 940 | 941 | >>> class Color(Enum): # doctest: +SKIP 942 | ... _start_ = 1 943 | ... red = auto() 944 | ... green = auto() 945 | ... blue = auto() 946 | ... 947 | >>> Color.blue 948 | 949 | 950 | init 951 | ^^^^ 952 | 953 | If you need an ``__init__`` method that does nothing besides save its 954 | arguments, ``init`` is for you:: 955 | 956 | >>> class Planet(Enum, init='mass radius'): # doctest: +SKIP 957 | ... MERCURY = (3.303e+23, 2.4397e6) 958 | ... EARTH = (5.976e+24, 6.37814e6) 959 | ... JUPITER = (1.9e+27, 7.1492e7) 960 | ... URANUS = (8.686e+25, 2.5559e7) 961 | ... G = constant(6.67300E-11) 962 | ... @property 963 | ... def surface_gravity(self): 964 | ... # universal gravitational constant (m3 kg-1 s-2) 965 | ... return self.G * self.mass / (self.radius * self.radius) 966 | ... 967 | >>> Planet.JUPITER.value 968 | (1.9e+27, 71492000.0) 969 | >>> Planet.JUPITER.mass 970 | 1.9e+27 971 | 972 | .. note:: 973 | 974 | Just as with ``start`` above, in Python 2 you must put the keyword as a 975 | _sunder_ in the class body -- ``_init_ = 'mass radius'``. 976 | 977 | init and missing values 978 | ^^^^^^^^^^^^^^^^^^^^^^^ 979 | 980 | If ``_init_`` calls for values that are not supplied, ``_generate_next_value_`` 981 | will be called in an effort to generate them. Here is an example in Python 2:: 982 | 983 | >>> from aenum import Enum 984 | >>> class SelectionEnum(Enum): 985 | ... _init_ = 'db user' 986 | ... def __new__(cls, *args, **kwds): 987 | ... count = len(cls.__members__) 988 | ... obj = object.__new__(cls) 989 | ... obj._count = count 990 | ... obj._value_ = args 991 | ... return obj 992 | ... @staticmethod 993 | ... def _generate_next_value_(name, start, count, values, *args, **kwds): 994 | ... return (name, ) + args 995 | ... 996 | >>> class NotificationType(SelectionEnum): 997 | ... # usually, name is the same as db 998 | ... # but not for blanks 999 | ... blank = '', '' 1000 | ... C = 'Catalog' 1001 | ... S = 'Sheet' 1002 | ... B = 'Both' 1003 | ... 1004 | >>> NotificationType.blank 1005 | 1006 | >>> NotificationType.B 1007 | 1008 | >>> NotificationType.B.db 1009 | 'B' 1010 | >>> NotificationType.B.user 1011 | 'Both' 1012 | 1013 | combining Flag with other data types 1014 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1015 | 1016 | Flag does support being combined with other data types. To support this you 1017 | need to provide a ``_create_pseudo_member_values_`` method which will be called 1018 | with the members in a composite flag. You may also need to provide a custom 1019 | ``__new__`` method:: 1020 | 1021 | >>> class AnsiFlag(str, Flag): 1022 | ... def __new__(cls, value, code): 1023 | ... str_value = '\x1b[%sm' % code 1024 | ... obj = str.__new__(cls, str_value) 1025 | ... obj._value_ = value 1026 | ... obj.code = code 1027 | ... return obj 1028 | ... @classmethod 1029 | ... def _create_pseudo_member_values_(cls, members, *values): 1030 | ... code = ';'.join(m.code for m in members) 1031 | ... return values + (code, ) 1032 | ... _order_ = 'FG_Red FG_Green BG_Magenta BG_White' 1033 | ... FG_Red = '31' # ESC [ 31 m # red 1034 | ... FG_Green = '32' # ESC [ 32 m # green 1035 | ... BG_Magenta = '45' # ESC [ 35 m # magenta 1036 | ... BG_White = '47' # ESC [ 37 m # white 1037 | ... 1038 | >>> color = AnsiFlag.BG_White | AnsiFlag.FG_Red 1039 | >>> repr(color) 1040 | '' 1041 | >>> str.__repr__(color) 1042 | "'\\x1b[31;47m'" 1043 | 1044 | .. note:: 1045 | 1046 | If you do not provide your own ``_create_pseudo_member_values_`` the flags 1047 | may still combine, but may be missing functionality. 1048 | 1049 | 1050 | Decorators 1051 | ---------- 1052 | 1053 | unique 1054 | ^^^^^^ 1055 | 1056 | A ``class`` decorator specifically for enumerations. It searches an 1057 | enumeration's ``__members__`` gathering any aliases it finds; if any are 1058 | found ``ValueError`` is raised with the details:: 1059 | 1060 | >>> @unique 1061 | ... class NoDupes(Enum): 1062 | ... first = 'one' 1063 | ... second = 'two' 1064 | ... third = 'two' 1065 | Traceback (most recent call last): 1066 | ... 1067 | ValueError: duplicate names found in : third -> second 1068 | 1069 | 1070 | Interesting examples 1071 | -------------------- 1072 | 1073 | While ``Enum`` and ``IntEnum`` are expected to cover the majority of 1074 | use-cases, they cannot cover them all. Here are recipes for some different 1075 | types of enumerations that can be used directly (the first three are included 1076 | in the module), or as examples for creating one's own. 1077 | 1078 | 1079 | AutoNumber 1080 | ^^^^^^^^^^ 1081 | 1082 | Avoids having to specify the value for each enumeration member:: 1083 | 1084 | >>> class AutoNumber(Enum): 1085 | ... def __new__(cls): 1086 | ... value = len(cls.__members__) + 1 1087 | ... obj = object.__new__(cls) 1088 | ... obj._value_ = value 1089 | ... return obj 1090 | ... 1091 | >>> class Color(AutoNumber): 1092 | ... _order_ = "red green blue" # only needed in 2.x 1093 | ... red = () 1094 | ... green = () 1095 | ... blue = () 1096 | ... 1097 | >>> Color.green.value == 2 1098 | True 1099 | 1100 | .. note:: 1101 | 1102 | The `__new__` method, if defined, is used during creation of the Enum 1103 | members; it is then replaced by Enum's `__new__` which is used after 1104 | class creation for lookup of existing members. Due to the way Enums are 1105 | supposed to behave, there is no way to customize Enum's `__new__` without 1106 | modifying the class after it is created. 1107 | 1108 | 1109 | UniqueEnum 1110 | ^^^^^^^^^^ 1111 | 1112 | Raises an error if a duplicate member name is found instead of creating an 1113 | alias:: 1114 | 1115 | >>> class UniqueEnum(Enum): 1116 | ... def __init__(self, *args): 1117 | ... cls = self.__class__ 1118 | ... if any(self.value == e.value for e in cls): 1119 | ... a = self.name 1120 | ... e = cls(self.value).name 1121 | ... raise ValueError( 1122 | ... "aliases not allowed in UniqueEnum: %r --> %r" 1123 | ... % (a, e)) 1124 | ... 1125 | >>> class Color(UniqueEnum): 1126 | ... _order_ = 'red green blue' 1127 | ... red = 1 1128 | ... green = 2 1129 | ... blue = 3 1130 | ... grene = 2 1131 | Traceback (most recent call last): 1132 | ... 1133 | ValueError: aliases not allowed in UniqueEnum: 'grene' --> 'green' 1134 | 1135 | 1136 | OrderedEnum 1137 | ^^^^^^^^^^^ 1138 | 1139 | An ordered enumeration that is not based on ``IntEnum`` and so maintains 1140 | the normal ``Enum`` invariants (such as not being comparable to other 1141 | enumerations):: 1142 | 1143 | >>> class OrderedEnum(Enum): 1144 | ... def __ge__(self, other): 1145 | ... if self.__class__ is other.__class__: 1146 | ... return self._value_ >= other._value_ 1147 | ... return NotImplemented 1148 | ... def __gt__(self, other): 1149 | ... if self.__class__ is other.__class__: 1150 | ... return self._value_ > other._value_ 1151 | ... return NotImplemented 1152 | ... def __le__(self, other): 1153 | ... if self.__class__ is other.__class__: 1154 | ... return self._value_ <= other._value_ 1155 | ... return NotImplemented 1156 | ... def __lt__(self, other): 1157 | ... if self.__class__ is other.__class__: 1158 | ... return self._value_ < other._value_ 1159 | ... return NotImplemented 1160 | ... 1161 | >>> class Grade(OrderedEnum): 1162 | ... __ordered__ = 'A B C D F' 1163 | ... A = 5 1164 | ... B = 4 1165 | ... C = 3 1166 | ... D = 2 1167 | ... F = 1 1168 | ... 1169 | >>> Grade.C < Grade.A 1170 | True 1171 | 1172 | 1173 | Planet 1174 | ^^^^^^ 1175 | 1176 | If ``__new__`` or ``__init__`` is defined the value of the enum member 1177 | will be passed to those methods:: 1178 | 1179 | >>> class Planet(Enum): 1180 | ... MERCURY = (3.303e+23, 2.4397e6) 1181 | ... VENUS = (4.869e+24, 6.0518e6) 1182 | ... EARTH = (5.976e+24, 6.37814e6) 1183 | ... MARS = (6.421e+23, 3.3972e6) 1184 | ... JUPITER = (1.9e+27, 7.1492e7) 1185 | ... SATURN = (5.688e+26, 6.0268e7) 1186 | ... URANUS = (8.686e+25, 2.5559e7) 1187 | ... NEPTUNE = (1.024e+26, 2.4746e7) 1188 | ... def __init__(self, mass, radius): 1189 | ... self.mass = mass # in kilograms 1190 | ... self.radius = radius # in meters 1191 | ... @property 1192 | ... def surface_gravity(self): 1193 | ... # universal gravitational constant (m3 kg-1 s-2) 1194 | ... G = 6.67300E-11 1195 | ... return G * self.mass / (self.radius * self.radius) 1196 | ... 1197 | >>> Planet.EARTH.value 1198 | (5.976e+24, 6378140.0) 1199 | >>> Planet.EARTH.surface_gravity 1200 | 9.802652743337129 1201 | 1202 | 1203 | How are Enums different? 1204 | ------------------------ 1205 | 1206 | Enums have a custom metaclass that affects many aspects of both derived Enum 1207 | classes and their instances (members). 1208 | 1209 | 1210 | Enum Classes 1211 | ^^^^^^^^^^^^ 1212 | 1213 | The ``EnumMeta`` metaclass is responsible for providing the 1214 | ``__contains__``, ``__dir__``, ``__iter__`` and other methods that 1215 | allow one to do things with an ``Enum`` class that fail on a typical 1216 | class, such as ``list(Color)`` or ``some_var in Color``. ``EnumMeta`` is 1217 | responsible for ensuring that various other methods on the final ``Enum`` 1218 | class are correct (such as ``__new__``, ``__getnewargs__``, 1219 | ``__str__`` and ``__repr__``). 1220 | 1221 | .. note:: 1222 | 1223 | ``__dir__`` is not changed in the Python 2 line as it messes up some 1224 | of the decorators included in the stdlib. 1225 | 1226 | 1227 | Enum Members (aka instances) 1228 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1229 | 1230 | The most interesting thing about Enum members is that they are singletons. 1231 | ``EnumMeta`` creates them all while it is creating the ``Enum`` 1232 | class itself, and then puts a custom ``__new__`` in place to ensure 1233 | that no new ones are ever instantiated by returning only the existing 1234 | member instances. 1235 | 1236 | 1237 | Finer Points 1238 | ^^^^^^^^^^^^ 1239 | 1240 | ``Enum`` members are instances of an ``Enum`` class, and are 1241 | accessible as `EnumClass.member1.member2` -- but only if no other 1242 | constant/property exists:: 1243 | 1244 | >>> class FieldTypes(Enum): 1245 | ... name = 1 1246 | ... value = 2 1247 | ... size = 3 1248 | ... 1249 | >>> FieldTypes.size 1250 | 1251 | >>> FieldTypes.value.size 1252 | 1253 | >>> FieldTypes.size.value # NOT 1254 | 3 1255 | 1256 | 1257 | The ``__members__`` attribute is only available on the class. 1258 | 1259 | 1260 | ``__members__`` is always an ``OrderedDict``, with the order being the 1261 | definition order in Python 3.x or the order in ``_order_`` in Python 2.7; 1262 | if no ``_order_`` was specified in Python 2.7 then the order of 1263 | ``__members__`` is either increasing value or alphabetically by name. 1264 | 1265 | If you give your ``Enum`` subclass extra methods, like the `Planet`_ 1266 | class above, those methods will show up in a `dir` of the member, 1267 | but not of the class (in Python 3.x):: 1268 | 1269 | --> dir(Planet) 1270 | ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 1271 | 'VENUS', '__class__', '__doc__', '__members__', '__module__'] 1272 | --> dir(Planet.EARTH) 1273 | ['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value'] 1274 | 1275 | A ``__new__`` method will only be used for the creation of the 1276 | ``Enum`` members -- after that it is replaced. This means if you wish to 1277 | change how ``Enum`` members are looked up you either have to write a 1278 | helper function or a ``classmethod``. 1279 | 1280 | .. note:: 1281 | 1282 | If you create your own ``__new__`` you should set the ``_value_`` in it; 1283 | if you do not, aenum will try to, but will raise a ``TypeError`` if it 1284 | cannot. 1285 | 1286 | If the stdlib ``enum`` is available (Python 3.4+ and it hasn't been shadowed 1287 | by, for example, ``enum34``) then aenum will be a subclass of it. 1288 | 1289 | To use the ``AddValue``, ``MultiValue``, ``NoAlias``, and ``Unique`` flags 1290 | in Py2 or Py2/Py3 codebases, use ``_settings_ = ...`` in the class body. 1291 | 1292 | To use ``init`` in Py2 or Py2/Py3 codebases use ``_init_`` in the class body. 1293 | 1294 | To use ``start`` in Py2 or Py2/Py3 codebases use ``_start_`` in the class body. 1295 | 1296 | When creating class bodies dynamically, put any variables you need to use into 1297 | ``_ignore_``:: 1298 | 1299 | >>> from datetime import timedelta 1300 | >>> from aenum import NoAlias 1301 | >>> class Period(timedelta, Enum): 1302 | ... ''' 1303 | ... different lengths of time 1304 | ... ''' 1305 | ... _init_ = 'value period' 1306 | ... _settings_ = NoAlias 1307 | ... _ignore_ = 'Period i' 1308 | ... Period = vars() 1309 | ... for i in range(31): 1310 | ... Period['day_%d' % i] = i, 'day' 1311 | ... for i in range(15): 1312 | ... Period['week_%d' % i] = i*7, 'week' 1313 | ... 1314 | >>> hasattr(Period, '_ignore_') 1315 | False 1316 | >>> hasattr(Period, 'Period') 1317 | False 1318 | >>> hasattr(Period, 'i') 1319 | False 1320 | 1321 | The name listed in ``_ignore_``, as well as ``_ignore_`` itself, will not be 1322 | present in the final enumeration as neither attributes nor members. 1323 | 1324 | .. note:: 1325 | 1326 | except for __dunder__ attributes/methods, all _sunder_ attributes must 1327 | be before any thing else in the class body 1328 | 1329 | .. note:: 1330 | 1331 | all _sunder_ attributes that affect member creation are only looked up in 1332 | the last ``Enum`` class listed in the class header 1333 | 1334 | 1335 | Creating NamedTuples 1336 | -------------------- 1337 | 1338 | Simple 1339 | ^^^^^^ 1340 | 1341 | The most common way to create a new NamedTuple will be via the functional API:: 1342 | 1343 | >>> from aenum import NamedTuple 1344 | >>> Book = NamedTuple('Book', 'title author genre', module=__name__) 1345 | 1346 | This creates a ``NamedTuple`` called ``Book`` that will always contain three 1347 | items, each of which is also addressable as ``title``, ``author``, or ``genre``. 1348 | 1349 | ``Book`` instances can be created using positional or keyword argements or a 1350 | mixture of the two:: 1351 | 1352 | >>> b1 = Book('Lord of the Rings', 'J.R.R. Tolkien', 'fantasy') 1353 | >>> b2 = Book(title='Jhereg', author='Steven Brust', genre='fantasy') 1354 | >>> b3 = Book('Empire', 'Orson Scott Card', genre='scifi') 1355 | 1356 | If too few or too many arguments are used a ``TypeError`` will be raised:: 1357 | 1358 | >>> b4 = Book('Hidden Empire') 1359 | Traceback (most recent call last): 1360 | ... 1361 | TypeError: values not provided for field(s): author, genre 1362 | >>> b5 = Book(genre='business') 1363 | Traceback (most recent call last): 1364 | ... 1365 | TypeError: values not provided for field(s): title, author 1366 | 1367 | As a ``class`` the above ``Book`` ``NamedTuple`` would look like:: 1368 | 1369 | >>> class Book(NamedTuple): 1370 | ... title = 0 1371 | ... author = 1 1372 | ... genre = 2 1373 | ... 1374 | 1375 | For compatibility with the stdlib ``namedtuple``, NamedTuple also has the 1376 | ``_asdict``, ``_make``, and ``_replace`` methods, and the ``_fields`` 1377 | attribute, which all function similarly:: 1378 | 1379 | >>> class Point(NamedTuple): 1380 | ... x = 0, 'horizontal coordinate', 1 1381 | ... y = 1, 'vertical coordinate', -1 1382 | ... 1383 | >>> class Color(NamedTuple): 1384 | ... r = 0, 'red component', 11 1385 | ... g = 1, 'green component', 29 1386 | ... b = 2, 'blue component', 37 1387 | ... 1388 | >>> Pixel = NamedTuple('Pixel', Point+Color, module=__name__) 1389 | >>> pixel = Pixel(99, -101, 255, 128, 0) 1390 | 1391 | >>> pixel._asdict() # doctest: +SKIP 1392 | OrderedDict([('x', 99), ('y', -101), ('r', 255), ('g', 128), ('b', 0)]) 1393 | 1394 | >>> Point._make((4, 5)) 1395 | Point(x=4, y=5) 1396 | 1397 | >>> purple = Color(127, 0, 127) 1398 | >>> mid_gray = purple._replace(g=127) 1399 | >>> mid_gray 1400 | Color(r=127, g=127, b=127) 1401 | 1402 | >>> pixel._fields 1403 | ['x', 'y', 'r', 'g', 'b'] 1404 | 1405 | >>> Pixel._fields 1406 | ['x', 'y', 'r', 'g', 'b'] 1407 | 1408 | 1409 | Advanced 1410 | ^^^^^^^^ 1411 | 1412 | The simple method of creating ``NamedTuples`` requires always specifying all 1413 | possible arguments when creating instances; failure to do so will raise 1414 | exceptions:: 1415 | 1416 | >>> class Point(NamedTuple): 1417 | ... x = 0 1418 | ... y = 1 1419 | ... 1420 | >>> Point() 1421 | Traceback (most recent call last): 1422 | ... 1423 | TypeError: values not provided for field(s): x, y 1424 | >>> Point(1) 1425 | Traceback (most recent call last): 1426 | ... 1427 | TypeError: values not provided for field(s): y 1428 | >>> Point(y=2) 1429 | Traceback (most recent call last): 1430 | ... 1431 | TypeError: values not provided for field(s): x 1432 | 1433 | However, it is possible to specify both docstrings and default values when 1434 | creating a ``NamedTuple`` using the class method:: 1435 | 1436 | >>> class Point(NamedTuple): 1437 | ... x = 0, 'horizontal coordinate', 0 1438 | ... y = 1, 'vertical coordinate', 0 1439 | ... 1440 | >>> Point() 1441 | Point(x=0, y=0) 1442 | >>> Point(1) 1443 | Point(x=1, y=0) 1444 | >>> Point(y=2) 1445 | Point(x=0, y=2) 1446 | 1447 | It is also possible to create ``NamedTuples`` that only have named attributes 1448 | for certain fields; any fields without names can still be accessed by index:: 1449 | 1450 | >>> class Person(NamedTuple): 1451 | ... fullname = 2 1452 | ... phone = 5 1453 | ... 1454 | >>> p = Person('Ethan', 'Furman', 'Ethan Furman', 1455 | ... 'ethan at stoneleaf dot us', 1456 | ... 'ethan.furman', '999.555.1212') 1457 | >>> p 1458 | Person('Ethan', 'Furman', 'Ethan Furman', 'ethan at stoneleaf dot us', 1459 | 'ethan.furman', '999.555.1212') 1460 | >>> p.fullname 1461 | 'Ethan Furman' 1462 | >>> p.phone 1463 | '999.555.1212' 1464 | >>> p[0] 1465 | 'Ethan' 1466 | 1467 | In the above example the last named field was also the last field possible; in 1468 | those cases where you don't need to have the last possible field named, you can 1469 | provide a ``_size_`` of ``TupleSize.minimum`` to declare that more fields are 1470 | okay:: 1471 | 1472 | >>> from aenum import TupleSize 1473 | >>> class Person(NamedTuple): 1474 | ... _size_ = TupleSize.minimum 1475 | ... first = 0 1476 | ... last = 1 1477 | ... 1478 | 1479 | or, optionally if using Python 3:: 1480 | 1481 | >>> class Person(NamedTuple, size=TupleSize.minimum): # doctest: +SKIP 1482 | ... first = 0 1483 | ... last = 1 1484 | 1485 | and in use:: 1486 | 1487 | >>> Person('Ethan', 'Furman') 1488 | Person(first='Ethan', last='Furman') 1489 | 1490 | >>> Person('Ethan', 'Furman', 'ethan.furman') 1491 | Person('Ethan', 'Furman', 'ethan.furman') 1492 | 1493 | >>> Person('Ethan', 'Furman', 'ethan.furman', 'yay Python!') 1494 | Person('Ethan', 'Furman', 'ethan.furman', 'yay Python!') 1495 | 1496 | >>> Person('Ethan') 1497 | Traceback (most recent call last): 1498 | ... 1499 | TypeError: values not provided for field(s): last 1500 | 1501 | Also, for those cases where even named fields may not be present, you can 1502 | specify ``TupleSize.variable``:: 1503 | 1504 | >>> class Person(NamedTuple): 1505 | ... _size_ = TupleSize.variable 1506 | ... first = 0 1507 | ... last = 1 1508 | ... 1509 | 1510 | >>> Person('Ethan') 1511 | Person('Ethan') 1512 | 1513 | >>> Person(last='Furman') 1514 | Traceback (most recent call last): 1515 | ... 1516 | TypeError: values not provided for field(s): first 1517 | 1518 | Creating new ``NamedTuples`` from existing ``NamedTuples`` is simple:: 1519 | 1520 | >>> Point = NamedTuple('Point', 'x y') 1521 | >>> Color = NamedTuple('Color', 'r g b') 1522 | >>> Pixel = NamedTuple('Pixel', Point+Color, module=__name__) 1523 | >>> Pixel 1524 | 1525 | 1526 | The existing fields in the bases classes are renumbered to fit the new class, 1527 | but keep their doc strings and default values. If you use standard 1528 | subclassing:: 1529 | 1530 | >>> Point = NamedTuple('Point', 'x y') 1531 | >>> class Pixel(Point): 1532 | ... r = 2, 'red component', 11 1533 | ... g = 3, 'green component', 29 1534 | ... b = 4, 'blue component', 37 1535 | ... 1536 | >>> Pixel.__fields__ 1537 | ['x', 'y', 'r', 'g', 'b'] 1538 | 1539 | You must manage the numbering yourself. 1540 | 1541 | 1542 | Creating NamedConstants 1543 | ----------------------- 1544 | 1545 | A ``NamedConstant`` class is created much like an ``Enum``:: 1546 | 1547 | >>> from aenum import NamedConstant 1548 | >>> class Konstant(NamedConstant): 1549 | ... PI = 3.14159 1550 | ... TAU = 2 * PI 1551 | 1552 | >>> Konstant.PI 1553 | 1554 | 1555 | >> print(Konstant.PI) 1556 | 3.14159 1557 | 1558 | >>> Konstant.PI = 'apple' 1559 | Traceback (most recent call last): 1560 | ... 1561 | AttributeError: cannot rebind constant 1562 | 1563 | >>> del Konstant.PI 1564 | Traceback (most recent call last): 1565 | ... 1566 | AttributeError: cannot delete constant 1567 | -------------------------------------------------------------------------------- /aenum/test_v3.py: -------------------------------------------------------------------------------- 1 | from . import EnumMeta, Enum, IntEnum, Flag, IntFlag, StrEnum, UniqueEnum, AutoEnum, AddValueEnum 2 | from . import NamedTuple, TupleSize, MagicValue, AddValue, NoAlias, Unique, MultiValue 3 | from . import AutoNumberEnum,MultiValueEnum, OrderedEnum, unique, skip, extend_enum, auto 4 | from ._enum import StdlibEnumMeta, StdlibEnum, StdlibIntEnum, StdlibFlag, StdlibIntFlag, StdlibStrEnum 5 | from . import pyver, PY3_3, PY3_4, PY3_5, PY3_6, PY3_7, PY3_11 6 | from . import add_stdlib_integration, remove_stdlib_integration 7 | 8 | from collections import OrderedDict 9 | from datetime import timedelta 10 | from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL 11 | from unittest import TestCase, main 12 | 13 | import os 14 | import sys 15 | import tempfile 16 | import textwrap 17 | import unittest 18 | 19 | try: 20 | import pyparsing 21 | except (ImportError, SyntaxError): 22 | pyparsing = None 23 | 24 | try: 25 | RecursionError 26 | except NameError: 27 | # python3.4 28 | RecursionError = RuntimeError 29 | 30 | class TestEnumV3(TestCase): 31 | 32 | def setUp(self): 33 | class Season(Enum): 34 | SPRING = 1 35 | SUMMER = 2 36 | AUTUMN = 3 37 | WINTER = 4 38 | self.Season = Season 39 | 40 | class Konstants(float, Enum): 41 | E = 2.7182818 42 | PI = 3.1415926 43 | TAU = 2 * PI 44 | self.Konstants = Konstants 45 | 46 | class Grades(IntEnum): 47 | A = 5 48 | B = 4 49 | C = 3 50 | D = 2 51 | F = 0 52 | self.Grades = Grades 53 | 54 | class Directional(str, Enum): 55 | EAST = 'east' 56 | WEST = 'west' 57 | NORTH = 'north' 58 | SOUTH = 'south' 59 | self.Directional = Directional 60 | 61 | from datetime import date 62 | class Holiday(date, Enum): 63 | NEW_YEAR = 2013, 1, 1 64 | IDES_OF_MARCH = 2013, 3, 15 65 | self.Holiday = Holiday 66 | 67 | @unittest.skipUnless(StdlibEnumMeta, 'Stdlib enum not available') 68 | def test_stdlib_inheritence(self): 69 | # 3.4 70 | self.assertTrue(issubclass(self.Season, StdlibEnum)) 71 | self.assertTrue(isinstance(self.Season.SPRING, StdlibEnum)) 72 | self.assertTrue(issubclass(self.Grades, StdlibEnum)) 73 | self.assertFalse(issubclass(self.Grades, StdlibIntEnum)) 74 | self.assertTrue(isinstance(self.Grades.A, StdlibEnum)) 75 | self.assertFalse(isinstance(self.Grades.A, StdlibIntEnum)) 76 | # 77 | if pyver >= PY3_6: 78 | class AFlag(Flag): 79 | one = 1 80 | self.assertTrue(issubclass(AFlag, StdlibEnum)) 81 | self.assertTrue(isinstance(AFlag.one, StdlibEnum)) 82 | self.assertTrue(issubclass(AFlag, StdlibFlag)) 83 | self.assertTrue(isinstance(AFlag.one, StdlibFlag)) 84 | # 85 | class AnIntFlag(IntFlag): 86 | one = 1 87 | self.assertTrue(issubclass(AnIntFlag, StdlibEnum)) 88 | self.assertTrue(isinstance(AnIntFlag.one, StdlibEnum)) 89 | self.assertTrue(issubclass(AnIntFlag, StdlibFlag)) 90 | self.assertTrue(isinstance(AnIntFlag.one, StdlibFlag)) 91 | self.assertFalse(issubclass(AnIntFlag, StdlibIntFlag)) 92 | self.assertFalse(isinstance(AnIntFlag.one, StdlibIntFlag)) 93 | # 94 | if pyver >= PY3_11: 95 | class AStrEnum(StrEnum): 96 | one = '1' 97 | self.assertTrue(issubclass(AStrEnum, StdlibEnum)) 98 | self.assertTrue(isinstance(AStrEnum.one, StdlibEnum)) 99 | self.assertFalse(issubclass(AStrEnum, StdlibStrEnum)) 100 | self.assertFalse(isinstance(AStrEnum.one, StdlibStrEnum)) 101 | 102 | @unittest.skipUnless(StdlibEnumMeta, 'Stdlib enum not available') 103 | def test_stdlib_bad_getattribute(self): 104 | try: 105 | add_stdlib_integration() 106 | class BadEnumType(StdlibEnumMeta): 107 | def __getattribute__(cls, name): 108 | obj = super().__getattribute__(name) 109 | if isinstance(obj, cls): 110 | obj.deprecate() 111 | return obj 112 | with self.assertRaisesRegex(RecursionError, 'endless recursion'): 113 | class BaseEnum(StdlibEnum): 114 | pass 115 | class BadEnum(BaseEnum, metaclass=BadEnumType): 116 | FOO = 'bar' 117 | finally: 118 | remove_stdlib_integration() 119 | # 120 | class OkayEnum(StdlibEnum, metaclass=BadEnumType): 121 | FOO = 'bar' 122 | 123 | @unittest.skipUnless(pyver >= PY3_5, '__qualname__ requires python 3.5 or greater') 124 | def test_pickle_enum_function_with_qualname(self): 125 | Theory = Enum('Theory', 'rule law supposition', qualname='spanish_inquisition') 126 | globals()['spanish_inquisition'] = Theory 127 | test_pickle_dump_load(self.assertTrue, Theory.rule) 128 | test_pickle_dump_load(self.assertTrue, Theory) 129 | 130 | def test_auto_init(self): 131 | class Planet(Enum, init='mass radius'): 132 | MERCURY = (3.303e+23, 2.4397e6) 133 | VENUS = (4.869e+24, 6.0518e6) 134 | EARTH = (5.976e+24, 6.37814e6) 135 | MARS = (6.421e+23, 3.3972e6) 136 | JUPITER = (1.9e+27, 7.1492e7) 137 | SATURN = (5.688e+26, 6.0268e7) 138 | URANUS = (8.686e+25, 2.5559e7) 139 | NEPTUNE = (1.024e+26, 2.4746e7) 140 | @property 141 | def surface_gravity(self): 142 | # universal gravitational constant (m3 kg-1 s-2) 143 | G = 6.67300E-11 144 | return G * self.mass / (self.radius * self.radius) 145 | self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80) 146 | self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6)) 147 | 148 | def test_auto_init_with_value(self): 149 | class Color(Enum, init='value, rgb'): 150 | RED = 1, (1, 0, 0) 151 | BLUE = 2, (0, 1, 0) 152 | GREEN = 3, (0, 0, 1) 153 | self.assertEqual(Color.RED.value, 1) 154 | self.assertEqual(Color.BLUE.value, 2) 155 | self.assertEqual(Color.GREEN.value, 3) 156 | self.assertEqual(Color.RED.rgb, (1, 0, 0)) 157 | self.assertEqual(Color.BLUE.rgb, (0, 1, 0)) 158 | self.assertEqual(Color.GREEN.rgb, (0, 0, 1)) 159 | 160 | def test_auto_turns_off(self): 161 | with self.assertRaises(NameError): 162 | class Color(Enum, settings=MagicValue): 163 | red 164 | green 165 | blue 166 | def hello(self): 167 | print('Hello! My serial is %s.' % self.value) 168 | rose 169 | with self.assertRaises(NameError): 170 | class Color(Enum, settings=MagicValue): 171 | red 172 | green 173 | blue 174 | def __init__(self, *args): 175 | pass 176 | rose 177 | 178 | def test_magic(self): 179 | class Color(Enum, settings=MagicValue): 180 | red, green, blue 181 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) 182 | self.assertEqual(Color.red.value, 1) 183 | 184 | def test_ignore_not_overridden(self): 185 | with self.assertRaisesRegex(TypeError, 'object is not callable'): 186 | class Color(Flag): 187 | _ignore_ = 'irrelevent' 188 | _settings_ = MagicValue 189 | @property 190 | def shade(self): 191 | print('I am light', self.name.lower()) 192 | 193 | def test_magic_start(self): 194 | class Color(Enum, settings=MagicValue, start=0): 195 | red, green, blue 196 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) 197 | self.assertEqual(Color.red.value, 0) 198 | 199 | def test_dir_on_class(self): 200 | Season = self.Season 201 | self.assertEqual( 202 | set(dir(Season)), 203 | set(['__class__', '__doc__', '__members__', '__module__', 204 | 'SPRING', 'SUMMER', 'AUTUMN', 'WINTER', 205 | '__init_subclass__', '__name__', '__getitem__', '__len__', 206 | '__contains__', '__iter__', '__qualname__', 207 | ])) 208 | 209 | def test_dir_on_item(self): 210 | Season = self.Season 211 | self.assertEqual( 212 | set(dir(Season.WINTER)), 213 | set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value', 'values']), 214 | ) 215 | 216 | def test_dir_with_added_behavior(self): 217 | class Test(Enum): 218 | this = 'that' 219 | these = 'those' 220 | def wowser(self): 221 | return ("Wowser! I'm %s!" % self.name) 222 | self.assertEqual( 223 | set(dir(Test)), 224 | set([ 225 | '__class__', '__doc__', '__members__', '__module__', 'this', 'these', 226 | '__init_subclass__', '__name__', '__getitem__', '__len__', 227 | '__contains__', '__iter__', '__qualname__', 228 | ])) 229 | self.assertEqual( 230 | set(dir(Test.this)), 231 | set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value', 'values', 'wowser']), 232 | ) 233 | 234 | def test_dir_on_sub_with_behavior_on_super(self): 235 | # see issue22506 236 | class SuperEnum(Enum): 237 | def invisible(self): 238 | return "did you see me?" 239 | class SubEnum(SuperEnum): 240 | sample = 5 241 | self.assertEqual( 242 | set(dir(SubEnum.sample)), 243 | set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value', 'values', 'invisible']), 244 | ) 245 | 246 | def test_members_are_always_ordered(self): 247 | class AlwaysOrdered(Enum): 248 | first = 1 249 | second = 2 250 | third = 3 251 | self.assertTrue(type(AlwaysOrdered.__members__) is OrderedDict) 252 | 253 | def test_comparisons(self): 254 | def bad_compare(): 255 | Season.SPRING > 4 256 | Season = self.Season 257 | self.assertNotEqual(Season.SPRING, 1) 258 | self.assertRaises(TypeError, bad_compare) 259 | 260 | class Part(Enum): 261 | SPRING = 1 262 | CLIP = 2 263 | BARREL = 3 264 | 265 | self.assertNotEqual(Season.SPRING, Part.SPRING) 266 | def bad_compare(): 267 | Season.SPRING < Part.CLIP 268 | self.assertRaises(TypeError, bad_compare) 269 | 270 | def test_duplicate_name(self): 271 | with self.assertRaises(TypeError): 272 | class Color1(Enum): 273 | red = 1 274 | green = 2 275 | blue = 3 276 | red = 4 277 | 278 | with self.assertRaises(TypeError): 279 | class Color2(Enum): 280 | red = 1 281 | green = 2 282 | blue = 3 283 | def red(self): 284 | return 'red' 285 | 286 | with self.assertRaises(TypeError): 287 | class Color3(Enum): 288 | @property 289 | def red(self): 290 | return 'redder' 291 | red = 1 292 | green = 2 293 | blue = 3 294 | 295 | def test_duplicate_value_with_unique(self): 296 | with self.assertRaises(ValueError): 297 | class Color(Enum, settings=Unique): 298 | red = 1 299 | green = 2 300 | blue = 3 301 | rojo = 1 302 | 303 | def test_duplicate_value_with_noalias(self): 304 | class Color(Enum, settings=NoAlias): 305 | red = 1 306 | green = 2 307 | blue = 3 308 | rojo = 1 309 | self.assertFalse(Color.red is Color.rojo) 310 | self.assertEqual(Color.red.value, 1) 311 | self.assertEqual(Color.rojo.value, 1) 312 | self.assertEqual(len(Color), 4) 313 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue, Color.rojo]) 314 | 315 | def test_noalias_value_lookup(self): 316 | class Color(Enum, settings=NoAlias): 317 | red = 1 318 | green = 2 319 | blue = 3 320 | rojo = 1 321 | self.assertRaises(TypeError, Color, 2) 322 | 323 | def test_multivalue(self): 324 | class Color(Enum, settings=MultiValue): 325 | red = 1, 'red' 326 | green = 2, 'green' 327 | blue = 3, 'blue' 328 | self.assertEqual(Color.red.value, 1) 329 | self.assertIs(Color('green'), Color.green) 330 | self.assertEqual(Color.blue.values, (3, 'blue')) 331 | 332 | def test_multivalue_with_duplicate_values(self): 333 | with self.assertRaises(ValueError): 334 | class Color(Enum, settings=MultiValue): 335 | red = 1, 'red' 336 | green = 2, 'green' 337 | blue = 3, 'blue', 'red' 338 | 339 | def test_multivalue_with_duplicate_values_and_noalias(self): 340 | with self.assertRaises(TypeError): 341 | class Color(Enum, settings=(MultiValue, NoAlias)): 342 | red = 1, 'red' 343 | green = 2, 'green' 344 | blue = 3, 'blue', 'red' 345 | 346 | def test_multivalue_and_auto(self): 347 | with self.assertRaisesRegex(TypeError, r'MultiValue and MagicValue are mutually exclusive'): 348 | class Color(Enum, settings=(MultiValue, MagicValue)): 349 | red 350 | green = 3, 'green' 351 | blue 352 | 353 | def test_autonumber_and_init(self): 354 | class Field(IntEnum, settings=AddValue, init='__doc__'): 355 | TYPE = "Char, Date, Logical, etc." 356 | START = "Field offset in record" 357 | self.assertEqual(Field.TYPE, 1) 358 | self.assertEqual(Field.START, 2) 359 | self.assertEqual(Field.TYPE.__doc__, 'Char, Date, Logical, etc.') 360 | self.assertEqual(Field.START.__doc__, 'Field offset in record') 361 | self.assertFalse(hasattr(Field, '_order_')) 362 | 363 | def test_autovalue_and_init(self): 364 | class Field(IntEnum, init='value __doc__'): 365 | TYPE = "Char, Date, Logical, etc." 366 | START = "Field offset in record" 367 | self.assertEqual(Field.TYPE, 1) 368 | self.assertEqual(Field.START.__doc__, 'Field offset in record') 369 | 370 | def test_autonumber_and_start(self): 371 | class Field(IntEnum, init='__doc__', settings=AddValue, start=0): 372 | TYPE = "Char, Date, Logical, etc." 373 | START = "Field offset in record" 374 | self.assertEqual(Field.TYPE, 0) 375 | self.assertEqual(Field.START, 1) 376 | self.assertEqual(Field.TYPE.__doc__, 'Char, Date, Logical, etc.') 377 | self.assertEqual(Field.START.__doc__, 'Field offset in record') 378 | 379 | def test_autonumber_and_init_and_some_values(self): 380 | class Field(IntEnum, init='value __doc__'): 381 | TYPE = "Char, Date, Logical, etc." 382 | START = "Field offset in record" 383 | BLAH = 5, "test blah" 384 | BELCH = 'test belch' 385 | self.assertEqual(Field.TYPE, 1) 386 | self.assertEqual(Field.START, 2) 387 | self.assertEqual(Field.BLAH, 5) 388 | self.assertEqual(Field.BELCH, 6) 389 | self.assertEqual(Field.TYPE.__doc__, 'Char, Date, Logical, etc.') 390 | self.assertEqual(Field.START.__doc__, 'Field offset in record') 391 | self.assertEqual(Field.BLAH.__doc__, 'test blah') 392 | self.assertEqual(Field.BELCH.__doc__, 'test belch') 393 | 394 | def test_autonumber_with_irregular_values(self): 395 | class Point(AutoNumberEnum, init='x y'): 396 | first = 7, 9 397 | second = 11, 13 398 | self.assertEqual(Point.first.value, 1) 399 | self.assertEqual(Point.first.x, 7) 400 | self.assertEqual(Point.first.y, 9) 401 | self.assertEqual(Point.second.value, 2) 402 | self.assertEqual(Point.second.x, 11) 403 | self.assertEqual(Point.second.y, 13) 404 | with self.assertRaisesRegex(TypeError, '.*number of fields provided do not match init ...x., .y.. != .3, 11, 13..'): 405 | class Point(AutoNumberEnum, init='x y'): 406 | first = 7, 9 407 | second = 3, 11, 13 408 | class Color(AutoNumberEnum, init='__doc__'): 409 | # interactions between AutoNumberEnum and _generate_next_value_ may not be pretty 410 | red = () 411 | green = 'red' 412 | blue = () 413 | self.assertTrue(Color.red.__doc__, 1) 414 | self.assertEqual(Color.green.__doc__, 'red') 415 | self.assertTrue(Color.blue.__doc__, 2) 416 | 417 | def test_autonumber_and_property(self): 418 | with self.assertRaises(TypeError): 419 | class Color(AutoEnum): 420 | _ignore_ = () 421 | red = () 422 | green = () 423 | blue = () 424 | @property 425 | def cap_name(self) -> str: 426 | return self.name.title() 427 | 428 | def test_autoenum(self): 429 | class Color(AutoEnum): 430 | red 431 | green 432 | blue 433 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) 434 | self.assertEqual([m.value for m in Color], [1, 2, 3]) 435 | self.assertEqual([m.name for m in Color], ['red', 'green', 'blue']) 436 | 437 | def test_autoenum_with_str(self): 438 | class Color(AutoEnum): 439 | def _generate_next_value_(name, start, count, last_values): 440 | return name 441 | red 442 | green 443 | blue 444 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) 445 | self.assertEqual([m.value for m in Color], ['red', 'green', 'blue']) 446 | self.assertEqual([m.name for m in Color], ['red', 'green', 'blue']) 447 | 448 | def test_autoenum_and_default_ignore(self): 449 | class Color(AutoEnum): 450 | red 451 | green 452 | blue 453 | @property 454 | def cap_name(self): 455 | return self.name.title() 456 | self.assertEqual(Color.blue.cap_name, 'Blue') 457 | 458 | def test_autonumber_and_overridden_ignore(self): 459 | with self.assertRaises(TypeError): 460 | class Color(AutoEnum): 461 | _ignore_ = 'staticmethod' 462 | red 463 | green 464 | blue 465 | @property 466 | def cap_name(self) -> str: 467 | return self.name.title() 468 | 469 | def test_autonumber_and_multiple_assignment(self): 470 | class Color(AutoEnum): 471 | _ignore_ = 'property' 472 | red 473 | green 474 | blue = cyan 475 | @property 476 | def cap_name(self) -> str: 477 | return self.name.title() 478 | self.assertEqual(Color.blue.cap_name, 'Cyan') 479 | 480 | def test_multivalue_and_autonumber_inherited(self): 481 | class Measurement(int, Enum, settings=(MultiValue, AddValue), start=0): 482 | one = "20110721" 483 | two = "20120911" 484 | three = "20110518" 485 | M = Measurement 486 | self.assertEqual(M.one, 0) 487 | self.assertTrue(M.one is M(0) is M('20110721')) 488 | 489 | def test_combine_new_settings_with_old_settings(self): 490 | class Auto(Enum, settings=Unique): 491 | pass 492 | with self.assertRaises(ValueError): 493 | class AutoUnique(Auto, settings=MagicValue): 494 | BLAH 495 | BLUH 496 | ICK = 1 497 | 498 | def test_timedelta(self): 499 | class Period(timedelta, Enum): 500 | ''' 501 | different lengths of time 502 | ''' 503 | _init_ = 'value period' 504 | _settings_ = NoAlias 505 | _ignore_ = 'Period i' 506 | Period = vars() 507 | for i in range(31): 508 | Period['day_%d' % i] = i, 'day' 509 | for i in range(15): 510 | Period['week_%d' % i] = i*7, 'week' 511 | for i in range(12): 512 | Period['month_%d' % i] = i*30, 'month' 513 | OneDay = day_1 514 | OneWeek = week_1 515 | self.assertFalse(hasattr(Period, '_ignore_')) 516 | self.assertFalse(hasattr(Period, 'Period')) 517 | self.assertFalse(hasattr(Period, 'i')) 518 | self.assertTrue(isinstance(Period.day_1, timedelta)) 519 | 520 | def test_extend_enum_plain(self): 521 | class Color(UniqueEnum): 522 | red = 1 523 | green = 2 524 | blue = 3 525 | extend_enum(Color, 'brown', 4) 526 | self.assertEqual(Color.brown.name, 'brown') 527 | self.assertEqual(Color.brown.value, 4) 528 | self.assertTrue(Color.brown in Color) 529 | self.assertEqual(len(Color), 4) 530 | 531 | def test_extend_enum_shadow(self): 532 | class Color(UniqueEnum): 533 | red = 1 534 | green = 2 535 | blue = 3 536 | extend_enum(Color, 'value', 4) 537 | self.assertEqual(Color.value.name, 'value') 538 | self.assertEqual(Color.value.value, 4) 539 | self.assertTrue(Color.value in Color) 540 | self.assertEqual(len(Color), 4) 541 | self.assertEqual(Color.red.value, 1) 542 | 543 | def test_extend_enum_generate(self): 544 | class Foo(AutoEnum): 545 | def _generate_next_value_(name, start, count, values, *args, **kwds): 546 | return name 547 | a 548 | b 549 | # 550 | extend_enum(Foo, 'c') 551 | self.assertEqual(Foo.a.value, 'a') 552 | self.assertEqual(Foo.b.value, 'b') 553 | self.assertEqual(Foo.c.value, 'c') 554 | 555 | def test_extend_enum_unique_with_duplicate(self): 556 | with self.assertRaises(ValueError): 557 | class Color(Enum, settings=Unique): 558 | red = 1 559 | green = 2 560 | blue = 3 561 | extend_enum(Color, 'value', 1) 562 | 563 | def test_extend_enum_multivalue_with_duplicate(self): 564 | with self.assertRaises(ValueError): 565 | class Color(Enum, settings=MultiValue): 566 | red = 1, 'rojo' 567 | green = 2, 'verde' 568 | blue = 3, 'azul' 569 | extend_enum(Color, 'value', 2) 570 | 571 | def test_extend_enum_noalias_with_duplicate(self): 572 | class Color(Enum, settings=NoAlias): 573 | red = 1 574 | green = 2 575 | blue = 3 576 | extend_enum(Color, 'value', 3, ) 577 | self.assertRaises(TypeError, Color, 3) 578 | self.assertFalse(Color.value is Color.blue) 579 | self.assertTrue(Color.value.value, 3) 580 | 581 | def test_no_duplicates(self): 582 | def bad_duplicates(): 583 | class Color(UniqueEnum): 584 | red = 1 585 | green = 2 586 | blue = 3 587 | class Color(UniqueEnum): 588 | red = 1 589 | green = 2 590 | blue = 3 591 | grene = 2 592 | self.assertRaises(ValueError, bad_duplicates) 593 | 594 | def test_no_duplicates_kinda(self): 595 | class Silly(UniqueEnum): 596 | one = 1 597 | two = 'dos' 598 | name = 3 599 | class Sillier(IntEnum, UniqueEnum): 600 | single = 1 601 | name = 2 602 | triple = 3 603 | value = 4 604 | 605 | def test_auto_number(self): 606 | class Color(Enum, settings=MagicValue): 607 | red 608 | blue 609 | green 610 | 611 | self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) 612 | self.assertEqual(Color.red.value, 1) 613 | self.assertEqual(Color.blue.value, 2) 614 | self.assertEqual(Color.green.value, 3) 615 | 616 | def test_auto_name(self): 617 | class Color(Enum, settings=MagicValue): 618 | def _generate_next_value_(name, start, count, last): 619 | return name 620 | red 621 | blue 622 | green 623 | 624 | self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) 625 | self.assertEqual(Color.red.value, 'red') 626 | self.assertEqual(Color.blue.value, 'blue') 627 | self.assertEqual(Color.green.value, 'green') 628 | 629 | def test_auto_name_inherit(self): 630 | class AutoNameEnum(Enum): 631 | def _generate_next_value_(name, start, count, last): 632 | return name 633 | class Color(AutoNameEnum, settings=MagicValue): 634 | red 635 | blue 636 | green 637 | 638 | self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) 639 | self.assertEqual(Color.red.value, 'red') 640 | self.assertEqual(Color.blue.value, 'blue') 641 | self.assertEqual(Color.green.value, 'green') 642 | 643 | def test_auto_garbage(self): 644 | class Color(Enum): 645 | _settings_ = MagicValue 646 | red = 'red' 647 | blue 648 | self.assertEqual(Color.blue.value, 1) 649 | 650 | def test_auto_garbage_corrected(self): 651 | class Color(Enum, settings=MagicValue): 652 | red = 'red' 653 | blue = 2 654 | green 655 | 656 | self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) 657 | self.assertEqual(Color.red.value, 'red') 658 | self.assertEqual(Color.blue.value, 2) 659 | self.assertEqual(Color.green.value, 3) 660 | 661 | def test_duplicate_auto(self): 662 | class Dupes(Enum, settings=MagicValue): 663 | first = primero 664 | second 665 | third 666 | self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes)) 667 | 668 | def test_order_as_function(self): 669 | # first with _init_ 670 | class TestSequence(Enum): 671 | _init_ = 'value, sequence' 672 | _order_ = lambda member: member.sequence 673 | item_id = 'An$(1,6)', 0 # Item Code 674 | company_id = 'An$(7,2)', 1 # Company Code 675 | warehouse_no = 'An$(9,4)', 2 # Warehouse Number 676 | company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY 677 | key_type = 'Cn$(19,3)', 4 # Key Type = '1**' 678 | available = 'Zn$(1,1)', 5 # Available? 679 | contract_item = 'Bn(2,1)', 6 # Contract Item? 680 | sales_category = 'Fn', 7 # Sales Category 681 | gl_category = 'Rn$(5,1)', 8 # G/L Category 682 | warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category 683 | inv_units = 'Qn$(7,2)', 10 # Inv Units 684 | for i, member in enumerate(TestSequence): 685 | self.assertEqual(i, member.sequence) 686 | ts = TestSequence 687 | self.assertEqual(ts.item_id.name, 'item_id') 688 | self.assertEqual(ts.item_id.value, 'An$(1,6)') 689 | self.assertEqual(ts.item_id.sequence, 0) 690 | self.assertEqual(ts.company_id.name, 'company_id') 691 | self.assertEqual(ts.company_id.value, 'An$(7,2)') 692 | self.assertEqual(ts.company_id.sequence, 1) 693 | self.assertEqual(ts.warehouse_no.name, 'warehouse_no') 694 | self.assertEqual(ts.warehouse_no.value, 'An$(9,4)') 695 | self.assertEqual(ts.warehouse_no.sequence, 2) 696 | self.assertEqual(ts.company.name, 'company') 697 | self.assertEqual(ts.company.value, 'Hn$(13,6)') 698 | self.assertEqual(ts.company.sequence, 3) 699 | self.assertEqual(ts.key_type.name, 'key_type') 700 | self.assertEqual(ts.key_type.value, 'Cn$(19,3)') 701 | self.assertEqual(ts.key_type.sequence, 4) 702 | self.assertEqual(ts.available.name, 'available') 703 | self.assertEqual(ts.available.value, 'Zn$(1,1)') 704 | self.assertEqual(ts.available.sequence, 5) 705 | self.assertEqual(ts.contract_item.name, 'contract_item') 706 | self.assertEqual(ts.contract_item.value, 'Bn(2,1)') 707 | self.assertEqual(ts.contract_item.sequence, 6) 708 | self.assertEqual(ts.sales_category.name, 'sales_category') 709 | self.assertEqual(ts.sales_category.value, 'Fn') 710 | self.assertEqual(ts.sales_category.sequence, 7) 711 | self.assertEqual(ts.gl_category.name, 'gl_category') 712 | self.assertEqual(ts.gl_category.value, 'Rn$(5,1)') 713 | self.assertEqual(ts.gl_category.sequence, 8) 714 | self.assertEqual(ts.warehouse_category.name, 'warehouse_category') 715 | self.assertEqual(ts.warehouse_category.value, 'Sn$(6,1)') 716 | self.assertEqual(ts.warehouse_category.sequence, 9) 717 | self.assertEqual(ts.inv_units.name, 'inv_units') 718 | self.assertEqual(ts.inv_units.value, 'Qn$(7,2)') 719 | self.assertEqual(ts.inv_units.sequence, 10) 720 | # and then without 721 | class TestSequence(Enum): 722 | _order_ = lambda member: member.value[1] 723 | item_id = 'An$(1,6)', 0 # Item Code 724 | company_id = 'An$(7,2)', 1 # Company Code 725 | warehouse_no = 'An$(9,4)', 2 # Warehouse Number 726 | company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY 727 | key_type = 'Cn$(19,3)', 4 # Key Type = '1**' 728 | available = 'Zn$(1,1)', 5 # Available? 729 | contract_item = 'Bn(2,1)', 6 # Contract Item? 730 | sales_category = 'Fn', 7 # Sales Category 731 | gl_category = 'Rn$(5,1)', 8 # G/L Category 732 | warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category 733 | inv_units = 'Qn$(7,2)', 10 # Inv Units 734 | for i, member in enumerate(TestSequence): 735 | self.assertEqual(i, member.value[1]) 736 | ts = TestSequence 737 | self.assertEqual(ts.item_id.name, 'item_id') 738 | self.assertEqual(ts.item_id.value, ('An$(1,6)', 0)) 739 | self.assertEqual(ts.company_id.name, 'company_id') 740 | self.assertEqual(ts.company_id.value, ('An$(7,2)', 1)) 741 | self.assertEqual(ts.warehouse_no.name, 'warehouse_no') 742 | self.assertEqual(ts.warehouse_no.value, ('An$(9,4)', 2)) 743 | self.assertEqual(ts.company.name, 'company') 744 | self.assertEqual(ts.company.value, ('Hn$(13,6)', 3)) 745 | self.assertEqual(ts.key_type.name, 'key_type') 746 | self.assertEqual(ts.key_type.value, ('Cn$(19,3)', 4)) 747 | self.assertEqual(ts.available.name, 'available') 748 | self.assertEqual(ts.available.value, ('Zn$(1,1)', 5)) 749 | self.assertEqual(ts.contract_item.name, 'contract_item') 750 | self.assertEqual(ts.contract_item.value, ('Bn(2,1)', 6)) 751 | self.assertEqual(ts.sales_category.name, 'sales_category') 752 | self.assertEqual(ts.sales_category.value, ('Fn', 7)) 753 | self.assertEqual(ts.gl_category.name, 'gl_category') 754 | self.assertEqual(ts.gl_category.value, ('Rn$(5,1)', 8)) 755 | self.assertEqual(ts.warehouse_category.name, 'warehouse_category') 756 | self.assertEqual(ts.warehouse_category.value, ('Sn$(6,1)', 9)) 757 | self.assertEqual(ts.inv_units.name, 'inv_units') 758 | self.assertEqual(ts.inv_units.value, ('Qn$(7,2)', 10)) 759 | # then with _init_ but without value 760 | with self.assertRaises(TypeError): 761 | class TestSequence(Enum): 762 | _init_ = 'sequence' 763 | _order_ = lambda member: member.sequence 764 | item_id = 'An$(1,6)', 0 # Item Code 765 | company_id = 'An$(7,2)', 1 # Company Code 766 | warehouse_no = 'An$(9,4)', 2 # Warehouse Number 767 | company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY 768 | key_type = 'Cn$(19,3)', 4 # Key Type = '1**' 769 | available = 'Zn$(1,1)', 5 # Available? 770 | contract_item = 'Bn(2,1)', 6 # Contract Item? 771 | sales_category = 'Fn', 7 # Sales Category 772 | gl_category = 'Rn$(5,1)', 8 # G/L Category 773 | warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category 774 | inv_units = 'Qn$(7,2)', 10 # Inv Units 775 | # finally, out of order so Python 3 barfs 776 | with self.assertRaises(TypeError): 777 | class TestSequence(Enum): 778 | _init_ = 'sequence' 779 | _order_ = lambda member: member.sequence 780 | item_id = 'An$(1,6)', 0 # Item Code 781 | warehouse_no = 'An$(9,4)', 2 # Warehouse Number 782 | company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY 783 | company_id = 'An$(7,2)', 1 # Company Code 784 | inv_units = 'Qn$(7,2)', 10 # Inv Units 785 | available = 'Zn$(1,1)', 5 # Available? 786 | contract_item = 'Bn(2,1)', 6 # Contract Item? 787 | sales_category = 'Fn', 7 # Sales Category 788 | key_type = 'Cn$(19,3)', 4 # Key Type = '1**' 789 | gl_category = 'Rn$(5,1)', 8 # G/L Category 790 | warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category 791 | 792 | if pyver >= PY3_3: 793 | def test_missing(self): 794 | class Color(Enum): 795 | red = 1 796 | green = 2 797 | blue = 3 798 | @classmethod 799 | def _missing_(cls, item): 800 | if item == 'three': 801 | return cls.blue 802 | elif item == 'bad return': 803 | # trigger internal error 804 | return 5 805 | elif item == 'error out': 806 | raise ZeroDivisionError 807 | else: 808 | # trigger not found 809 | return None 810 | self.assertIs(Color('three'), Color.blue) 811 | self.assertRaisesRegex(ValueError, '7 is not a valid Color', Color, 7) 812 | self.assertRaisesRegex(TypeError, 'error in .*_missing_', Color, 'bad return') 813 | self.assertRaises(ZeroDivisionError, Color, 'error out') 814 | 815 | def test_enum_of_types(self): 816 | """Support using Enum to refer to types deliberately.""" 817 | class MyTypes(Enum): 818 | i = int 819 | f = float 820 | s = str 821 | self.assertEqual(MyTypes.i.value, int) 822 | self.assertEqual(MyTypes.f.value, float) 823 | self.assertEqual(MyTypes.s.value, str) 824 | class Foo: 825 | pass 826 | class Bar: 827 | pass 828 | class MyTypes2(Enum): 829 | a = Foo 830 | b = Bar 831 | self.assertEqual(MyTypes2.a.value, Foo) 832 | self.assertEqual(MyTypes2.b.value, Bar) 833 | class SpamEnumNotInner: 834 | pass 835 | class SpamEnum(Enum): 836 | spam = SpamEnumNotInner 837 | self.assertEqual(SpamEnum.spam.value, SpamEnumNotInner) 838 | 839 | def test_nested_classes_in_enum_do_not_create_members(self): 840 | """Support locally-defined nested classes.""" 841 | # manually set __qualname__ to remove testing framework noise 842 | class Outer(Enum): 843 | __qualname__ = "Outer" 844 | a = 1 845 | b = 2 846 | class Inner(Enum): 847 | __qualname__ = "Outer.Inner" 848 | foo = 10 849 | bar = 11 850 | self.assertTrue(isinstance(Outer.Inner, type)) 851 | self.assertEqual(Outer.a.value, 1) 852 | self.assertEqual(Outer.Inner.foo.value, 10) 853 | self.assertEqual( 854 | list(Outer.Inner), 855 | [Outer.Inner.foo, Outer.Inner.bar], 856 | ) 857 | self.assertEqual( 858 | list(Outer), 859 | [Outer.a, Outer.b], 860 | ) 861 | 862 | if pyver == PY3_4: 863 | def test_class_nested_enum_and_pickle_protocol_four(self): 864 | # would normally just have this directly in the class namespace 865 | class NestedEnum(Enum): 866 | twigs = 'common' 867 | shiny = 'rare' 868 | 869 | self.__class__.NestedEnum = NestedEnum 870 | self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__ 871 | test_pickle_exception( 872 | self.assertRaises, PicklingError, self.NestedEnum.twigs, 873 | protocol=(0, 3)) 874 | test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs, 875 | protocol=(4, HIGHEST_PROTOCOL)) 876 | 877 | elif pyver >= PY3_5: 878 | def test_class_nested_enum_and_pickle_protocol_four(self): 879 | # would normally just have this directly in the class namespace 880 | class NestedEnum(Enum): 881 | twigs = 'common' 882 | shiny = 'rare' 883 | 884 | self.__class__.NestedEnum = NestedEnum 885 | self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__ 886 | test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs, 887 | protocol=(0, HIGHEST_PROTOCOL)) 888 | 889 | if pyver >= PY3_4: 890 | def test_enum_injection(self): 891 | class Color(Enum): 892 | _order_ = 'BLACK WHITE' 893 | BLACK = Color('black', '#000') 894 | WHITE = Color('white', '#fff') 895 | 896 | def __init__(self, label, hex): 897 | self.label = label 898 | self.hex = hex 899 | 900 | self.assertEqual([Color.BLACK, Color.WHITE], list(Color)) 901 | self.assertEqual(Color.WHITE.hex, '#fff') 902 | self.assertEqual(Color.BLACK.label, 'black') 903 | 904 | def test_subclasses_with_getnewargs_ex(self): 905 | class NamedInt(int): 906 | __qualname__ = 'NamedInt' # needed for pickle protocol 4 907 | def __new__(cls, *args): 908 | _args = args 909 | if len(args) < 2: 910 | raise TypeError("name and value must be specified") 911 | name, args = args[0], args[1:] 912 | self = int.__new__(cls, *args) 913 | self._intname = name 914 | self._args = _args 915 | return self 916 | def __getnewargs_ex__(self): 917 | return self._args, {} 918 | @property 919 | def __name__(self): 920 | return self._intname 921 | def __repr__(self): 922 | # repr() is updated to include the name and type info 923 | return "{}({!r}, {})".format(type(self).__name__, 924 | self.__name__, 925 | int.__repr__(self)) 926 | def __str__(self): 927 | # str() is unchanged, even if it relies on the repr() fallback 928 | base = int 929 | base_str = base.__str__ 930 | if base_str.__objclass__ is object: 931 | return base.__repr__(self) 932 | return base_str(self) 933 | # for simplicity, we only define one operator that 934 | # propagates expressions 935 | def __add__(self, other): 936 | temp = int(self) + int( other) 937 | if isinstance(self, NamedInt) and isinstance(other, NamedInt): 938 | return NamedInt( 939 | '({0} + {1})'.format(self.__name__, other.__name__), 940 | temp ) 941 | else: 942 | return temp 943 | 944 | class NEI(NamedInt, Enum): 945 | __qualname__ = 'NEI' # needed for pickle protocol 4 946 | x = ('the-x', 1) 947 | y = ('the-y', 2) 948 | 949 | 950 | self.assertIs(NEI.__new__, Enum.__new__) 951 | self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") 952 | globals()['NamedInt'] = NamedInt 953 | globals()['NEI'] = NEI 954 | NI5 = NamedInt('test', 5) 955 | self.assertEqual(NI5, 5) 956 | test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, HIGHEST_PROTOCOL)) 957 | self.assertEqual(NEI.y.value, 2) 958 | test_pickle_dump_load(self.assertTrue, NEI.y, protocol=(4, HIGHEST_PROTOCOL)) 959 | 960 | def test_multiple_superclasses_repr(self): 961 | class _EnumSuperClass(metaclass=EnumMeta): 962 | pass 963 | class E(_EnumSuperClass, Enum): 964 | A = 1 965 | self.assertEqual(repr(E.A), "") 966 | 967 | 968 | class TestOrderV3(TestCase): 969 | """ 970 | Test definition order versus _order_ order. 971 | """ 972 | 973 | def test_same_members(self): 974 | class Color(Enum): 975 | _order_ = 'red green blue' 976 | red = 1 977 | green = 2 978 | blue = 3 979 | 980 | def test_same_members_with_aliases(self): 981 | class Color(Enum): 982 | _order_ = 'red green blue' 983 | red = 1 984 | green = 2 985 | blue = 3 986 | verde = green 987 | 988 | def test_same_members_wrong_order(self): 989 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 990 | class Color(Enum): 991 | _order_ = 'red green blue' 992 | red = 1 993 | blue = 3 994 | green = 2 995 | 996 | def test_order_has_extra_members(self): 997 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 998 | class Color(Enum): 999 | _order_ = 'red green blue purple' 1000 | red = 1 1001 | green = 2 1002 | blue = 3 1003 | 1004 | def test_order_has_extra_members_with_aliases(self): 1005 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1006 | class Color(Enum): 1007 | _order_ = 'red green blue purple' 1008 | red = 1 1009 | green = 2 1010 | blue = 3 1011 | verde = green 1012 | 1013 | def test_enum_has_extra_members(self): 1014 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1015 | class Color(Enum): 1016 | _order_ = 'red green blue' 1017 | red = 1 1018 | green = 2 1019 | blue = 3 1020 | purple = 4 1021 | 1022 | def test_enum_has_extra_members_with_aliases(self): 1023 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1024 | class Color(Enum): 1025 | _order_ = 'red green blue' 1026 | red = 1 1027 | green = 2 1028 | blue = 3 1029 | purple = 4 1030 | verde = green 1031 | 1032 | def test_same_members_flag(self): 1033 | class Color(Flag): 1034 | _order_ = 'red green blue' 1035 | red = 1 1036 | green = 2 1037 | blue = 4 1038 | 1039 | def test_same_members_with_aliases_flag(self): 1040 | class Color(Flag): 1041 | _order_ = 'red green blue' 1042 | red = 1 1043 | green = 2 1044 | blue = 4 1045 | verde = green 1046 | 1047 | def test_same_members_wrong_order_falg(self): 1048 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1049 | class Color(Flag): 1050 | _order_ = 'red green blue' 1051 | red = 1 1052 | blue = 4 1053 | green = 2 1054 | 1055 | def test_order_has_extra_members_flag(self): 1056 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1057 | class Color(Flag): 1058 | _order_ = 'red green blue purple' 1059 | red = 1 1060 | green = 2 1061 | blue = 4 1062 | 1063 | def test_order_has_extra_members_with_aliases_flag(self): 1064 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1065 | class Color(Flag): 1066 | _order_ = 'red green blue purple' 1067 | red = 1 1068 | green = 2 1069 | blue = 4 1070 | verde = green 1071 | 1072 | def test_enum_has_extra_members_flag(self): 1073 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1074 | class Color(Flag): 1075 | _order_ = 'red green blue' 1076 | red = 1 1077 | green = 2 1078 | blue = 4 1079 | purple = 8 1080 | 1081 | def test_enum_has_extra_members_with_aliases_flag(self): 1082 | with self.assertRaisesRegex(TypeError, 'member order does not match _order_'): 1083 | class Color(Flag): 1084 | _order_ = 'red green blue' 1085 | red = 1 1086 | green = 2 1087 | blue = 4 1088 | purple = 8 1089 | verde = green 1090 | 1091 | 1092 | class TestNamedTupleV3(TestCase): 1093 | 1094 | def test_fixed_size(self): 1095 | class Book(NamedTuple, size=TupleSize.fixed): 1096 | title = 0 1097 | author = 1 1098 | genre = 2 1099 | b = Book('Teckla', 'Steven Brust', 'fantasy') 1100 | self.assertTrue('Teckla' in b) 1101 | self.assertTrue('Steven Brust' in b) 1102 | self.assertTrue('fantasy' in b) 1103 | self.assertEqual(b.title, 'Teckla') 1104 | self.assertEqual(b.author, 'Steven Brust') 1105 | self.assertRaises(TypeError, Book, 'Teckla', 'Steven Brust') 1106 | self.assertRaises(TypeError, Book, 'Teckla') 1107 | 1108 | def test_minimum_size(self): 1109 | class Book(NamedTuple, size=TupleSize.minimum): 1110 | title = 0 1111 | author = 1 1112 | b = Book('Teckla', 'Steven Brust', 'fantasy') 1113 | self.assertTrue('Teckla' in b) 1114 | self.assertTrue('Steven Brust' in b) 1115 | self.assertTrue('fantasy' in b) 1116 | self.assertEqual(b.title, 'Teckla') 1117 | self.assertEqual(b.author, 'Steven Brust') 1118 | self.assertEqual(b[2], 'fantasy') 1119 | b = Book('Teckla', 'Steven Brust') 1120 | self.assertTrue('Teckla' in b) 1121 | self.assertTrue('Steven Brust' in b) 1122 | self.assertEqual(b.title, 'Teckla') 1123 | self.assertEqual(b.author, 'Steven Brust') 1124 | self.assertRaises(TypeError, Book, 'Teckla') 1125 | 1126 | def test_variable_size(self): 1127 | class Book(NamedTuple, size=TupleSize.variable): 1128 | title = 0 1129 | author = 1 1130 | genre = 2 1131 | b = Book('Teckla', 'Steven Brust', 'fantasy') 1132 | self.assertTrue('Teckla' in b) 1133 | self.assertTrue('Steven Brust' in b) 1134 | self.assertTrue('fantasy' in b) 1135 | self.assertEqual(b.title, 'Teckla') 1136 | self.assertEqual(b.author, 'Steven Brust') 1137 | self.assertEqual(b.genre, 'fantasy') 1138 | b = Book('Teckla', 'Steven Brust') 1139 | self.assertTrue('Teckla' in b) 1140 | self.assertTrue('Steven Brust' in b) 1141 | self.assertEqual(b.title, 'Teckla') 1142 | self.assertEqual(b.author, 'Steven Brust') 1143 | self.assertRaises(AttributeError, getattr, b, 'genre') 1144 | self.assertRaises(TypeError, Book, title='Teckla', genre='fantasy') 1145 | self.assertRaises(TypeError, Book, author='Steven Brust') 1146 | 1147 | 1148 | 1149 | class TestStackoverflowAnswersV3(TestCase): 1150 | 1151 | def test_self_referential_directions(self): 1152 | # https://stackoverflow.com/a/64000706/208880 1153 | class Directions(Enum): 1154 | # 1155 | NORTH = 1, 0 1156 | WEST = 0, 1 1157 | SOUTH = -1, 0 1158 | EAST = 0, -1 1159 | # 1160 | def __init__(self, x, y): 1161 | self.x = x 1162 | self.y = y 1163 | if len(self.__class__): 1164 | # make links 1165 | all = list(self.__class__) 1166 | left, right = all[0], all[-1] 1167 | self.left = left 1168 | self.right = right 1169 | left.right = self 1170 | right.left = self 1171 | # 1172 | D = Directions 1173 | self.assertEqual(D.NORTH.value, (1, 0)) 1174 | self.assertTrue(D.NORTH.left is D.WEST) 1175 | self.assertTrue(D.SOUTH.right is D.WEST) 1176 | 1177 | def test_self_referential_rock_paper_scissors(self): 1178 | # https://stackoverflow.com/a/57085357/208880 1179 | class RPS(Enum): 1180 | # 1181 | Rock = "rock" 1182 | Paper = "paper" 1183 | Scissors = "scissors" 1184 | # 1185 | def __init__(self, value): 1186 | if len(self.__class__): 1187 | # make links 1188 | all = list(self.__class__) 1189 | first, previous = all[0], all[-1] 1190 | first.beats = self 1191 | self.beats = previous 1192 | # 1193 | self.assertTrue(RPS.Rock.beats is RPS.Scissors) 1194 | self.assertTrue(RPS.Scissors.beats is RPS.Paper) 1195 | self.assertTrue(RPS.Paper.beats is RPS.Rock) 1196 | 1197 | def test_arduino_headers(self): 1198 | # https://stackoverflow.com/q/65048495/208880 1199 | class CHeader(Enum): 1200 | def __init_subclass__(cls, **kwds): 1201 | # write Enums to C header file 1202 | cls_name = cls.__name__ 1203 | header_path = getattr(cls, '_%s__header' % cls_name) 1204 | with open(header_path, 'w') as fh: 1205 | fh.write('initial header stuff here\n') 1206 | for enum in cls: 1207 | fh.write('#define %s %r\n' % (enum.name, enum.value)) 1208 | class Arduino(CHeader): 1209 | __header = os.path.join(tempdir, 'arduino.h') 1210 | ONE = 1 1211 | TWO = 2 1212 | with open(os.path.join(tempdir, 'arduino.h')) as fh: 1213 | data = fh.read() 1214 | self.assertEqual(textwrap.dedent("""\ 1215 | initial header stuff here 1216 | #define ONE 1 1217 | #define TWO 2 1218 | """), 1219 | data, 1220 | ) 1221 | 1222 | def test_create_C_like_Enum(self): 1223 | # https://stackoverflow.com/a/35965438/208880 1224 | class Id(Enum, settings=MagicValue, start=0): 1225 | # 1226 | NONE # 0x0 1227 | HEARTBEAT # 0x1 1228 | FLUID_TRANSFER_REQUEST 1229 | FLUID_TRANSFER_STATUS_MSG 1230 | FLUID_TRANSFER_ERROR_MSG 1231 | # ... 1232 | # 1233 | # Camera App Messages 1234 | START_SENDING_PICTURES = 0x010000 1235 | STOP_SENDING_PICTURES 1236 | START_RECORDING_VIDEO_REQ 1237 | STOP_RECORDING_VIDEO_REQ 1238 | # ... 1239 | # 1240 | # Sensor Calibration 1241 | VOLUME_REQUEST = 0x020000 1242 | START_CAL 1243 | CLI_COMMAND_REQUEST 1244 | CLI_COMMAND_RESPONSE 1245 | # 1246 | # File Mananger 1247 | NEW_DELIVERY_REQ = 0x30000 1248 | GET_DELIVERY_FILE_REQ 1249 | GET_FILE_REQ 1250 | # 1251 | ACK_NACK 1252 | RESPONSE 1253 | # 1254 | LAST_ID 1255 | # 1256 | self.assertEqual(Id.NONE.value, 0) 1257 | self.assertEqual(Id.FLUID_TRANSFER_ERROR_MSG.value, 4) 1258 | self.assertEqual(Id.START_SENDING_PICTURES.value, 0x010000) 1259 | self.assertEqual(Id.STOP_RECORDING_VIDEO_REQ.value, 0x010003) 1260 | self.assertEqual(Id.START_CAL.value, 0x020001) 1261 | self.assertEqual(Id.LAST_ID.value, 0x30005) 1262 | 1263 | 1264 | @unittest.skipUnless(pyparsing, 'pyparsing not installed') 1265 | def test_c_header_scanner(self): 1266 | # https://stackoverflow.com/questions/58732872/208880 1267 | with open(os.path.join(tempdir, 'c_plus_plus.h'), 'w') as fh: 1268 | fh.write(""" 1269 | stuff before 1270 | enum hello { 1271 | Zero, 1272 | One, 1273 | Two, 1274 | Three, 1275 | Five=5, 1276 | Six, 1277 | Ten=10 1278 | }; 1279 | in the middle 1280 | enum blah 1281 | { 1282 | alpha, 1283 | beta, 1284 | gamma = 10 , 1285 | zeta = 50 1286 | }; 1287 | at the end 1288 | """) 1289 | from pyparsing import Group, Optional, Suppress, Word, ZeroOrMore 1290 | from pyparsing import alphas, alphanums, nums 1291 | # 1292 | CPPEnum = None 1293 | class CPPEnumType(EnumMeta): 1294 | # 1295 | @classmethod 1296 | def __prepare__(metacls, clsname, bases, **kwds): 1297 | # return a standard dictionary for the initial processing 1298 | return {} 1299 | # 1300 | def __init__(clsname, *args , **kwds): 1301 | super(CPPEnumType, clsname).__init__(*args) 1302 | # 1303 | def __new__(metacls, clsname, bases, clsdict, **kwds): 1304 | if CPPEnum is None: 1305 | # first time through, ignore the rest 1306 | enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds) 1307 | enum_dict.update(clsdict) 1308 | return super(CPPEnumType, metacls).__new__(metacls, clsname, bases, enum_dict, **kwds) 1309 | members = [] 1310 | # 1311 | # remove _file and _name using `pop()` as they will cause problems in EnumMeta 1312 | try: 1313 | file = clsdict.pop('_file') 1314 | except KeyError: 1315 | raise TypeError('_file not specified') 1316 | cpp_enum_name = clsdict.pop('_name', clsname.lower()) 1317 | with open(file) as fh: 1318 | file_contents = fh.read() 1319 | # 1320 | # syntax we don't want to see in the final parse tree 1321 | LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,") 1322 | _enum = Suppress("enum") 1323 | identifier = Word(alphas, alphanums + "_") 1324 | integer = Word(nums) 1325 | enumValue = Group(identifier("name") + Optional(EQ + integer("value"))) 1326 | enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue)) 1327 | enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE 1328 | # 1329 | # find the cpp_enum_name ignoring other syntax and other enums 1330 | for item, start, stop in enum.scanString(file_contents): 1331 | if item.enum != cpp_enum_name: 1332 | continue 1333 | id = 0 1334 | for entry in item.names: 1335 | if entry.value != "": 1336 | id = int(entry.value) 1337 | members.append((entry.name.upper(), id)) 1338 | id += 1 1339 | # 1340 | # get the real EnumDict 1341 | enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds) 1342 | # transfer the original dict content, names starting with '_' first 1343 | items = list(clsdict.items()) 1344 | items.sort(key=lambda p: (0 if p[0][0] == '_' else 1, p)) 1345 | for name, value in items: 1346 | enum_dict[name] = value 1347 | # add the members 1348 | for name, value in members: 1349 | enum_dict[name] = value 1350 | return super(CPPEnumType, metacls).__new__(metacls, clsname, bases, enum_dict, **kwds) 1351 | # 1352 | class CPPEnum(IntEnum, metaclass=CPPEnumType): 1353 | pass 1354 | # 1355 | class Hello(CPPEnum): 1356 | _file = os.path.join(tempdir, 'c_plus_plus.h') 1357 | # 1358 | class Blah(CPPEnum): 1359 | _file = os.path.join(tempdir, 'c_plus_plus.h') 1360 | _name = 'blah' 1361 | # 1362 | self.assertEqual( 1363 | list(Hello), 1364 | [Hello.ZERO, Hello.ONE, Hello.TWO, Hello.THREE, Hello.FIVE, Hello.SIX, Hello.TEN], 1365 | ) 1366 | self.assertEqual(Hello.ZERO.value, 0) 1367 | self.assertEqual(Hello.THREE.value, 3) 1368 | self.assertEqual(Hello.SIX.value, 6) 1369 | self.assertEqual(Hello.TEN.value, 10) 1370 | # 1371 | self.assertEqual( 1372 | list(Blah), 1373 | [Blah.ALPHA, Blah.BETA, Blah.GAMMA, Blah.ZETA], 1374 | ) 1375 | self.assertEqual(Blah.ALPHA.value, 0) 1376 | self.assertEqual(Blah.BETA.value, 1) 1377 | self.assertEqual(Blah.GAMMA.value, 10) 1378 | self.assertEqual(Blah.ZETA.value, 50) 1379 | 1380 | class TestIssuesV3(TestCase): 1381 | """ 1382 | Problems that were stated in issues. 1383 | """ 1384 | 1385 | def test_auto_multi_int_1(self): 1386 | class Measurement(int, AddValueEnum, MultiValueEnum, start=0): 1387 | one = "20110721" 1388 | two = "20120911" 1389 | three = "20110518" 1390 | self.assertEqual([m.value for m in Measurement], [0, 1, 2]) 1391 | self.assertEqual([m.name for m in Measurement], ['one', 'two', 'three']) 1392 | self.assertIs(Measurement(0), Measurement.one) 1393 | self.assertIs(Measurement('20110721'), Measurement.one) 1394 | self.assertIs(Measurement(1), Measurement.two) 1395 | self.assertIs(Measurement('20120911'), Measurement.two) 1396 | self.assertIs(Measurement(2), Measurement.three) 1397 | self.assertIs(Measurement('20110518'), Measurement.three) 1398 | 1399 | def test_auto_multi_int_2(self): 1400 | class Measurement(int, Enum, settings=(MultiValue, AddValue), start=0): 1401 | one = "20110721" 1402 | two = "20120911" 1403 | three = "20110518" 1404 | self.assertEqual([m.value for m in Measurement], [0, 1, 2]) 1405 | self.assertEqual([m.name for m in Measurement], ['one', 'two', 'three']) 1406 | self.assertIs(Measurement(0), Measurement.one) 1407 | self.assertIs(Measurement('20110721'), Measurement.one) 1408 | self.assertIs(Measurement(1), Measurement.two) 1409 | self.assertIs(Measurement('20120911'), Measurement.two) 1410 | self.assertIs(Measurement(2), Measurement.three) 1411 | self.assertIs(Measurement('20110518'), Measurement.three) 1412 | 1413 | def test_extend_enum_with_init(self): 1414 | class Color(Enum, settings=MultiValue, init='foo bar'): 1415 | red = '1', 'yes' 1416 | green = '2', 'no' 1417 | blue = '3', 'maybe' 1418 | self.assertEqual(Color.red.value, '1') 1419 | self.assertEqual(Color.red.foo, '1') 1420 | self.assertEqual(Color.red.bar, 'yes') 1421 | extend_enum(Color, 'opacity', '4', 'never') 1422 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue, Color.opacity]) 1423 | self.assertEqual(Color.opacity.value, '4') 1424 | self.assertEqual(Color.opacity.name, 'opacity') 1425 | self.assertTrue(Color('4') is Color.opacity) 1426 | self.assertTrue(Color('never') is Color.opacity) 1427 | 1428 | class TestExtendEnumV3(TestCase): 1429 | 1430 | def test_extend_enum_plain(self): 1431 | class Color(Enum): 1432 | red = 1 1433 | green = 2 1434 | blue = 3 1435 | self.assertRaisesRegex(TypeError, '.blue. already in use as .Color.blue: 3.', extend_enum, Color, 'blue', 5) 1436 | # 1437 | extend_enum(Color, 'brown', 4) 1438 | self.assertEqual(Color.brown.name, 'brown') 1439 | self.assertEqual(Color.brown.value, 4) 1440 | self.assertTrue(Color.brown in Color) 1441 | self.assertEqual(Color(4), Color.brown) 1442 | self.assertEqual(Color['brown'], Color.brown) 1443 | self.assertEqual(len(Color), 4) 1444 | # 1445 | extend_enum(Color, 'mauve') 1446 | self.assertEqual(Color.mauve.name, 'mauve') 1447 | self.assertEqual(Color.mauve.value, 5) 1448 | self.assertTrue(Color.mauve in Color) 1449 | self.assertEqual(Color(5), Color.mauve) 1450 | self.assertEqual(Color['mauve'], Color.mauve) 1451 | self.assertEqual(len(Color), 5) 1452 | 1453 | def test_extend_enum_alias(self): 1454 | class Color(Enum): 1455 | red = 1 1456 | green = 2 1457 | blue = 3 1458 | extend_enum(Color, 'rojo', 1) 1459 | self.assertEqual(Color.rojo.name, 'red') 1460 | self.assertEqual(Color.rojo.value, 1) 1461 | self.assertTrue(Color.rojo in Color) 1462 | self.assertEqual(Color(1), Color.rojo) 1463 | self.assertEqual(Color['rojo'], Color.red) 1464 | self.assertEqual(len(Color), 3) 1465 | 1466 | def test_extend_enum_unique(self): 1467 | class Color(UniqueEnum): 1468 | red = 1 1469 | green = 2 1470 | blue = 3 1471 | self.assertRaisesRegex(ValueError, r' is a duplicate of ', extend_enum, Color, 'rojo', 1) 1472 | # 1473 | self.assertEqual(Color.red.name, 'red') 1474 | self.assertEqual(Color.red.value, 1) 1475 | self.assertTrue(Color.red in Color) 1476 | self.assertEqual(Color(1), Color.red) 1477 | self.assertEqual(Color['red'], Color.red) 1478 | self.assertEqual(Color.green.name, 'green') 1479 | self.assertEqual(Color.green.value, 2) 1480 | self.assertTrue(Color.green in Color) 1481 | self.assertEqual(Color(2), Color.green) 1482 | self.assertEqual(Color['blue'], Color.blue) 1483 | self.assertEqual(Color.blue.name, 'blue') 1484 | self.assertEqual(Color.blue.value, 3) 1485 | self.assertTrue(Color.blue in Color) 1486 | self.assertEqual(Color(3), Color.blue) 1487 | self.assertEqual(len(Color), 3) 1488 | # 1489 | extend_enum(Color, 'brown', 4) 1490 | self.assertEqual(Color.brown.name, 'brown') 1491 | self.assertEqual(Color.brown.value, 4) 1492 | self.assertTrue(Color.brown in Color) 1493 | self.assertEqual(Color(4), Color.brown) 1494 | self.assertEqual(Color['brown'], Color.brown) 1495 | self.assertEqual(len(Color), 4) 1496 | # 1497 | self.assertRaisesRegex(ValueError, ' is a duplicate of ', extend_enum, Color, 'verde', 2) 1498 | # 1499 | extend_enum(Color, 'mauve') 1500 | self.assertEqual(Color.mauve.name, 'mauve') 1501 | self.assertEqual(Color.mauve.value, 5) 1502 | self.assertTrue(Color.mauve in Color) 1503 | self.assertEqual(Color(5), Color.mauve) 1504 | self.assertEqual(Color['mauve'], Color.mauve) 1505 | self.assertEqual(len(Color), 5) 1506 | 1507 | 1508 | def test_extend_enum_shadow_property(self): 1509 | class Color(Enum): 1510 | red = 1 1511 | green = 2 1512 | blue = 3 1513 | extend_enum(Color, 'value', 4) 1514 | self.assertEqual(Color.value.name, 'value') 1515 | self.assertEqual(Color.value.value, 4) 1516 | self.assertTrue(Color.value in Color) 1517 | self.assertEqual(Color(4), Color.value) 1518 | self.assertEqual(Color['value'], Color.value) 1519 | self.assertEqual(len(Color), 4) 1520 | self.assertEqual(Color.red.value, 1) 1521 | 1522 | def test_extend_enum_shadow_base(self): 1523 | class hohum(object): 1524 | def cyan(self): 1525 | "cyanize a color" 1526 | return self.value 1527 | class Color(hohum, Enum): 1528 | red = 1 1529 | green = 2 1530 | blue = 3 1531 | self.assertRaisesRegex(TypeError, r'already in use in superclass', extend_enum, Color, 'cyan', 4) 1532 | self.assertEqual(len(Color), 3) 1533 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) 1534 | 1535 | def test_extend_enum_multivalue(self): 1536 | class Color(MultiValueEnum): 1537 | red = 1, 4, 7 1538 | green = 2, 5, 8 1539 | blue = 3, 6, 9 1540 | extend_enum(Color, 'brown', 10, 20) 1541 | self.assertEqual(Color.brown.name, 'brown') 1542 | self.assertEqual(Color.brown.value, 10) 1543 | self.assertTrue(Color.brown in Color) 1544 | self.assertEqual(Color(10), Color.brown) 1545 | self.assertEqual(Color(20), Color.brown) 1546 | self.assertEqual(Color['brown'], Color.brown) 1547 | self.assertEqual(len(Color), 4) 1548 | # 1549 | self.assertRaisesRegex(ValueError, 'no values specified for MultiValue enum', extend_enum, Color, 'mauve') 1550 | 1551 | def test_extend_enum_multivalue_alias(self): 1552 | class Color(MultiValueEnum): 1553 | red = 1, 4, 7 1554 | green = 2, 5, 8 1555 | blue = 3, 6, 9 1556 | self.assertRaisesRegex(ValueError, r' is a duplicate of ', extend_enum, Color, 'rojo', 7) 1557 | self.assertEqual(Color.red.name, 'red') 1558 | self.assertEqual(Color.red.value, 1) 1559 | self.assertTrue(Color.red in Color) 1560 | self.assertEqual(Color(1), Color.red) 1561 | self.assertEqual(Color(4), Color.red) 1562 | self.assertEqual(Color(7), Color.red) 1563 | self.assertEqual(Color['red'], Color.red) 1564 | self.assertEqual(Color.green.name, 'green') 1565 | self.assertEqual(Color.green.value, 2) 1566 | self.assertTrue(Color.green in Color) 1567 | self.assertEqual(Color(2), Color.green) 1568 | self.assertEqual(Color(5), Color.green) 1569 | self.assertEqual(Color(8), Color.green) 1570 | self.assertEqual(Color['blue'], Color.blue) 1571 | self.assertEqual(Color.blue.name, 'blue') 1572 | self.assertEqual(Color.blue.value, 3) 1573 | self.assertTrue(Color.blue in Color) 1574 | self.assertEqual(Color(3), Color.blue) 1575 | self.assertEqual(Color(6), Color.blue) 1576 | self.assertEqual(Color(9), Color.blue) 1577 | self.assertEqual(len(Color), 3) 1578 | 1579 | def test_extend_enum_multivalue_str(self): 1580 | class M(str, MultiValueEnum): 1581 | VALUE_1 = 'value_1', 'VALUE_1' 1582 | VALUE_2 = 'value_2', 'VALUE_2' 1583 | VALUE_3 = 'value_3', 'VALUE_3' 1584 | self.assertTrue(M._member_type_ is str) 1585 | extend_enum(M, 'VALUE_4', 'value_4', 'VALUE_4') 1586 | self.assertEqual(list(M), [M.VALUE_1, M.VALUE_2, M.VALUE_3, M.VALUE_4]) 1587 | self.assertTrue(M('value_4') is M.VALUE_4) 1588 | self.assertTrue(M('VALUE_4') is M.VALUE_4) 1589 | self.assertTrue(M.VALUE_4.name == 'VALUE_4') 1590 | self.assertTrue(M.VALUE_4.value == 'value_4') 1591 | 1592 | def test_extend_intenum(self): 1593 | class Index(IntEnum): 1594 | DeviceType = 0x1000 1595 | ErrorRegister = 0x1001 1596 | 1597 | for name, value in ( 1598 | ('ControlWord', 0x6040), 1599 | ('StatusWord', 0x6041), 1600 | ('OperationMode', 0x6060), 1601 | ): 1602 | extend_enum(Index, name, value) 1603 | 1604 | self.assertEqual(len(Index), 5) 1605 | self.assertEqual(list(Index), [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode]) 1606 | self.assertEqual(Index.DeviceType.value, 0x1000) 1607 | self.assertEqual(Index.StatusWord.value, 0x6041) 1608 | 1609 | def test_extend_multi_init(self): 1610 | class HTTPStatus(IntEnum): 1611 | def __new__(cls, value, phrase, description): 1612 | obj = int.__new__(cls, value) 1613 | obj._value_ = value 1614 | 1615 | obj.phrase = phrase 1616 | obj.description = description 1617 | return obj 1618 | CONTINUE = 100, 'Continue', 'Request received, please continue' 1619 | SWITCHING_PROTOCOLS = 101, 'Switching Protocols', 'Switching to new protocol; obey Upgrade header' 1620 | PROCESSING = 102, 'Processing', '' 1621 | length = 3 1622 | extend_enum(HTTPStatus, 'BAD_SPAM', 513, 'Too greasy', 'for a train') 1623 | extend_enum(HTTPStatus, 'BAD_EGGS', 514, 'Too green', '') 1624 | self.assertEqual(len(HTTPStatus), length+2) 1625 | self.assertEqual( 1626 | list(HTTPStatus)[-2:], 1627 | [HTTPStatus.BAD_SPAM, HTTPStatus.BAD_EGGS], 1628 | ) 1629 | self.assertEqual(HTTPStatus.BAD_SPAM.value, 513) 1630 | self.assertEqual(HTTPStatus.BAD_SPAM.name, 'BAD_SPAM') 1631 | self.assertEqual(HTTPStatus.BAD_SPAM.phrase, 'Too greasy') 1632 | self.assertEqual(HTTPStatus.BAD_SPAM.description, 'for a train') 1633 | self.assertEqual(HTTPStatus.BAD_EGGS.value, 514) 1634 | self.assertEqual(HTTPStatus.BAD_EGGS.name, 'BAD_EGGS') 1635 | self.assertEqual(HTTPStatus.BAD_EGGS.phrase, 'Too green') 1636 | self.assertEqual(HTTPStatus.BAD_EGGS.description, '') 1637 | 1638 | def test_extend_flag(self): 1639 | class Color(Flag): 1640 | BLACK = 0 1641 | RED = 1 1642 | GREEN = 2 1643 | BLUE = 4 1644 | extend_enum(Color, 'MAGENTA') 1645 | self.assertTrue(Color(8) is Color.MAGENTA) 1646 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1647 | self.assertEqual(Color.MAGENTA.value, 8) 1648 | extend_enum(Color, 'PURPLE', 11) 1649 | self.assertTrue(Color(11) is Color.PURPLE) 1650 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1651 | self.assertEqual(Color.PURPLE.value, 11) 1652 | self.assertTrue(issubclass(Color, Flag)) 1653 | 1654 | def test_extend_flag_backwards(self): 1655 | class Color(Flag): 1656 | BLACK = 0 1657 | RED = 1 1658 | GREEN = 2 1659 | BLUE = 4 1660 | extend_enum(Color, 'PURPLE', 11) 1661 | self.assertTrue(Color(11) is Color.PURPLE) 1662 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1663 | self.assertEqual(Color.PURPLE.value, 11) 1664 | self.assertTrue(issubclass(Color, Flag)) 1665 | # 1666 | extend_enum(Color, 'MAGENTA') 1667 | self.assertTrue(Color(8) is Color.MAGENTA) 1668 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1669 | self.assertEqual(Color.MAGENTA.value, 8) 1670 | # 1671 | extend_enum(Color, 'mauve') 1672 | self.assertEqual(Color.mauve.name, 'mauve') 1673 | self.assertEqual(Color.mauve.value, 16) 1674 | self.assertTrue(Color.mauve in Color) 1675 | self.assertEqual(Color(16), Color.mauve) 1676 | self.assertEqual(Color['mauve'], Color.mauve) 1677 | self.assertEqual(len(Color), 5) 1678 | 1679 | def test_extend_intflag(self): 1680 | class Color(IntFlag): 1681 | BLACK = 0 1682 | RED = 1 1683 | GREEN = 2 1684 | BLUE = 4 1685 | extend_enum(Color, 'MAGENTA') 1686 | self.assertTrue(Color(8) is Color.MAGENTA) 1687 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1688 | self.assertEqual(Color.MAGENTA.value, 8) 1689 | extend_enum(Color, 'PURPLE', 11) 1690 | self.assertTrue(Color(11) is Color.PURPLE) 1691 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1692 | self.assertEqual(Color.PURPLE.value, 11) 1693 | self.assertTrue(issubclass(Color, Flag)) 1694 | # 1695 | extend_enum(Color, 'mauve') 1696 | self.assertEqual(Color.mauve.name, 'mauve') 1697 | self.assertEqual(Color.mauve.value, 16) 1698 | self.assertTrue(Color.mauve in Color) 1699 | self.assertEqual(Color(16), Color.mauve) 1700 | self.assertEqual(Color['mauve'], Color.mauve) 1701 | self.assertEqual(len(Color), 5) 1702 | 1703 | def test_extend_intflag_backwards(self): 1704 | class Color(IntFlag): 1705 | BLACK = 0 1706 | RED = 1 1707 | GREEN = 2 1708 | BLUE = 4 1709 | extend_enum(Color, 'PURPLE', 11) 1710 | self.assertTrue(Color(11) is Color.PURPLE) 1711 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1712 | self.assertEqual(Color.PURPLE.value, 11) 1713 | self.assertTrue(issubclass(Color, Flag)) 1714 | # 1715 | extend_enum(Color, 'MAGENTA') 1716 | self.assertTrue(Color(8) is Color.MAGENTA) 1717 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1718 | self.assertEqual(Color.MAGENTA.value, 8) 1719 | # 1720 | extend_enum(Color, 'mauve') 1721 | self.assertEqual(Color.mauve.name, 'mauve') 1722 | self.assertEqual(Color.mauve.value, 16) 1723 | self.assertTrue(Color.mauve in Color) 1724 | self.assertEqual(Color(16), Color.mauve) 1725 | self.assertEqual(Color['mauve'], Color.mauve) 1726 | self.assertEqual(len(Color), 5) 1727 | 1728 | def test_extend_strenum(self): 1729 | class Color(StrEnum): 1730 | RED = auto() 1731 | GREEN = auto() 1732 | BLUE = auto() 1733 | extend_enum(Color, 'BLACK') 1734 | self.assertEqual(Color.BLACK.name, 'BLACK') 1735 | self.assertEqual(Color.BLACK.value, 'black') 1736 | self.assertEqual(len(Color), 4) 1737 | 1738 | @unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available') 1739 | def test_extend_enum_stdlib(self): 1740 | class Color(StdlibEnum): 1741 | red = 1 1742 | green = 2 1743 | blue = 3 1744 | self.assertEqual(getattr(Color.red, '_values_', None), None) 1745 | extend_enum(Color, 'brown', 4) 1746 | self.assertEqual(Color.brown.name, 'brown') 1747 | self.assertEqual(Color.brown.value, 4) 1748 | self.assertTrue(Color.brown in Color) 1749 | self.assertEqual(Color(4), Color.brown) 1750 | self.assertEqual(Color['brown'], Color.brown) 1751 | self.assertEqual(len(Color), 4) 1752 | 1753 | @unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available') 1754 | def test_extend_enum_plain_stdlib(self): 1755 | class Color(StdlibEnum): 1756 | red = 1 1757 | green = 2 1758 | blue = 3 1759 | self.assertRaisesRegex(TypeError, 'already in use as', extend_enum, Color, 'blue', 5) 1760 | # 1761 | extend_enum(Color, 'brown', 4) 1762 | self.assertEqual(Color.brown.name, 'brown') 1763 | self.assertEqual(Color.brown.value, 4) 1764 | self.assertTrue(Color.brown in Color) 1765 | self.assertEqual(Color(4), Color.brown) 1766 | self.assertEqual(Color['brown'], Color.brown) 1767 | self.assertEqual(len(Color), 4) 1768 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue, Color.brown]) 1769 | self.assertEqual([c.value for c in Color], [1, 2, 3, 4]) 1770 | # 1771 | extend_enum(Color, 'mauve') 1772 | self.assertEqual(Color.mauve.name, 'mauve') 1773 | self.assertEqual(Color.mauve.value, 5) 1774 | self.assertTrue(Color.mauve in Color) 1775 | self.assertEqual(Color(5), Color.mauve) 1776 | self.assertEqual(Color['mauve'], Color.mauve) 1777 | self.assertEqual(len(Color), 5) 1778 | 1779 | @unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available') 1780 | def test_extend_enum_alias_stdlib(self): 1781 | class Color(StdlibEnum): 1782 | red = 1 1783 | green = 2 1784 | blue = 3 1785 | extend_enum(Color, 'rojo', 1) 1786 | self.assertEqual(Color.rojo.name, 'red') 1787 | self.assertEqual(Color.rojo.value, 1) 1788 | self.assertTrue(Color.rojo in Color) 1789 | self.assertEqual(Color(1), Color.rojo) 1790 | self.assertEqual(Color['rojo'], Color.red) 1791 | self.assertEqual(len(Color), 3) 1792 | 1793 | @unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available') 1794 | def test_extend_enum_shadow_property_stdlib(self): 1795 | class Color(StdlibEnum): 1796 | red = 1 1797 | green = 2 1798 | blue = 3 1799 | extend_enum(Color, 'value', 4) 1800 | self.assertEqual(Color.value.name, 'value') 1801 | self.assertEqual(Color.value.value, 4) 1802 | self.assertTrue(Color.value in Color) 1803 | self.assertEqual(Color(4), Color.value) 1804 | self.assertEqual(Color['value'], Color.value) 1805 | self.assertEqual(len(Color), 4) 1806 | self.assertEqual(Color.red.value, 1) 1807 | 1808 | @unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available') 1809 | def test_extend_enum_shadow_base_stdlib(self): 1810 | class hohum(object): 1811 | def cyan(self): 1812 | "cyanize a color" 1813 | return self.value 1814 | class Color(hohum, StdlibEnum): 1815 | red = 1 1816 | green = 2 1817 | blue = 3 1818 | self.assertRaisesRegex(TypeError, r'already in use in superclass', extend_enum, Color, 'cyan', 4) 1819 | self.assertEqual(len(Color), 3) 1820 | self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) 1821 | 1822 | @unittest.skipUnless(StdlibIntEnum, 'Stdlib IntEnum not available') 1823 | def test_extend_intenum_stdlib(self): 1824 | class Index(StdlibIntEnum): 1825 | DeviceType = 0x1000 1826 | ErrorRegister = 0x1001 1827 | 1828 | for name, value in ( 1829 | ('ControlWord', 0x6040), 1830 | ('StatusWord', 0x6041), 1831 | ('OperationMode', 0x6060), 1832 | ): 1833 | extend_enum(Index, name, value) 1834 | 1835 | self.assertEqual(len(Index), 5) 1836 | self.assertEqual(list(Index), [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode]) 1837 | self.assertEqual(Index.DeviceType.value, 0x1000) 1838 | self.assertEqual(Index.StatusWord.value, 0x6041) 1839 | 1840 | @unittest.skipUnless(StdlibIntEnum, 'Stdlib IntEnum not available') 1841 | def test_extend_multi_init_stdlib(self): 1842 | class HTTPStatus(StdlibIntEnum): 1843 | def __new__(cls, value, phrase, description): 1844 | obj = int.__new__(cls, value) 1845 | obj._value_ = value 1846 | 1847 | obj.phrase = phrase 1848 | obj.description = description 1849 | return obj 1850 | CONTINUE = 100, 'Continue', 'Request received, please continue' 1851 | SWITCHING_PROTOCOLS = 101, 'Switching Protocols', 'Switching to new protocol; obey Upgrade header' 1852 | PROCESSING = 102, 'Processing', '' 1853 | length = 3 1854 | extend_enum(HTTPStatus, 'BAD_SPAM', 513, 'Too greasy', 'for a train') 1855 | extend_enum(HTTPStatus, 'BAD_EGGS', 514, 'Too green', '') 1856 | self.assertEqual(len(HTTPStatus), length+2) 1857 | self.assertEqual( 1858 | list(HTTPStatus)[-2:], 1859 | [HTTPStatus.BAD_SPAM, HTTPStatus.BAD_EGGS], 1860 | ) 1861 | self.assertEqual(HTTPStatus.BAD_SPAM.value, 513) 1862 | self.assertEqual(HTTPStatus.BAD_SPAM.name, 'BAD_SPAM') 1863 | self.assertEqual(HTTPStatus.BAD_SPAM.phrase, 'Too greasy') 1864 | self.assertEqual(HTTPStatus.BAD_SPAM.description, 'for a train') 1865 | self.assertEqual(HTTPStatus.BAD_EGGS.value, 514) 1866 | self.assertEqual(HTTPStatus.BAD_EGGS.name, 'BAD_EGGS') 1867 | self.assertEqual(HTTPStatus.BAD_EGGS.phrase, 'Too green') 1868 | self.assertEqual(HTTPStatus.BAD_EGGS.description, '') 1869 | 1870 | @unittest.skipUnless(StdlibFlag, 'Stdlib Flag not available') 1871 | def test_extend_flag_stdlib(self): 1872 | class Color(StdlibFlag): 1873 | BLACK = 0 1874 | RED = 1 1875 | GREEN = 2 1876 | BLUE = 4 1877 | extend_enum(Color, 'MAGENTA') 1878 | self.assertTrue(Color(8) is Color.MAGENTA) 1879 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1880 | self.assertEqual(Color.MAGENTA.value, 8) 1881 | extend_enum(Color, 'PURPLE', 11) 1882 | self.assertTrue(Color(11) is Color.PURPLE) 1883 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1884 | self.assertEqual(Color.PURPLE.value, 11) 1885 | self.assertTrue(issubclass(Color, StdlibFlag)) 1886 | 1887 | @unittest.skipUnless(StdlibFlag and pyver < PY3_11, 'Stdlib Flag not available') 1888 | def test_extend_flag_backwards_stdlib(self): 1889 | class Color(StdlibFlag): 1890 | BLACK = 0 1891 | RED = 1 1892 | GREEN = 2 1893 | BLUE = 4 1894 | extend_enum(Color, 'PURPLE', 11) 1895 | self.assertTrue(Color(11) is Color.PURPLE) 1896 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1897 | self.assertEqual(Color.PURPLE.value, 11) 1898 | self.assertTrue(issubclass(Color, StdlibFlag)) 1899 | # 1900 | extend_enum(Color, 'MAGENTA') 1901 | self.assertTrue(Color(16) is Color.MAGENTA) 1902 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1903 | self.assertEqual(Color.MAGENTA.value,16) 1904 | # 1905 | extend_enum(Color, 'mauve') 1906 | self.assertEqual(Color.mauve.name, 'mauve') 1907 | self.assertEqual(Color.mauve.value, 32) 1908 | self.assertTrue(Color.mauve in Color) 1909 | self.assertEqual(Color(32), Color.mauve) 1910 | self.assertEqual(Color['mauve'], Color.mauve) 1911 | 1912 | @unittest.skipUnless(StdlibFlag and pyver >= PY3_11, 'Stdlib Flag not available') 1913 | def test_extend_flag_backwards_stdlib(self): 1914 | class Color(StdlibFlag): 1915 | BLACK = 0 1916 | RED = 1 1917 | GREEN = 2 1918 | BLUE = 4 1919 | extend_enum(Color, 'PURPLE', 11) 1920 | self.assertTrue(Color(11) is Color.PURPLE) 1921 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1922 | self.assertEqual(Color.PURPLE.value, 11) 1923 | self.assertTrue(issubclass(Color, StdlibFlag)) 1924 | # 1925 | extend_enum(Color, 'MAGENTA') 1926 | self.assertTrue(Color(8) is Color.MAGENTA) 1927 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1928 | self.assertEqual(Color.MAGENTA.value,8) 1929 | # 1930 | extend_enum(Color, 'mauve') 1931 | self.assertEqual(Color.mauve.name, 'mauve') 1932 | self.assertEqual(Color.mauve.value, 16) 1933 | self.assertTrue(Color.mauve in Color) 1934 | self.assertEqual(Color(16), Color.mauve) 1935 | self.assertEqual(Color['mauve'], Color.mauve) 1936 | 1937 | @unittest.skipUnless(StdlibIntFlag, 'Stdlib IntFlag not available') 1938 | def test_extend_intflag_stdlib(self): 1939 | class Color(StdlibIntFlag): 1940 | BLACK = 0 1941 | RED = 1 1942 | GREEN = 2 1943 | BLUE = 4 1944 | extend_enum(Color, 'MAGENTA') 1945 | self.assertTrue(Color(8) is Color.MAGENTA) 1946 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1947 | self.assertEqual(Color.MAGENTA.value, 8) 1948 | extend_enum(Color, 'PURPLE', 11) 1949 | self.assertTrue(Color(11) is Color.PURPLE) 1950 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1951 | self.assertEqual(Color.PURPLE.value, 11) 1952 | self.assertTrue(issubclass(Color, StdlibFlag)) 1953 | # 1954 | extend_enum(Color, 'mauve') 1955 | self.assertEqual(Color.mauve.name, 'mauve') 1956 | self.assertEqual(Color.mauve.value, 16) 1957 | self.assertTrue(Color.mauve in Color) 1958 | self.assertEqual(Color(16), Color.mauve) 1959 | self.assertEqual(Color['mauve'], Color.mauve) 1960 | 1961 | @unittest.skipUnless(StdlibIntFlag, 'Stdlib IntFlag not available') 1962 | def test_extend_intflag_backwards_stdlib(self): 1963 | class Color(StdlibIntFlag): 1964 | BLACK = 0 1965 | RED = 1 1966 | GREEN = 2 1967 | BLUE = 4 1968 | if pyver >= PY3_11: 1969 | # flags make more sense in 3.11 1970 | length = 5 1971 | MAGENTA = 8 1972 | mauve = 16 1973 | else: 1974 | length = 7 1975 | MAGENTA = 16 1976 | mauve = 32 1977 | extend_enum(Color, 'PURPLE', 11) 1978 | self.assertTrue(Color(11) is Color.PURPLE) 1979 | self.assertTrue(isinstance(Color.PURPLE, Color)) 1980 | self.assertEqual(Color.PURPLE.value, 11) 1981 | self.assertTrue(issubclass(Color, StdlibFlag)) 1982 | # 1983 | extend_enum(Color, 'MAGENTA') 1984 | self.assertTrue(Color(MAGENTA) is Color.MAGENTA) 1985 | self.assertTrue(isinstance(Color.MAGENTA, Color)) 1986 | self.assertEqual(Color.MAGENTA.value, MAGENTA) 1987 | # 1988 | extend_enum(Color, 'mauve') 1989 | self.assertEqual(Color.mauve.name, 'mauve') 1990 | self.assertEqual(Color.mauve.value, mauve) 1991 | self.assertTrue(Color.mauve in Color) 1992 | self.assertEqual(Color(mauve), Color.mauve) 1993 | self.assertEqual(Color['mauve'], Color.mauve) 1994 | self.assertEqual(len(Color), length, list(Color)) 1995 | 1996 | @unittest.skipUnless(StdlibStrEnum, 'Stdlib StrEnum not available') 1997 | def test_extend_strenum_stdlib(self): 1998 | class Color(StrEnum): 1999 | RED = auto() 2000 | GREEN = auto() 2001 | BLUE = auto() 2002 | extend_enum(Color, 'BLACK') 2003 | self.assertEqual(Color.BLACK.name, 'BLACK') 2004 | self.assertEqual(Color.BLACK.value, 'black') 2005 | self.assertEqual(len(Color), 4) 2006 | 2007 | 2008 | def test_extend_error(self): 2009 | class MyEnum(Enum, init="var1 var2"): 2010 | pass 2011 | self.assertRaisesRegex(TypeError, 'missing value for: .var2.', extend_enum, MyEnum, "NEW_MEMBER", "my_var1") 2012 | 2013 | 2014 | if __name__ == '__main__': 2015 | raise RuntimeError("'test_v3.py' should not be run by itself; it's included in 'test.py'") 2016 | -------------------------------------------------------------------------------- /aenum/test_v37.py: -------------------------------------------------------------------------------- 1 | from . import Enum 2 | from dataclasses import dataclass 3 | from unittest import TestCase 4 | 5 | 6 | class TestEnumV37(TestCase): 7 | 8 | def test_repr_with_dataclass(self): 9 | "ensure dataclass-mixin has correct repr()" 10 | # 11 | # check overridden dataclass __repr__ is used 12 | # 13 | from dataclasses import dataclass, field 14 | @dataclass(repr=False) 15 | class Foo: 16 | __qualname__ = 'Foo' 17 | a: int 18 | def __repr__(self): 19 | return 'ha hah!' 20 | class Entries(Foo, Enum): 21 | ENTRY1 = 1 22 | self.assertEqual(repr(Entries.ENTRY1), '') 23 | self.assertEqual(Entries.ENTRY1.value, Foo(1)) 24 | self.assertTrue(isinstance(Entries.ENTRY1, Foo)) 25 | self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_) 26 | # 27 | # check auto-generated dataclass __repr__ is not used 28 | # 29 | @dataclass 30 | class CreatureDataMixin: 31 | __qualname__ = 'CreatureDataMixin' 32 | size: str 33 | legs: int 34 | tail: bool = field(repr=False, default=True) 35 | class Creature(CreatureDataMixin, Enum): 36 | __qualname__ = 'Creature' 37 | BEETLE = ('small', 6) 38 | DOG = ('medium', 4) 39 | self.assertEqual(repr(Creature.DOG), "") 40 | # 41 | # check inherited repr used 42 | # 43 | class Huh: 44 | def __repr__(self): 45 | return 'inherited' 46 | @dataclass(repr=False) 47 | class CreatureDataMixin(Huh): 48 | __qualname__ = 'CreatureDataMixin' 49 | size: str 50 | legs: int 51 | tail: bool = field(repr=False, default=True) 52 | class Creature(CreatureDataMixin, Enum): 53 | __qualname__ = 'Creature' 54 | BEETLE = ('small', 6) 55 | DOG = ('medium', 4) 56 | self.assertEqual(repr(Creature.DOG), "") 57 | # 58 | # check default object.__repr__ used if nothing provided 59 | # 60 | @dataclass(repr=False) 61 | class CreatureDataMixin: 62 | __qualname__ = 'CreatureDataMixin' 63 | size: str 64 | legs: int 65 | tail: bool = field(repr=False, default=True) 66 | class Creature(CreatureDataMixin, Enum): 67 | __qualname__ = 'Creature' 68 | BEETLE = ('small', 6) 69 | DOG = ('medium', 4) 70 | self.assertRegex(repr(Creature.DOG), "") 71 | 72 | 73 | if __name__ == '__main__': 74 | raise RuntimeError("'test_v3.py' should not be run by itself; it's included in 'test.py'") 75 | 76 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | import setuptools 3 | setuptools 4 | except ImportError: 5 | pass 6 | from distutils.core import setup 7 | import sys 8 | 9 | long_desc = '''\ 10 | Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants 11 | 12 | WARNING: Version 3.1 has breaking changes in custom Enum settings 13 | WARNING: 14 | WARNING: AutoNumber has been removed 15 | WARNING: AutoValue has been removed 16 | 17 | aenum includes a Python stdlib Enum-compatible data type, as well as a metaclass-based NamedTuple implementation and a NamedConstant class. 18 | 19 | An Enum is a set of symbolic names (members) bound to unique, constant values. Within an enumeration, the members can be compared by identity, and the enumeration itself can be iterated over. Support exists for unique values, multiple values, auto-numbering, and suspension of aliasing (members with the same value are not identical), plus the ability to have values automatically bound to attributes. 20 | 21 | A NamedTuple is a class-based, fixed-length tuple with a name for each possible position accessible using attribute-access notation as well as the standard index notation. 22 | 23 | A NamedConstant is a class whose members cannot be rebound; it lacks all other Enum capabilities, however. 24 | 25 | Enum classes: 26 | 27 | - Enum: Base class for creating enumerated constants. 28 | 29 | - IntEnum: Base class for creating enumerated constants that are also 30 | subclasses of int. 31 | 32 | - Flag: Base class for creating enumerated constants that can be combined 33 | using the bitwise operations without losing their Flag membership. 34 | 35 | - IntFlag: Base class for creating enumerated constants that can be combined 36 | using the bitwise operators without losing their IntFlag membership. 37 | IntFlag members are also subclasses of int. 38 | 39 | - AutoNumberEnum: Derived class that automatically assigns an int value to each 40 | member. 41 | 42 | - OrderedEnum: Derived class that adds <, <=, >=, and > methods to an Enum. 43 | 44 | - UniqueEnum: Derived class that ensures only one name is bound to any one 45 | value. 46 | 47 | Utility functions include: 48 | 49 | - convert: helper to convert target global variables into an Enum 50 | 51 | - constant: helper class for creating constant members 52 | 53 | - enum: helper class for creating members with keywords 54 | 55 | - enum_property: property to enable enum members to have same named attributes 56 | (e.g. `name` and `value`) 57 | 58 | - export: helper to insert Enum members into a namespace (usually globals()) 59 | 60 | - extend_enum: add new members to enumerations after creation 61 | 62 | - module: inserts NamedConstant and Enum classes into sys.modules 63 | where it will appear to be a module whose top-level names 64 | cannot be rebound 65 | 66 | - skip: class that prevents attributes from being converted to a 67 | constant or enum member 68 | 69 | - unique: decorator that ensures no duplicate members 70 | ''' 71 | 72 | data = dict( 73 | name='aenum', 74 | version='3.1.16a1', 75 | url='https://github.com/ethanfurman/aenum', 76 | packages=['aenum'], 77 | package_data={ 78 | 'aenum' : [ 79 | 'LICENSE', 80 | 'README.md', 81 | 'doc/aenum.rst', 82 | 'doc/aenum.pdf', 83 | ] 84 | }, 85 | include_package_data=True, 86 | license='BSD License', 87 | description="Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants", 88 | long_description=long_desc, 89 | provides=['aenum'], 90 | author='Ethan Furman', 91 | author_email='ethan@stoneleaf.us', 92 | classifiers=[ 93 | 'Development Status :: 5 - Production/Stable', 94 | 'Intended Audience :: Developers', 95 | 'License :: OSI Approved :: BSD License', 96 | 'Programming Language :: Python', 97 | 'Topic :: Software Development', 98 | 'Programming Language :: Python :: 2.7', 99 | 'Programming Language :: Python :: 3.3', 100 | 'Programming Language :: Python :: 3.4', 101 | 'Programming Language :: Python :: 3.5', 102 | 'Programming Language :: Python :: 3.6', 103 | 'Programming Language :: Python :: 3.7', 104 | 'Programming Language :: Python :: 3.8', 105 | 'Programming Language :: Python :: 3.9', 106 | 'Programming Language :: Python :: 3.10', 107 | 'Programming Language :: Python :: 3.11', 108 | 'Programming Language :: Python :: 3.12', 109 | 'Programming Language :: Python :: 3.13', 110 | ], 111 | ) 112 | 113 | py2_only = ('aenum/_py2.py', ) 114 | py3_only = ('aenum/test_v3.py', 'aenum/test_v37.py', 'aenum/_py3.py') 115 | make = [ 116 | 'rst2pdf aenum/doc/aenum.rst --output=aenum/doc/aenum.pdf', 117 | ] 118 | 119 | if __name__ == '__main__': 120 | if 'install' in sys.argv: 121 | import os 122 | if sys.version_info[0] != 2: 123 | for file in py2_only: 124 | try: 125 | os.unlink(file) 126 | except OSError: 127 | pass 128 | if sys.version_info[0] != 3: 129 | for file in py3_only: 130 | try: 131 | os.unlink(file) 132 | except OSError: 133 | pass 134 | setup(**data) 135 | --------------------------------------------------------------------------------