Bug 1497575: [staging-release] Vendor mozilla-version; r=firefox-build-system-reviewers,mshal
authorTom Prince <mozilla@hocat.ca>
Tue, 16 Oct 2018 21:14:56 +0000
changeset 500176 cc20c7974801cb69129d1bf78b40b9f92f364b98
parent 500175 21477e61b5e12d48fe081f0576f2b6816c3b1606
child 500177 8b563ebe7cdad2b7f739cd53dfec0fc6ea9581f8
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfirefox-build-system-reviewers, mshal
bugs1497575
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1497575: [staging-release] Vendor mozilla-version; r=firefox-build-system-reviewers,mshal Differential Revision: https://phabricator.services.mozilla.com/D8624
build/virtualenv_packages.txt
third_party/python/enum34/MANIFEST.in
third_party/python/enum34/PKG-INFO
third_party/python/enum34/README
third_party/python/enum34/enum/LICENSE
third_party/python/enum34/enum/README
third_party/python/enum34/enum/__init__.py
third_party/python/enum34/enum/doc/enum.pdf
third_party/python/enum34/enum/doc/enum.rst
third_party/python/enum34/enum/test.py
third_party/python/enum34/setup.cfg
third_party/python/enum34/setup.py
third_party/python/moz.build
third_party/python/mozilla-version/LICENSE
third_party/python/mozilla-version/MANIFEST.in
third_party/python/mozilla-version/PKG-INFO
third_party/python/mozilla-version/README.md
third_party/python/mozilla-version/mozilla_version/__init__.py
third_party/python/mozilla-version/mozilla_version/balrog.py
third_party/python/mozilla-version/mozilla_version/errors.py
third_party/python/mozilla-version/mozilla_version/gecko.py
third_party/python/mozilla-version/mozilla_version/parser.py
third_party/python/mozilla-version/mozilla_version/test/__init__.py
third_party/python/mozilla-version/mozilla_version/test/test_balrog.py
third_party/python/mozilla-version/mozilla_version/test/test_gecko.py
third_party/python/mozilla-version/mozilla_version/test/test_version.py
third_party/python/mozilla-version/mozilla_version/version.py
third_party/python/mozilla-version/requirements-coveralls.txt
third_party/python/mozilla-version/requirements-docs.txt
third_party/python/mozilla-version/requirements-test.txt
third_party/python/mozilla-version/requirements.txt
third_party/python/mozilla-version/requirements.txt.in
third_party/python/mozilla-version/setup.cfg
third_party/python/mozilla-version/setup.py
third_party/python/mozilla-version/version.txt
third_party/python/requirements.in
third_party/python/requirements.txt
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -9,20 +9,22 @@ mozilla.pth:python/l10n
 mozilla.pth:third_party/python/atomicwrites
 mozilla.pth:third_party/python/attrs/src
 mozilla.pth:third_party/python/blessings
 mozilla.pth:third_party/python/Click
 mozilla.pth:third_party/python/compare-locales
 mozilla.pth:third_party/python/configobj
 mozilla.pth:third_party/python/cram
 mozilla.pth:third_party/python/dlmanager
+mozilla.pth:third_party/python/enum34
 mozilla.pth:third_party/python/fluent
 mozilla.pth:third_party/python/funcsigs
 mozilla.pth:third_party/python/futures
 mozilla.pth:third_party/python/more-itertools
+mozilla.pth:third_party/python/mozilla-version
 mozilla.pth:third_party/python/gyp/pylib
 mozilla.pth:third_party/python/python-hglib
 mozilla.pth:third_party/python/pluggy
 mozilla.pth:third_party/python/jsmin
 !windows:optional:setup.py:third_party/python/psutil:build_ext:--inplace
 !windows:mozilla.pth:third_party/python/psutil
 windows:mozilla.pth:third_party/python/psutil-cp27-none-win_amd64
 mozilla.pth:third_party/python/pylru
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/MANIFEST.in
@@ -0,0 +1,9 @@
+exclude enum/*
+include setup.py
+include README
+include enum/__init__.py
+include enum/test.py
+include enum/LICENSE
+include enum/README
+include enum/doc/enum.pdf
+include enum/doc/enum.rst
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/PKG-INFO
@@ -0,0 +1,62 @@
+Metadata-Version: 1.1
+Name: enum34
+Version: 1.1.6
+Summary: Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4
+Home-page: https://bitbucket.org/stoneleaf/enum34
+Author: Ethan Furman
+Author-email: ethan@stoneleaf.us
+License: BSD License
+Description: enum --- support for enumerations
+        ========================================
+        
+        An enumeration 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.
+        
+            from enum import Enum
+        
+            class Fruit(Enum):
+                apple = 1
+                banana = 2
+                orange = 3
+        
+            list(Fruit)
+            # [<Fruit.apple: 1>, <Fruit.banana: 2>, <Fruit.orange: 3>]
+        
+            len(Fruit)
+            # 3
+        
+            Fruit.banana
+            # <Fruit.banana: 2>
+        
+            Fruit['banana']
+            # <Fruit.banana: 2>
+        
+            Fruit(2)
+            # <Fruit.banana: 2>
+        
+            Fruit.banana is Fruit['banana'] is Fruit(2)
+            # True
+        
+            Fruit.banana.name
+            # 'banana'
+        
+            Fruit.banana.value
+            # 2
+        
+        Repository and Issue Tracker at https://bitbucket.org/stoneleaf/enum34.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development
+Classifier: Programming Language :: Python :: 2.4
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Provides: enum
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/README
@@ -0,0 +1,3 @@
+enum34 is the new Python stdlib enum module available in Python 3.4
+backported for previous versions of Python from 2.4 to 3.3.
+tested on 2.6, 2.7, and 3.3+
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/enum/LICENSE
@@ -0,0 +1,32 @@
+Copyright (c) 2013, Ethan Furman.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    Redistributions of source code must retain the above
+    copyright notice, this list of conditions and the
+    following disclaimer.
+
+    Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following
+    disclaimer in the documentation and/or other materials
+    provided with the distribution.
+
+    Neither the name Ethan Furman nor the names of any
+    contributors may be used to endorse or promote products
+    derived from this software without specific prior written
+    permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/enum/README
@@ -0,0 +1,3 @@
+enum34 is the new Python stdlib enum module available in Python 3.4
+backported for previous versions of Python from 2.4 to 3.3.
+tested on 2.6, 2.7, and 3.3+
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/enum/__init__.py
@@ -0,0 +1,837 @@
+"""Python Enumerations"""
+
+import sys as _sys
+
+__all__ = ['Enum', 'IntEnum', 'unique']
+
+version = 1, 1, 6
+
+pyver = float('%s.%s' % _sys.version_info[:2])
+
+try:
+    any
+except NameError:
+    def any(iterable):
+        for element in iterable:
+            if element:
+                return True
+        return False
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    OrderedDict = None
+
+try:
+    basestring
+except NameError:
+    # In Python 2 basestring is the ancestor of both str and unicode
+    # in Python 3 it's just str, but was missing in 3.1
+    basestring = str
+
+try:
+    unicode
+except NameError:
+    # In Python 3 unicode no longer exists (it's just str)
+    unicode = str
+
+class _RouteClassAttributeToGetattr(object):
+    """Route attribute access on a class to __getattr__.
+
+    This is a descriptor, used to define attributes that act differently when
+    accessed through an instance and through a class.  Instance access remains
+    normal, but access to an attribute through a class will be routed to the
+    class's __getattr__ method; this is done by raising AttributeError.
+
+    """
+    def __init__(self, fget=None):
+        self.fget = fget
+
+    def __get__(self, instance, ownerclass=None):
+        if instance is None:
+            raise AttributeError()
+        return self.fget(instance)
+
+    def __set__(self, instance, value):
+        raise AttributeError("can't set attribute")
+
+    def __delete__(self, instance):
+        raise AttributeError("can't delete attribute")
+
+
+def _is_descriptor(obj):
+    """Returns True if obj is a descriptor, False otherwise."""
+    return (
+            hasattr(obj, '__get__') or
+            hasattr(obj, '__set__') or
+            hasattr(obj, '__delete__'))
+
+
+def _is_dunder(name):
+    """Returns True if a __dunder__ name, False otherwise."""
+    return (name[:2] == name[-2:] == '__' and
+            name[2:3] != '_' and
+            name[-3:-2] != '_' and
+            len(name) > 4)
+
+
+def _is_sunder(name):
+    """Returns True if a _sunder_ name, False otherwise."""
+    return (name[0] == name[-1] == '_' and
+            name[1:2] != '_' and
+            name[-2:-1] != '_' and
+            len(name) > 2)
+
+
+def _make_class_unpicklable(cls):
+    """Make the given class un-picklable."""
+    def _break_on_call_reduce(self, protocol=None):
+        raise TypeError('%r cannot be pickled' % self)
+    cls.__reduce_ex__ = _break_on_call_reduce
+    cls.__module__ = '<unknown>'
+
+
+class _EnumDict(dict):
+    """Track enum member order and ensure member names are not reused.
+
+    EnumMeta will use the names found in self._member_names as the
+    enumeration member names.
+
+    """
+    def __init__(self):
+        super(_EnumDict, self).__init__()
+        self._member_names = []
+
+    def __setitem__(self, key, value):
+        """Changes anything not dundered or not a descriptor.
+
+        If a descriptor is added with the same name as an enum member, the name
+        is removed from _member_names (this may leave a hole in the numerical
+        sequence of values).
+
+        If an enum member name is used twice, an error is raised; duplicate
+        values are not checked for.
+
+        Single underscore (sunder) names are reserved.
+
+        Note:   in 3.x __order__ is simply discarded as a not necessary piece
+                leftover from 2.x
+
+        """
+        if pyver >= 3.0 and key in ('_order_', '__order__'):
+            return
+        elif key == '__order__':
+            key = '_order_'
+        if _is_sunder(key):
+            if key != '_order_':
+                raise ValueError('_names_ are reserved for future Enum use')
+        elif _is_dunder(key):
+            pass
+        elif key in self._member_names:
+            # descriptor overwriting an enum?
+            raise TypeError('Attempted to reuse key: %r' % key)
+        elif not _is_descriptor(value):
+            if key in self:
+                # enum overwriting a descriptor?
+                raise TypeError('Key already defined as: %r' % self[key])
+            self._member_names.append(key)
+        super(_EnumDict, self).__setitem__(key, value)
+
+
+# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
+# EnumMeta finishes running the first time the Enum class doesn't exist.  This
+# is also why there are checks in EnumMeta like `if Enum is not None`
+Enum = None
+
+
+class EnumMeta(type):
+    """Metaclass for Enum"""
+    @classmethod
+    def __prepare__(metacls, cls, bases):
+        return _EnumDict()
+
+    def __new__(metacls, cls, bases, classdict):
+        # an Enum class is final once enumeration items have been defined; it
+        # cannot be mixed with other types (int, float, etc.) if it has an
+        # inherited __new__ unless a new __new__ is defined (or the resulting
+        # class will fail).
+        if type(classdict) is dict:
+            original_dict = classdict
+            classdict = _EnumDict()
+            for k, v in original_dict.items():
+                classdict[k] = v
+
+        member_type, first_enum = metacls._get_mixins_(bases)
+        __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
+                                                        first_enum)
+        # save enum items into separate mapping so they don't get baked into
+        # the new class
+        members = dict((k, classdict[k]) for k in classdict._member_names)
+        for name in classdict._member_names:
+            del classdict[name]
+
+        # py2 support for definition order
+        _order_ = classdict.get('_order_')
+        if _order_ is None:
+            if pyver < 3.0:
+                try:
+                    _order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])]
+                except TypeError:
+                    _order_ = [name for name in sorted(members.keys())]
+            else:
+                _order_ = classdict._member_names
+        else:
+            del classdict['_order_']
+            if pyver < 3.0:
+                _order_ = _order_.replace(',', ' ').split()
+                aliases = [name for name in members if name not in _order_]
+                _order_ += aliases
+
+        # check for illegal enum names (any others?)
+        invalid_names = set(members) & set(['mro'])
+        if invalid_names:
+            raise ValueError('Invalid enum member name(s): %s' % (
+                ', '.join(invalid_names), ))
+
+        # save attributes from super classes so we know if we can take
+        # the shortcut of storing members in the class dict
+        base_attributes = set([a for b in bases for a in b.__dict__])
+        # create our new Enum type
+        enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)
+        enum_class._member_names_ = []               # names in random order
+        if OrderedDict is not None:
+            enum_class._member_map_ = OrderedDict()
+        else:
+            enum_class._member_map_ = {}             # name->value map
+        enum_class._member_type_ = member_type
+
+        # Reverse value->name map for hashable values.
+        enum_class._value2member_map_ = {}
+
+        # instantiate them, checking for duplicates as we go
+        # we instantiate first instead of checking for duplicates first in case
+        # a custom __new__ is doing something funky with the values -- such as
+        # auto-numbering ;)
+        if __new__ is None:
+            __new__ = enum_class.__new__
+        for member_name in _order_:
+            value = members[member_name]
+            if not isinstance(value, tuple):
+                args = (value, )
+            else:
+                args = value
+            if member_type is tuple:   # special case for tuple enums
+                args = (args, )     # wrap it one more time
+            if not use_args or not args:
+                enum_member = __new__(enum_class)
+                if not hasattr(enum_member, '_value_'):
+                    enum_member._value_ = value
+            else:
+                enum_member = __new__(enum_class, *args)
+                if not hasattr(enum_member, '_value_'):
+                    enum_member._value_ = member_type(*args)
+            value = enum_member._value_
+            enum_member._name_ = member_name
+            enum_member.__objclass__ = enum_class
+            enum_member.__init__(*args)
+            # If another member with the same value was already defined, the
+            # new member becomes an alias to the existing one.
+            for name, canonical_member in enum_class._member_map_.items():
+                if canonical_member.value == enum_member._value_:
+                    enum_member = canonical_member
+                    break
+            else:
+                # Aliases don't appear in member names (only in __members__).
+                enum_class._member_names_.append(member_name)
+            # performance boost for any member that would not shadow
+            # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr)
+            if member_name not in base_attributes:
+                setattr(enum_class, member_name, enum_member)
+            # now add to _member_map_
+            enum_class._member_map_[member_name] = enum_member
+            try:
+                # This may fail if value is not hashable. We can't add the value
+                # to the map, and by-value lookups for this value will be
+                # linear.
+                enum_class._value2member_map_[value] = enum_member
+            except TypeError:
+                pass
+
+
+        # If a custom type is mixed into the Enum, and it does not know how
+        # to pickle itself, pickle.dumps will succeed but pickle.loads will
+        # fail.  Rather than have the error show up later and possibly far
+        # from the source, sabotage the pickle protocol for this class so
+        # that pickle.dumps also fails.
+        #
+        # However, if the new class implements its own __reduce_ex__, do not
+        # sabotage -- it's on them to make sure it works correctly.  We use
+        # __reduce_ex__ instead of any of the others as it is preferred by
+        # pickle over __reduce__, and it handles all pickle protocols.
+        unpicklable = False
+        if '__reduce_ex__' not in classdict:
+            if member_type is not object:
+                methods = ('__getnewargs_ex__', '__getnewargs__',
+                        '__reduce_ex__', '__reduce__')
+                if not any(m in member_type.__dict__ for m in methods):
+                    _make_class_unpicklable(enum_class)
+                    unpicklable = True
+
+
+        # double check that repr and friends are not the mixin's or various
+        # things break (such as pickle)
+        for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
+            class_method = getattr(enum_class, name)
+            obj_method = getattr(member_type, name, None)
+            enum_method = getattr(first_enum, name, None)
+            if name not in classdict and class_method is not enum_method:
+                if name == '__reduce_ex__' and unpicklable:
+                    continue
+                setattr(enum_class, name, enum_method)
+
+        # method resolution and int's are not playing nice
+        # Python's less than 2.6 use __cmp__
+
+        if pyver < 2.6:
+
+            if issubclass(enum_class, int):
+                setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
+
+        elif pyver < 3.0:
+
+            if issubclass(enum_class, int):
+                for method in (
+                        '__le__',
+                        '__lt__',
+                        '__gt__',
+                        '__ge__',
+                        '__eq__',
+                        '__ne__',
+                        '__hash__',
+                        ):
+                    setattr(enum_class, method, getattr(int, method))
+
+        # replace any other __new__ with our own (as long as Enum is not None,
+        # anyway) -- again, this is to support pickle
+        if Enum is not None:
+            # if the user defined their own __new__, save it before it gets
+            # clobbered in case they subclass later
+            if save_new:
+                setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])
+            setattr(enum_class, '__new__', Enum.__dict__['__new__'])
+        return enum_class
+
+    def __bool__(cls):
+        """
+        classes/types should always be True.
+        """
+        return True
+
+    def __call__(cls, value, names=None, module=None, type=None, start=1):
+        """Either returns an existing member, or creates a new enum class.
+
+        This method is used both when an enum class is given a value to match
+        to an enumeration member (i.e. Color(3)) and for the functional API
+        (i.e. Color = Enum('Color', names='red green blue')).
+
+        When used for the functional API: `module`, if set, will be stored in
+        the new class' __module__ attribute; `type`, if set, will be mixed in
+        as the first base class.
+
+        Note: if `module` is not set this routine will attempt to discover the
+        calling module by walking the frame stack; if this is unsuccessful
+        the resulting class will not be pickleable.
+
+        """
+        if names is None:  # simple value lookup
+            return cls.__new__(cls, value)
+        # otherwise, functional API: we're creating a new Enum type
+        return cls._create_(value, names, module=module, type=type, start=start)
+
+    def __contains__(cls, member):
+        return isinstance(member, cls) and member.name in cls._member_map_
+
+    def __delattr__(cls, attr):
+        # nicer error message when someone tries to delete an attribute
+        # (see issue19025).
+        if attr in cls._member_map_:
+            raise AttributeError(
+                    "%s: cannot delete Enum member." % cls.__name__)
+        super(EnumMeta, cls).__delattr__(attr)
+
+    def __dir__(self):
+        return (['__class__', '__doc__', '__members__', '__module__'] +
+                self._member_names_)
+
+    @property
+    def __members__(cls):
+        """Returns a mapping of member name->value.
+
+        This mapping lists all enum members, including aliases. Note that this
+        is a copy of the internal mapping.
+
+        """
+        return cls._member_map_.copy()
+
+    def __getattr__(cls, name):
+        """Return the enum member matching `name`
+
+        We use __getattr__ instead of descriptors or inserting into the enum
+        class' __dict__ in order to support `name` and `value` being both
+        properties for enum members (which live in the class' __dict__) and
+        enum members themselves.
+
+        """
+        if _is_dunder(name):
+            raise AttributeError(name)
+        try:
+            return cls._member_map_[name]
+        except KeyError:
+            raise AttributeError(name)
+
+    def __getitem__(cls, name):
+        return cls._member_map_[name]
+
+    def __iter__(cls):
+        return (cls._member_map_[name] for name in cls._member_names_)
+
+    def __reversed__(cls):
+        return (cls._member_map_[name] for name in reversed(cls._member_names_))
+
+    def __len__(cls):
+        return len(cls._member_names_)
+
+    __nonzero__ = __bool__
+
+    def __repr__(cls):
+        return "<enum %r>" % cls.__name__
+
+    def __setattr__(cls, name, value):
+        """Block attempts to reassign Enum members.
+
+        A simple assignment to the class namespace only changes one of the
+        several possible ways to get an Enum member from the Enum class,
+        resulting in an inconsistent Enumeration.
+
+        """
+        member_map = cls.__dict__.get('_member_map_', {})
+        if name in member_map:
+            raise AttributeError('Cannot reassign members.')
+        super(EnumMeta, cls).__setattr__(name, value)
+
+    def _create_(cls, class_name, names=None, module=None, type=None, start=1):
+        """Convenience method to create a new Enum class.
+
+        `names` can be:
+
+        * A string containing member names, separated either with spaces or
+          commas.  Values are auto-numbered from 1.
+        * An iterable of member names.  Values are auto-numbered from 1.
+        * An iterable of (member name, value) pairs.
+        * A mapping of member name -> value.
+
+        """
+        if pyver < 3.0:
+            # if class_name is unicode, attempt a conversion to ASCII
+            if isinstance(class_name, unicode):
+                try:
+                    class_name = class_name.encode('ascii')
+                except UnicodeEncodeError:
+                    raise TypeError('%r is not representable in ASCII' % class_name)
+        metacls = cls.__class__
+        if type is None:
+            bases = (cls, )
+        else:
+            bases = (type, cls)
+        classdict = metacls.__prepare__(class_name, bases)
+        _order_ = []
+
+        # special processing needed for names?
+        if isinstance(names, basestring):
+            names = names.replace(',', ' ').split()
+        if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
+            names = [(e, i+start) for (i, e) in enumerate(names)]
+
+        # Here, names is either an iterable of (name, value) or a mapping.
+        item = None  # in case names is empty
+        for item in names:
+            if isinstance(item, basestring):
+                member_name, member_value = item, names[item]
+            else:
+                member_name, member_value = item
+            classdict[member_name] = member_value
+            _order_.append(member_name)
+        # only set _order_ in classdict if name/value was not from a mapping
+        if not isinstance(item, basestring):
+            classdict['_order_'] = ' '.join(_order_)
+        enum_class = metacls.__new__(metacls, class_name, bases, classdict)
+
+        # TODO: replace the frame hack if a blessed way to know the calling
+        # module is ever developed
+        if module is None:
+            try:
+                module = _sys._getframe(2).f_globals['__name__']
+            except (AttributeError, ValueError):
+                pass
+        if module is None:
+            _make_class_unpicklable(enum_class)
+        else:
+            enum_class.__module__ = module
+
+        return enum_class
+
+    @staticmethod
+    def _get_mixins_(bases):
+        """Returns the type for creating enum members, and the first inherited
+        enum class.
+
+        bases: the tuple of bases that was given to __new__
+
+        """
+        if not bases or Enum is None:
+            return object, Enum
+
+
+        # double check that we are not subclassing a class with existing
+        # enumeration members; while we're at it, see if any other data
+        # type has been mixed in so we can use the correct __new__
+        member_type = first_enum = None
+        for base in bases:
+            if  (base is not Enum and
+                    issubclass(base, Enum) and
+                    base._member_names_):
+                raise TypeError("Cannot extend enumerations")
+        # base is now the last base in bases
+        if not issubclass(base, Enum):
+            raise TypeError("new enumerations must be created as "
+                    "`ClassName([mixin_type,] enum_type)`")
+
+        # get correct mix-in type (either mix-in type of Enum subclass, or
+        # first base if last base is Enum)
+        if not issubclass(bases[0], Enum):
+            member_type = bases[0]     # first data type
+            first_enum = bases[-1]  # enum type
+        else:
+            for base in bases[0].__mro__:
+                # most common: (IntEnum, int, Enum, object)
+                # possible:    (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
+                #               <class 'int'>, <Enum 'Enum'>,
+                #               <class 'object'>)
+                if issubclass(base, Enum):
+                    if first_enum is None:
+                        first_enum = base
+                else:
+                    if member_type is None:
+                        member_type = base
+
+        return member_type, first_enum
+
+    if pyver < 3.0:
+        @staticmethod
+        def _find_new_(classdict, member_type, first_enum):
+            """Returns the __new__ to be used for creating the enum members.
+
+            classdict: the class dictionary given to __new__
+            member_type: the data type whose __new__ will be used by default
+            first_enum: enumeration to check for an overriding __new__
+
+            """
+            # now find the correct __new__, checking to see of one was defined
+            # by the user; also check earlier enum classes in case a __new__ was
+            # saved as __member_new__
+            __new__ = classdict.get('__new__', None)
+            if __new__:
+                return None, True, True      # __new__, save_new, use_args
+
+            N__new__ = getattr(None, '__new__')
+            O__new__ = getattr(object, '__new__')
+            if Enum is None:
+                E__new__ = N__new__
+            else:
+                E__new__ = Enum.__dict__['__new__']
+            # check all possibles for __member_new__ before falling back to
+            # __new__
+            for method in ('__member_new__', '__new__'):
+                for possible in (member_type, first_enum):
+                    try:
+                        target = possible.__dict__[method]
+                    except (AttributeError, KeyError):
+                        target = getattr(possible, method, None)
+                    if target not in [
+                            None,
+                            N__new__,
+                            O__new__,
+                            E__new__,
+                            ]:
+                        if method == '__member_new__':
+                            classdict['__new__'] = target
+                            return None, False, True
+                        if isinstance(target, staticmethod):
+                            target = target.__get__(member_type)
+                        __new__ = target
+                        break
+                if __new__ is not None:
+                    break
+            else:
+                __new__ = object.__new__
+
+            # if a non-object.__new__ is used then whatever value/tuple was
+            # assigned to the enum member name will be passed to __new__ and to the
+            # new enum member's __init__
+            if __new__ is object.__new__:
+                use_args = False
+            else:
+                use_args = True
+
+            return __new__, False, use_args
+    else:
+        @staticmethod
+        def _find_new_(classdict, member_type, first_enum):
+            """Returns the __new__ to be used for creating the enum members.
+
+            classdict: the class dictionary given to __new__
+            member_type: the data type whose __new__ will be used by default
+            first_enum: enumeration to check for an overriding __new__
+
+            """
+            # now find the correct __new__, checking to see of one was defined
+            # by the user; also check earlier enum classes in case a __new__ was
+            # saved as __member_new__
+            __new__ = classdict.get('__new__', None)
+
+            # should __new__ be saved as __member_new__ later?
+            save_new = __new__ is not None
+
+            if __new__ is None:
+                # check all possibles for __member_new__ before falling back to
+                # __new__
+                for method in ('__member_new__', '__new__'):
+                    for possible in (member_type, first_enum):
+                        target = getattr(possible, method, None)
+                        if target not in (
+                                None,
+                                None.__new__,
+                                object.__new__,
+                                Enum.__new__,
+                                ):
+                            __new__ = target
+                            break
+                    if __new__ is not None:
+                        break
+                else:
+                    __new__ = object.__new__
+
+            # if a non-object.__new__ is used then whatever value/tuple was
+            # assigned to the enum member name will be passed to __new__ and to the
+            # new enum member's __init__
+            if __new__ is object.__new__:
+                use_args = False
+            else:
+                use_args = True
+
+            return __new__, save_new, use_args
+
+
+########################################################
+# In order to support Python 2 and 3 with a single
+# codebase we have to create the Enum methods separately
+# and then use the `type(name, bases, dict)` method to
+# create the class.
+########################################################
+temp_enum_dict = {}
+temp_enum_dict['__doc__'] = "Generic enumeration.\n\n    Derive from this class to define new enumerations.\n\n"
+
+def __new__(cls, value):
+    # all enum instances are actually created during class construction
+    # without calling this method; this method is called by the metaclass'
+    # __call__ (i.e. Color(3) ), and by pickle
+    if type(value) is cls:
+        # For lookups like Color(Color.red)
+        value = value.value
+        #return value
+    # by-value search for a matching enum member
+    # see if it's in the reverse mapping (for hashable values)
+    try:
+        if value in cls._value2member_map_:
+            return cls._value2member_map_[value]
+    except TypeError:
+        # not there, now do long search -- O(n) behavior
+        for member in cls._member_map_.values():
+            if member.value == value:
+                return member
+    raise ValueError("%s is not a valid %s" % (value, cls.__name__))
+temp_enum_dict['__new__'] = __new__
+del __new__
+
+def __repr__(self):
+    return "<%s.%s: %r>" % (
+            self.__class__.__name__, self._name_, self._value_)
+temp_enum_dict['__repr__'] = __repr__
+del __repr__
+
+def __str__(self):
+    return "%s.%s" % (self.__class__.__name__, self._name_)
+temp_enum_dict['__str__'] = __str__
+del __str__
+
+if pyver >= 3.0:
+    def __dir__(self):
+        added_behavior = [
+                m
+                for cls in self.__class__.mro()
+                for m in cls.__dict__
+                if m[0] != '_' and m not in self._member_map_
+                ]
+        return (['__class__', '__doc__', '__module__', ] + added_behavior)
+    temp_enum_dict['__dir__'] = __dir__
+    del __dir__
+
+def __format__(self, format_spec):
+    # mixed-in Enums should use the mixed-in type's __format__, otherwise
+    # we can get strange results with the Enum name showing up instead of
+    # the value
+
+    # pure Enum branch
+    if self._member_type_ is object:
+        cls = str
+        val = str(self)
+    # mix-in branch
+    else:
+        cls = self._member_type_
+        val = self.value
+    return cls.__format__(val, format_spec)
+temp_enum_dict['__format__'] = __format__
+del __format__
+
+
+####################################
+# Python's less than 2.6 use __cmp__
+
+if pyver < 2.6:
+
+    def __cmp__(self, other):
+        if type(other) is self.__class__:
+            if self is other:
+                return 0
+            return -1
+        return NotImplemented
+        raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__cmp__'] = __cmp__
+    del __cmp__
+
+else:
+
+    def __le__(self, other):
+        raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__le__'] = __le__
+    del __le__
+
+    def __lt__(self, other):
+        raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__lt__'] = __lt__
+    del __lt__
+
+    def __ge__(self, other):
+        raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__ge__'] = __ge__
+    del __ge__
+
+    def __gt__(self, other):
+        raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__gt__'] = __gt__
+    del __gt__
+
+
+def __eq__(self, other):
+    if type(other) is self.__class__:
+        return self is other
+    return NotImplemented
+temp_enum_dict['__eq__'] = __eq__
+del __eq__
+
+def __ne__(self, other):
+    if type(other) is self.__class__:
+        return self is not other
+    return NotImplemented
+temp_enum_dict['__ne__'] = __ne__
+del __ne__
+
+def __hash__(self):
+    return hash(self._name_)
+temp_enum_dict['__hash__'] = __hash__
+del __hash__
+
+def __reduce_ex__(self, proto):
+    return self.__class__, (self._value_, )
+temp_enum_dict['__reduce_ex__'] = __reduce_ex__
+del __reduce_ex__
+
+# _RouteClassAttributeToGetattr is used to provide access to the `name`
+# and `value` properties of enum members while keeping some measure of
+# protection from modification, while still allowing for an enumeration
+# to have members named `name` and `value`.  This works because enumeration
+# members are not set directly on the enum class -- __getattr__ is
+# used to look them up.
+
+@_RouteClassAttributeToGetattr
+def name(self):
+    return self._name_
+temp_enum_dict['name'] = name
+del name
+
+@_RouteClassAttributeToGetattr
+def value(self):
+    return self._value_
+temp_enum_dict['value'] = value
+del value
+
+@classmethod
+def _convert(cls, name, module, filter, source=None):
+    """
+    Create a new Enum subclass that replaces a collection of global constants
+    """
+    # convert all constants from source (or module) that pass filter() to
+    # a new Enum called name, and export the enum and its members back to
+    # module;
+    # also, replace the __reduce_ex__ method so unpickling works in
+    # previous Python versions
+    module_globals = vars(_sys.modules[module])
+    if source:
+        source = vars(source)
+    else:
+        source = module_globals
+    members = dict((name, value) for name, value in source.items() if filter(name))
+    cls = cls(name, members, module=module)
+    cls.__reduce_ex__ = _reduce_ex_by_name
+    module_globals.update(cls.__members__)
+    module_globals[name] = cls
+    return cls
+temp_enum_dict['_convert'] = _convert
+del _convert
+
+Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
+del temp_enum_dict
+
+# Enum has now been created
+###########################
+
+class IntEnum(int, Enum):
+    """Enum where members are also (and must be) ints"""
+
+def _reduce_ex_by_name(self, proto):
+    return self.name
+
+def unique(enumeration):
+    """Class decorator that ensures only unique members exist in an enumeration."""
+    duplicates = []
+    for name, member in enumeration.__members__.items():
+        if name != member.name:
+            duplicates.append((name, member.name))
+    if duplicates:
+        duplicate_names = ', '.join(
+                ["%s -> %s" % (alias, name) for (alias, name) in duplicates]
+                )
+        raise ValueError('duplicate names found in %r: %s' %
+                (enumeration, duplicate_names)
+                )
+    return enumeration
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/enum/doc/enum.pdf
@@ -0,0 +1,2237 @@
+%PDF-1.4
+%東京 ReportLab Generated PDF document http://www.reportlab.com
+1 0 obj
+<< /F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R /F5 8 0 R /F6 15 0 R >>
+endobj
+2 0 obj
+<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
+endobj
+3 0 obj
+<< /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font >>
+endobj
+4 0 obj
+<< /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font >>
+endobj
+5 0 obj
+<< /BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font >>
+endobj
+6 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 22 0 R /XYZ 62.69291 639.3236 0 ] /Rect [ 335.1805 574.4272 405.2473 586.4272 ] /Subtype /Link /Type /Annot >>
+endobj
+7 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 22 0 R /XYZ 62.69291 639.3236 0 ] /Rect [ 255.9742 427.4272 321.5378 439.4272 ] /Subtype /Link /Type /Annot >>
+endobj
+8 0 obj
+<< /BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font >>
+endobj
+9 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 26 0 R /XYZ 62.69291 278.1236 0 ] /Rect [ 82.69291 182.2272 201.0729 194.2272 ] /Subtype /Link /Type /Annot >>
+endobj
+10 0 obj
+<< /Annots [ 6 0 R 7 0 R 9 0 R ] /Contents 56 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 
+  /Trans <<  >> /Type /Page >>
+endobj
+11 0 obj
+<< /Contents 57 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+12 0 obj
+<< /Contents 58 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+13 0 obj
+<< /Contents 59 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+14 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 22 0 R /XYZ 62.69291 157.2236 0 ] /Rect [ 101.6029 741.7736 141.6229 753.7736 ] /Subtype /Link /Type /Annot >>
+endobj
+15 0 obj
+<< /BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F6 /Subtype /Type1 /Type /Font >>
+endobj
+16 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 25 0 R /XYZ 62.69291 359.6236 0 ] /Rect [ 327.1529 585.5736 392.7329 597.5736 ] /Subtype /Link /Type /Annot >>
+endobj
+17 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 22 0 R /XYZ 62.69291 639.3236 0 ] /Rect [ 181.1917 312.1736 246.4634 324.1736 ] /Subtype /Link /Type /Annot >>
+endobj
+18 0 obj
+<< /Annots [ 14 0 R 16 0 R 17 0 R ] /Contents 60 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 
+  /Trans <<  >> /Type /Page >>
+endobj
+19 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 26 0 R /XYZ 62.69291 610.8236 0 ] /Rect [ 326.1329 511.3736 357.2629 523.3736 ] /Subtype /Link /Type /Annot >>
+endobj
+20 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 25 0 R /XYZ 62.69291 359.6236 0 ] /Rect [ 241.1229 185.9736 306.7029 197.9736 ] /Subtype /Link /Type /Annot >>
+endobj
+21 0 obj
+<< /Annots [ 19 0 R 20 0 R ] /Contents 61 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 
+  /Trans <<  >> /Type /Page >>
+endobj
+22 0 obj
+<< /Contents 62 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+23 0 obj
+<< /Contents 63 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+24 0 obj
+<< /Contents 64 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+25 0 obj
+<< /Contents 65 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+26 0 obj
+<< /Contents 66 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans <<  >> 
+  /Type /Page >>
+endobj
+27 0 obj
+<< /Border [ 0 0 0 ] /Contents () /Dest [ 26 0 R /XYZ 62.69291 610.8236 0 ] /Rect [ 309.4094 299.5736 340.6461 311.5736 ] /Subtype /Link /Type /Annot >>
+endobj
+28 0 obj
+<< /Annots [ 27 0 R ] /Contents 67 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 55 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 
+  /Trans <<  >> /Type /Page >>
+endobj
+29 0 obj
+<< /Outlines 31 0 R /PageLabels 68 0 R /PageMode /UseNone /Pages 55 0 R /Type /Catalog >>
+endobj
+30 0 obj
+<< /Author () /CreationDate (D:20160515202831+08'00') /Creator (\(unspecified\)) /Keywords () /Producer (ReportLab PDF Library - www.reportlab.com) /Subject (\(unspecified\)) 
+  /Title (enum --- support for enumerations) >>
+endobj
+31 0 obj
+<< /Count 27 /First 32 0 R /Last 51 0 R /Type /Outlines >>
+endobj
+32 0 obj
+<< /Dest [ 10 0 R /XYZ 62.69291 660.6772 0 ] /Next 33 0 R /Parent 31 0 R /Title (Module Contents) >>
+endobj
+33 0 obj
+<< /Dest [ 10 0 R /XYZ 62.69291 477.6772 0 ] /Next 34 0 R /Parent 31 0 R /Prev 32 0 R /Title (Creating an Enum) >>
+endobj
+34 0 obj
+<< /Dest [ 11 0 R /XYZ 62.69291 197.0236 0 ] /Next 35 0 R /Parent 31 0 R /Prev 33 0 R /Title (Programmatic access to enumeration members and their attributes) >>
+endobj
+35 0 obj
+<< /Dest [ 12 0 R /XYZ 62.69291 501.4236 0 ] /Next 36 0 R /Parent 31 0 R /Prev 34 0 R /Title (Duplicating enum members and values) >>
+endobj
+36 0 obj
+<< /Dest [ 13 0 R /XYZ 62.69291 215.0236 0 ] /Next 37 0 R /Parent 31 0 R /Prev 35 0 R /Title (Comparisons) >>
+endobj
+37 0 obj
+<< /Dest [ 18 0 R /XYZ 62.69291 362.4236 0 ] /Next 38 0 R /Parent 31 0 R /Prev 36 0 R /Title (Allowed members and attributes of enumerations) >>
+endobj
+38 0 obj
+<< /Dest [ 21 0 R /XYZ 62.69291 498.6236 0 ] /Next 39 0 R /Parent 31 0 R /Prev 37 0 R /Title (Restricted subclassing of enumerations) >>
+endobj
+39 0 obj
+<< /Dest [ 21 0 R /XYZ 62.69291 173.2236 0 ] /Next 40 0 R /Parent 31 0 R /Prev 38 0 R /Title (Pickling) >>
+endobj
+40 0 obj
+<< /Dest [ 22 0 R /XYZ 62.69291 635.8236 0 ] /Next 41 0 R /Parent 31 0 R /Prev 39 0 R /Title (Functional API) >>
+endobj
+41 0 obj
+<< /Count 2 /Dest [ 22 0 R /XYZ 62.69291 187.2236 0 ] /First 42 0 R /Last 43 0 R /Next 44 0 R /Parent 31 0 R 
+  /Prev 40 0 R /Title (Derived Enumerations) >>
+endobj
+42 0 obj
+<< /Dest [ 22 0 R /XYZ 62.69291 154.2236 0 ] /Next 43 0 R /Parent 41 0 R /Title (IntEnum) >>
+endobj
+43 0 obj
+<< /Dest [ 23 0 R /XYZ 62.69291 217.4236 0 ] /Parent 41 0 R /Prev 42 0 R /Title (Others) >>
+endobj
+44 0 obj
+<< /Count 1 /Dest [ 24 0 R /XYZ 62.69291 567.0236 0 ] /First 45 0 R /Last 45 0 R /Next 46 0 R /Parent 31 0 R 
+  /Prev 41 0 R /Title (Decorators) >>
+endobj
+45 0 obj
+<< /Dest [ 24 0 R /XYZ 62.69291 534.0236 0 ] /Parent 44 0 R /Title (unique) >>
+endobj
+46 0 obj
+<< /Count 4 /Dest [ 24 0 R /XYZ 62.69291 356.8236 0 ] /First 47 0 R /Last 50 0 R /Next 51 0 R /Parent 31 0 R 
+  /Prev 44 0 R /Title (Interesting examples) >>
+endobj
+47 0 obj
+<< /Dest [ 24 0 R /XYZ 62.69291 281.8236 0 ] /Next 48 0 R /Parent 46 0 R /Title (AutoNumber) >>
+endobj
+48 0 obj
+<< /Dest [ 25 0 R /XYZ 62.69291 641.8236 0 ] /Next 49 0 R /Parent 46 0 R /Prev 47 0 R /Title (UniqueEnum) >>
+endobj
+49 0 obj
+<< /Dest [ 25 0 R /XYZ 62.69291 356.6236 0 ] /Next 50 0 R /Parent 46 0 R /Prev 48 0 R /Title (OrderedEnum) >>
+endobj
+50 0 obj
+<< /Dest [ 26 0 R /XYZ 62.69291 607.8236 0 ] /Parent 46 0 R /Prev 49 0 R /Title (Planet) >>
+endobj
+51 0 obj
+<< /Count 3 /Dest [ 26 0 R /XYZ 62.69291 274.6236 0 ] /First 52 0 R /Last 54 0 R /Parent 31 0 R /Prev 46 0 R 
+  /Title (How are Enums different?) >>
+endobj
+52 0 obj
+<< /Dest [ 26 0 R /XYZ 62.69291 211.6236 0 ] /Next 53 0 R /Parent 51 0 R /Title (Enum Classes) >>
+endobj
+53 0 obj
+<< /Dest [ 28 0 R /XYZ 62.69291 664.0236 0 ] /Next 54 0 R /Parent 51 0 R /Prev 52 0 R /Title (Enum Members \(aka instances\)) >>
+endobj
+54 0 obj
+<< /Dest [ 28 0 R /XYZ 62.69291 592.0236 0 ] /Parent 51 0 R /Prev 53 0 R /Title (Finer Points) >>
+endobj
+55 0 obj
+<< /Count 12 /Kids [ 10 0 R 11 0 R 12 0 R 13 0 R 18 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 
+  26 0 R 28 0 R ] /Type /Pages >>
+endobj
+56 0 obj
+<< /Length 6458 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 741.0236 cm
+q
+BT 1 0 0 1 0 4 Tm 73.71488 0 Td 24 TL /F2 20 Tf 0 0 0 rg (enum ) Tj /F3 20 Tf 0 0 0 rg (--- support for enumerations) Tj T* -73.71488 0 Td ET
+Q
+Q
+q
+1 0 0 1 62.69291 702.6772 cm
+n 0 14.17323 m 469.8898 14.17323 l S
+Q
+q
+1 0 0 1 62.69291 672.6772 cm
+q
+BT 1 0 0 1 0 14 Tm 2.091318 Tw 12 TL /F1 10 Tf 0 0 0 rg (An enumeration is a set of symbolic names \(members\) bound to unique, constant values. Within an) Tj T* 0 Tw (enumeration, the members can be compared by identity, and the enumeration itself can be iterated over.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 639.6772 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Module Contents) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 609.6772 cm
+q
+BT 1 0 0 1 0 14 Tm 2.027485 Tw 12 TL /F1 10 Tf 0 0 0 rg (This module defines two enumeration classes that can be used to define unique sets of names and) Tj T* 0 Tw (values: ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F4 10 Tf 0 0 0 rg (IntEnum) Tj /F1 10 Tf 0 0 0 rg (. It also defines one decorator, ) Tj /F4 10 Tf 0 0 0 rg (unique) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 591.6772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F4 10 Tf 12 TL (Enum) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 561.6772 cm
+q
+BT 1 0 0 1 0 14 Tm 1.128443 Tw 12 TL /F1 10 Tf 0 0 0 rg (Base class for creating enumerated constants. See section ) Tj 0 0 .501961 rg (Functional API ) Tj 0 0 0 rg (for an alternate construction) Tj T* 0 Tw (syntax.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 543.6772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F4 10 Tf 12 TL (IntEnum) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 525.6772 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Base class for creating enumerated constants that are also subclasses of ) Tj /F4 10 Tf 0 0 0 rg (int) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 507.6772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F4 10 Tf 12 TL (unique) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 489.6772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Enum class decorator that ensures only one name is bound to any one value.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 456.6772 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Creating an Enum) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 414.6772 cm
+q
+BT 1 0 0 1 0 26 Tm 2.432651 Tw 12 TL /F1 10 Tf 0 0 0 rg (Enumerations are created using the ) Tj /F4 10 Tf 0 0 0 rg (class ) Tj /F1 10 Tf 0 0 0 rg (syntax, which makes them easy to read and write. An) Tj T* 0 Tw .533555 Tw (alternative creation method is described in ) Tj 0 0 .501961 rg (Functional API) Tj 0 0 0 rg (. To define an enumeration, subclass ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (as) Tj T* 0 Tw (follows:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 333.4772 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( from enum import Enum) Tj T* (>) Tj (>) Tj (>) Tj ( class Color\(Enum\):) Tj T* (...     red = 1) Tj T* (...     green = 2) Tj T* (...     blue = 3) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 313.4772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Note: Nomenclature) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 307.4772 cm
+Q
+q
+1 0 0 1 62.69291 229.4772 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm  T* ET
+q
+1 0 0 1 20 72 cm
+Q
+q
+1 0 0 1 20 72 cm
+Q
+q
+1 0 0 1 20 60 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 -3 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The class ) Tj /F4 10 Tf 0 0 0 rg (Color ) Tj /F1 10 Tf 0 0 0 rg (is an ) Tj /F5 10 Tf (enumeration ) Tj /F1 10 Tf (\(or ) Tj /F5 10 Tf (enum) Tj /F1 10 Tf (\)) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 20 54 cm
+Q
+q
+1 0 0 1 20 30 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 9 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 14 Tm 5.568863 Tw 12 TL /F1 10 Tf 0 0 0 rg (The attributes ) Tj /F4 10 Tf 0 0 0 rg (Color.red) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F4 10 Tf 0 0 0 rg (Color.green) Tj /F1 10 Tf 0 0 0 rg (, etc., are ) Tj /F5 10 Tf (enumeration members ) Tj /F1 10 Tf (\(or ) Tj /F5 10 Tf (enum) Tj T* 0 Tw (members) Tj /F1 10 Tf (\).) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 20 24 cm
+Q
+q
+1 0 0 1 20 0 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 9 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 14 Tm 1.471318 Tw 12 TL /F1 10 Tf 0 0 0 rg (The enum members have ) Tj /F5 10 Tf (names ) Tj /F1 10 Tf (and ) Tj /F5 10 Tf (values ) Tj /F1 10 Tf (\(the name of ) Tj /F4 10 Tf 0 0 0 rg (Color.red ) Tj /F1 10 Tf 0 0 0 rg (is ) Tj /F4 10 Tf 0 0 0 rg (red) Tj /F1 10 Tf 0 0 0 rg (, the value of) Tj T* 0 Tw /F4 10 Tf 0 0 0 rg (Color.blue ) Tj /F1 10 Tf 0 0 0 rg (is ) Tj /F4 10 Tf 0 0 0 rg (3) Tj /F1 10 Tf 0 0 0 rg (, etc.\)) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 20 0 cm
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 229.4772 cm
+Q
+q
+1 0 0 1 62.69291 211.4772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Note:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 205.4772 cm
+Q
+q
+1 0 0 1 62.69291 181.4772 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm  T* ET
+q
+1 0 0 1 20 0 cm
+q
+BT 1 0 0 1 0 14 Tm .126235 Tw 12 TL /F1 10 Tf 0 0 0 rg (Even though we use the ) Tj /F4 10 Tf 0 0 0 rg (class ) Tj /F1 10 Tf 0 0 0 rg (syntax to create Enums, Enums are not normal Python classes. See) Tj T* 0 Tw 0 0 .501961 rg (How are Enums different? ) Tj 0 0 0 rg (for more details.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 181.4772 cm
+Q
+q
+1 0 0 1 62.69291 163.4772 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Enumeration members have human readable string representations:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 118.2772 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( print\(Color.red\)) Tj T* (Color.red) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 98.27717 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (...while their ) Tj /F4 10 Tf 0 0 0 rg (repr ) Tj /F1 10 Tf 0 0 0 rg (has more information:) Tj T* ET
+Q
+Q
+ 
+endstream
+endobj
+57 0 obj
+<< /Length 4174 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 727.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( print\(repr\(Color.red\)\)) Tj T* (<) Tj (Color.red: 1) Tj (>) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 707.8236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F5 10 Tf (type ) Tj /F1 10 Tf (of an enumeration member is the enumeration it belongs to:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 626.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( type\(Color.red\)) Tj T* (<) Tj (enum 'Color') Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( isinstance\(Color.green, Color\)) Tj T* (True) Tj T* (>) Tj (>) Tj (>) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 606.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Enum members also have a property that contains just their item name:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 561.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( print\(Color.red.name\)) Tj T* (red) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 529.4236 cm
+q
+BT 1 0 0 1 0 14 Tm .464985 Tw 12 TL /F1 10 Tf 0 0 0 rg (Enumerations support iteration. In Python 3.x definition order is used; in Python 2.x the definition order is) Tj T* 0 Tw (not available, but class attribute ) Tj /F4 10 Tf 0 0 0 rg (__order__ ) Tj /F1 10 Tf 0 0 0 rg (is supported; otherwise, value order is used:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 340.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 180 re B*
+Q
+q
+BT 1 0 0 1 0 158 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Shake\(Enum\):) Tj T* (...   __order__ = 'vanilla chocolate cookies mint'  # only needed in 2.x) Tj T* (...   vanilla = 7) Tj T* (...   chocolate = 4) Tj T* (...   cookies = 9) Tj T* (...   mint = 3) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( for shake in Shake:) Tj T* (...   print\(shake\)) Tj T* (...) Tj T* (Shake.vanilla) Tj T* (Shake.chocolate) Tj T* (Shake.cookies) Tj T* (Shake.mint) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 308.2236 cm
+q
+BT 1 0 0 1 0 14 Tm 1.893735 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf 0 0 0 rg (__order__ ) Tj /F1 10 Tf 0 0 0 rg (attribute is always removed, and in 3.x it is also ignored \(order is definition order\);) Tj T* 0 Tw (however, in the stdlib version it will be ignored but not removed.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 290.2236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Enumeration members are hashable, so they can be used in dictionaries and sets:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 209.0236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( apples = {}) Tj T* (>) Tj (>) Tj (>) Tj ( apples[Color.red] = 'red delicious') Tj T* (>) Tj (>) Tj (>) Tj ( apples[Color.green] = 'granny smith') Tj T* (>) Tj (>) Tj (>) Tj ( apples == {Color.red: 'red delicious', Color.green: 'granny smith'}) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 155.0236 cm
+q
+BT 1 0 0 1 0 24.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Programmatic access to enumeration members and) Tj T* (their attributes) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 113.0236 cm
+q
+BT 1 0 0 1 0 26 Tm 3.541797 Tw 12 TL /F1 10 Tf 0 0 0 rg (Sometimes it's useful to access members in enumerations programmatically \(i.e. situations where) Tj T* 0 Tw .922651 Tw /F4 10 Tf 0 0 0 rg (Color.red ) Tj /F1 10 Tf 0 0 0 rg (won't do because the exact color is not known at program-writing time\). ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (allows such) Tj T* 0 Tw (access:) Tj T* ET
+Q
+Q
+ 
+endstream
+endobj
+58 0 obj
+<< /Length 3791 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 703.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 60 re B*
+Q
+q
+BT 1 0 0 1 0 38 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Color\(1\)) Tj T* (<) Tj (Color.red: 1) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Color\(3\)) Tj T* (<) Tj (Color.blue: 3) Tj (>) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 683.8236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you want to access enum members by ) Tj /F5 10 Tf (name) Tj /F1 10 Tf (, use item access:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 614.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 60 re B*
+Q
+q
+BT 1 0 0 1 0 38 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Color['red']) Tj T* (<) Tj (Color.red: 1) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Color['green']) Tj T* (<) Tj (Color.green: 2) Tj (>) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 594.6236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If have an enum member and need its ) Tj /F4 10 Tf 0 0 0 rg (name ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F4 10 Tf 0 0 0 rg (value) Tj /F1 10 Tf 0 0 0 rg (:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 513.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( member = Color.red) Tj T* (>) Tj (>) Tj (>) Tj ( member.name) Tj T* ('red') Tj T* (>) Tj (>) Tj (>) Tj ( member.value) Tj T* (1) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 480.4236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Duplicating enum members and values) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 450.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .13832 Tw (Having two enum members \(or any other attribute\) with the same name is invalid; in Python 3.x this would) Tj T* 0 Tw (raise an error, but in Python 2.x the second member simply overwrites the first:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 249.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 192 re B*
+Q
+q
+BT 1 0 0 1 0 170 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( # python 2.x) Tj T* (>) Tj (>) Tj (>) Tj ( class Shape\(Enum\):) Tj T* (...   square = 2) Tj T* (...   square = 3) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( Shape.square) Tj T* (<) Tj (Shape.square: 3) Tj (>) Tj  T*  T* (>) Tj (>) Tj (>) Tj ( # python 3.x) Tj T* (>) Tj (>) Tj (>) Tj ( class Shape\(Enum\):) Tj T* (...   square = 2) Tj T* (...   square = 3) Tj T* (Traceback \(most recent call last\):) Tj T* (...) Tj T* (TypeError: Attempted to reuse key: 'square') Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 205.2236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 26 Tm /F1 10 Tf 12 TL .384987 Tw (However, two enum members are allowed to have the same value. Given two members A and B with the) Tj T* 0 Tw .444772 Tw (same value \(and A defined first\), B is an alias to A. By-value lookup of the value of A and B will return A.) Tj T* 0 Tw (By-name lookup of B will also return A:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 88.02362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 492 108 re B*
+Q
+q
+BT 1 0 0 1 0 86 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Shape\(Enum\):) Tj T* (...   __order__ = 'square diamond circle alias_for_square'  # only needed in 2.x) Tj T* (...   square = 2) Tj T* (...   diamond = 1) Tj T* (...   circle = 3) Tj T* (...   alias_for_square = 2) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( Shape.square) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+ 
+endstream
+endobj
+59 0 obj
+<< /Length 4406 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 691.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (<) Tj (Shape.square: 2) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Shape.alias_for_square) Tj T* (<) Tj (Shape.square: 2) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Shape\(2\)) Tj T* (<) Tj (Shape.square: 2) Tj (>) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 659.8236 cm
+q
+BT 1 0 0 1 0 14 Tm 1.074104 Tw 12 TL /F1 10 Tf 0 0 0 rg (Allowing aliases is not always desirable. ) Tj /F4 10 Tf 0 0 0 rg (unique ) Tj /F1 10 Tf 0 0 0 rg (can be used to ensure that none exist in a particular) Tj T* 0 Tw (enumeration:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 506.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 144 re B*
+Q
+q
+BT 1 0 0 1 0 122 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( from enum import unique) Tj T* (>) Tj (>) Tj (>) Tj ( @unique) Tj T* (... class Mistake\(Enum\):) Tj T* (...   __order__ = 'one two three four'  # only needed in 2.x) Tj T* (...   one = 1) Tj T* (...   two = 2) Tj T* (...   three = 3) Tj T* (...   four = 3) Tj T* (Traceback \(most recent call last\):) Tj T* (...) Tj T* (ValueError: duplicate names found in ) Tj (<) Tj (enum 'Mistake') Tj (>) Tj (: four -) Tj (>) Tj ( three) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 486.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Iterating over the members of an enum does not provide the aliases:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 441.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( list\(Shape\)) Tj T* ([) Tj (<) Tj (Shape.square: 2) Tj (>) Tj (, ) Tj (<) Tj (Shape.diamond: 1) Tj (>) Tj (, ) Tj (<) Tj (Shape.circle: 3) Tj (>) Tj (]) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 409.4236 cm
+q
+BT 1 0 0 1 0 14 Tm 1.307126 Tw 12 TL /F1 10 Tf 0 0 0 rg (The special attribute ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (is a dictionary mapping names to members. It includes all names) Tj T* 0 Tw (defined in the enumeration, including the aliases:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 304.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 96 re B*
+Q
+q
+BT 1 0 0 1 0 74 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( for name, member in sorted\(Shape.__members__.items\(\)\):) Tj T* (...   name, member) Tj T* (...) Tj T* (\('alias_for_square', ) Tj (<) Tj (Shape.square: 2) Tj (>) Tj (\)) Tj T* (\('circle', ) Tj (<) Tj (Shape.circle: 3) Tj (>) Tj (\)) Tj T* (\('diamond', ) Tj (<) Tj (Shape.diamond: 1) Tj (>) Tj (\)) Tj T* (\('square', ) Tj (<) Tj (Shape.square: 2) Tj (>) Tj (\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 272.2236 cm
+q
+BT 1 0 0 1 0 14 Tm .080751 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (attribute can be used for detailed programmatic access to the enumeration members.) Tj T* 0 Tw (For example, finding all the aliases:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 227.0236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 486 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( [name for name, member in Shape.__members__.items\(\) if member.name != name]) Tj T* (['alias_for_square']) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 194.0236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Comparisons) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 176.0236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Enumeration members are compared by identity:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 82.82362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 84 re B*
+Q
+q
+BT 1 0 0 1 0 62 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Color.red is Color.red) Tj T* (True) Tj T* (>) Tj (>) Tj (>) Tj ( Color.red is Color.blue) Tj T* (False) Tj T* (>) Tj (>) Tj (>) Tj ( Color.red is not Color.blue) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+ 
+endstream
+endobj
+60 0 obj
+<< /Length 4521 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 741.0236 cm
+q
+BT 1 0 0 1 0 14 Tm 1.131647 Tw 12 TL /F1 10 Tf 0 0 0 rg (Ordered comparisons between enumeration values are ) Tj /F5 10 Tf (not ) Tj /F1 10 Tf (supported. Enum members are not integers) Tj T* 0 Tw (\(but see ) Tj 0 0 .501961 rg (IntEnum ) Tj 0 0 0 rg (below\):) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 671.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 60 re B*
+Q
+q
+BT 1 0 0 1 0 38 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Color.red ) Tj (<) Tj ( Color.blue) Tj T* (Traceback \(most recent call last\):) Tj T* (  File ") Tj (<) Tj (stdin) Tj (>) Tj (", line 1, in ) Tj (<) Tj (module) Tj (>) Tj  T* (TypeError: unorderable types: Color\(\) ) Tj (<) Tj ( Color\(\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 651.8236 cm
+Q
+q
+1 0 0 1 62.69291 568.8236 cm
+.960784 .960784 .862745 rg
+n 0 83 469.8898 -83 re f*
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 6 57 Tm  T* ET
+q
+1 0 0 1 16 52 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2.5 Tm /F6 12.5 Tf 15 TL (Warning) Tj T* ET
+Q
+Q
+q
+1 0 0 1 16 16 cm
+q
+BT 1 0 0 1 0 14 Tm .189398 Tw 12 TL /F1 10 Tf 0 0 0 rg (In Python 2 ) Tj /F5 10 Tf (everything ) Tj /F1 10 Tf (is ordered, even though the ordering may not make sense. If you want your) Tj T* 0 Tw (enumerations to have a sensible ordering check out the ) Tj 0 0 .501961 rg (OrderedEnum ) Tj 0 0 0 rg (recipe below.) Tj T* ET
+Q
+Q
+q
+1 J
+1 j
+.662745 .662745 .662745 RG
+.5 w
+n 0 83 m 469.8898 83 l S
+n 0 0 m 469.8898 0 l S
+n 0 0 m 0 83 l S
+n 469.8898 0 m 469.8898 83 l S
+Q
+Q
+q
+1 0 0 1 62.69291 562.8236 cm
+Q
+q
+1 0 0 1 62.69291 544.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Equality comparisons are defined though:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 451.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 84 re B*
+Q
+q
+BT 1 0 0 1 0 62 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Color.blue == Color.red) Tj T* (False) Tj T* (>) Tj (>) Tj (>) Tj ( Color.blue != Color.red) Tj T* (True) Tj T* (>) Tj (>) Tj (>) Tj ( Color.blue == Color.blue) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 419.6236 cm
+q
+BT 1 0 0 1 0 14 Tm 2.582706 Tw 12 TL /F1 10 Tf 0 0 0 rg (Comparisons against non-enumeration values will always compare not equal \(again, ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (was) Tj T* 0 Tw (explicitly designed to behave differently, see below\):) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 374.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Color.blue == 2) Tj T* (False) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 341.4236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Allowed members and attributes of enumerations) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 287.4236 cm
+q
+BT 1 0 0 1 0 38 Tm 2.755697 Tw 12 TL /F1 10 Tf 0 0 0 rg (The examples above use integers for enumeration values. Using integers is short and handy \(and) Tj T* 0 Tw .241751 Tw (provided by default by the ) Tj 0 0 .501961 rg (Functional API) Tj 0 0 0 rg (\), but not strictly enforced. In the vast majority of use-cases, one) Tj T* 0 Tw .848221 Tw (doesn't care what the actual value of an enumeration is. But if the value ) Tj /F5 10 Tf (is ) Tj /F1 10 Tf (important, enumerations can) Tj T* 0 Tw (have arbitrary values.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 257.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .638735 Tw (Enumerations are Python classes, and can have methods and special methods as usual. If we have this) Tj T* 0 Tw (enumeration:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 80.22362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 168 re B*
+Q
+q
+BT 1 0 0 1 0 146 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Mood\(Enum\):) Tj T* (...   funky = 1) Tj T* (...   happy = 3) Tj T* (...) Tj T* (...   def describe\(self\):) Tj T* (...     # self is the member here) Tj T* (...     return self.name, self.value) Tj T* (...) Tj T* (...   def __str__\(self\):) Tj T* (...     return 'my custom str! {0}'.format\(self.value\)) Tj T* (...) Tj T* (...   @classmethod) Tj T* (...   def favorite_mood\(cls\):) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+ 
+endstream
+endobj
+61 0 obj
+<< /Length 4627 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 727.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (...     # cls here is the enumeration) Tj T* (...     return cls.happy) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 707.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Then:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 614.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 84 re B*
+Q
+q
+BT 1 0 0 1 0 62 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Mood.favorite_mood\(\)) Tj T* (<) Tj (Mood.happy: 3) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Mood.happy.describe\(\)) Tj T* (\('happy', 3\)) Tj T* (>) Tj (>) Tj (>) Tj ( str\(Mood.funky\)) Tj T* ('my custom str! 1') Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 558.6236 cm
+q
+BT 1 0 0 1 0 38 Tm 3.14186 Tw 12 TL /F1 10 Tf 0 0 0 rg (The rules for what is allowed are as follows: _sunder_ names \(starting and ending with a single) Tj T* 0 Tw .310651 Tw (underscore\) are reserved by enum and cannot be used; all other attributes defined within an enumeration) Tj T* 0 Tw 2.199213 Tw (will become members of this enumeration, with the exception of ) Tj /F5 10 Tf (__dunder__ ) Tj /F1 10 Tf (names and descriptors) Tj T* 0 Tw (\(methods are also descriptors\).) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 540.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Note:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 534.6236 cm
+Q
+q
+1 0 0 1 62.69291 510.6236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm  T* ET
+q
+1 0 0 1 20 0 cm
+q
+BT 1 0 0 1 0 14 Tm .979213 Tw 12 TL /F1 10 Tf 0 0 0 rg (If your enumeration defines ) Tj /F4 10 Tf 0 0 0 rg (__new__ ) Tj /F1 10 Tf 0 0 0 rg (and/or ) Tj /F4 10 Tf 0 0 0 rg (__init__ ) Tj /F1 10 Tf 0 0 0 rg (then whatever value\(s\) were given to the) Tj T* 0 Tw (enum member will be passed into those methods. See ) Tj 0 0 .501961 rg (Planet ) Tj 0 0 0 rg (for an example.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 510.6236 cm
+Q
+q
+1 0 0 1 62.69291 477.6236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Restricted subclassing of enumerations) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 447.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .778735 Tw (Subclassing an enumeration is allowed only if the enumeration does not define any members. So this is) Tj T* 0 Tw (forbidden:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 366.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class MoreColor\(Color\):) Tj T* (...   pink = 17) Tj T* (Traceback \(most recent call last\):) Tj T* (...) Tj T* (TypeError: Cannot extend enumerations) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 346.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (But this is allowed:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 229.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 108 re B*
+Q
+q
+BT 1 0 0 1 0 86 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Foo\(Enum\):) Tj T* (...   def some_behavior\(self\):) Tj T* (...     pass) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( class Bar\(Foo\):) Tj T* (...   happy = 1) Tj T* (...   sad = 2) Tj T* (...) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 185.2236 cm
+q
+BT 1 0 0 1 0 26 Tm .127984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Allowing subclassing of enums that define members would lead to a violation of some important invariants) Tj T* 0 Tw 1.889985 Tw (of types and instances. On the other hand, it makes sense to allow sharing some common behavior) Tj T* 0 Tw (between a group of enumerations. \(See ) Tj 0 0 .501961 rg (OrderedEnum ) Tj 0 0 0 rg (for an example.\)) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 152.2236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Pickling) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 134.2236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Enumerations can be pickled and unpickled:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 89.02362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( from enum.test_enum import Fruit) Tj T* (>) Tj (>) Tj (>) Tj ( from pickle import dumps, loads) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+ 
+endstream
+endobj
+62 0 obj
+<< /Length 5372 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 727.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+BT 1 0 0 1 0 14 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Fruit.tomato is loads\(dumps\(Fruit.tomato, 2\)\)) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 695.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.256457 Tw (The usual restrictions for pickling apply: picklable enums must be defined in the top level of a module,) Tj T* 0 Tw (since unpickling requires them to be importable from that module.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 677.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Note:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 671.8236 cm
+Q
+q
+1 0 0 1 62.69291 647.8236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm  T* ET
+q
+1 0 0 1 20 0 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .081163 Tw (With pickle protocol version 4 \(introduced in Python 3.4\) it is possible to easily pickle enums nested in) Tj T* 0 Tw (other classes.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 647.8236 cm
+Q
+q
+1 0 0 1 62.69291 614.8236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Functional API) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 596.8236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (class is callable, providing the following functional API:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 467.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 120 re B*
+Q
+q
+BT 1 0 0 1 0 98 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Animal = Enum\('Animal', 'ant bee cat dog'\)) Tj T* (>) Tj (>) Tj (>) Tj ( Animal) Tj T* (<) Tj (enum 'Animal') Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Animal.ant) Tj T* (<) Tj (Animal.ant: 1) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( Animal.ant.value) Tj T* (1) Tj T* (>) Tj (>) Tj (>) Tj ( list\(Animal\)) Tj T* ([) Tj (<) Tj (Animal.ant: 1) Tj (>) Tj (, ) Tj (<) Tj (Animal.bee: 2) Tj (>) Tj (, ) Tj (<) Tj (Animal.cat: 3) Tj (>) Tj (, ) Tj (<) Tj (Animal.dog: 4) Tj (>) Tj (]) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 435.6236 cm
+q
+BT 1 0 0 1 0 14 Tm .602209 Tw 12 TL /F1 10 Tf 0 0 0 rg (The semantics of this API resemble ) Tj /F4 10 Tf 0 0 0 rg (namedtuple) Tj /F1 10 Tf 0 0 0 rg (. The first argument of the call to ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (is the name of) Tj T* 0 Tw (the enumeration.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 369.6236 cm
+q
+BT 1 0 0 1 0 50 Tm 1.326412 Tw 12 TL /F1 10 Tf 0 0 0 rg (The second argument is the ) Tj /F5 10 Tf (source ) Tj /F1 10 Tf (of enumeration member names. It can be a whitespace-separated) Tj T* 0 Tw .993516 Tw (string of names, a sequence of names, a sequence of 2-tuples with key/value pairs, or a mapping \(e.g.) Tj T* 0 Tw 1.168555 Tw (dictionary\) of names to values. The last two options enable assigning arbitrary values to enumerations;) Tj T* 0 Tw .537485 Tw (the others auto-assign increasing integers starting with 1. A new class derived from ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (is returned. In) Tj T* 0 Tw (other words, the above assignment to ) Tj /F4 10 Tf 0 0 0 rg (Animal ) Tj /F1 10 Tf 0 0 0 rg (is equivalent to:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 288.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Animals\(Enum\):) Tj T* (...   ant = 1) Tj T* (...   bee = 2) Tj T* (...   cat = 3) Tj T* (...   dog = 4) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 232.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 38 Tm /F1 10 Tf 12 TL 1.239984 Tw (Pickling enums created with the functional API can be tricky as frame stack implementation details are) Tj T* 0 Tw .937132 Tw (used to try and figure out which module the enumeration is being created in \(e.g. it will fail if you use a) Tj T* 0 Tw 1.321163 Tw (utility function in separate module, and also may not work on IronPython or Jython\). The solution is to) Tj T* 0 Tw (specify the module name explicitly as follows:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 199.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 24 re B*
+Q
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( Animals = Enum\('Animals', 'ant bee cat dog', module=__name__\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 166.2236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Derived Enumerations) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 136.2236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (IntEnum) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 94.22362 cm
+q
+BT 1 0 0 1 0 26 Tm 1.99832 Tw 12 TL /F1 10 Tf 0 0 0 rg (A variation of ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (is provided which is also a subclass of ) Tj /F4 10 Tf 0 0 0 rg (int) Tj /F1 10 Tf 0 0 0 rg (. Members of an ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (can be) Tj T* 0 Tw .087984 Tw (compared to integers; by extension, integer enumerations of different types can also be compared to each) Tj T* 0 Tw (other:) Tj T* ET
+Q
+Q
+ 
+endstream
+endobj
+63 0 obj
+<< /Length 4141 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 571.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 192 re B*
+Q
+q
+BT 1 0 0 1 0 170 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( from enum import IntEnum) Tj T* (>) Tj (>) Tj (>) Tj ( class Shape\(IntEnum\):) Tj T* (...   circle = 1) Tj T* (...   square = 2) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( class Request\(IntEnum\):) Tj T* (...   post = 1) Tj T* (...   get = 2) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( Shape == 1) Tj T* (False) Tj T* (>) Tj (>) Tj (>) Tj ( Shape.circle == 1) Tj T* (True) Tj T* (>) Tj (>) Tj (>) Tj ( Shape.circle == Request.post) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 551.8236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (However, they still can't be compared to standard ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (enumerations:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 410.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 132 re B*
+Q
+q
+BT 1 0 0 1 0 110 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Shape\(IntEnum\):) Tj T* (...   circle = 1) Tj T* (...   square = 2) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( class Color\(Enum\):) Tj T* (...   red = 1) Tj T* (...   green = 2) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( Shape.circle == Color.red) Tj T* (False) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 390.6236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (values behave like integers in other ways you'd expect:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 297.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 84 re B*
+Q
+q
+BT 1 0 0 1 0 62 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( int\(Shape.circle\)) Tj T* (1) Tj T* (>) Tj (>) Tj (>) Tj ( ['a', 'b', 'c'][Shape.circle]) Tj T* ('b') Tj T* (>) Tj (>) Tj (>) Tj ( [i for i in range\(Shape.square\)]) Tj T* ([0, 1]) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 229.4236 cm
+q
+BT 1 0 0 1 0 50 Tm 1.197126 Tw 12 TL /F1 10 Tf 0 0 0 rg (For the vast majority of code, ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (is strongly recommended, since ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (breaks some semantic) Tj T* 0 Tw .793318 Tw (promises of an enumeration \(by being comparable to integers, and thus by transitivity to other unrelated) Tj T* 0 Tw .554985 Tw (enumerations\). It should be used only in special cases where there's no other choice; for example, when) Tj T* 0 Tw .746136 Tw (integer constants are replaced with enumerations and backwards compatibility is required with code that) Tj T* 0 Tw (still expects integers.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 199.4236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (Others) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 181.4236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (While ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (is part of the ) Tj /F4 10 Tf 0 0 0 rg (enum ) Tj /F1 10 Tf 0 0 0 rg (module, it would be very simple to implement independently:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 136.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F4 10 Tf 12 TL (class IntEnum\(int, Enum\):) Tj T* (    pass) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 104.2236 cm
+q
+BT 1 0 0 1 0 14 Tm .361412 Tw 12 TL /F1 10 Tf 0 0 0 rg (This demonstrates how similar derived enumerations can be defined; for example a ) Tj /F4 10 Tf 0 0 0 rg (StrEnum ) Tj /F1 10 Tf 0 0 0 rg (that mixes) Tj T* 0 Tw (in ) Tj /F4 10 Tf 0 0 0 rg (str ) Tj /F1 10 Tf 0 0 0 rg (instead of ) Tj /F4 10 Tf 0 0 0 rg (int) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 86.22362 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Some rules:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 80.22362 cm
+Q
+q
+1 0 0 1 62.69291 80.22362 cm
+Q
+ 
+endstream
+endobj
+64 0 obj
+<< /Length 7108 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 741.0236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 9 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 14 Tm .477318 Tw 12 TL /F1 10 Tf 0 0 0 rg (When subclassing ) Tj /F4 10 Tf 0 0 0 rg (Enum) Tj /F1 10 Tf 0 0 0 rg (, mix-in types must appear before ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (itself in the sequence of bases, as) Tj T* 0 Tw (in the ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (example above.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 735.0236 cm
+Q
+q
+1 0 0 1 62.69291 699.0236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 21 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 26 Tm 1.147045 Tw 12 TL /F1 10 Tf 0 0 0 rg (While ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (can have members of any type, once you mix in an additional type, all the members) Tj T* 0 Tw .420574 Tw (must have values of that type, e.g. ) Tj /F4 10 Tf 0 0 0 rg (int ) Tj /F1 10 Tf 0 0 0 rg (above. This restriction does not apply to mix-ins which only) Tj T* 0 Tw (add methods and don't specify another data type such as ) Tj /F4 10 Tf 0 0 0 rg (int ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F4 10 Tf 0 0 0 rg (str) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 693.0236 cm
+Q
+q
+1 0 0 1 62.69291 669.0236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 9 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 14 Tm .100542 Tw 12 TL /F1 10 Tf 0 0 0 rg (When another data type is mixed in, the ) Tj /F4 10 Tf 0 0 0 rg (value ) Tj /F1 10 Tf 0 0 0 rg (attribute is ) Tj /F5 10 Tf (not the same ) Tj /F1 10 Tf (as the enum member itself,) Tj T* 0 Tw (although it is equivalant and will compare equal.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 663.0236 cm
+Q
+q
+1 0 0 1 62.69291 609.0236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 39 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (4.) Tj T* -5.66 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 27 cm
+q
+BT 1 0 0 1 0 14 Tm 1.85998 Tw 12 TL /F1 10 Tf 0 0 0 rg (%-style formatting: ) Tj /F4 10 Tf 0 0 0 rg (%s ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F4 10 Tf 0 0 0 rg (%r ) Tj /F1 10 Tf 0 0 0 rg (call ) Tj /F4 10 Tf 0 0 0 rg (Enum) Tj /F1 10 Tf 0 0 0 rg ('s ) Tj /F4 10 Tf 0 0 0 rg (__str__ ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F4 10 Tf 0 0 0 rg (__repr__ ) Tj /F1 10 Tf 0 0 0 rg (respectively; other codes) Tj T* 0 Tw (\(such as ) Tj /F4 10 Tf 0 0 0 rg (%i ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F4 10 Tf 0 0 0 rg (%h ) Tj /F1 10 Tf 0 0 0 rg (for IntEnum\) treat the enum member as its mixed-in type.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 14 Tm .067045 Tw 12 TL /F1 10 Tf 0 0 0 rg (Note: Prior to Python 3.4 there is a bug in ) Tj /F4 10 Tf 0 0 0 rg (str) Tj /F1 10 Tf 0 0 0 rg ('s %-formatting: ) Tj /F4 10 Tf 0 0 0 rg (int ) Tj /F1 10 Tf 0 0 0 rg (subclasses are printed as strings) Tj T* 0 Tw (and not numbers when the ) Tj /F4 10 Tf 0 0 0 rg (%d) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F4 10 Tf 0 0 0 rg (%i) Tj /F1 10 Tf 0 0 0 rg (, or ) Tj /F4 10 Tf 0 0 0 rg (%u ) Tj /F1 10 Tf 0 0 0 rg (codes are used.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 603.0236 cm
+Q
+q
+1 0 0 1 62.69291 579.0236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+q
+1 0 0 1 6 9 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (5.) Tj T* -5.66 0 Td ET
+Q
+Q
+q
+1 0 0 1 23 -3 cm
+q
+BT 1 0 0 1 0 14 Tm 1.880751 Tw 12 TL /F4 10 Tf 0 0 0 rg (str.__format__ ) Tj /F1 10 Tf 0 0 0 rg (\(or ) Tj /F4 10 Tf 0 0 0 rg (format) Tj /F1 10 Tf 0 0 0 rg (\) will use the mixed-in type's ) Tj /F4 10 Tf 0 0 0 rg (__format__) Tj /F1 10 Tf 0 0 0 rg (. If the ) Tj /F4 10 Tf 0 0 0 rg (Enum) Tj /F1 10 Tf 0 0 0 rg ('s ) Tj /F4 10 Tf 0 0 0 rg (str ) Tj /F1 10 Tf 0 0 0 rg (or) Tj T* 0 Tw /F4 10 Tf 0 0 0 rg (repr ) Tj /F1 10 Tf 0 0 0 rg (is desired use the ) Tj /F4 10 Tf 0 0 0 rg (!s ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F4 10 Tf 0 0 0 rg (!r) Tj /F1 10 Tf 0 0 0 rg ( ) Tj /F4 10 Tf 0 0 0 rg (str ) Tj /F1 10 Tf 0 0 0 rg (format codes.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 579.0236 cm
+Q
+q
+1 0 0 1 62.69291 546.0236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Decorators) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 516.0236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (unique) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 486.0236 cm
+q
+BT 1 0 0 1 0 14 Tm .287251 Tw 12 TL /F1 10 Tf 0 0 0 rg (A ) Tj /F4 10 Tf 0 0 0 rg (class ) Tj /F1 10 Tf 0 0 0 rg (decorator specifically for enumerations. It searches an enumeration's ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (gathering) Tj T* 0 Tw (any aliases it finds; if any are found ) Tj /F4 10 Tf 0 0 0 rg (ValueError ) Tj /F1 10 Tf 0 0 0 rg (is raised with the details:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 368.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 108 re B*
+Q
+q
+BT 1 0 0 1 0 86 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( @unique) Tj T* (... class NoDupes\(Enum\):) Tj T* (...    first = 'one') Tj T* (...    second = 'two') Tj T* (...    third = 'two') Tj T* (Traceback \(most recent call last\):) Tj T* (...) Tj T* (ValueError: duplicate names found in ) Tj (<) Tj (enum 'NoDupes') Tj (>) Tj (: third -) Tj (>) Tj ( second) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 335.8236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (Interesting examples) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 293.8236 cm
+q
+BT 1 0 0 1 0 26 Tm .593735 Tw 12 TL /F1 10 Tf 0 0 0 rg (While ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (and ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (are expected to cover the majority of use-cases, they cannot cover them all.) Tj T* 0 Tw .897045 Tw (Here are recipes for some different types of enumerations that can be used directly, or as examples for) Tj T* 0 Tw (creating one's own.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 263.8236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (AutoNumber) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 245.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Avoids having to specify the value for each enumeration member:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 80.62362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 156 re B*
+Q
+q
+BT 1 0 0 1 0 134 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class AutoNumber\(Enum\):) Tj T* (...     def __new__\(cls\):) Tj T* (...         value = len\(cls.__members__\) + 1) Tj T* (...         obj = object.__new__\(cls\)) Tj T* (...         obj._value_ = value) Tj T* (...         return obj) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( class Color\(AutoNumber\):) Tj T* (...     __order__ = "red green blue"  # only needed in 2.x) Tj T* (...     red = \(\)) Tj T* (...     green = \(\)) Tj T* (...     blue = \(\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+ 
+endstream
+endobj
+65 0 obj
+<< /Length 4158 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 715.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 48 re B*
+Q
+q
+BT 1 0 0 1 0 26 Tm 12 TL /F4 10 Tf 0 0 0 rg (...) Tj T* (>) Tj (>) Tj (>) Tj ( Color.green.value == 2) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 695.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Note:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 689.8236 cm
+Q
+q
+1 0 0 1 62.69291 653.8236 cm
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm  T* ET
+q
+1 0 0 1 20 0 cm
+q
+BT 1 0 0 1 0 26 Tm .144104 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F5 10 Tf 0 0 0 rg (__new__ ) Tj /F1 10 Tf 0 0 0 rg (method, if defined, is used during creation of the Enum members; it is then replaced by) Tj T* 0 Tw .799985 Tw (Enum's ) Tj /F5 10 Tf 0 0 0 rg (__new__ ) Tj /F1 10 Tf 0 0 0 rg (which is used after class creation for lookup of existing members. Due to the way) Tj T* 0 Tw (Enums are supposed to behave, there is no way to customize Enum's ) Tj /F5 10 Tf 0 0 0 rg (__new__) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 653.8236 cm
+Q
+q
+1 0 0 1 62.69291 623.8236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (UniqueEnum) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 605.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Raises an error if a duplicate member name is found instead of creating an alias:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 368.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 228 re B*
+Q
+q
+BT 1 0 0 1 0 206 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class UniqueEnum\(Enum\):) Tj T* (...     def __init__\(self, *args\):) Tj T* (...         cls = self.__class__) Tj T* (...         if any\(self.value == e.value for e in cls\):) Tj T* (...             a = self.name) Tj T* (...             e = cls\(self.value\).name) Tj T* (...             raise ValueError\() Tj T* (...                     "aliases not allowed in UniqueEnum:  %r --) Tj (>) Tj ( %r") Tj T* (...                     % \(a, e\)\)) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( class Color\(UniqueEnum\):) Tj T* (...     red = 1) Tj T* (...     green = 2) Tj T* (...     blue = 3) Tj T* (...     grene = 2) Tj T* (Traceback \(most recent call last\):) Tj T* (...) Tj T* (ValueError: aliases not allowed in UniqueEnum:  'grene' --) Tj (>) Tj ( 'green') Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 338.6236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (OrderedEnum) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 308.6236 cm
+q
+BT 1 0 0 1 0 14 Tm 1.335984 Tw 12 TL /F1 10 Tf 0 0 0 rg (An ordered enumeration that is not based on ) Tj /F4 10 Tf 0 0 0 rg (IntEnum ) Tj /F1 10 Tf 0 0 0 rg (and so maintains the normal ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (invariants) Tj T* 0 Tw (\(such as not being comparable to other enumerations\):) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 83.42362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 216 re B*
+Q
+q
+BT 1 0 0 1 0 194 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class OrderedEnum\(Enum\):) Tj T* (...     def __ge__\(self, other\):) Tj T* (...         if self.__class__ is other.__class__:) Tj T* (...             return self._value_ ) Tj (>) Tj (= other._value_) Tj T* (...         return NotImplemented) Tj T* (...     def __gt__\(self, other\):) Tj T* (...         if self.__class__ is other.__class__:) Tj T* (...             return self._value_ ) Tj (>) Tj ( other._value_) Tj T* (...         return NotImplemented) Tj T* (...     def __le__\(self, other\):) Tj T* (...         if self.__class__ is other.__class__:) Tj T* (...             return self._value_ ) Tj (<) Tj (= other._value_) Tj T* (...         return NotImplemented) Tj T* (...     def __lt__\(self, other\):) Tj T* (...         if self.__class__ is other.__class__:) Tj T* (...             return self._value_ ) Tj (<) Tj ( other._value_) Tj T* (...         return NotImplemented) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+ 
+endstream
+endobj
+66 0 obj
+<< /Length 4039 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 619.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 144 re B*
+Q
+q
+BT 1 0 0 1 0 122 Tm 12 TL /F4 10 Tf 0 0 0 rg (...) Tj T* (>) Tj (>) Tj (>) Tj ( class Grade\(OrderedEnum\):) Tj T* (...     __ordered__ = 'A B C D F') Tj T* (...     A = 5) Tj T* (...     B = 4) Tj T* (...     C = 3) Tj T* (...     D = 2) Tj T* (...     F = 1) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( Grade.C ) Tj (<) Tj ( Grade.A) Tj T* (True) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 589.8236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (Planet) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 571.8236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If ) Tj /F4 10 Tf 0 0 0 rg (__new__ ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F4 10 Tf 0 0 0 rg (__init__ ) Tj /F1 10 Tf 0 0 0 rg (is defined the value of the enum member will be passed to those methods:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 286.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 276 re B*
+Q
+q
+BT 1 0 0 1 0 254 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class Planet\(Enum\):) Tj T* (...     MERCURY = \(3.303e+23, 2.4397e6\)) Tj T* (...     VENUS   = \(4.869e+24, 6.0518e6\)) Tj T* (...     EARTH   = \(5.976e+24, 6.37814e6\)) Tj T* (...     MARS    = \(6.421e+23, 3.3972e6\)) Tj T* (...     JUPITER = \(1.9e+27,   7.1492e7\)) Tj T* (...     SATURN  = \(5.688e+26, 6.0268e7\)) Tj T* (...     URANUS  = \(8.686e+25, 2.5559e7\)) Tj T* (...     NEPTUNE = \(1.024e+26, 2.4746e7\)) Tj T* (...     def __init__\(self, mass, radius\):) Tj T* (...         self.mass = mass       # in kilograms) Tj T* (...         self.radius = radius   # in meters) Tj T* (...     @property) Tj T* (...     def surface_gravity\(self\):) Tj T* (...         # universal gravitational constant  \(m3 kg-1 s-2\)) Tj T* (...         G = 6.67300E-11) Tj T* (...         return G * self.mass / \(self.radius * self.radius\)) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( Planet.EARTH.value) Tj T* (\(5.976e+24, 6378140.0\)) Tj T* (>) Tj (>) Tj (>) Tj ( Planet.EARTH.surface_gravity) Tj T* (9.802652743337129) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 253.6236 cm
+q
+BT 1 0 0 1 0 3.5 Tm 21 TL /F3 17.5 Tf 0 0 0 rg (How are Enums different?) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 223.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 2.090651 Tw (Enums have a custom metaclass that affects many aspects of both derived Enum classes and their) Tj T* 0 Tw (instances \(members\).) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 193.6236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (Enum Classes) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 127.6236 cm
+q
+BT 1 0 0 1 0 50 Tm 1.263615 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf 0 0 0 rg (EnumMeta ) Tj /F1 10 Tf 0 0 0 rg (metaclass is responsible for providing the ) Tj /F4 10 Tf 0 0 0 rg (__contains__) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F4 10 Tf 0 0 0 rg (__dir__) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F4 10 Tf 0 0 0 rg (__iter__ ) Tj /F1 10 Tf 0 0 0 rg (and) Tj T* 0 Tw 2.264724 Tw (other methods that allow one to do things with an ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (class that fail on a typical class, such as) Tj T* 0 Tw 2.594147 Tw /F4 10 Tf 0 0 0 rg (list\(Color\) ) Tj /F1 10 Tf 0 0 0 rg (or ) Tj /F4 10 Tf 0 0 0 rg (some_var) Tj ( ) Tj (in) Tj ( ) Tj (Color) Tj /F1 10 Tf 0 0 0 rg (. ) Tj /F4 10 Tf 0 0 0 rg (EnumMeta ) Tj /F1 10 Tf 0 0 0 rg (is responsible for ensuring that various other) Tj T* 0 Tw 2.196905 Tw (methods on the final ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (class are correct \(such as ) Tj /F4 10 Tf 0 0 0 rg (__new__) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F4 10 Tf 0 0 0 rg (__getnewargs__) Tj /F1 10 Tf 0 0 0 rg (, ) Tj /F4 10 Tf 0 0 0 rg (__str__ ) Tj /F1 10 Tf 0 0 0 rg (and) Tj T* 0 Tw /F4 10 Tf 0 0 0 rg (__repr__) Tj /F1 10 Tf 0 0 0 rg (\).) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 115.6236 cm
+Q
+ 
+endstream
+endobj
+67 0 obj
+<< /Length 5453 >>
+stream
+1 0 0 1 0 0 cm  BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 682.0236 cm
+.960784 .960784 .862745 rg
+n 0 83 469.8898 -83 re f*
+0 0 0 rg
+BT /F1 10 Tf 12 TL ET
+BT 1 0 0 1 6 57 Tm  T* ET
+q
+1 0 0 1 16 52 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 2.5 Tm /F6 12.5 Tf 15 TL (Note) Tj T* ET
+Q
+Q
+q
+1 0 0 1 16 16 cm
+q
+BT 1 0 0 1 0 14 Tm .686654 Tw 12 TL /F4 10 Tf 0 0 0 rg (__dir__ ) Tj /F1 10 Tf 0 0 0 rg (is not changed in the Python 2 line as it messes up some of the decorators included in) Tj T* 0 Tw (the stdlib.) Tj T* ET
+Q
+Q
+q
+1 J
+1 j
+.662745 .662745 .662745 RG
+.5 w
+n 0 83 m 469.8898 83 l S
+n 0 0 m 469.8898 0 l S
+n 0 0 m 0 83 l S
+n 469.8898 0 m 469.8898 83 l S
+Q
+Q
+q
+1 0 0 1 62.69291 676.0236 cm
+Q
+q
+1 0 0 1 62.69291 646.0236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (Enum Members \(aka instances\)) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 604.0236 cm
+q
+BT 1 0 0 1 0 26 Tm .491984 Tw 12 TL /F1 10 Tf 0 0 0 rg (The most interesting thing about Enum members is that they are singletons. ) Tj /F4 10 Tf 0 0 0 rg (EnumMeta ) Tj /F1 10 Tf 0 0 0 rg (creates them all) Tj T* 0 Tw .084988 Tw (while it is creating the ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (class itself, and then puts a custom ) Tj /F4 10 Tf 0 0 0 rg (__new__ ) Tj /F1 10 Tf 0 0 0 rg (in place to ensure that no new) Tj T* 0 Tw (ones are ever instantiated by returning only the existing member instances.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 574.0236 cm
+q
+BT 1 0 0 1 0 3 Tm 18 TL /F3 15 Tf 0 0 0 rg (Finer Points) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 520.0236 cm
+q
+BT 1 0 0 1 0 38 Tm 5.488555 Tw 12 TL /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (members are instances of an ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (class, and even though they are accessible as) Tj T* 0 Tw 1.504147 Tw /F5 10 Tf 0 0 0 rg (EnumClass.member1.member2) Tj /F1 10 Tf 0 0 0 rg (, they should not be accessed directly from the member as that lookup) Tj T* 0 Tw .329985 Tw (may fail or, worse, return something besides the ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (member you were looking for \(changed in version) Tj T* 0 Tw (1.1.1\):) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 390.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 120 re B*
+Q
+q
+BT 1 0 0 1 0 98 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( class FieldTypes\(Enum\):) Tj T* (...     name = 1) Tj T* (...     value = 2) Tj T* (...     size = 3) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( FieldTypes.value.size) Tj T* (<) Tj (FieldTypes.size: 3) Tj (>) Tj  T* (>) Tj (>) Tj (>) Tj ( FieldTypes.size.value) Tj T* (3) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 370.8236 cm
+q
+BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (attribute is only available on the class.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 316.8236 cm
+q
+BT 1 0 0 1 0 38 Tm 1.374651 Tw 12 TL /F1 10 Tf 0 0 0 rg (In Python 3.x ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (is always an ) Tj /F4 10 Tf 0 0 0 rg (OrderedDict) Tj /F1 10 Tf 0 0 0 rg (, with the order being the definition order. In) Tj T* 0 Tw 3.009213 Tw (Python 2.7 ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (is an ) Tj /F4 10 Tf 0 0 0 rg (OrderedDict ) Tj /F1 10 Tf 0 0 0 rg (if ) Tj /F4 10 Tf 0 0 0 rg (__order__ ) Tj /F1 10 Tf 0 0 0 rg (was specified, and a plain ) Tj /F4 10 Tf 0 0 0 rg (dict) Tj T* 0 Tw 1.851318 Tw /F1 10 Tf 0 0 0 rg (otherwise. In all other Python 2.x versions ) Tj /F4 10 Tf 0 0 0 rg (__members__ ) Tj /F1 10 Tf 0 0 0 rg (is a plain ) Tj /F4 10 Tf 0 0 0 rg (dict ) Tj /F1 10 Tf 0 0 0 rg (even if ) Tj /F4 10 Tf 0 0 0 rg (__order__ ) Tj /F1 10 Tf 0 0 0 rg (was) Tj T* 0 Tw (specified as the ) Tj /F4 10 Tf 0 0 0 rg (OrderedDict ) Tj /F1 10 Tf 0 0 0 rg (type didn't exist yet.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 286.8236 cm
+q
+BT 1 0 0 1 0 14 Tm .106654 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you give your ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (subclass extra methods, like the ) Tj 0 0 .501961 rg (Planet ) Tj 0 0 0 rg (class above, those methods will show up in) Tj T* 0 Tw (a ) Tj /F5 10 Tf 0 0 0 rg (dir ) Tj /F1 10 Tf 0 0 0 rg (of the member, but not of the class:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 205.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+BT 1 0 0 1 0 50 Tm 12 TL /F4 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( dir\(Planet\)) Tj T* (['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS',) Tj T* ('VENUS', '__class__', '__doc__', '__members__', '__module__']) Tj T* (>) Tj (>) Tj (>) Tj ( dir\(Planet.EARTH\)) Tj T* (['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 161.6236 cm
+q
+BT 1 0 0 1 0 26 Tm .938935 Tw 12 TL /F1 10 Tf 0 0 0 rg (A ) Tj /F4 10 Tf 0 0 0 rg (__new__ ) Tj /F1 10 Tf 0 0 0 rg (method will only be used for the creation of the ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (members -- after that it is replaced.) Tj T* 0 Tw .949461 Tw (This means if you wish to change how ) Tj /F4 10 Tf 0 0 0 rg (Enum ) Tj /F1 10 Tf 0 0 0 rg (members are looked up you either have to write a helper) Tj T* 0 Tw (function or a ) Tj /F4 10 Tf 0 0 0 rg (classmethod) Tj /F1 10 Tf 0 0 0 rg (.) Tj T* ET
+Q
+Q
+ 
+endstream
+endobj
+68 0 obj
+<< /Nums [ 0 69 0 R 1 70 0 R 2 71 0 R 3 72 0 R 4 73 0 R 
+  5 74 0 R 6 75 0 R 7 76 0 R 8 77 0 R 9 78 0 R 
+  10 79 0 R 11 80 0 R ] >>
+endobj
+69 0 obj
+<< /S /D /St 1 >>
+endobj
+70 0 obj
+<< /S /D /St 2 >>
+endobj
+71 0 obj
+<< /S /D /St 3 >>
+endobj
+72 0 obj
+<< /S /D /St 4 >>
+endobj
+73 0 obj
+<< /S /D /St 5 >>
+endobj
+74 0 obj
+<< /S /D /St 6 >>
+endobj
+75 0 obj
+<< /S /D /St 7 >>
+endobj
+76 0 obj
+<< /S /D /St 8 >>
+endobj
+77 0 obj
+<< /S /D /St 9 >>
+endobj
+78 0 obj
+<< /S /D /St 10 >>
+endobj
+79 0 obj
+<< /S /D /St 11 >>
+endobj
+80 0 obj
+<< /S /D /St 12 >>
+endobj
+xref
+0 81
+0000000000 65535 f
+0000000075 00000 n
+0000000160 00000 n
+0000000270 00000 n
+0000000383 00000 n
+0000000498 00000 n
+0000000606 00000 n
+0000000777 00000 n
+0000000948 00000 n
+0000001066 00000 n
+0000001237 00000 n
+0000001477 00000 n
+0000001687 00000 n
+0000001897 00000 n
+0000002107 00000 n
+0000002279 00000 n
+0000002402 00000 n
+0000002574 00000 n
+0000002746 00000 n
+0000002989 00000 n
+0000003161 00000 n
+0000003333 00000 n
+0000003569 00000 n
+0000003779 00000 n
+0000003989 00000 n
+0000004199 00000 n
+0000004409 00000 n
+0000004619 00000 n
+0000004791 00000 n
+0000005020 00000 n
+0000005129 00000 n
+0000005373 00000 n
+0000005451 00000 n
+0000005571 00000 n
+0000005705 00000 n
+0000005886 00000 n
+0000006039 00000 n
+0000006168 00000 n
+0000006332 00000 n
+0000006488 00000 n
+0000006614 00000 n
+0000006746 00000 n
+0000006924 00000 n
+0000007036 00000 n
+0000007147 00000 n
+0000007315 00000 n
+0000007413 00000 n
+0000007591 00000 n
+0000007706 00000 n
+0000007834 00000 n
+0000007963 00000 n
+0000008074 00000 n
+0000008243 00000 n
+0000008360 00000 n
+0000008508 00000 n
+0000008625 00000 n
+0000008771 00000 n
+0000015286 00000 n
+0000019517 00000 n
+0000023365 00000 n
+0000027828 00000 n
+0000032406 00000 n
+0000037090 00000 n
+0000042519 00000 n
+0000046717 00000 n
+0000053882 00000 n
+0000058097 00000 n
+0000062193 00000 n
+0000067703 00000 n
+0000067856 00000 n
+0000067893 00000 n
+0000067930 00000 n
+0000067967 00000 n
+0000068004 00000 n
+0000068041 00000 n
+0000068078 00000 n
+0000068115 00000 n
+0000068152 00000 n
+0000068189 00000 n
+0000068227 00000 n
+0000068265 00000 n
+trailer
+<< /ID 
+ % ReportLab generated PDF document -- digest (http://www.reportlab.com)
+ [(<}|~gm\352\320\235=\001p\220v\224\336) (<}|~gm\352\320\235=\001p\220v\224\336)]
+ /Info 30 0 R /Root 29 0 R /Size 81 >>
+startxref
+68303
+%%EOF
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/enum/doc/enum.rst
@@ -0,0 +1,735 @@
+``enum`` --- support for enumerations
+========================================
+
+.. :synopsis: enumerations are sets of symbolic names bound to unique, constant
+  values.
+.. :moduleauthor:: Ethan Furman <ethan@stoneleaf.us>
+.. :sectionauthor:: Barry Warsaw <barry@python.org>,
+.. :sectionauthor:: Eli Bendersky <eliben@gmail.com>,
+.. :sectionauthor:: Ethan Furman <ethan@stoneleaf.us>
+
+----------------
+
+An enumeration 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.
+
+
+Module Contents
+---------------
+
+This module defines two enumeration classes that can be used to define unique
+sets of names and values: ``Enum`` and ``IntEnum``.  It also defines
+one decorator, ``unique``.
+
+``Enum``
+
+Base class for creating enumerated constants.  See section `Functional API`_
+for an alternate construction syntax.
+
+``IntEnum``
+
+Base class for creating enumerated constants that are also subclasses of ``int``.
+
+``unique``
+
+Enum class decorator that ensures only one name is bound to any one value.
+
+
+Creating an Enum
+----------------
+
+Enumerations are created using the ``class`` syntax, which makes them
+easy to read and write.  An alternative creation method is described in
+`Functional API`_.  To define an enumeration, subclass ``Enum`` as
+follows::
+
+    >>> from enum import Enum
+    >>> class Color(Enum):
+    ...     red = 1
+    ...     green = 2
+    ...     blue = 3
+
+Note: Nomenclature
+
+  - The class ``Color`` is an *enumeration* (or *enum*)
+  - The attributes ``Color.red``, ``Color.green``, etc., are
+    *enumeration members* (or *enum members*).
+  - The enum members have *names* and *values* (the name of
+    ``Color.red`` is ``red``, the value of ``Color.blue`` is
+    ``3``, etc.)
+    
+Note:
+
+    Even though we use the ``class`` syntax to create Enums, Enums
+    are not normal Python classes.  See `How are Enums different?`_ for
+    more details.
+
+Enumeration members have human readable string representations::
+
+    >>> print(Color.red)
+    Color.red
+
+...while their ``repr`` has more information::
+
+    >>> print(repr(Color.red))
+    <Color.red: 1>
+
+The *type* of an enumeration member is the enumeration it belongs to::
+
+    >>> type(Color.red)
+    <enum 'Color'>
+    >>> isinstance(Color.green, Color)
+    True
+    >>>
+
+Enum members also have a property that contains just their item name::
+
+    >>> print(Color.red.name)
+    red
+
+Enumerations support iteration.  In Python 3.x definition order is used; in
+Python 2.x the definition order is not available, but class attribute
+``__order__`` is supported;  otherwise, value order is used::
+
+    >>> class Shake(Enum):
+    ...   __order__ = 'vanilla chocolate cookies mint'  # only needed in 2.x
+    ...   vanilla = 7
+    ...   chocolate = 4
+    ...   cookies = 9
+    ...   mint = 3
+    ...
+    >>> for shake in Shake:
+    ...   print(shake)
+    ...
+    Shake.vanilla
+    Shake.chocolate
+    Shake.cookies
+    Shake.mint
+
+The ``__order__`` attribute is always removed, and in 3.x it is also ignored
+(order is definition order); however, in the stdlib version it will be ignored
+but not removed.
+
+Enumeration members are hashable, so they can be used in dictionaries and sets::
+
+    >>> apples = {}
+    >>> apples[Color.red] = 'red delicious'
+    >>> apples[Color.green] = 'granny smith'
+    >>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'}
+    True
+
+
+Programmatic access to enumeration members and their attributes
+---------------------------------------------------------------
+
+Sometimes it's useful to access members in enumerations programmatically (i.e.
+situations where ``Color.red`` won't do because the exact color is not known
+at program-writing time).  ``Enum`` allows such access::
+
+    >>> Color(1)
+    <Color.red: 1>
+    >>> Color(3)
+    <Color.blue: 3>
+
+If you want to access enum members by *name*, use item access::
+
+    >>> Color['red']
+    <Color.red: 1>
+    >>> Color['green']
+    <Color.green: 2>
+
+If have an enum member and need its ``name`` or ``value``::
+
+    >>> member = Color.red
+    >>> member.name
+    'red'
+    >>> member.value
+    1
+
+
+Duplicating enum members and values
+-----------------------------------
+
+Having two enum members (or any other attribute) with the same name is invalid;
+in Python 3.x this would raise an error, but in Python 2.x the second member
+simply overwrites the first::
+
+    >>> # python 2.x
+    >>> class Shape(Enum):
+    ...   square = 2
+    ...   square = 3
+    ...
+    >>> Shape.square
+    <Shape.square: 3>
+
+    >>> # python 3.x
+    >>> class Shape(Enum):
+    ...   square = 2
+    ...   square = 3
+    Traceback (most recent call last):
+    ...
+    TypeError: Attempted to reuse key: 'square'
+
+However, two enum members are allowed to have the same value.  Given two members
+A and B with the same value (and A defined first), B is an alias to A.  By-value
+lookup of the value of A and B will return A.  By-name lookup of B will also
+return A::
+
+    >>> class Shape(Enum):
+    ...   __order__ = 'square diamond circle alias_for_square'  # only needed in 2.x
+    ...   square = 2
+    ...   diamond = 1
+    ...   circle = 3
+    ...   alias_for_square = 2
+    ...
+    >>> Shape.square
+    <Shape.square: 2>
+    >>> Shape.alias_for_square
+    <Shape.square: 2>
+    >>> Shape(2)
+    <Shape.square: 2>
+
+
+Allowing aliases is not always desirable.  ``unique`` can be used to ensure
+that none exist in a particular enumeration::
+
+    >>> from enum import unique
+    >>> @unique
+    ... class Mistake(Enum):
+    ...   __order__ = 'one two three four'  # only needed in 2.x
+    ...   one = 1
+    ...   two = 2
+    ...   three = 3
+    ...   four = 3
+    Traceback (most recent call last):
+    ...
+    ValueError: duplicate names found in <enum 'Mistake'>: four -> three
+
+Iterating over the members of an enum does not provide the aliases::
+
+    >>> list(Shape)
+    [<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]
+
+The special attribute ``__members__`` is a dictionary mapping names to members.
+It includes all names defined in the enumeration, including the aliases::
+
+    >>> for name, member in sorted(Shape.__members__.items()):
+    ...   name, member
+    ...
+    ('alias_for_square', <Shape.square: 2>)
+    ('circle', <Shape.circle: 3>)
+    ('diamond', <Shape.diamond: 1>)
+    ('square', <Shape.square: 2>)
+
+The ``__members__`` attribute can be used for detailed programmatic access to
+the enumeration members.  For example, finding all the aliases::
+
+    >>> [name for name, member in Shape.__members__.items() if member.name != name]
+    ['alias_for_square']
+
+Comparisons
+-----------
+
+Enumeration members are compared by identity::
+
+    >>> Color.red is Color.red
+    True
+    >>> Color.red is Color.blue
+    False
+    >>> Color.red is not Color.blue
+    True
+
+Ordered comparisons between enumeration values are *not* supported.  Enum
+members are not integers (but see `IntEnum`_ below)::
+
+    >>> Color.red < Color.blue
+    Traceback (most recent call last):
+      File "<stdin>", line 1, in <module>
+    TypeError: unorderable types: Color() < Color()
+
+.. warning::
+
+    In Python 2 *everything* is ordered, even though the ordering may not
+    make sense.  If you want your enumerations to have a sensible ordering
+    check out the `OrderedEnum`_ recipe below.
+
+
+Equality comparisons are defined though::
+
+    >>> Color.blue == Color.red
+    False
+    >>> Color.blue != Color.red
+    True
+    >>> Color.blue == Color.blue
+    True
+
+Comparisons against non-enumeration values will always compare not equal
+(again, ``IntEnum`` was explicitly designed to behave differently, see
+below)::
+
+    >>> Color.blue == 2
+    False
+
+
+Allowed members and attributes of enumerations
+----------------------------------------------
+
+The examples above use integers for enumeration values.  Using integers is
+short and handy (and provided by default by the `Functional API`_), but not
+strictly enforced.  In the vast majority of use-cases, one doesn't care what
+the actual value of an enumeration is.  But if the value *is* important,
+enumerations can have arbitrary values.
+
+Enumerations are Python classes, and can have methods and special methods as
+usual.  If we have this enumeration::
+
+    >>> class Mood(Enum):
+    ...   funky = 1
+    ...   happy = 3
+    ... 
+    ...   def describe(self):
+    ...     # self is the member here
+    ...     return self.name, self.value
+    ... 
+    ...   def __str__(self):
+    ...     return 'my custom str! {0}'.format(self.value)
+    ... 
+    ...   @classmethod
+    ...   def favorite_mood(cls):
+    ...     # cls here is the enumeration
+    ...     return cls.happy
+
+Then::
+
+    >>> Mood.favorite_mood()
+    <Mood.happy: 3>
+    >>> Mood.happy.describe()
+    ('happy', 3)
+    >>> str(Mood.funky)
+    'my custom str! 1'
+
+The rules for what is allowed are as follows: _sunder_ names (starting and
+ending with a single underscore) are reserved by enum and cannot be used;
+all other attributes defined within an enumeration will become members of this
+enumeration, with the exception of *__dunder__* names and descriptors (methods
+are also descriptors).
+
+Note:
+
+    If your enumeration defines ``__new__`` and/or ``__init__`` then
+    whatever value(s) were given to the enum member will be passed into
+    those methods.  See `Planet`_ for an example.
+
+
+Restricted subclassing of enumerations
+--------------------------------------
+
+Subclassing an enumeration is allowed only if the enumeration does not define
+any members.  So this is forbidden::
+
+    >>> class MoreColor(Color):
+    ...   pink = 17
+    Traceback (most recent call last):
+    ...
+    TypeError: Cannot extend enumerations
+
+But this is allowed::
+
+    >>> class Foo(Enum):
+    ...   def some_behavior(self):
+    ...     pass
+    ...
+    >>> class Bar(Foo):
+    ...   happy = 1
+    ...   sad = 2
+    ...
+
+Allowing subclassing of enums that define members would lead to a violation of
+some important invariants of types and instances.  On the other hand, it makes
+sense to allow sharing some common behavior between a group of enumerations.
+(See `OrderedEnum`_ for an example.)
+
+
+Pickling
+--------
+
+Enumerations can be pickled and unpickled::
+
+    >>> from enum.test_enum import Fruit
+    >>> from pickle import dumps, loads
+    >>> Fruit.tomato is loads(dumps(Fruit.tomato, 2))
+    True
+
+The usual restrictions for pickling apply: picklable enums must be defined in
+the top level of a module, since unpickling requires them to be importable
+from that module.
+
+Note:
+
+    With pickle protocol version 4 (introduced in Python 3.4) it is possible
+    to easily pickle enums nested in other classes.
+
+
+
+Functional API
+--------------
+
+The ``Enum`` class is callable, providing the following functional API::
+
+    >>> Animal = Enum('Animal', 'ant bee cat dog')
+    >>> Animal
+    <enum 'Animal'>
+    >>> Animal.ant
+    <Animal.ant: 1>
+    >>> Animal.ant.value
+    1
+    >>> list(Animal)
+    [<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]
+
+The semantics of this API resemble ``namedtuple``. The first argument
+of the call to ``Enum`` is the name of the enumeration. 
+
+The second argument is the *source* of enumeration member names.  It can be a
+whitespace-separated string of names, a sequence of names, a sequence of
+2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to
+values.  The last two options enable assigning arbitrary values to
+enumerations; the others auto-assign increasing integers starting with 1.  A
+new class derived from ``Enum`` is returned.  In other words, the above
+assignment to ``Animal`` is equivalent to::
+
+    >>> class Animals(Enum):
+    ...   ant = 1
+    ...   bee = 2
+    ...   cat = 3
+    ...   dog = 4
+
+Pickling enums created with the functional API can be tricky as frame stack
+implementation details are used to try and figure out which module the
+enumeration is being created in (e.g. it will fail if you use a utility
+function in separate module, and also may not work on IronPython or Jython).
+The solution is to specify the module name explicitly as follows::
+
+    >>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)
+
+Derived Enumerations
+--------------------
+
+IntEnum
+^^^^^^^
+
+A variation of ``Enum`` is provided which is also a subclass of
+``int``.  Members of an ``IntEnum`` can be compared to integers;
+by extension, integer enumerations of different types can also be compared
+to each other::
+
+    >>> from enum import IntEnum
+    >>> class Shape(IntEnum):
+    ...   circle = 1
+    ...   square = 2
+    ...
+    >>> class Request(IntEnum):
+    ...   post = 1
+    ...   get = 2
+    ...
+    >>> Shape == 1
+    False
+    >>> Shape.circle == 1
+    True
+    >>> Shape.circle == Request.post
+    True
+
+However, they still can't be compared to standard ``Enum`` enumerations::
+
+    >>> class Shape(IntEnum):
+    ...   circle = 1
+    ...   square = 2
+    ...
+    >>> class Color(Enum):
+    ...   red = 1
+    ...   green = 2
+    ...
+    >>> Shape.circle == Color.red
+    False
+
+``IntEnum`` values behave like integers in other ways you'd expect::
+
+    >>> int(Shape.circle)
+    1
+    >>> ['a', 'b', 'c'][Shape.circle]
+    'b'
+    >>> [i for i in range(Shape.square)]
+    [0, 1]
+
+For the vast majority of code, ``Enum`` is strongly recommended,
+since ``IntEnum`` breaks some semantic promises of an enumeration (by
+being comparable to integers, and thus by transitivity to other
+unrelated enumerations).  It should be used only in special cases where
+there's no other choice; for example, when integer constants are
+replaced with enumerations and backwards compatibility is required with code
+that still expects integers.
+
+
+Others
+^^^^^^
+
+While ``IntEnum`` is part of the ``enum`` module, it would be very
+simple to implement independently::
+
+    class IntEnum(int, Enum):
+        pass
+
+This demonstrates how similar derived enumerations can be defined; for example
+a ``StrEnum`` that mixes in ``str`` instead of ``int``.
+
+Some rules:
+
+1. When subclassing ``Enum``, mix-in types must appear before
+   ``Enum`` itself in the sequence of bases, as in the ``IntEnum``
+   example above.
+2. While ``Enum`` can have members of any type, once you mix in an
+   additional type, all the members must have values of that type, e.g.
+   ``int`` above.  This restriction does not apply to mix-ins which only
+   add methods and don't specify another data type such as ``int`` or
+   ``str``.
+3. When another data type is mixed in, the ``value`` attribute is *not the
+   same* as the enum member itself, although it is equivalant and will compare
+   equal.
+4. %-style formatting:  ``%s`` and ``%r`` call ``Enum``'s ``__str__`` and
+   ``__repr__`` respectively; other codes (such as ``%i`` or ``%h`` for
+   IntEnum) treat the enum member as its mixed-in type.
+
+   Note: Prior to Python 3.4 there is a bug in ``str``'s %-formatting: ``int``
+   subclasses are printed as strings and not numbers when the ``%d``, ``%i``,
+   or ``%u`` codes are used.
+5. ``str.__format__`` (or ``format``) will use the mixed-in
+   type's ``__format__``.  If the ``Enum``'s ``str`` or
+   ``repr`` is desired use the ``!s`` or ``!r`` ``str`` format codes.
+
+
+Decorators
+----------
+
+unique
+^^^^^^
+
+A ``class`` decorator specifically for enumerations.  It searches an
+enumeration's ``__members__`` gathering any aliases it finds; if any are
+found ``ValueError`` is raised with the details::
+
+    >>> @unique
+    ... class NoDupes(Enum):
+    ...    first = 'one'
+    ...    second = 'two'
+    ...    third = 'two'
+    Traceback (most recent call last):
+    ...
+    ValueError: duplicate names found in <enum 'NoDupes'>: third -> second
+
+
+Interesting examples
+--------------------
+
+While ``Enum`` and ``IntEnum`` are expected to cover the majority of
+use-cases, they cannot cover them all.  Here are recipes for some different
+types of enumerations that can be used directly, or as examples for creating
+one's own.
+
+
+AutoNumber
+^^^^^^^^^^
+
+Avoids having to specify the value for each enumeration member::
+
+    >>> class AutoNumber(Enum):
+    ...     def __new__(cls):
+    ...         value = len(cls.__members__) + 1
+    ...         obj = object.__new__(cls)
+    ...         obj._value_ = value
+    ...         return obj
+    ...
+    >>> class Color(AutoNumber):
+    ...     __order__ = "red green blue"  # only needed in 2.x
+    ...     red = ()
+    ...     green = ()
+    ...     blue = ()
+    ...
+    >>> Color.green.value == 2
+    True
+
+Note:
+
+    The `__new__` method, if defined, is used during creation of the Enum
+    members; it is then replaced by Enum's `__new__` which is used after
+    class creation for lookup of existing members.  Due to the way Enums are
+    supposed to behave, there is no way to customize Enum's `__new__`.
+
+
+UniqueEnum
+^^^^^^^^^^
+
+Raises an error if a duplicate member name is found instead of creating an
+alias::
+
+    >>> class UniqueEnum(Enum):
+    ...     def __init__(self, *args):
+    ...         cls = self.__class__
+    ...         if any(self.value == e.value for e in cls):
+    ...             a = self.name
+    ...             e = cls(self.value).name
+    ...             raise ValueError(
+    ...                     "aliases not allowed in UniqueEnum:  %r --> %r"
+    ...                     % (a, e))
+    ... 
+    >>> class Color(UniqueEnum):
+    ...     red = 1
+    ...     green = 2
+    ...     blue = 3
+    ...     grene = 2
+    Traceback (most recent call last):
+    ...
+    ValueError: aliases not allowed in UniqueEnum:  'grene' --> 'green'
+    
+
+OrderedEnum
+^^^^^^^^^^^
+
+An ordered enumeration that is not based on ``IntEnum`` and so maintains
+the normal ``Enum`` invariants (such as not being comparable to other
+enumerations)::
+
+    >>> class OrderedEnum(Enum):
+    ...     def __ge__(self, other):
+    ...         if self.__class__ is other.__class__:
+    ...             return self._value_ >= other._value_
+    ...         return NotImplemented
+    ...     def __gt__(self, other):
+    ...         if self.__class__ is other.__class__:
+    ...             return self._value_ > other._value_
+    ...         return NotImplemented
+    ...     def __le__(self, other):
+    ...         if self.__class__ is other.__class__:
+    ...             return self._value_ <= other._value_
+    ...         return NotImplemented
+    ...     def __lt__(self, other):
+    ...         if self.__class__ is other.__class__:
+    ...             return self._value_ < other._value_
+    ...         return NotImplemented
+    ...
+    >>> class Grade(OrderedEnum):
+    ...     __ordered__ = 'A B C D F'
+    ...     A = 5
+    ...     B = 4
+    ...     C = 3
+    ...     D = 2
+    ...     F = 1
+    ...
+    >>> Grade.C < Grade.A
+    True
+
+
+Planet
+^^^^^^
+
+If ``__new__`` or ``__init__`` is defined the value of the enum member
+will be passed to those methods::
+
+    >>> class Planet(Enum):
+    ...     MERCURY = (3.303e+23, 2.4397e6)
+    ...     VENUS   = (4.869e+24, 6.0518e6)
+    ...     EARTH   = (5.976e+24, 6.37814e6)
+    ...     MARS    = (6.421e+23, 3.3972e6)
+    ...     JUPITER = (1.9e+27,   7.1492e7)
+    ...     SATURN  = (5.688e+26, 6.0268e7)
+    ...     URANUS  = (8.686e+25, 2.5559e7)
+    ...     NEPTUNE = (1.024e+26, 2.4746e7)
+    ...     def __init__(self, mass, radius):
+    ...         self.mass = mass       # in kilograms
+    ...         self.radius = radius   # in meters
+    ...     @property
+    ...     def surface_gravity(self):
+    ...         # universal gravitational constant  (m3 kg-1 s-2)
+    ...         G = 6.67300E-11
+    ...         return G * self.mass / (self.radius * self.radius)
+    ... 
+    >>> Planet.EARTH.value
+    (5.976e+24, 6378140.0)
+    >>> Planet.EARTH.surface_gravity
+    9.802652743337129
+
+
+How are Enums different?
+------------------------
+
+Enums have a custom metaclass that affects many aspects of both derived Enum
+classes and their instances (members).
+
+
+Enum Classes
+^^^^^^^^^^^^
+
+The ``EnumMeta`` metaclass is responsible for providing the
+``__contains__``, ``__dir__``, ``__iter__`` and other methods that
+allow one to do things with an ``Enum`` class that fail on a typical
+class, such as ``list(Color)`` or ``some_var in Color``.  ``EnumMeta`` is
+responsible for ensuring that various other methods on the final ``Enum``
+class are correct (such as ``__new__``, ``__getnewargs__``,
+``__str__`` and ``__repr__``).
+
+.. note::
+
+    ``__dir__`` is not changed in the Python 2 line as it messes up some
+    of the decorators included in the stdlib.
+
+
+Enum Members (aka instances)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The most interesting thing about Enum members is that they are singletons.
+``EnumMeta`` creates them all while it is creating the ``Enum``
+class itself, and then puts a custom ``__new__`` in place to ensure
+that no new ones are ever instantiated by returning only the existing
+member instances.
+
+
+Finer Points
+^^^^^^^^^^^^
+
+``Enum`` members are instances of an ``Enum`` class, and even though they
+are accessible as `EnumClass.member1.member2`, they should not be
+accessed directly from the member as that lookup may fail or, worse,
+return something besides the ``Enum`` member you were looking for
+(changed in version 1.1.1)::
+
+    >>> class FieldTypes(Enum):
+    ...     name = 1
+    ...     value = 2
+    ...     size = 3
+    ...
+    >>> FieldTypes.value.size
+    <FieldTypes.size: 3>
+    >>> FieldTypes.size.value
+    3
+
+The ``__members__`` attribute is only available on the class.
+
+In Python 3.x ``__members__`` is always an ``OrderedDict``, with the order being
+the definition order.  In Python 2.7 ``__members__`` is an ``OrderedDict`` if
+``__order__`` was specified, and a plain ``dict`` otherwise.  In all other Python
+2.x versions ``__members__`` is a plain ``dict`` even if ``__order__`` was specified
+as the ``OrderedDict`` type didn't exist yet.
+
+If you give your ``Enum`` subclass extra methods, like the `Planet`_
+class above, those methods will show up in a `dir` of the member,
+but not of the class::
+
+    >>> dir(Planet)
+    ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS',
+    'VENUS', '__class__', '__doc__', '__members__', '__module__']
+    >>> dir(Planet.EARTH)
+    ['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
+
+A ``__new__`` method will only be used for the creation of the
+``Enum`` members -- after that it is replaced.  This means if you wish to
+change how ``Enum`` members are looked up you either have to write a
+helper function or a ``classmethod``.
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/enum/test.py
@@ -0,0 +1,1820 @@
+from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
+import sys
+import unittest
+pyver = float('%s.%s' % sys.version_info[:2])
+if pyver < 2.5:
+    sys.path.insert(0, '.')
+import enum
+from enum import Enum, IntEnum, unique, EnumMeta
+
+if pyver < 2.6:
+    from __builtin__ import enumerate as bltin_enumerate
+    def enumerate(thing, start=0):
+        result = []
+        for i, item in bltin_enumerate(thing):
+            i = i + start
+            result.append((i, item))
+        return result
+
+try:
+    any
+except NameError:
+    def any(iterable):
+        for element in iterable:
+            if element:
+                return True
+        return False
+
+try:
+    unicode
+except NameError:
+    unicode = str
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    OrderedDict = None
+
+# for pickle tests
+try:
+    class Stooges(Enum):
+        LARRY = 1
+        CURLY = 2
+        MOE = 3
+except Exception:
+    Stooges = sys.exc_info()[1]
+
+try:
+    class IntStooges(int, Enum):
+        LARRY = 1
+        CURLY = 2
+        MOE = 3
+except Exception:
+    IntStooges = sys.exc_info()[1]
+
+try:
+    class FloatStooges(float, Enum):
+        LARRY = 1.39
+        CURLY = 2.72
+        MOE = 3.142596
+except Exception:
+    FloatStooges = sys.exc_info()[1]
+
+# for pickle test and subclass tests
+try:
+    class StrEnum(str, Enum):
+        'accepts only string values'
+    class Name(StrEnum):
+        BDFL = 'Guido van Rossum'
+        FLUFL = 'Barry Warsaw'
+except Exception:
+    Name = sys.exc_info()[1]
+
+try:
+    Question = Enum('Question', 'who what when where why', module=__name__)
+except Exception:
+    Question = sys.exc_info()[1]
+
+try:
+    Answer = Enum('Answer', 'him this then there because')
+except Exception:
+    Answer = sys.exc_info()[1]
+
+try:
+    Theory = Enum('Theory', 'rule law supposition', qualname='spanish_inquisition')
+except Exception:
+    Theory = sys.exc_info()[1]
+
+# for doctests
+try:
+    class Fruit(Enum):
+        tomato = 1
+        banana = 2
+        cherry = 3
+except Exception:
+    pass
+
+def test_pickle_dump_load(assertion, source, target=None,
+        protocol=(0, HIGHEST_PROTOCOL)):
+    start, stop = protocol
+    failures = []
+    for protocol in range(start, stop+1):
+        try:
+            if target is None:
+                assertion(loads(dumps(source, protocol=protocol)) is source)
+            else:
+                assertion(loads(dumps(source, protocol=protocol)), target)
+        except Exception:
+            exc, tb = sys.exc_info()[1:]
+            failures.append('%2d: %s' %(protocol, exc))
+    if failures:
+        raise ValueError('Failed with protocols: %s' % ', '.join(failures))
+
+def test_pickle_exception(assertion, exception, obj,
+        protocol=(0, HIGHEST_PROTOCOL)):
+    start, stop = protocol
+    failures = []
+    for protocol in range(start, stop+1):
+        try:
+            assertion(exception, dumps, obj, protocol=protocol)
+        except Exception:
+            exc = sys.exc_info()[1]
+            failures.append('%d: %s %s' % (protocol, exc.__class__.__name__, exc))
+    if failures:
+        raise ValueError('Failed with protocols: %s' % ', '.join(failures))
+
+
+class TestHelpers(unittest.TestCase):
+    # _is_descriptor, _is_sunder, _is_dunder
+
+    def test_is_descriptor(self):
+        class foo:
+            pass
+        for attr in ('__get__','__set__','__delete__'):
+            obj = foo()
+            self.assertFalse(enum._is_descriptor(obj))
+            setattr(obj, attr, 1)
+            self.assertTrue(enum._is_descriptor(obj))
+
+    def test_is_sunder(self):
+        for s in ('_a_', '_aa_'):
+            self.assertTrue(enum._is_sunder(s))
+
+        for s in ('a', 'a_', '_a', '__a', 'a__', '__a__', '_a__', '__a_', '_',
+                '__', '___', '____', '_____',):
+            self.assertFalse(enum._is_sunder(s))
+
+    def test_is_dunder(self):
+        for s in ('__a__', '__aa__'):
+            self.assertTrue(enum._is_dunder(s))
+        for s in ('a', 'a_', '_a', '__a', 'a__', '_a_', '_a__', '__a_', '_',
+                '__', '___', '____', '_____',):
+            self.assertFalse(enum._is_dunder(s))
+
+
+class TestEnum(unittest.TestCase):
+    def setUp(self):
+        class Season(Enum):
+            SPRING = 1
+            SUMMER = 2
+            AUTUMN = 3
+            WINTER = 4
+        self.Season = Season
+
+        class Konstants(float, Enum):
+            E = 2.7182818
+            PI = 3.1415926
+            TAU = 2 * PI
+        self.Konstants = Konstants
+
+        class Grades(IntEnum):
+            A = 5
+            B = 4
+            C = 3
+            D = 2
+            F = 0
+        self.Grades = Grades
+
+        class Directional(str, Enum):
+            EAST = 'east'
+            WEST = 'west'
+            NORTH = 'north'
+            SOUTH = 'south'
+        self.Directional = Directional
+
+        from datetime import date
+        class Holiday(date, Enum):
+            NEW_YEAR = 2013, 1, 1
+            IDES_OF_MARCH = 2013, 3, 15
+        self.Holiday = Holiday
+
+    if pyver >= 3.0:     # do not specify custom `dir` on previous versions
+        def test_dir_on_class(self):
+            Season = self.Season
+            self.assertEqual(
+                set(dir(Season)),
+                set(['__class__', '__doc__', '__members__', '__module__',
+                    'SPRING', 'SUMMER', 'AUTUMN', 'WINTER']),
+                )
+
+        def test_dir_on_item(self):
+            Season = self.Season
+            self.assertEqual(
+                set(dir(Season.WINTER)),
+                set(['__class__', '__doc__', '__module__', 'name', 'value']),
+                )
+
+        def test_dir_with_added_behavior(self):
+            class Test(Enum):
+                this = 'that'
+                these = 'those'
+                def wowser(self):
+                    return ("Wowser! I'm %s!" % self.name)
+            self.assertEqual(
+                    set(dir(Test)),
+                    set(['__class__', '__doc__', '__members__', '__module__', 'this', 'these']),
+                    )
+            self.assertEqual(
+                    set(dir(Test.this)),
+                    set(['__class__', '__doc__', '__module__', 'name', 'value', 'wowser']),
+                    )
+
+        def test_dir_on_sub_with_behavior_on_super(self):
+            # see issue22506
+            class SuperEnum(Enum):
+                def invisible(self):
+                    return "did you see me?"
+            class SubEnum(SuperEnum):
+                sample = 5
+            self.assertEqual(
+                    set(dir(SubEnum.sample)),
+                    set(['__class__', '__doc__', '__module__', 'name', 'value', 'invisible']),
+                    )
+
+    if pyver >= 2.7:    # OrderedDict first available here
+        def test_members_is_ordereddict_if_ordered(self):
+            class Ordered(Enum):
+                __order__ = 'first second third'
+                first = 'bippity'
+                second = 'boppity'
+                third = 'boo'
+            self.assertTrue(type(Ordered.__members__) is OrderedDict)
+
+        def test_members_is_ordereddict_if_not_ordered(self):
+            class Unordered(Enum):
+                this = 'that'
+                these = 'those'
+            self.assertTrue(type(Unordered.__members__) is OrderedDict)
+
+    if pyver >= 3.0:     # all objects are ordered in Python 2.x
+        def test_members_is_always_ordered(self):
+            class AlwaysOrdered(Enum):
+                first = 1
+                second = 2
+                third = 3
+            self.assertTrue(type(AlwaysOrdered.__members__) is OrderedDict)
+
+        def test_comparisons(self):
+            def bad_compare():
+                Season.SPRING > 4
+            Season = self.Season
+            self.assertNotEqual(Season.SPRING, 1)
+            self.assertRaises(TypeError, bad_compare)
+
+            class Part(Enum):
+                SPRING = 1
+                CLIP = 2
+                BARREL = 3
+
+            self.assertNotEqual(Season.SPRING, Part.SPRING)
+            def bad_compare():
+                Season.SPRING < Part.CLIP
+            self.assertRaises(TypeError, bad_compare)
+
+    def test_enum_in_enum_out(self):
+        Season = self.Season
+        self.assertTrue(Season(Season.WINTER) is Season.WINTER)
+
+    def test_enum_value(self):
+        Season = self.Season
+        self.assertEqual(Season.SPRING.value, 1)
+
+    def test_intenum_value(self):
+        self.assertEqual(IntStooges.CURLY.value, 2)
+
+    def test_enum(self):
+        Season = self.Season
+        lst = list(Season)
+        self.assertEqual(len(lst), len(Season))
+        self.assertEqual(len(Season), 4, Season)
+        self.assertEqual(
+            [Season.SPRING, Season.SUMMER, Season.AUTUMN, Season.WINTER], lst)
+
+        for i, season in enumerate('SPRING SUMMER AUTUMN WINTER'.split()):
+            i += 1
+            e = Season(i)
+            self.assertEqual(e, getattr(Season, season))
+            self.assertEqual(e.value, i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, season)
+            self.assertTrue(e in Season)
+            self.assertTrue(type(e) is Season)
+            self.assertTrue(isinstance(e, Season))
+            self.assertEqual(str(e), 'Season.' + season)
+            self.assertEqual(
+                    repr(e),
+                    '<Season.%s: %s>' % (season, i),
+                    )
+
+    def test_value_name(self):
+        Season = self.Season
+        self.assertEqual(Season.SPRING.name, 'SPRING')
+        self.assertEqual(Season.SPRING.value, 1)
+        def set_name(obj, new_value):
+            obj.name = new_value
+        def set_value(obj, new_value):
+            obj.value = new_value
+        self.assertRaises(AttributeError, set_name, Season.SPRING, 'invierno', )
+        self.assertRaises(AttributeError, set_value, Season.SPRING, 2)
+
+    def test_attribute_deletion(self):
+        class Season(Enum):
+            SPRING = 1
+            SUMMER = 2
+            AUTUMN = 3
+            WINTER = 4
+
+            def spam(cls):
+                pass
+
+        self.assertTrue(hasattr(Season, 'spam'))
+        del Season.spam
+        self.assertFalse(hasattr(Season, 'spam'))
+
+        self.assertRaises(AttributeError, delattr, Season, 'SPRING')
+        self.assertRaises(AttributeError, delattr, Season, 'DRY')
+        self.assertRaises(AttributeError, delattr, Season.SPRING, 'name')
+
+    def test_bool_of_class(self):
+        class Empty(Enum):
+            pass
+        self.assertTrue(bool(Empty))
+
+    def test_bool_of_member(self):
+        class Count(Enum):
+            zero = 0
+            one = 1
+            two = 2
+        for member in Count:
+            self.assertTrue(bool(member))
+
+    def test_invalid_names(self):
+        def create_bad_class_1():
+            class Wrong(Enum):
+                mro = 9
+        def create_bad_class_2():
+            class Wrong(Enum):
+                _reserved_ = 3
+        self.assertRaises(ValueError, create_bad_class_1)
+        self.assertRaises(ValueError, create_bad_class_2)
+
+    def test_contains(self):
+        Season = self.Season
+        self.assertTrue(Season.AUTUMN in Season)
+        self.assertTrue(3 not in Season)
+
+        val = Season(3)
+        self.assertTrue(val in Season)
+
+        class OtherEnum(Enum):
+            one = 1; two = 2
+        self.assertTrue(OtherEnum.two not in Season)
+
+    if pyver >= 2.6:     # when `format` came into being
+
+        def test_format_enum(self):
+            Season = self.Season
+            self.assertEqual('{0}'.format(Season.SPRING),
+                             '{0}'.format(str(Season.SPRING)))
+            self.assertEqual( '{0:}'.format(Season.SPRING),
+                              '{0:}'.format(str(Season.SPRING)))
+            self.assertEqual('{0:20}'.format(Season.SPRING),
+                             '{0:20}'.format(str(Season.SPRING)))
+            self.assertEqual('{0:^20}'.format(Season.SPRING),
+                             '{0:^20}'.format(str(Season.SPRING)))
+            self.assertEqual('{0:>20}'.format(Season.SPRING),
+                             '{0:>20}'.format(str(Season.SPRING)))
+            self.assertEqual('{0:<20}'.format(Season.SPRING),
+                             '{0:<20}'.format(str(Season.SPRING)))
+
+        def test_format_enum_custom(self):
+            class TestFloat(float, Enum):
+                one = 1.0
+                two = 2.0
+                def __format__(self, spec):
+                    return 'TestFloat success!'
+            self.assertEqual('{0}'.format(TestFloat.one), 'TestFloat success!')
+
+        def assertFormatIsValue(self, spec, member):
+            self.assertEqual(spec.format(member), spec.format(member.value))
+
+        def test_format_enum_date(self):
+            Holiday = self.Holiday
+            self.assertFormatIsValue('{0}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:20}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:^20}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:>20}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:<20}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:%Y %m}', Holiday.IDES_OF_MARCH)
+            self.assertFormatIsValue('{0:%Y %m %M:00}', Holiday.IDES_OF_MARCH)
+
+        def test_format_enum_float(self):
+            Konstants = self.Konstants
+            self.assertFormatIsValue('{0}', Konstants.TAU)
+            self.assertFormatIsValue('{0:}', Konstants.TAU)
+            self.assertFormatIsValue('{0:20}', Konstants.TAU)
+            self.assertFormatIsValue('{0:^20}', Konstants.TAU)
+            self.assertFormatIsValue('{0:>20}', Konstants.TAU)
+            self.assertFormatIsValue('{0:<20}', Konstants.TAU)
+            self.assertFormatIsValue('{0:n}', Konstants.TAU)
+            self.assertFormatIsValue('{0:5.2}', Konstants.TAU)
+            self.assertFormatIsValue('{0:f}', Konstants.TAU)
+
+        def test_format_enum_int(self):
+            Grades = self.Grades
+            self.assertFormatIsValue('{0}', Grades.C)
+            self.assertFormatIsValue('{0:}', Grades.C)
+            self.assertFormatIsValue('{0:20}', Grades.C)
+            self.assertFormatIsValue('{0:^20}', Grades.C)
+            self.assertFormatIsValue('{0:>20}', Grades.C)
+            self.assertFormatIsValue('{0:<20}', Grades.C)
+            self.assertFormatIsValue('{0:+}', Grades.C)
+            self.assertFormatIsValue('{0:08X}', Grades.C)
+            self.assertFormatIsValue('{0:b}', Grades.C)
+
+        def test_format_enum_str(self):
+            Directional = self.Directional
+            self.assertFormatIsValue('{0}', Directional.WEST)
+            self.assertFormatIsValue('{0:}', Directional.WEST)
+            self.assertFormatIsValue('{0:20}', Directional.WEST)
+            self.assertFormatIsValue('{0:^20}', Directional.WEST)
+            self.assertFormatIsValue('{0:>20}', Directional.WEST)
+            self.assertFormatIsValue('{0:<20}', Directional.WEST)
+
+    def test_hash(self):
+        Season = self.Season
+        dates = {}
+        dates[Season.WINTER] = '1225'
+        dates[Season.SPRING] = '0315'
+        dates[Season.SUMMER] = '0704'
+        dates[Season.AUTUMN] = '1031'
+        self.assertEqual(dates[Season.AUTUMN], '1031')
+
+    def test_enum_duplicates(self):
+        _order_ = "SPRING SUMMER AUTUMN WINTER"
+        class Season(Enum):
+            SPRING = 1
+            SUMMER = 2
+            AUTUMN = FALL = 3
+            WINTER = 4
+            ANOTHER_SPRING = 1
+        lst = list(Season)
+        self.assertEqual(
+            lst,
+            [Season.SPRING, Season.SUMMER,
+             Season.AUTUMN, Season.WINTER,
+            ])
+        self.assertTrue(Season.FALL is Season.AUTUMN)
+        self.assertEqual(Season.FALL.value, 3)
+        self.assertEqual(Season.AUTUMN.value, 3)
+        self.assertTrue(Season(3) is Season.AUTUMN)
+        self.assertTrue(Season(1) is Season.SPRING)
+        self.assertEqual(Season.FALL.name, 'AUTUMN')
+        self.assertEqual(
+                set([k for k,v in Season.__members__.items() if v.name != k]),
+                set(['FALL', 'ANOTHER_SPRING']),
+                )
+
+    if pyver >= 3.0:
+        cls = vars()
+        result = {'Enum':Enum}
+        exec("""def test_duplicate_name(self):
+            with self.assertRaises(TypeError):
+                class Color(Enum):
+                    red = 1
+                    green = 2
+                    blue = 3
+                    red = 4
+
+            with self.assertRaises(TypeError):
+                class Color(Enum):
+                    red = 1
+                    green = 2
+                    blue = 3
+                    def red(self):
+                        return 'red'
+
+            with self.assertRaises(TypeError):
+                class Color(Enum):
+                    @property
+
+                    def red(self):
+                        return 'redder'
+                    red = 1
+                    green = 2
+                    blue = 3""",
+            result)
+        cls['test_duplicate_name'] = result['test_duplicate_name']
+
+    def test_enum_with_value_name(self):
+        class Huh(Enum):
+            name = 1
+            value = 2
+        self.assertEqual(
+            list(Huh),
+            [Huh.name, Huh.value],
+            )
+        self.assertTrue(type(Huh.name) is Huh)
+        self.assertEqual(Huh.name.name, 'name')
+        self.assertEqual(Huh.name.value, 1)
+
+    def test_intenum_from_scratch(self):
+        class phy(int, Enum):
+            pi = 3
+            tau = 2 * pi
+        self.assertTrue(phy.pi < phy.tau)
+
+    def test_intenum_inherited(self):
+        class IntEnum(int, Enum):
+            pass
+        class phy(IntEnum):
+            pi = 3
+            tau = 2 * pi
+        self.assertTrue(phy.pi < phy.tau)
+
+    def test_floatenum_from_scratch(self):
+        class phy(float, Enum):
+            pi = 3.1415926
+            tau = 2 * pi
+        self.assertTrue(phy.pi < phy.tau)
+
+    def test_floatenum_inherited(self):
+        class FloatEnum(float, Enum):
+            pass
+        class phy(FloatEnum):
+            pi = 3.1415926
+            tau = 2 * pi
+        self.assertTrue(phy.pi < phy.tau)
+
+    def test_strenum_from_scratch(self):
+        class phy(str, Enum):
+            pi = 'Pi'
+            tau = 'Tau'
+        self.assertTrue(phy.pi < phy.tau)
+
+    def test_strenum_inherited(self):
+        class StrEnum(str, Enum):
+            pass
+        class phy(StrEnum):
+            pi = 'Pi'
+            tau = 'Tau'
+        self.assertTrue(phy.pi < phy.tau)
+
+    def test_intenum(self):
+        class WeekDay(IntEnum):
+            SUNDAY = 1
+            MONDAY = 2
+            TUESDAY = 3
+            WEDNESDAY = 4
+            THURSDAY = 5
+            FRIDAY = 6
+            SATURDAY = 7
+
+        self.assertEqual(['a', 'b', 'c'][WeekDay.MONDAY], 'c')
+        self.assertEqual([i for i in range(WeekDay.TUESDAY)], [0, 1, 2])
+
+        lst = list(WeekDay)
+        self.assertEqual(len(lst), len(WeekDay))
+        self.assertEqual(len(WeekDay), 7)
+        target = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY'
+        target = target.split()
+        for i, weekday in enumerate(target):
+            i += 1
+            e = WeekDay(i)
+            self.assertEqual(e, i)
+            self.assertEqual(int(e), i)
+            self.assertEqual(e.name, weekday)
+            self.assertTrue(e in WeekDay)
+            self.assertEqual(lst.index(e)+1, i)
+            self.assertTrue(0 < e < 8)
+            self.assertTrue(type(e) is WeekDay)
+            self.assertTrue(isinstance(e, int))
+            self.assertTrue(isinstance(e, Enum))
+
+    def test_intenum_duplicates(self):
+        class WeekDay(IntEnum):
+            __order__ = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY'
+            SUNDAY = 1
+            MONDAY = 2
+            TUESDAY = TEUSDAY = 3
+            WEDNESDAY = 4
+            THURSDAY = 5
+            FRIDAY = 6
+            SATURDAY = 7
+        self.assertTrue(WeekDay.TEUSDAY is WeekDay.TUESDAY)
+        self.assertEqual(WeekDay(3).name, 'TUESDAY')
+        self.assertEqual([k for k,v in WeekDay.__members__.items()
+                if v.name != k], ['TEUSDAY', ])
+
+    def test_pickle_enum(self):
+        if isinstance(Stooges, Exception):
+            raise Stooges
+        test_pickle_dump_load(self.assertTrue, Stooges.CURLY)
+        test_pickle_dump_load(self.assertTrue, Stooges)
+
+    def test_pickle_int(self):
+        if isinstance(IntStooges, Exception):
+            raise IntStooges
+        test_pickle_dump_load(self.assertTrue, IntStooges.CURLY)
+        test_pickle_dump_load(self.assertTrue, IntStooges)
+
+    def test_pickle_float(self):
+        if isinstance(FloatStooges, Exception):
+            raise FloatStooges
+        test_pickle_dump_load(self.assertTrue, FloatStooges.CURLY)
+        test_pickle_dump_load(self.assertTrue, FloatStooges)
+
+    def test_pickle_enum_function(self):
+        if isinstance(Answer, Exception):
+            raise Answer
+        test_pickle_dump_load(self.assertTrue, Answer.him)
+        test_pickle_dump_load(self.assertTrue, Answer)
+
+    def test_pickle_enum_function_with_module(self):
+        if isinstance(Question, Exception):
+            raise Question
+        test_pickle_dump_load(self.assertTrue, Question.who)
+        test_pickle_dump_load(self.assertTrue, Question)
+
+    if pyver == 3.4:
+        def test_class_nested_enum_and_pickle_protocol_four(self):
+            # would normally just have this directly in the class namespace
+            class NestedEnum(Enum):
+                twigs = 'common'
+                shiny = 'rare'
+
+            self.__class__.NestedEnum = NestedEnum
+            self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__
+            test_pickle_exception(
+                    self.assertRaises, PicklingError, self.NestedEnum.twigs,
+                    protocol=(0, 3))
+            test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs,
+                    protocol=(4, HIGHEST_PROTOCOL))
+
+    elif pyver == 3.5:
+        def test_class_nested_enum_and_pickle_protocol_four(self):
+            # would normally just have this directly in the class namespace
+            class NestedEnum(Enum):
+                twigs = 'common'
+                shiny = 'rare'
+
+            self.__class__.NestedEnum = NestedEnum
+            self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__
+            test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs,
+                    protocol=(0, HIGHEST_PROTOCOL))
+
+    def test_exploding_pickle(self):
+        BadPickle = Enum('BadPickle', 'dill sweet bread-n-butter')
+        enum._make_class_unpicklable(BadPickle)
+        globals()['BadPickle'] = BadPickle
+        test_pickle_exception(self.assertRaises, TypeError, BadPickle.dill)
+        test_pickle_exception(self.assertRaises, PicklingError, BadPickle)
+
+    def test_string_enum(self):
+        class SkillLevel(str, Enum):
+            master = 'what is the sound of one hand clapping?'
+            journeyman = 'why did the chicken cross the road?'
+            apprentice = 'knock, knock!'
+        self.assertEqual(SkillLevel.apprentice, 'knock, knock!')
+
+    def test_getattr_getitem(self):
+        class Period(Enum):
+            morning = 1
+            noon = 2
+            evening = 3
+            night = 4
+        self.assertTrue(Period(2) is Period.noon)
+        self.assertTrue(getattr(Period, 'night') is Period.night)
+        self.assertTrue(Period['morning'] is Period.morning)
+
+    def test_getattr_dunder(self):
+        Season = self.Season
+        self.assertTrue(getattr(Season, '__hash__'))
+
+    def test_iteration_order(self):
+        class Season(Enum):
+            _order_ = 'SUMMER WINTER AUTUMN SPRING'
+            SUMMER = 2
+            WINTER = 4
+            AUTUMN = 3
+            SPRING = 1
+        self.assertEqual(
+                list(Season),
+                [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING],
+                )
+
+    def test_iteration_order_reversed(self):
+        self.assertEqual(
+                list(reversed(self.Season)),
+                [self.Season.WINTER, self.Season.AUTUMN, self.Season.SUMMER,
+                 self.Season.SPRING]
+                )
+
+    def test_iteration_order_with_unorderable_values(self):
+        class Complex(Enum):
+            a = complex(7, 9)
+            b = complex(3.14, 2)
+            c = complex(1, -1)
+            d = complex(-77, 32)
+        self.assertEqual(
+                list(Complex),
+                [Complex.a, Complex.b, Complex.c, Complex.d],
+                )
+
+    def test_programatic_function_string(self):
+        SummerMonth = Enum('SummerMonth', 'june july august')
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_string_with_start(self):
+        SummerMonth = Enum('SummerMonth', 'june july august', start=10)
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split(), 10):
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_string_list(self):
+        SummerMonth = Enum('SummerMonth', ['june', 'july', 'august'])
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_string_list_with_start(self):
+        SummerMonth = Enum('SummerMonth', ['june', 'july', 'august'], start=20)
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split(), 20):
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_iterable(self):
+        SummerMonth = Enum(
+                'SummerMonth',
+                (('june', 1), ('july', 2), ('august', 3))
+                )
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_from_dict(self):
+        SummerMonth = Enum(
+                'SummerMonth',
+                dict((('june', 1), ('july', 2), ('august', 3)))
+                )
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        if pyver < 3.0:
+            self.assertEqual(
+                    [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                    lst,
+                    )
+        for i, month in enumerate('june july august'.split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_type(self):
+        SummerMonth = Enum('SummerMonth', 'june july august', type=int)
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_type_with_start(self):
+        SummerMonth = Enum('SummerMonth', 'june july august', type=int, start=30)
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split(), 30):
+            e = SummerMonth(i)
+            self.assertEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_type_from_subclass(self):
+        SummerMonth = IntEnum('SummerMonth', 'june july august')
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_type_from_subclass_with_start(self):
+        SummerMonth = IntEnum('SummerMonth', 'june july august', start=40)
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate('june july august'.split(), 40):
+            e = SummerMonth(i)
+            self.assertEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_unicode(self):
+        SummerMonth = Enum('SummerMonth', unicode('june july august'))
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate(unicode('june july august').split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_unicode_list(self):
+        SummerMonth = Enum('SummerMonth', [unicode('june'), unicode('july'), unicode('august')])
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate(unicode('june july august').split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_unicode_iterable(self):
+        SummerMonth = Enum(
+                'SummerMonth',
+                ((unicode('june'), 1), (unicode('july'), 2), (unicode('august'), 3))
+                )
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate(unicode('june july august').split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_from_unicode_dict(self):
+        SummerMonth = Enum(
+                'SummerMonth',
+                dict(((unicode('june'), 1), (unicode('july'), 2), (unicode('august'), 3)))
+                )
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        if pyver < 3.0:
+            self.assertEqual(
+                    [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                    lst,
+                    )
+        for i, month in enumerate(unicode('june july august').split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(int(e.value), i)
+            self.assertNotEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_unicode_type(self):
+        SummerMonth = Enum('SummerMonth', unicode('june july august'), type=int)
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate(unicode('june july august').split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programatic_function_unicode_type_from_subclass(self):
+        SummerMonth = IntEnum('SummerMonth', unicode('june july august'))
+        lst = list(SummerMonth)
+        self.assertEqual(len(lst), len(SummerMonth))
+        self.assertEqual(len(SummerMonth), 3, SummerMonth)
+        self.assertEqual(
+                [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                lst,
+                )
+        for i, month in enumerate(unicode('june july august').split()):
+            i += 1
+            e = SummerMonth(i)
+            self.assertEqual(e, i)
+            self.assertEqual(e.name, month)
+            self.assertTrue(e in SummerMonth)
+            self.assertTrue(type(e) is SummerMonth)
+
+    def test_programmatic_function_unicode_class(self):
+        if pyver < 3.0:
+            class_names = unicode('SummerMonth'), 'S\xfcmm\xe9rM\xf6nth'.decode('latin1')
+        else:
+            class_names = 'SummerMonth', 'S\xfcmm\xe9rM\xf6nth'
+        for i, class_name in enumerate(class_names):
+            if pyver < 3.0 and i == 1:
+                self.assertRaises(TypeError, Enum, class_name, unicode('june july august'))
+            else:
+                SummerMonth = Enum(class_name, unicode('june july august'))
+                lst = list(SummerMonth)
+                self.assertEqual(len(lst), len(SummerMonth))
+                self.assertEqual(len(SummerMonth), 3, SummerMonth)
+                self.assertEqual(
+                        [SummerMonth.june, SummerMonth.july, SummerMonth.august],
+                        lst,
+                        )
+                for i, month in enumerate(unicode('june july august').split()):
+                    i += 1
+                    e = SummerMonth(i)
+                    self.assertEqual(e.value, i)
+                    self.assertEqual(e.name, month)
+                    self.assertTrue(e in SummerMonth)
+                    self.assertTrue(type(e) is SummerMonth)
+
+    def test_subclassing(self):
+        if isinstance(Name, Exception):
+            raise Name
+        self.assertEqual(Name.BDFL, 'Guido van Rossum')
+        self.assertTrue(Name.BDFL, Name('Guido van Rossum'))
+        self.assertTrue(Name.BDFL is getattr(Name, 'BDFL'))
+        test_pickle_dump_load(self.assertTrue, Name.BDFL)
+
+    def test_extending(self):
+        def bad_extension():
+            class Color(Enum):
+                red = 1
+                green = 2
+                blue = 3
+            class MoreColor(Color):
+                cyan = 4
+                magenta = 5
+                yellow = 6
+        self.assertRaises(TypeError, bad_extension)
+
+    def test_exclude_methods(self):
+        class whatever(Enum):
+            this = 'that'
+            these = 'those'
+            def really(self):
+                return 'no, not %s' % self.value
+        self.assertFalse(type(whatever.really) is whatever)
+        self.assertEqual(whatever.this.really(), 'no, not that')
+
+    def test_wrong_inheritance_order(self):
+        def wrong_inherit():
+            class Wrong(Enum, str):
+                NotHere = 'error before this point'
+        self.assertRaises(TypeError, wrong_inherit)
+
+    def test_intenum_transitivity(self):
+        class number(IntEnum):
+            one = 1
+            two = 2
+            three = 3
+        class numero(IntEnum):
+            uno = 1
+            dos = 2
+            tres = 3
+        self.assertEqual(number.one, numero.uno)
+        self.assertEqual(number.two, numero.dos)
+        self.assertEqual(number.three, numero.tres)
+
+    def test_introspection(self):
+        class Number(IntEnum):
+            one = 100
+            two = 200
+        self.assertTrue(Number.one._member_type_ is int)
+        self.assertTrue(Number._member_type_ is int)
+        class String(str, Enum):
+            yarn = 'soft'
+            rope = 'rough'
+            wire = 'hard'
+        self.assertTrue(String.yarn._member_type_ is str)
+        self.assertTrue(String._member_type_ is str)
+        class Plain(Enum):
+            vanilla = 'white'
+            one = 1
+        self.assertTrue(Plain.vanilla._member_type_ is object)
+        self.assertTrue(Plain._member_type_ is object)
+
+    def test_wrong_enum_in_call(self):
+        class Monochrome(Enum):
+            black = 0
+            white = 1
+        class Gender(Enum):
+            male = 0
+            female = 1
+        self.assertRaises(ValueError, Monochrome, Gender.male)
+
+    def test_wrong_enum_in_mixed_call(self):
+        class Monochrome(IntEnum):
+            black = 0
+            white = 1
+        class Gender(Enum):
+            male = 0
+            female = 1
+        self.assertRaises(ValueError, Monochrome, Gender.male)
+
+    def test_mixed_enum_in_call_1(self):
+        class Monochrome(IntEnum):
+            black = 0
+            white = 1
+        class Gender(IntEnum):
+            male = 0
+            female = 1
+        self.assertTrue(Monochrome(Gender.female) is Monochrome.white)
+
+    def test_mixed_enum_in_call_2(self):
+        class Monochrome(Enum):
+            black = 0
+            white = 1
+        class Gender(IntEnum):
+            male = 0
+            female = 1
+        self.assertTrue(Monochrome(Gender.male) is Monochrome.black)
+
+    def test_flufl_enum(self):
+        class Fluflnum(Enum):
+            def __int__(self):
+                return int(self.value)
+        class MailManOptions(Fluflnum):
+            option1 = 1
+            option2 = 2
+            option3 = 3
+        self.assertEqual(int(MailManOptions.option1), 1)
+
+    def test_no_such_enum_member(self):
+        class Color(Enum):
+            red = 1
+            green = 2
+            blue = 3
+        self.assertRaises(ValueError, Color, 4)
+        self.assertRaises(KeyError, Color.__getitem__, 'chartreuse')
+
+    def test_new_repr(self):
+        class Color(Enum):
+            red = 1
+            green = 2
+            blue = 3
+            def __repr__(self):
+                return "don't you just love shades of %s?" % self.name
+        self.assertEqual(
+                repr(Color.blue),
+                "don't you just love shades of blue?",
+                )
+
+    def test_inherited_repr(self):
+        class MyEnum(Enum):
+            def __repr__(self):
+                return "My name is %s." % self.name
+        class MyIntEnum(int, MyEnum):
+            this = 1
+            that = 2
+            theother = 3
+        self.assertEqual(repr(MyIntEnum.that), "My name is that.")
+
+    def test_multiple_mixin_mro(self):
+        class auto_enum(EnumMeta):
+            def __new__(metacls, cls, bases, classdict):
+                original_dict = classdict
+                classdict = enum._EnumDict()
+                for k, v in original_dict.items():
+                    classdict[k] = v
+                temp = type(classdict)()
+                names = set(classdict._member_names)
+                i = 0
+                for k in classdict._member_names:
+                    v = classdict[k]
+                    if v == ():
+                        v = i
+                    else:
+                        i = v
+                    i += 1
+                    temp[k] = v
+                for k, v in classdict.items():
+                    if k not in names:
+                        temp[k] = v
+                return super(auto_enum, metacls).__new__(
+                        metacls, cls, bases, temp)
+
+        AutoNumberedEnum = auto_enum('AutoNumberedEnum', (Enum,), {})
+
+        AutoIntEnum = auto_enum('AutoIntEnum', (IntEnum,), {})
+
+        class TestAutoNumber(AutoNumberedEnum):
+            a = ()
+            b = 3
+            c = ()
+
+        class TestAutoInt(AutoIntEnum):
+            a = ()
+            b = 3
+            c = ()
+
+    def test_subclasses_with_getnewargs(self):
+        class NamedInt(int):
+            __qualname__ = 'NamedInt'  # needed for pickle protocol 4
+            def __new__(cls, *args):
+                _args = args
+                if len(args) < 1:
+                    raise TypeError("name and value must be specified")
+                name, args = args[0], args[1:]
+                self = int.__new__(cls, *args)
+                self._intname = name
+                self._args = _args
+                return self
+            def __getnewargs__(self):
+                return self._args
+            @property
+            def __name__(self):
+                return self._intname
+            def __repr__(self):
+                # repr() is updated to include the name and type info
+                return "%s(%r, %s)" % (type(self).__name__,
+                                             self.__name__,
+                                             int.__repr__(self))
+            def __str__(self):
+                # str() is unchanged, even if it relies on the repr() fallback
+                base = int
+                base_str = base.__str__
+                if base_str.__objclass__ is object:
+                    return base.__repr__(self)
+                return base_str(self)
+            # for simplicity, we only define one operator that
+            # propagates expressions
+            def __add__(self, other):
+                temp = int(self) + int( other)
+                if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+                    return NamedInt(
+                        '(%s + %s)' % (self.__name__, other.__name__),
+                        temp )
+                else:
+                    return temp
+
+        class NEI(NamedInt, Enum):
+            __qualname__ = 'NEI'  # needed for pickle protocol 4
+            x = ('the-x', 1)
+            y = ('the-y', 2)
+
+        self.assertTrue(NEI.__new__ is Enum.__new__)
+        self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+        globals()['NamedInt'] = NamedInt
+        globals()['NEI'] = NEI
+        NI5 = NamedInt('test', 5)
+        self.assertEqual(NI5, 5)
+        test_pickle_dump_load(self.assertTrue, NI5, 5)
+        self.assertEqual(NEI.y.value, 2)
+        test_pickle_dump_load(self.assertTrue, NEI.y)
+
+    if pyver >= 3.4:
+        def test_subclasses_with_getnewargs_ex(self):
+            class NamedInt(int):
+                __qualname__ = 'NamedInt'       # needed for pickle protocol 4
+                def __new__(cls, *args):
+                    _args = args
+                    if len(args) < 2:
+                        raise TypeError("name and value must be specified")
+                    name, args = args[0], args[1:]
+                    self = int.__new__(cls, *args)
+                    self._intname = name
+                    self._args = _args
+                    return self
+                def __getnewargs_ex__(self):
+                    return self._args, {}
+                @property
+                def __name__(self):
+                    return self._intname
+                def __repr__(self):
+                    # repr() is updated to include the name and type info
+                    return "{}({!r}, {})".format(type(self).__name__,
+                                                 self.__name__,
+                                                 int.__repr__(self))
+                def __str__(self):
+                    # str() is unchanged, even if it relies on the repr() fallback
+                    base = int
+                    base_str = base.__str__
+                    if base_str.__objclass__ is object:
+                        return base.__repr__(self)
+                    return base_str(self)
+                # for simplicity, we only define one operator that
+                # propagates expressions
+                def __add__(self, other):
+                    temp = int(self) + int( other)
+                    if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+                        return NamedInt(
+                            '({0} + {1})'.format(self.__name__, other.__name__),
+                            temp )
+                    else:
+                        return temp
+
+            class NEI(NamedInt, Enum):
+                __qualname__ = 'NEI'      # needed for pickle protocol 4
+                x = ('the-x', 1)
+                y = ('the-y', 2)
+
+
+            self.assertIs(NEI.__new__, Enum.__new__)
+            self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+            globals()['NamedInt'] = NamedInt
+            globals()['NEI'] = NEI
+            NI5 = NamedInt('test', 5)
+            self.assertEqual(NI5, 5)
+            test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, HIGHEST_PROTOCOL))
+            self.assertEqual(NEI.y.value, 2)
+            test_pickle_dump_load(self.assertTrue, NEI.y, protocol=(4, HIGHEST_PROTOCOL))
+
+    def test_subclasses_with_reduce(self):
+        class NamedInt(int):
+            __qualname__ = 'NamedInt'       # needed for pickle protocol 4
+            def __new__(cls, *args):
+                _args = args
+                if len(args) < 1:
+                    raise TypeError("name and value must be specified")
+                name, args = args[0], args[1:]
+                self = int.__new__(cls, *args)
+                self._intname = name
+                self._args = _args
+                return self
+            def __reduce__(self):
+                return self.__class__, self._args
+            @property
+            def __name__(self):
+                return self._intname
+            def __repr__(self):
+                # repr() is updated to include the name and type info
+                return "%s(%r, %s)" % (type(self).__name__,
+                                             self.__name__,
+                                             int.__repr__(self))
+            def __str__(self):
+                # str() is unchanged, even if it relies on the repr() fallback
+                base = int
+                base_str = base.__str__
+                if base_str.__objclass__ is object:
+                    return base.__repr__(self)
+                return base_str(self)
+            # for simplicity, we only define one operator that
+            # propagates expressions
+            def __add__(self, other):
+                temp = int(self) + int( other)
+                if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+                    return NamedInt(
+                        '(%s + %s)' % (self.__name__, other.__name__),
+                        temp )
+                else:
+                    return temp
+
+        class NEI(NamedInt, Enum):
+            __qualname__ = 'NEI'      # needed for pickle protocol 4
+            x = ('the-x', 1)
+            y = ('the-y', 2)
+
+
+        self.assertTrue(NEI.__new__ is Enum.__new__)
+        self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+        globals()['NamedInt'] = NamedInt
+        globals()['NEI'] = NEI
+        NI5 = NamedInt('test', 5)
+        self.assertEqual(NI5, 5)
+        test_pickle_dump_load(self.assertEqual, NI5, 5)
+        self.assertEqual(NEI.y.value, 2)
+        test_pickle_dump_load(self.assertTrue, NEI.y)
+
+    def test_subclasses_with_reduce_ex(self):
+        class NamedInt(int):
+            __qualname__ = 'NamedInt'       # needed for pickle protocol 4
+            def __new__(cls, *args):
+                _args = args
+                if len(args) < 1:
+                    raise TypeError("name and value must be specified")
+                name, args = args[0], args[1:]
+                self = int.__new__(cls, *args)
+                self._intname = name
+                self._args = _args
+                return self
+            def __reduce_ex__(self, proto):
+                return self.__class__, self._args
+            @property
+            def __name__(self):
+                return self._intname
+            def __repr__(self):
+                # repr() is updated to include the name and type info
+                return "%s(%r, %s)" % (type(self).__name__,
+                                             self.__name__,
+                                             int.__repr__(self))
+            def __str__(self):
+                # str() is unchanged, even if it relies on the repr() fallback
+                base = int
+                base_str = base.__str__
+                if base_str.__objclass__ is object:
+                    return base.__repr__(self)
+                return base_str(self)
+            # for simplicity, we only define one operator that
+            # propagates expressions
+            def __add__(self, other):
+                temp = int(self) + int( other)
+                if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+                    return NamedInt(
+                        '(%s + %s)' % (self.__name__, other.__name__),
+                        temp )
+                else:
+                    return temp
+
+        class NEI(NamedInt, Enum):
+            __qualname__ = 'NEI'      # needed for pickle protocol 4
+            x = ('the-x', 1)
+            y = ('the-y', 2)
+
+
+        self.assertTrue(NEI.__new__ is Enum.__new__)
+        self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+        globals()['NamedInt'] = NamedInt
+        globals()['NEI'] = NEI
+        NI5 = NamedInt('test', 5)
+        self.assertEqual(NI5, 5)
+        test_pickle_dump_load(self.assertEqual, NI5, 5)
+        self.assertEqual(NEI.y.value, 2)
+        test_pickle_dump_load(self.assertTrue, NEI.y)
+
+    def test_subclasses_without_direct_pickle_support(self):
+        class NamedInt(int):
+            __qualname__ = 'NamedInt'
+            def __new__(cls, *args):
+                _args = args
+                name, args = args[0], args[1:]
+                if len(args) == 0:
+                    raise TypeError("name and value must be specified")
+                self = int.__new__(cls, *args)
+                self._intname = name
+                self._args = _args
+                return self
+            @property
+            def __name__(self):
+                return self._intname
+            def __repr__(self):
+                # repr() is updated to include the name and type info
+                return "%s(%r, %s)" % (type(self).__name__,
+                                             self.__name__,
+                                             int.__repr__(self))
+            def __str__(self):
+                # str() is unchanged, even if it relies on the repr() fallback
+                base = int
+                base_str = base.__str__
+                if base_str.__objclass__ is object:
+                    return base.__repr__(self)
+                return base_str(self)
+            # for simplicity, we only define one operator that
+            # propagates expressions
+            def __add__(self, other):
+                temp = int(self) + int( other)
+                if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+                    return NamedInt(
+                        '(%s + %s)' % (self.__name__, other.__name__),
+                        temp )
+                else:
+                    return temp
+
+        class NEI(NamedInt, Enum):
+            __qualname__ = 'NEI'
+            x = ('the-x', 1)
+            y = ('the-y', 2)
+
+        self.assertTrue(NEI.__new__ is Enum.__new__)
+        self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+        globals()['NamedInt'] = NamedInt
+        globals()['NEI'] = NEI
+        NI5 = NamedInt('test', 5)
+        self.assertEqual(NI5, 5)
+        self.assertEqual(NEI.y.value, 2)
+        test_pickle_exception(self.assertRaises, TypeError, NEI.x)
+        test_pickle_exception(self.assertRaises, PicklingError, NEI)
+
+    def test_subclasses_without_direct_pickle_support_using_name(self):
+        class NamedInt(int):
+            __qualname__ = 'NamedInt'
+            def __new__(cls, *args):
+                _args = args
+                name, args = args[0], args[1:]
+                if len(args) == 0:
+                    raise TypeError("name and value must be specified")
+                self = int.__new__(cls, *args)
+                self._intname = name
+                self._args = _args
+                return self
+            @property
+            def __name__(self):
+                return self._intname
+            def __repr__(self):
+                # repr() is updated to include the name and type info
+                return "%s(%r, %s)" % (type(self).__name__,
+                                             self.__name__,
+                                             int.__repr__(self))
+            def __str__(self):
+                # str() is unchanged, even if it relies on the repr() fallback
+                base = int
+                base_str = base.__str__
+                if base_str.__objclass__ is object:
+                    return base.__repr__(self)
+                return base_str(self)
+            # for simplicity, we only define one operator that
+            # propagates expressions
+            def __add__(self, other):
+                temp = int(self) + int( other)
+                if isinstance(self, NamedInt) and isinstance(other, NamedInt):
+                    return NamedInt(
+                        '(%s + %s)' % (self.__name__, other.__name__),
+                        temp )
+                else:
+                    return temp
+
+        class NEI(NamedInt, Enum):
+            __qualname__ = 'NEI'
+            x = ('the-x', 1)
+            y = ('the-y', 2)
+            def __reduce_ex__(self, proto):
+                return getattr, (self.__class__, self._name_)
+
+        self.assertTrue(NEI.__new__ is Enum.__new__)
+        self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
+        globals()['NamedInt'] = NamedInt
+        globals()['NEI'] = NEI
+        NI5 = NamedInt('test', 5)
+        self.assertEqual(NI5, 5)
+        self.assertEqual(NEI.y.value, 2)
+        test_pickle_dump_load(self.assertTrue, NEI.y)
+        test_pickle_dump_load(self.assertTrue, NEI)
+
+    def test_tuple_subclass(self):
+        class SomeTuple(tuple, Enum):
+            __qualname__ = 'SomeTuple'
+            first = (1, 'for the money')
+            second = (2, 'for the show')
+            third = (3, 'for the music')
+        self.assertTrue(type(SomeTuple.first) is SomeTuple)
+        self.assertTrue(isinstance(SomeTuple.second, tuple))
+        self.assertEqual(SomeTuple.third, (3, 'for the music'))
+        globals()['SomeTuple'] = SomeTuple
+        test_pickle_dump_load(self.assertTrue, SomeTuple.first)
+
+    def test_duplicate_values_give_unique_enum_items(self):
+        class AutoNumber(Enum):
+            __order__ = 'enum_m enum_d enum_y'
+            enum_m = ()
+            enum_d = ()
+            enum_y = ()
+            def __new__(cls):
+                value = len(cls.__members__) + 1
+                obj = object.__new__(cls)
+                obj._value_ = value
+                return obj
+            def __int__(self):
+                return int(self._value_)
+        self.assertEqual(int(AutoNumber.enum_d), 2)
+        self.assertEqual(AutoNumber.enum_y.value, 3)
+        self.assertTrue(AutoNumber(1) is AutoNumber.enum_m)
+        self.assertEqual(
+            list(AutoNumber),
+            [AutoNumber.enum_m, AutoNumber.enum_d, AutoNumber.enum_y],
+            )
+
+    def test_inherited_new_from_enhanced_enum(self):
+        class AutoNumber2(Enum):
+            def __new__(cls):
+                value = len(cls.__members__) + 1
+                obj = object.__new__(cls)
+                obj._value_ = value
+                return obj
+            def __int__(self):
+                return int(self._value_)
+        class Color(AutoNumber2):
+            _order_ = 'red green blue'
+            red = ()
+            green = ()
+            blue = ()
+        self.assertEqual(len(Color), 3, "wrong number of elements: %d (should be %d)" % (len(Color), 3))
+        self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
+        if pyver >= 3.0:
+            self.assertEqual(list(map(int, Color)), [1, 2, 3])
+
+    def test_inherited_new_from_mixed_enum(self):
+        class AutoNumber3(IntEnum):
+            def __new__(cls):
+                value = len(cls.__members__) + 1
+                obj = int.__new__(cls, value)
+                obj._value_ = value
+                return obj
+        class Color(AutoNumber3):
+            red = ()
+            green = ()
+            blue = ()
+        self.assertEqual(len(Color), 3, "wrong number of elements: %d (should be %d)" % (len(Color), 3))
+        Color.red
+        Color.green
+        Color.blue
+
+    def test_equality(self):
+        class AlwaysEqual:
+            def __eq__(self, other):
+                return True
+        class OrdinaryEnum(Enum):
+            a = 1
+        self.assertEqual(AlwaysEqual(), OrdinaryEnum.a)
+        self.assertEqual(OrdinaryEnum.a, AlwaysEqual())
+
+    def test_ordered_mixin(self):
+        class OrderedEnum(Enum):
+            def __ge__(self, other):
+                if self.__class__ is other.__class__:
+                    return self._value_ >= other._value_
+                return NotImplemented
+            def __gt__(self, other):
+                if self.__class__ is other.__class__:
+                    return self._value_ > other._value_
+                return NotImplemented
+            def __le__(self, other):
+                if self.__class__ is other.__class__:
+                    return self._value_ <= other._value_
+                return NotImplemented
+            def __lt__(self, other):
+                if self.__class__ is other.__class__:
+                    return self._value_ < other._value_
+                return NotImplemented
+        class Grade(OrderedEnum):
+            __order__ = 'A B C D F'
+            A = 5
+            B = 4
+            C = 3
+            D = 2
+            F = 1
+        self.assertEqual(list(Grade), [Grade.A, Grade.B, Grade.C, Grade.D, Grade.F])
+        self.assertTrue(Grade.A > Grade.B)
+        self.assertTrue(Grade.F <= Grade.C)
+        self.assertTrue(Grade.D < Grade.A)
+        self.assertTrue(Grade.B >= Grade.B)
+
+    def test_extending2(self):
+        def bad_extension():
+            class Shade(Enum):
+                def shade(self):
+                    print(self.name)
+            class Color(Shade):
+                red = 1
+                green = 2
+                blue = 3
+            class MoreColor(Color):
+                cyan = 4
+                magenta = 5
+                yellow = 6
+        self.assertRaises(TypeError, bad_extension)
+
+    def test_extending3(self):
+        class Shade(Enum):
+            def shade(self):
+                return self.name
+        class Color(Shade):
+            def hex(self):
+                return '%s hexlified!' % self.value
+        class MoreColor(Color):
+            cyan = 4
+            magenta = 5
+            yellow = 6
+        self.assertEqual(MoreColor.magenta.hex(), '5 hexlified!')
+
+    def test_no_duplicates(self):
+        def bad_duplicates():
+            class UniqueEnum(Enum):
+                def __init__(self, *args):
+                    cls = self.__class__
+                    if any(self.value == e.value for e in cls):
+                        a = self.name
+                        e = cls(self.value).name
+                        raise ValueError(
+                                "aliases not allowed in UniqueEnum:  %r --> %r"
+                                % (a, e)
+                                )
+            class Color(UniqueEnum):
+                red = 1
+                green = 2
+                blue = 3
+            class Color(UniqueEnum):
+                red = 1
+                green = 2
+                blue = 3
+                grene = 2
+        self.assertRaises(ValueError, bad_duplicates)
+
+    def test_init(self):
+        class Planet(Enum):
+            MERCURY = (3.303e+23, 2.4397e6)
+            VENUS   = (4.869e+24, 6.0518e6)
+            EARTH   = (5.976e+24, 6.37814e6)
+            MARS    = (6.421e+23, 3.3972e6)
+            JUPITER = (1.9e+27,   7.1492e7)
+            SATURN  = (5.688e+26, 6.0268e7)
+            URANUS  = (8.686e+25, 2.5559e7)
+            NEPTUNE = (1.024e+26, 2.4746e7)
+            def __init__(self, mass, radius):
+                self.mass = mass       # in kilograms
+                self.radius = radius   # in meters
+            @property
+            def surface_gravity(self):
+                # universal gravitational constant  (m3 kg-1 s-2)
+                G = 6.67300E-11
+                return G * self.mass / (self.radius * self.radius)
+        self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80)
+        self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
+
+    def test_nonhash_value(self):
+        class AutoNumberInAList(Enum):
+            def __new__(cls):
+                value = [len(cls.__members__) + 1]
+                obj = object.__new__(cls)
+                obj._value_ = value
+                return obj
+        class ColorInAList(AutoNumberInAList):
+            _order_ = 'red green blue'
+            red = ()
+            green = ()
+            blue = ()
+        self.assertEqual(list(ColorInAList), [ColorInAList.red, ColorInAList.green, ColorInAList.blue])
+        self.assertEqual(ColorInAList.red.value, [1])
+        self.assertEqual(ColorInAList([1]), ColorInAList.red)
+
+    def test_conflicting_types_resolved_in_new(self):
+        class LabelledIntEnum(int, Enum):
+            def __new__(cls, *args):
+                value, label = args
+                obj = int.__new__(cls, value)
+                obj.label = label
+                obj._value_ = value
+                return obj
+
+        class LabelledList(LabelledIntEnum):
+            unprocessed = (1, "Unprocessed")
+            payment_complete = (2, "Payment Complete")
+
+        self.assertEqual(list(LabelledList), [LabelledList.unprocessed, LabelledList.payment_complete])
+        self.assertEqual(LabelledList.unprocessed, 1)
+        self.assertEqual(LabelledList(1), LabelledList.unprocessed)
+
+    def test_empty_with_functional_api(self):
+        empty = enum.IntEnum('Foo', {})
+        self.assertEqual(len(empty), 0)
+
+
+class TestUnique(unittest.TestCase):
+    """2.4 doesn't allow class decorators, use function syntax."""
+
+    def test_unique_clean(self):
+        class Clean(Enum):
+            one = 1
+            two = 'dos'
+            tres = 4.0
+        unique(Clean)
+        class Cleaner(IntEnum):
+            single = 1
+            double = 2
+            triple = 3
+        unique(Cleaner)
+
+    def test_unique_dirty(self):
+        try:
+            class Dirty(Enum):
+                __order__ = 'one two tres'
+                one = 1
+                two = 'dos'
+                tres = 1
+            unique(Dirty)
+        except ValueError:
+            exc = sys.exc_info()[1]
+            message = exc.args[0]
+        self.assertTrue('tres -> one' in message)
+
+        try:
+            class Dirtier(IntEnum):
+                _order_ = 'single double triple turkey'
+                single = 1
+                double = 1
+                triple = 3
+                turkey = 3
+            unique(Dirtier)
+        except ValueError:
+            exc = sys.exc_info()[1]
+            message = exc.args[0]
+        self.assertTrue('double -> single' in message)
+        self.assertTrue('turkey -> triple' in message)
+
+
+class TestMe(unittest.TestCase):
+
+    pass
+
+if __name__ == '__main__':
+    unittest.main()
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/enum34/setup.py
@@ -0,0 +1,99 @@
+import os
+import sys
+import setuptools
+from distutils.core import setup
+
+
+if sys.version_info[:2] < (2, 7):
+    required = ['ordereddict']
+else:
+    required = []
+
+long_desc = '''\
+enum --- support for enumerations
+========================================
+
+An enumeration 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.
+
+    from enum import Enum
+
+    class Fruit(Enum):
+        apple = 1
+        banana = 2
+        orange = 3
+
+    list(Fruit)
+    # [<Fruit.apple: 1>, <Fruit.banana: 2>, <Fruit.orange: 3>]
+
+    len(Fruit)
+    # 3
+
+    Fruit.banana
+    # <Fruit.banana: 2>
+
+    Fruit['banana']
+    # <Fruit.banana: 2>
+
+    Fruit(2)
+    # <Fruit.banana: 2>
+
+    Fruit.banana is Fruit['banana'] is Fruit(2)
+    # True
+
+    Fruit.banana.name
+    # 'banana'
+
+    Fruit.banana.value
+    # 2
+
+Repository and Issue Tracker at https://bitbucket.org/stoneleaf/enum34.
+'''
+
+py2_only = ()
+py3_only = ()
+make = [
+        'rst2pdf enum/doc/enum.rst --output=enum/doc/enum.pdf',
+        ]
+
+
+data = dict(
+        name='enum34',
+        version='1.1.6',
+        url='https://bitbucket.org/stoneleaf/enum34',
+        packages=['enum'],
+        package_data={
+            'enum' : [
+                'LICENSE',
+                'README',
+                'doc/enum.rst',
+                'doc/enum.pdf',
+                'test.py',
+                ]
+            },
+        license='BSD License',
+        description='Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4',
+        long_description=long_desc,
+        provides=['enum'],
+        install_requires=required,
+        author='Ethan Furman',
+        author_email='ethan@stoneleaf.us',
+        classifiers=[
+            'Development Status :: 5 - Production/Stable',
+            'Intended Audience :: Developers',
+            'License :: OSI Approved :: BSD License',
+            'Programming Language :: Python',
+            'Topic :: Software Development',
+            'Programming Language :: Python :: 2.4',
+            'Programming Language :: Python :: 2.5',
+            'Programming Language :: Python :: 2.6',
+            'Programming Language :: Python :: 2.7',
+            'Programming Language :: Python :: 3.3',
+            'Programming Language :: Python :: 3.4',
+            'Programming Language :: Python :: 3.5',
+            ],
+        )
+
+if __name__ == '__main__':
+    setup(**data)
--- a/third_party/python/moz.build
+++ b/third_party/python/moz.build
@@ -21,31 +21,37 @@ with Files('compare-locales/**'):
     BUG_COMPONENT = ('Localization Infrastructure and Tools', 'compare-locales')
 
 with Files('configobj/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('dlmanager/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
+with Files('enum34/**'):
+    BUG_COMPONENT = ('Release Engineering', 'General Automation')
+
 with Files('futures/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('gdbpp/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('jsmin/**'):
     BUG_COMPONENT = ('Firefox for Android', 'Build Config & IDE Support')
 
 with Files('lldbutils/**'):
     BUG_COMPONENT = ('Core', 'General')
 
 with Files('mock-1.0.0/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
+with Files('mozilla-version/**'):
+    BUG_COMPONENT = ('Release Engineering', 'General Automation')
+
 with Files('psutil/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('py/**'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('pyasn1/**'):
     BUG_COMPONENT = ('Release Engineering', 'General Automation')
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/LICENSE
@@ -0,0 +1,363 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. "Contributor"
+
+     means each individual or legal entity that creates, contributes to the
+     creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+
+     means the combination of the Contributions of others (if any) used by a
+     Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+
+     means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+
+     means Source Code Form to which the initial Contributor has attached the
+     notice in Exhibit A, the Executable Form of such Source Code Form, and
+     Modifications of such Source Code Form, in each case including portions
+     thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+     means
+
+     a. that the initial Contributor has attached the notice described in
+        Exhibit B to the Covered Software; or
+
+     b. that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the terms of
+        a Secondary License.
+
+1.6. "Executable Form"
+
+     means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+
+     means a work that combines Covered Software with other material, in a
+     separate file or files, that is not Covered Software.
+
+1.8. "License"
+
+     means this document.
+
+1.9. "Licensable"
+
+     means having the right to grant, to the maximum extent possible, whether
+     at the time of the initial grant or subsequently, any and all of the
+     rights conveyed by this License.
+
+1.10. "Modifications"
+
+     means any of the following:
+
+     a. any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered Software; or
+
+     b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. "Patent Claims" of a Contributor
+
+      means any patent claim(s), including without limitation, method,
+      process, and apparatus claims, in any patent Licensable by such
+      Contributor that would be infringed, but for the grant of the License,
+      by the making, using, selling, offering for sale, having made, import,
+      or transfer of either its Contributions or its Contributor Version.
+
+1.12. "Secondary License"
+
+      means either the GNU General Public License, Version 2.0, the GNU Lesser
+      General Public License, Version 2.1, the GNU Affero General Public
+      License, Version 3.0, or any later versions of those licenses.
+
+1.13. "Source Code Form"
+
+      means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+
+      means an individual or a legal entity exercising rights under this
+      License. For legal entities, "You" includes any entity that controls, is
+      controlled by, or is under common control with You. For purposes of this
+      definition, "control" means (a) the power, direct or indirect, to cause
+      the direction or management of such entity, whether by contract or
+      otherwise, or (b) ownership of more than fifty percent (50%) of the
+      outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+     Each Contributor hereby grants You a world-wide, royalty-free,
+     non-exclusive license:
+
+     a. under intellectual property rights (other than patent or trademark)
+        Licensable by such Contributor to use, reproduce, make available,
+        modify, display, perform, distribute, and otherwise exploit its
+        Contributions, either on an unmodified basis, with Modifications, or
+        as part of a Larger Work; and
+
+     b. under Patent Claims of such Contributor to make, use, sell, offer for
+        sale, have made, import, and otherwise transfer either its
+        Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+     The licenses granted in Section 2.1 with respect to any Contribution
+     become effective for each Contribution on the date the Contributor first
+     distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+     The licenses granted in this Section 2 are the only rights granted under
+     this License. No additional rights or licenses will be implied from the
+     distribution or licensing of Covered Software under this License.
+     Notwithstanding Section 2.1(b) above, no patent license is granted by a
+     Contributor:
+
+     a. for any code that a Contributor has removed from Covered Software; or
+
+     b. for infringements caused by: (i) Your and any other third party's
+        modifications of Covered Software, or (ii) the combination of its
+        Contributions with other software (except as part of its Contributor
+        Version); or
+
+     c. under Patent Claims infringed by Covered Software in the absence of
+        its Contributions.
+
+     This License does not grant any rights in the trademarks, service marks,
+     or logos of any Contributor (except as may be necessary to comply with
+     the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+     No Contributor makes additional grants as a result of Your choice to
+     distribute the Covered Software under a subsequent version of this
+     License (see Section 10.2) or under the terms of a Secondary License (if
+     permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+     Each Contributor represents that the Contributor believes its
+     Contributions are its original creation(s) or it has sufficient rights to
+     grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+     This License is not intended to limit any rights You have under
+     applicable copyright doctrines of fair use, fair dealing, or other
+     equivalents.
+
+2.7. Conditions
+
+     Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+     Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+     All distribution of Covered Software in Source Code Form, including any
+     Modifications that You create or to which You contribute, must be under
+     the terms of this License. You must inform recipients that the Source
+     Code Form of the Covered Software is governed by the terms of this
+     License, and how they can obtain a copy of this License. You may not
+     attempt to alter or restrict the recipients' rights in the Source Code
+     Form.
+
+3.2. Distribution of Executable Form
+
+     If You distribute Covered Software in Executable Form then:
+
+     a. such Covered Software must also be made available in Source Code Form,
+        as described in Section 3.1, and You must inform recipients of the
+        Executable Form how they can obtain a copy of such Source Code Form by
+        reasonable means in a timely manner, at a charge no more than the cost
+        of distribution to the recipient; and
+
+     b. You may distribute such Executable Form under the terms of this
+        License, or sublicense it under different terms, provided that the
+        license for the Executable Form does not attempt to limit or alter the
+        recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+     You may create and distribute a Larger Work under terms of Your choice,
+     provided that You also comply with the requirements of this License for
+     the Covered Software. If the Larger Work is a combination of Covered
+     Software with a work governed by one or more Secondary Licenses, and the
+     Covered Software is not Incompatible With Secondary Licenses, this
+     License permits You to additionally distribute such Covered Software
+     under the terms of such Secondary License(s), so that the recipient of
+     the Larger Work may, at their option, further distribute the Covered
+     Software under the terms of either this License or such Secondary
+     License(s).
+
+3.4. Notices
+
+     You may not remove or alter the substance of any license notices
+     (including copyright notices, patent notices, disclaimers of warranty, or
+     limitations of liability) contained within the Source Code Form of the
+     Covered Software, except that You may alter any license notices to the
+     extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+     You may choose to offer, and to charge a fee for, warranty, support,
+     indemnity or liability obligations to one or more recipients of Covered
+     Software. However, You may do so only on Your own behalf, and not on
+     behalf of any Contributor. You must make it absolutely clear that any
+     such warranty, support, indemnity, or liability obligation is offered by
+     You alone, and You hereby agree to indemnify every Contributor for any
+     liability incurred by such Contributor as a result of warranty, support,
+     indemnity or liability terms You offer. You may include additional
+     disclaimers of warranty and limitations of liability specific to any
+     jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+   If it is impossible for You to comply with any of the terms of this License
+   with respect to some or all of the Covered Software due to statute,
+   judicial order, or regulation then You must: (a) comply with the terms of
+   this License to the maximum extent possible; and (b) describe the
+   limitations and the code they affect. Such description must be placed in a
+   text file included with all distributions of the Covered Software under
+   this License. Except to the extent prohibited by statute or regulation,
+   such description must be sufficiently detailed for a recipient of ordinary
+   skill to be able to understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+     fail to comply with any of its terms. However, if You become compliant,
+     then the rights granted under this License from a particular Contributor
+     are reinstated (a) provisionally, unless and until such Contributor
+     explicitly and finally terminates Your grants, and (b) on an ongoing
+     basis, if such Contributor fails to notify You of the non-compliance by
+     some reasonable means prior to 60 days after You have come back into
+     compliance. Moreover, Your grants from a particular Contributor are
+     reinstated on an ongoing basis if such Contributor notifies You of the
+     non-compliance by some reasonable means, this is the first time You have
+     received notice of non-compliance with this License from such
+     Contributor, and You become compliant prior to 30 days after Your receipt
+     of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+     infringement claim (excluding declaratory judgment actions,
+     counter-claims, and cross-claims) alleging that a Contributor Version
+     directly or indirectly infringes any patent, then the rights granted to
+     You by any and all Contributors for the Covered Software under Section
+     2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+     license agreements (excluding distributors and resellers) which have been
+     validly granted by You or Your distributors under this License prior to
+     termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+   Covered Software is provided under this License on an "as is" basis,
+   without warranty of any kind, either expressed, implied, or statutory,
+   including, without limitation, warranties that the Covered Software is free
+   of defects, merchantable, fit for a particular purpose or non-infringing.
+   The entire risk as to the quality and performance of the Covered Software
+   is with You. Should any Covered Software prove defective in any respect,
+   You (not any Contributor) assume the cost of any necessary servicing,
+   repair, or correction. This disclaimer of warranty constitutes an essential
+   part of this License. No use of  any Covered Software is authorized under
+   this License except under this disclaimer.
+
+7. Limitation of Liability
+
+   Under no circumstances and under no legal theory, whether tort (including
+   negligence), contract, or otherwise, shall any Contributor, or anyone who
+   distributes Covered Software as permitted above, be liable to You for any
+   direct, indirect, special, incidental, or consequential damages of any
+   character including, without limitation, damages for lost profits, loss of
+   goodwill, work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses, even if such party shall have been
+   informed of the possibility of such damages. This limitation of liability
+   shall not apply to liability for death or personal injury resulting from
+   such party's negligence to the extent applicable law prohibits such
+   limitation. Some jurisdictions do not allow the exclusion or limitation of
+   incidental or consequential damages, so this exclusion and limitation may
+   not apply to You.
+
+8. Litigation
+
+   Any litigation relating to this License may be brought only in the courts
+   of a jurisdiction where the defendant maintains its principal place of
+   business and such litigation shall be governed by laws of that
+   jurisdiction, without reference to its conflict-of-law provisions. Nothing
+   in this Section shall prevent a party's ability to bring cross-claims or
+   counter-claims.
+
+9. Miscellaneous
+
+   This License represents the complete agreement concerning the subject
+   matter hereof. If any provision of this License is held to be
+   unenforceable, such provision shall be reformed only to the extent
+   necessary to make it enforceable. Any law or regulation which provides that
+   the language of a contract shall be construed against the drafter shall not
+   be used to construe this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+      Mozilla Foundation is the license steward. Except as provided in Section
+      10.3, no one other than the license steward has the right to modify or
+      publish new versions of this License. Each version will be given a
+      distinguishing version number.
+
+10.2. Effect of New Versions
+
+      You may distribute the Covered Software under the terms of the version
+      of the License under which You originally received the Covered Software,
+      or under the terms of any subsequent version published by the license
+      steward.
+
+10.3. Modified Versions
+
+      If you create software not governed by this License, and you want to
+      create a new license for such software, you may create and use a
+      modified version of this License if you rename the license and remove
+      any references to the name of the license steward (except to note that
+      such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+      Licenses If You choose to distribute Source Code Form that is
+      Incompatible With Secondary Licenses under the terms of this version of
+      the License, the notice described in Exhibit B of this License must be
+      attached.
+
+Exhibit A - Source Code Form License Notice
+
+      This Source Code Form is subject to the
+      terms of the Mozilla Public License, v.
+      2.0. If a copy of the MPL was not
+      distributed with this file, You can
+      obtain one at
+      http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file,
+then You may include the notice in a location (such as a LICENSE file in a
+relevant directory) where a recipient would be likely to look for such a
+notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+
+      This Source Code Form is "Incompatible
+      With Secondary Licenses", as defined by
+      the Mozilla Public License, v. 2.0.
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/MANIFEST.in
@@ -0,0 +1,8 @@
+include LICENSE
+include README.md
+include version.txt
+include requirements*.txt
+include requirements.txt.in   # Used by setup.py
+
+recursive-exclude * __pycache__
+recursive-exclude * *.py[co]
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/PKG-INFO
@@ -0,0 +1,13 @@
+Metadata-Version: 1.1
+Name: mozilla-version
+Version: 0.3.0
+Summary: Process Firefox versions numbers. Tells whether they are valid or not, whether they are nightlies or regular releases, whether this version precedes that other.
+    
+Home-page: https://github.com/mozilla-releng/mozilla-version
+Author: Mozilla Release Engineering
+Author-email: release+python@mozilla.com
+License: MPL2
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/README.md
@@ -0,0 +1,28 @@
+# mozilla-version
+
+[![Build Status](https://travis-ci.org/mozilla-releng/mozilla-version.svg?branch=master)](https://travis-ci.org/mozilla-releng/mozilla-version) [![Coverage Status](https://coveralls.io/repos/github/mozilla-releng/mozilla-version/badge.svg?branch=master)](https://coveralls.io/github/mozilla-releng/mozilla-version?branch=master)[![Documentation Status](https://readthedocs.org/projects/mozilla-version/badge/?version=latest)](https://mozilla-version.readthedocs.io/en/latest/?badge=latest)
+
+
+Process Firefox versions numbers. Tell whether they are valid or not, whether they are nightlies or regular releases, whether this version precedes that other.
+
+## Documentation
+
+https://mozilla-version.readthedocs.io/en/latest/
+
+## Get the code
+
+Just install it from pip:
+
+```sh
+pip install mozilla-version
+```
+
+
+## Hack on the code
+```sh
+virtualenv venv         # create the virtualenv in ./venv
+. venv/bin/activate    # activate it
+git clone https://github.com/mozilla-releng/mozilla-version
+cd mozilla-version
+pip install mozilla-version
+```
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/__init__.py
@@ -0,0 +1,1 @@
+"""Defines characteristics of Mozilla's version numbers."""
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/balrog.py
@@ -0,0 +1,142 @@
+"""Defines characteristics of a Balrog release name.
+
+Balrog is the server that delivers Firefox and Thunderbird updates. Release names follow
+the pattern "{product}-{version}-build{build_number}"
+
+Examples:
+    .. code-block:: python
+
+        from mozilla_version.balrog import BalrogReleaseName
+
+        balrog_release = BalrogReleaseName.parse('firefox-60.0.1-build1')
+
+        balrog_release.product                 # firefox
+        balrog_release.version.major_number    # 60
+        str(balrog_release)                    # 'firefox-60.0.1-build1'
+
+        previous_release = BalrogReleaseName.parse('firefox-60.0-build2')
+        previous_release < balrog_release      # True
+
+        invalid = BalrogReleaseName.parse('60.0.1')           # raises PatternNotMatchedError
+        invalid = BalrogReleaseName.parse('firefox-60.0.1')   # raises PatternNotMatchedError
+
+        # Releases can be built thanks to version classes like FirefoxVersion
+        BalrogReleaseName('firefox', FirefoxVersion(60, 0, 1, 1))  # 'firefox-60.0.1-build1'
+
+"""
+
+import attr
+import re
+
+from mozilla_version.errors import PatternNotMatchedError
+from mozilla_version.parser import get_value_matched_by_regex
+from mozilla_version.gecko import (
+    GeckoVersion, FirefoxVersion, DeveditionVersion, FennecVersion, ThunderbirdVersion
+)
+
+
+_VALID_ENOUGH_BALROG_RELEASE_PATTERN = re.compile(
+    r"^(?P<product>[a-z]+)-(?P<version>.+)$", re.IGNORECASE
+)
+
+
+_SUPPORTED_PRODUCTS = {
+    'firefox': FirefoxVersion,
+    'devedition': DeveditionVersion,
+    'fennec': FennecVersion,
+    'thunderbird': ThunderbirdVersion,
+}
+
+
+def _supported_product(string):
+    product = string.lower()
+    if product not in _SUPPORTED_PRODUCTS:
+        raise PatternNotMatchedError(string, pattern='unknown product')
+    return product
+
+
+def _products_must_be_identical(method):
+    def checker(this, other):
+        if this.product != other.product:
+            raise ValueError('Cannot compare "{}" and "{}"'.format(this.product, other.product))
+        return method(this, other)
+    return checker
+
+
+@attr.s(frozen=True, cmp=False)
+class BalrogReleaseName(object):
+    """Class that validates and handles Balrog release names.
+
+    Raises:
+        PatternNotMatchedError: if a parsed string doesn't match the pattern of a valid release
+        MissingFieldError: if a mandatory field is missing in the string. Mandatory fields are
+            `product`, `major_number`, `minor_number`, and `build_number`
+        ValueError: if an integer can't be cast or is not (strictly) positive
+        TooManyTypesError: if the string matches more than 1 `VersionType`
+        NoVersionTypeError: if the string matches none.
+
+    """
+
+    product = attr.ib(type=str, converter=_supported_product)
+    version = attr.ib(type=GeckoVersion)
+
+    def __attrs_post_init__(self):
+        """Ensure attributes are sane all together."""
+        if self.version.build_number is None:
+            raise PatternNotMatchedError(self, pattern='build_number must exist')
+
+    @classmethod
+    def parse(cls, release_string):
+        """Construct an object representing a valid Firefox version number."""
+        regex_matches = _VALID_ENOUGH_BALROG_RELEASE_PATTERN.match(release_string)
+        if regex_matches is None:
+            raise PatternNotMatchedError(release_string, _VALID_ENOUGH_BALROG_RELEASE_PATTERN)
+
+        product = get_value_matched_by_regex('product', regex_matches, release_string)
+        try:
+            VersionClass = _SUPPORTED_PRODUCTS[product.lower()]
+        except KeyError:
+            raise PatternNotMatchedError(release_string, pattern='unknown product')
+
+        version_string = get_value_matched_by_regex('version', regex_matches, release_string)
+        version = VersionClass.parse(version_string)
+
+        return cls(product, version)
+
+    def __str__(self):
+        """Implement string representation.
+
+        Computes a new string based on the given attributes.
+        """
+        version_string = str(self.version).replace('build', '-build')
+        return '{}-{}'.format(self.product, version_string)
+
+    @_products_must_be_identical
+    def __eq__(self, other):
+        """Implement `==` operator."""
+        return self.version == other.version
+
+    @_products_must_be_identical
+    def __ne__(self, other):
+        """Implement `!=` operator."""
+        return self.version != other.version
+
+    @_products_must_be_identical
+    def __lt__(self, other):
+        """Implement `<` operator."""
+        return self.version < other.version
+
+    @_products_must_be_identical
+    def __le__(self, other):
+        """Implement `<=` operator."""
+        return self.version <= other.version
+
+    @_products_must_be_identical
+    def __gt__(self, other):
+        """Implement `>` operator."""
+        return self.version > other.version
+
+    @_products_must_be_identical
+    def __ge__(self, other):
+        """Implement `>=` operator."""
+        return self.version >= other.version
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/errors.py
@@ -0,0 +1,64 @@
+"""Defines all errors reported by mozilla-version."""
+
+
+class PatternNotMatchedError(ValueError):
+    """Error when a string doesn't match an expected pattern.
+
+    Args:
+        string (str): The string it was unable to match.
+        pattern (str): The pattern used it tried to match.
+    """
+
+    def __init__(self, string, pattern):
+        """Constructor."""
+        super(PatternNotMatchedError, self).__init__(
+            '"{}" does not match the pattern: {}'.format(string, pattern)
+        )
+
+
+class NoVersionTypeError(ValueError):
+    """Error when `version_string` matched the pattern, but was unable to find its type.
+
+    Args:
+        version_string (str): The string it was unable to guess the type.
+    """
+
+    def __init__(self, version_string):
+        """Constructor."""
+        super(NoVersionTypeError, self).__init__(
+            'Version "{}" matched the pattern of a valid version, but it is unable to find what type it is. \
+This is likely a bug in mozilla-version'.format(version_string)
+        )
+
+
+class MissingFieldError(ValueError):
+    """Error when `version_string` lacks an expected field.
+
+    Args:
+        version_string (str): The string it was unable to extract a given field.
+        field_name (str): The name of the missing field.
+    """
+
+    def __init__(self, version_string, field_name):
+        """Constructor."""
+        super(MissingFieldError, self).__init__(
+            'Release "{}" does not contain a valid {}'.format(version_string, field_name)
+        )
+
+
+class TooManyTypesError(ValueError):
+    """Error when `version_string` has too many types."""
+
+    def __init__(self, version_string, first_matched_type, second_matched_type):
+        """Constructor.
+
+        Args:
+            version_string (str): The string that gave too many types.
+            first_matched_type (str): The name of the first detected type.
+            second_matched_type (str): The name of the second detected type
+        """
+        super(TooManyTypesError, self).__init__(
+            'Release "{}" cannot match types "{}" and "{}"'.format(
+                version_string, first_matched_type, second_matched_type
+            )
+        )
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/gecko.py
@@ -0,0 +1,484 @@
+"""Defines characteristics of a Gecko version number, including Firefox.
+
+Examples:
+    .. code-block:: python
+
+        from mozilla_version.gecko import FirefoxVersion
+
+        version = FirefoxVersion.parse('60.0.1')
+
+        version.major_number    # 60
+        version.minor_number    # 0
+        version.patch_number    # 1
+
+        version.is_release  # True
+        version.is_beta     # False
+        version.is_nightly  # False
+
+        str(version)        # '60.0.1'
+
+        previous_version = FirefoxVersion.parse('60.0b14')
+        previous_version < version      # True
+
+        previous_version.beta_number    # 14
+        previous_version.major_number   # 60
+        previous_version.minor_number   # 0
+        previous_version.patch_number   # raises AttributeError
+
+        previous_version.is_beta     # True
+        previous_version.is_release  # False
+        previous_version.is_nightly  # False
+
+        invalid_version = FirefoxVersion.parse('60.1')      # raises PatternNotMatchedError
+        invalid_version = FirefoxVersion.parse('60.0.0')    # raises PatternNotMatchedError
+        version = FirefoxVersion.parse('60.0')    # valid
+
+        # Versions can be built by raw values
+        FirefoxVersion(60, 0))         # '60.0'
+        FirefoxVersion(60, 0, 1))      # '60.0.1'
+        FirefoxVersion(60, 1, 0))      # '60.1.0'
+        FirefoxVersion(60, 0, 1, 1))   # '60.0.1build1'
+        FirefoxVersion(60, 0, beta_number=1))       # '60.0b1'
+        FirefoxVersion(60, 0, is_nightly=True))     # '60.0a1'
+        FirefoxVersion(60, 0, is_aurora_or_devedition=True))    # '60.0a2'
+        FirefoxVersion(60, 0, is_esr=True))         # '60.0esr'
+        FirefoxVersion(60, 0, 1, is_esr=True))      # '60.0.1esr'
+
+"""
+
+import attr
+import re
+
+from mozilla_version.errors import (
+    PatternNotMatchedError, MissingFieldError, TooManyTypesError, NoVersionTypeError
+)
+from mozilla_version.parser import get_value_matched_by_regex
+from mozilla_version.version import VersionType
+
+
+def _positive_int(val):
+    if isinstance(val, float):
+        raise ValueError('"{}" must not be a float'.format(val))
+    val = int(val)
+    if val >= 0:
+        return val
+    raise ValueError('"{}" must be positive'.format(val))
+
+
+def _positive_int_or_none(val):
+    if val is None:
+        return val
+    return _positive_int(val)
+
+
+def _strictly_positive_int_or_none(val):
+    val = _positive_int_or_none(val)
+    if val is None or val > 0:
+        return val
+    raise ValueError('"{}" must be strictly positive'.format(val))
+
+
+def _does_regex_have_group(regex_matches, group_name):
+    try:
+        return regex_matches.group(group_name) is not None
+    except IndexError:
+        return False
+
+
+def _find_type(version):
+    version_type = None
+
+    def ensure_version_type_is_not_already_defined(previous_type, candidate_type):
+        if previous_type is not None:
+            raise TooManyTypesError(
+                str(version), previous_type, candidate_type
+            )
+
+    if version.is_nightly:
+        version_type = VersionType.NIGHTLY
+    if version.is_aurora_or_devedition:
+        ensure_version_type_is_not_already_defined(
+            version_type, VersionType.AURORA_OR_DEVEDITION
+        )
+        version_type = VersionType.AURORA_OR_DEVEDITION
+    if version.is_beta:
+        ensure_version_type_is_not_already_defined(version_type, VersionType.BETA)
+        version_type = VersionType.BETA
+    if version.is_esr:
+        ensure_version_type_is_not_already_defined(version_type, VersionType.ESR)
+        version_type = VersionType.ESR
+    if version.is_release:
+        ensure_version_type_is_not_already_defined(version_type, VersionType.RELEASE)
+        version_type = VersionType.RELEASE
+
+    if version_type is None:
+        raise NoVersionTypeError(str(version))
+
+    return version_type
+
+
+@attr.s(frozen=True, cmp=False)
+class GeckoVersion(object):
+    """Class that validates and handles version numbers for Gecko-based products.
+
+    You may want to use specific classes like FirefoxVersion. These classes define edge cases
+    that were shipped.
+
+    Raises:
+        PatternNotMatchedError: if the string doesn't match the pattern of a valid version number
+        MissingFieldError: if a mandatory field is missing in the string. Mandatory fields are
+            `major_number` and `minor_number`
+        ValueError: if an integer can't be cast or is not (strictly) positive
+        TooManyTypesError: if the string matches more than 1 `VersionType`
+        NoVersionTypeError: if the string matches none.
+
+    """
+
+    # XXX This pattern doesn't catch all subtleties of a Firefox version (like 32.5 isn't valid).
+    # This regex is intended to assign numbers. Then checks are done by attrs and
+    # __attrs_post_init__()
+    _VALID_ENOUGH_VERSION_PATTERN = re.compile(r"""
+        ^(?P<major_number>\d+)
+        \.(?P<minor_number>\d+)
+        (\.(?P<patch_number>\d+))?
+        (
+            (?P<is_nightly>a1)
+            |(?P<is_aurora_or_devedition>a2)
+            |b(?P<beta_number>\d+)
+            |(?P<is_esr>esr)
+        )?
+        -?(build(?P<build_number>\d+))?$""", re.VERBOSE)
+
+    _ALL_VERSION_NUMBERS_TYPES = (
+        'major_number', 'minor_number', 'patch_number', 'beta_number',
+    )
+
+    major_number = attr.ib(type=int, converter=_positive_int)
+    minor_number = attr.ib(type=int, converter=_positive_int)
+    patch_number = attr.ib(type=int, converter=_positive_int_or_none, default=None)
+    build_number = attr.ib(type=int, converter=_strictly_positive_int_or_none, default=None)
+    beta_number = attr.ib(type=int, converter=_strictly_positive_int_or_none, default=None)
+    is_nightly = attr.ib(type=bool, default=False)
+    is_aurora_or_devedition = attr.ib(type=bool, default=False)
+    is_esr = attr.ib(type=bool, default=False)
+    version_type = attr.ib(init=False, default=attr.Factory(_find_type, takes_self=True))
+
+    def __attrs_post_init__(self):
+        """Ensure attributes are sane all together."""
+        if (
+            (self.minor_number == 0 and self.patch_number == 0) or
+            (self.minor_number != 0 and self.patch_number is None) or
+            (self.beta_number is not None and self.patch_number is not None) or
+            (self.patch_number is not None and self.is_nightly) or
+            (self.patch_number is not None and self.is_aurora_or_devedition)
+        ):
+            raise PatternNotMatchedError(self, pattern='hard coded checks')
+
+    @classmethod
+    def parse(cls, version_string):
+        """Construct an object representing a valid Firefox version number."""
+        regex_matches = cls._VALID_ENOUGH_VERSION_PATTERN.match(version_string)
+
+        if regex_matches is None:
+            raise PatternNotMatchedError(version_string, cls._VALID_ENOUGH_VERSION_PATTERN)
+
+        args = {}
+
+        for field in ('major_number', 'minor_number'):
+            args[field] = get_value_matched_by_regex(field, regex_matches, version_string)
+        for field in ('patch_number', 'beta_number', 'build_number'):
+            try:
+                args[field] = get_value_matched_by_regex(field, regex_matches, version_string)
+            except MissingFieldError:
+                pass
+
+        return cls(
+            is_nightly=_does_regex_have_group(regex_matches, 'is_nightly'),
+            is_aurora_or_devedition=_does_regex_have_group(
+                regex_matches, 'is_aurora_or_devedition'
+            ),
+            is_esr=_does_regex_have_group(regex_matches, 'is_esr'),
+            **args
+        )
+
+    @property
+    def is_beta(self):
+        """Return `True` if `FirefoxVersion` was built with a string matching a beta version."""
+        return self.beta_number is not None
+
+    @property
+    def is_release(self):
+        """Return `True` if `FirefoxVersion` was built with a string matching a release version."""
+        return not (self.is_nightly or self.is_aurora_or_devedition or self.is_beta or self.is_esr)
+
+    def __str__(self):
+        """Implement string representation.
+
+        Computes a new string based on the given attributes.
+        """
+        semvers = [str(self.major_number), str(self.minor_number)]
+        if self.patch_number is not None:
+            semvers.append(str(self.patch_number))
+
+        string = '.'.join(semvers)
+
+        if self.is_nightly:
+            string = '{}a1'.format(string)
+        elif self.is_aurora_or_devedition:
+            string = '{}a2'.format(string)
+        elif self.is_beta:
+            string = '{}b{}'.format(string, self.beta_number)
+        elif self.is_esr:
+            string = '{}esr'.format(string)
+
+        if self.build_number is not None:
+            string = '{}build{}'.format(string, self.build_number)
+
+        return string
+
+    def __eq__(self, other):
+        """Implement `==` operator.
+
+        A version is considered equal to another if all numbers match and if they are of the same
+        `VersionType`. Like said in `VersionType`, release and ESR are considered equal (if they
+        share the same numbers). If a version contains a build number but not the other, the build
+        number won't be considered in the comparison.
+
+        Examples:
+            .. code-block:: python
+
+                assert GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0')
+                assert GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0esr')
+                assert GeckoVersion.parse('60.0') == GeckoVersion.parse('60.0build1')
+                assert GeckoVersion.parse('60.0build1') == GeckoVersion.parse('60.0build1')
+
+                assert GeckoVersion.parse('60.0') != GeckoVersion.parse('61.0')
+                assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.1.0')
+                assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0.1')
+                assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0a1')
+                assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0a2')
+                assert GeckoVersion.parse('60.0') != GeckoVersion.parse('60.0b1')
+                assert GeckoVersion.parse('60.0build1') != GeckoVersion.parse('60.0build2')
+
+        """
+        return self._compare(other) == 0
+
+    def __ne__(self, other):
+        """Implement `!=` operator."""
+        return self._compare(other) != 0
+
+    def __lt__(self, other):
+        """Implement `<` operator."""
+        return self._compare(other) < 0
+
+    def __le__(self, other):
+        """Implement `<=` operator."""
+        return self._compare(other) <= 0
+
+    def __gt__(self, other):
+        """Implement `>` operator."""
+        return self._compare(other) > 0
+
+    def __ge__(self, other):
+        """Implement `>=` operator."""
+        return self._compare(other) >= 0
+
+    def _compare(self, other):
+        """Compare this release with another.
+
+        Returns:
+            0 if equal
+            < 0 is this precedes the other
+            > 0 if the other precedes this
+
+        """
+        if isinstance(other, str):
+            other = GeckoVersion.parse(other)
+        elif not isinstance(other, GeckoVersion):
+            raise ValueError('Cannot compare "{}", type not supported!'.format(other))
+
+        for field in ('major_number', 'minor_number', 'patch_number'):
+            this_number = getattr(self, field)
+            this_number = 0 if this_number is None else this_number
+            other_number = getattr(other, field)
+            other_number = 0 if other_number is None else other_number
+
+            difference = this_number - other_number
+
+            if difference != 0:
+                return difference
+
+        channel_difference = self._compare_version_type(other)
+        if channel_difference != 0:
+            return channel_difference
+
+        if self.is_beta and other.is_beta:
+            beta_difference = self.beta_number - other.beta_number
+            if beta_difference != 0:
+                return beta_difference
+
+        # Build numbers are a special case. We might compare a regular version number
+        # (like "32.0b8") versus a release build (as in "32.0b8build1"). As a consequence,
+        # we only compare build_numbers when we both have them.
+        try:
+            return self.build_number - other.build_number
+        except TypeError:
+            pass
+
+        return 0
+
+    def _compare_version_type(self, other):
+        return self.version_type.compare(other.version_type)
+
+
+class _VersionWithEdgeCases(GeckoVersion):
+    def __attrs_post_init__(self):
+        for edge_case in self._RELEASED_EDGE_CASES:
+            if all(
+                getattr(self, number_type) == edge_case.get(number_type, None)
+                for number_type in self._ALL_VERSION_NUMBERS_TYPES
+            ):
+                if self.build_number is None:
+                    return
+                elif self.build_number == edge_case.get('build_number', None):
+                    return
+
+        super(_VersionWithEdgeCases, self).__attrs_post_init__()
+
+
+class FirefoxVersion(_VersionWithEdgeCases):
+    """Class that validates and handles Firefox version numbers."""
+
+    _RELEASED_EDGE_CASES = ({
+        'major_number': 33,
+        'minor_number': 1,
+        'build_number': 1,
+    }, {
+        'major_number': 33,
+        'minor_number': 1,
+        'build_number': 2,
+    }, {
+        'major_number': 33,
+        'minor_number': 1,
+        'build_number': 3,
+    }, {
+        'major_number': 38,
+        'minor_number': 0,
+        'patch_number': 5,
+        'beta_number': 1,
+        'build_number': 1,
+    }, {
+        'major_number': 38,
+        'minor_number': 0,
+        'patch_number': 5,
+        'beta_number': 1,
+        'build_number': 2,
+    }, {
+        'major_number': 38,
+        'minor_number': 0,
+        'patch_number': 5,
+        'beta_number': 2,
+        'build_number': 1,
+    }, {
+        'major_number': 38,
+        'minor_number': 0,
+        'patch_number': 5,
+        'beta_number': 3,
+        'build_number': 1,
+    })
+
+
+class DeveditionVersion(GeckoVersion):
+    """Class that validates and handles Devedition after it became an equivalent to beta."""
+
+    # No edge case were shipped
+
+    def __attrs_post_init__(self):
+        """Ensure attributes are sane all together."""
+        if (
+            (not self.is_beta) or
+            (self.major_number < 54) or
+            (self.major_number == 54 and self.beta_number < 11)
+        ):
+            raise PatternNotMatchedError(
+                self, pattern='Devedition as a product must be a beta >= 54.0b11'
+            )
+
+
+class FennecVersion(_VersionWithEdgeCases):
+    """Class that validates and handles Fennec (Firefox for Android) version numbers."""
+
+    _RELEASED_EDGE_CASES = ({
+        'major_number': 33,
+        'minor_number': 1,
+        'build_number': 1,
+    }, {
+        'major_number': 33,
+        'minor_number': 1,
+        'build_number': 2,
+    }, {
+        'major_number': 38,
+        'minor_number': 0,
+        'patch_number': 5,
+        'beta_number': 4,
+        'build_number': 1,
+    })
+
+
+class ThunderbirdVersion(_VersionWithEdgeCases):
+    """Class that validates and handles Thunderbird version numbers."""
+
+    _RELEASED_EDGE_CASES = ({
+        'major_number': 45,
+        'minor_number': 1,
+        'beta_number': 1,
+        'build_number': 1,
+    }, {
+        'major_number': 45,
+        'minor_number': 2,
+        'build_number': 1,
+    }, {
+        'major_number': 45,
+        'minor_number': 2,
+        'build_number': 2,
+    }, {
+        'major_number': 45,
+        'minor_number': 2,
+        'beta_number': 1,
+        'build_number': 2,
+    })
+
+
+class GeckoSnapVersion(GeckoVersion):
+    """Class that validates and handles Gecko's Snap version numbers.
+
+    Snap is a Linux packaging format developped by Canonical. Valid numbers are like "63.0b7-1",
+    "1" stands for "build1". Release Engineering set this scheme at the beginning of Snap and now
+    we can't rename published snap to the regular pattern like "63.0b7-build1".
+    """
+
+    # Our Snaps are recent enough to not list any edge case, yet.
+
+    # Differences between this regex and the one in GeckoVersion:
+    #   * no a2
+    #   * no "build"
+    #   * but mandatory dash and build number.
+    # Example: 63.0b7-1
+    _VALID_ENOUGH_VERSION_PATTERN = re.compile(r"""
+        ^(?P<major_number>\d+)
+        \.(?P<minor_number>\d+)
+        (\.(?P<patch_number>\d+))?
+        (
+            (?P<is_nightly>a1)
+            |b(?P<beta_number>\d+)
+            |(?P<is_esr>esr)
+        )?
+        -(?P<build_number>\d+)$""", re.VERBOSE)
+
+    def __str__(self):
+        """Implement string representation.
+
+        Returns format like "63.0b7-1"
+        """
+        string = super(GeckoSnapVersion, self).__str__()
+        return string.replace('build', '-')
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/parser.py
@@ -0,0 +1,15 @@
+"""Defines parser helpers."""
+
+from mozilla_version.errors import MissingFieldError
+
+
+def get_value_matched_by_regex(field_name, regex_matches, string):
+    """Ensure value stored in regex group exists."""
+    try:
+        value = regex_matches.group(field_name)
+        if value is not None:
+            return value
+    except IndexError:
+        pass
+
+    raise MissingFieldError(string, field_name)
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/test/test_balrog.py
@@ -0,0 +1,166 @@
+import pytest
+
+from mozilla_version.balrog import BalrogReleaseName
+from mozilla_version.errors import PatternNotMatchedError
+from mozilla_version.gecko import FirefoxVersion
+
+
+@pytest.mark.parametrize(
+    'product, major_number, minor_number, patch_number, beta_number, build_number, is_nightly, \
+is_aurora_or_devedition, is_esr, expected_output_string', ((
+    'firefox', 32, 0, None, None, 1, False, False, False, 'firefox-32.0-build1'
+), (
+    'firefox', 32, 0, 1, None, 2, False, False, False, 'firefox-32.0.1-build2'
+), (
+    'firefox', 32, 0, None, 3, 4, False, False, False, 'firefox-32.0b3-build4'
+), (
+    'firefox', 32, 0, None, None, 5, True, False, False, 'firefox-32.0a1-build5'
+), (
+    'firefox', 32, 0, None, None, 6, False, True, False, 'firefox-32.0a2-build6'
+), (
+    'firefox', 32, 0, None, None, 7, False, False, True, 'firefox-32.0esr-build7'
+), (
+    'firefox', 32, 0, 1, None, 8, False, False, True, 'firefox-32.0.1esr-build8'
+), (
+    'devedition', 54, 0, None, 12, 1, False, False, False, 'devedition-54.0b12-build1'
+), (
+    'fennec', 32, 0, None, None, 1, False, False, False, 'fennec-32.0-build1'
+), (
+    'thunderbird', 32, 0, None, None, 1, False, False, False, 'thunderbird-32.0-build1'
+)))
+def test_balrog_release_name_constructor_and_str(
+    product, major_number, minor_number, patch_number, beta_number, build_number, is_nightly,
+    is_aurora_or_devedition, is_esr, expected_output_string
+):
+    assert str(BalrogReleaseName(product, FirefoxVersion(
+        major_number=major_number,
+        minor_number=minor_number,
+        patch_number=patch_number,
+        build_number=build_number,
+        beta_number=beta_number,
+        is_nightly=is_nightly,
+        is_aurora_or_devedition=is_aurora_or_devedition,
+        is_esr=is_esr
+    ))) == expected_output_string
+
+
+@pytest.mark.parametrize('product, major_number, minor_number, patch_number, beta_number, build_number, is_nightly, is_aurora_or_devedition, is_esr, ExpectedErrorType', ((
+    ('nonexistingproduct', 32, 0, None, None, 1, False, False, False, PatternNotMatchedError),
+    ('firefox', 32, 0, None, None, None, False, False, False, PatternNotMatchedError),
+)))
+def test_fail_balrog_release_constructor(product, major_number, minor_number, patch_number, beta_number, build_number, is_nightly, is_aurora_or_devedition, is_esr, ExpectedErrorType):
+    with pytest.raises(ExpectedErrorType):
+        BalrogReleaseName(product, FirefoxVersion(
+            major_number=major_number,
+            minor_number=minor_number,
+            patch_number=patch_number,
+            beta_number=beta_number,
+            build_number=build_number,
+            is_nightly=is_nightly,
+            is_aurora_or_devedition=is_aurora_or_devedition,
+            is_esr=is_esr
+        ))
+
+
+@pytest.mark.parametrize('string, expected_string', ((
+    ('firefox-32.0-build1', 'firefox-32.0-build1'),
+    ('firefox-32.0.1-build2', 'firefox-32.0.1-build2'),
+    ('firefox-32.0b3-build4', 'firefox-32.0b3-build4'),
+    ('firefox-32.0a1-build5', 'firefox-32.0a1-build5'),
+    ('firefox-32.0a2-build6', 'firefox-32.0a2-build6'),
+    ('firefox-32.0esr-build7', 'firefox-32.0esr-build7'),
+    ('firefox-32.0.1esr-build8', 'firefox-32.0.1esr-build8'),
+
+    ('firefox-32.0build1', 'firefox-32.0-build1'),
+)))
+def test_balrog_release_name_parse(string, expected_string):
+    assert str(BalrogReleaseName.parse(string)) == expected_string
+
+
+@pytest.mark.parametrize('string, ExpectedErrorType', (
+    ('firefox-32.0', PatternNotMatchedError),
+
+    ('firefox32.0-build1', PatternNotMatchedError),
+    ('firefox32.0build1', PatternNotMatchedError),
+    ('firefox-32.0--build1', PatternNotMatchedError),
+    ('firefox-build1', PatternNotMatchedError),
+    ('nonexistingproduct-32.0-build1', PatternNotMatchedError),
+
+    ('firefox-32-build1', PatternNotMatchedError),
+    ('firefox-32.b2-build1', PatternNotMatchedError),
+    ('firefox-.1-build1', PatternNotMatchedError),
+    ('firefox-32.0.0-build1', PatternNotMatchedError),
+    ('firefox-32.2-build1', PatternNotMatchedError),
+    ('firefox-32.02-build1', PatternNotMatchedError),
+    ('firefox-32.0a0-build1', ValueError),
+    ('firefox-32.0b0-build1', ValueError),
+    ('firefox-32.0.1a1-build1', PatternNotMatchedError),
+    ('firefox-32.0.1a2-build1', PatternNotMatchedError),
+    ('firefox-32.0.1b2-build1', PatternNotMatchedError),
+    ('firefox-32.0-build0', ValueError),
+    ('firefox-32.0a1a2-build1', PatternNotMatchedError),
+    ('firefox-32.0a1b2-build1', PatternNotMatchedError),
+    ('firefox-32.0b2esr-build1', PatternNotMatchedError),
+    ('firefox-32.0esrb2-build1', PatternNotMatchedError),
+))
+def test_firefox_version_raises_when_invalid_version_is_given(string, ExpectedErrorType):
+    with pytest.raises(ExpectedErrorType):
+        BalrogReleaseName.parse(string)
+
+
+@pytest.mark.parametrize('previous, next', (
+    ('firefox-32.0-build1', 'firefox-33.0-build1'),
+    ('firefox-32.0-build1', 'firefox-32.1.0-build1'),
+    ('firefox-32.0-build1', 'firefox-32.0.1-build1'),
+    ('firefox-32.0-build1', 'firefox-32.0-build2'),
+
+    ('firefox-32.0a1-build1', 'firefox-32.0-build1'),
+    ('firefox-32.0a2-build1', 'firefox-32.0-build1'),
+    ('firefox-32.0b1-build1', 'firefox-32.0-build1'),
+
+    ('firefox-32.0.1-build1', 'firefox-33.0-build1'),
+    ('firefox-32.0.1-build1', 'firefox-32.1.0-build1'),
+    ('firefox-32.0.1-build1', 'firefox-32.0.2-build1'),
+    ('firefox-32.0.1-build1', 'firefox-32.0.1-build2'),
+
+    ('firefox-32.1.0-build1', 'firefox-33.0-build1'),
+    ('firefox-32.1.0-build1', 'firefox-32.2.0-build1'),
+    ('firefox-32.1.0-build1', 'firefox-32.1.1-build1'),
+    ('firefox-32.1.0-build1', 'firefox-32.1.0-build2'),
+
+    ('firefox-32.0b1-build1', 'firefox-33.0b1-build1'),
+    ('firefox-32.0b1-build1', 'firefox-32.0b2-build1'),
+    ('firefox-32.0b1-build1', 'firefox-32.0b1-build2'),
+
+    ('firefox-2.0-build1', 'firefox-10.0-build1'),
+    ('firefox-10.2.0-build1', 'firefox-10.10.0-build1'),
+    ('firefox-10.0.2-build1', 'firefox-10.0.10-build1'),
+    ('firefox-10.10.1-build1', 'firefox-10.10.10-build1'),
+    ('firefox-10.0-build2', 'firefox-10.0-build10'),
+    ('firefox-10.0b2-build1', 'firefox-10.0b10-build1'),
+))
+def test_balrog_release_implements_lt_operator(previous, next):
+    assert BalrogReleaseName.parse(previous) < BalrogReleaseName.parse(next)
+
+
+def test_fail_balrog_release_lt_operator():
+    with pytest.raises(ValueError):
+        assert BalrogReleaseName.parse('thunderbird-32.0-build1') < BalrogReleaseName.parse('Firefox-32.0-build2')
+
+
+def test_balrog_release_implements_remaining_comparision_operators():
+    assert BalrogReleaseName.parse('firefox-32.0-build1') == BalrogReleaseName.parse('firefox-32.0-build1')
+    assert BalrogReleaseName.parse('firefox-32.0-build1') != BalrogReleaseName.parse('firefox-33.0-build1')
+
+    assert BalrogReleaseName.parse('firefox-32.0-build1') <= BalrogReleaseName.parse('firefox-32.0-build1')
+    assert BalrogReleaseName.parse('firefox-32.0-build1') <= BalrogReleaseName.parse('firefox-33.0-build1')
+
+    assert BalrogReleaseName.parse('firefox-33.0-build1') >= BalrogReleaseName.parse('firefox-32.0-build1')
+    assert BalrogReleaseName.parse('firefox-33.0-build1') >= BalrogReleaseName.parse('firefox-33.0-build1')
+
+    assert BalrogReleaseName.parse('firefox-33.0-build1') > BalrogReleaseName.parse('firefox-32.0-build1')
+    assert not BalrogReleaseName.parse('firefox-33.0-build1') > BalrogReleaseName.parse('firefox-33.0-build1')
+
+    assert not BalrogReleaseName.parse('firefox-32.0-build1') < BalrogReleaseName.parse('firefox-32.0-build1')
+
+    assert BalrogReleaseName.parse('firefox-33.0-build1') != BalrogReleaseName.parse('firefox-32.0-build1')
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/test/test_gecko.py
@@ -0,0 +1,364 @@
+import pytest
+import re
+
+from distutils.version import StrictVersion, LooseVersion
+
+import mozilla_version.gecko
+
+from mozilla_version.errors import PatternNotMatchedError, TooManyTypesError, NoVersionTypeError
+from mozilla_version.gecko import (
+    FirefoxVersion, DeveditionVersion, ThunderbirdVersion, FennecVersion, GeckoSnapVersion
+)
+
+
+VALID_VERSIONS = {
+    '32.0a1': 'nightly',
+    '32.0a2': 'aurora_or_devedition',
+    '32.0b2': 'beta',
+    '32.0b10': 'beta',
+    '32.0': 'release',
+    '32.0.1': 'release',
+    '32.0esr': 'esr',
+    '32.0.1esr': 'esr',
+}
+
+
+@pytest.mark.parametrize('major_number, minor_number, patch_number, beta_number, build_number, is_nightly, is_aurora_or_devedition, is_esr, expected_output_string', ((
+    32, 0, None, None, None, False, False, False, '32.0'
+), (
+    32, 0, 1, None, None, False, False, False, '32.0.1'
+), (
+    32, 0, None, 3, None, False, False, False, '32.0b3'
+), (
+    32, 0, None, None, 10, False, False, False, '32.0build10'
+), (
+    32, 0, None, None, None, True, False, False, '32.0a1'
+), (
+    32, 0, None, None, None, False, True, False, '32.0a2'
+), (
+    32, 0, None, None, None, False, False, True, '32.0esr'
+), (
+    32, 0, 1, None, None, False, False, True, '32.0.1esr'
+)))
+def test_firefox_version_constructor_and_str(major_number, minor_number, patch_number, beta_number, build_number, is_nightly, is_aurora_or_devedition, is_esr, expected_output_string):
+    assert str(FirefoxVersion(
+        major_number=major_number,
+        minor_number=minor_number,
+        patch_number=patch_number,
+        beta_number=beta_number,
+        build_number=build_number,
+        is_nightly=is_nightly,
+        is_aurora_or_devedition=is_aurora_or_devedition,
+        is_esr=is_esr
+    )) == expected_output_string
+
+
+@pytest.mark.parametrize('major_number, minor_number, patch_number, beta_number, build_number, is_nightly, is_aurora_or_devedition, is_esr, ExpectedErrorType', ((
+    32, 0, None, 1, None, True, False, False, TooManyTypesError
+), (
+    32, 0, None, 1, None, False, True, False, TooManyTypesError
+), (
+    32, 0, None, 1, None, False, False, True, TooManyTypesError
+), (
+    32, 0, None, None, None, True, True, False, TooManyTypesError
+), (
+    32, 0, None, None, None, True, False, True, TooManyTypesError
+), (
+    32, 0, None, None, None, False, True, True, TooManyTypesError
+), (
+    32, 0, None, None, None, True, True, True, TooManyTypesError
+), (
+    32, 0, 0, None, None, False, False, False, PatternNotMatchedError
+), (
+    32, 0, None, 0, None, False, False, False, ValueError
+), (
+    32, 0, None, None, 0, False, False, False, ValueError
+), (
+    32, 0, 1, 1, None, False, False, False, PatternNotMatchedError
+), (
+    32, 0, 1, None, None, True, False, False, PatternNotMatchedError
+), (
+    32, 0, 1, None, None, False, True, False, PatternNotMatchedError
+), (
+    -1, 0, None, None, None, False, False, False, ValueError
+), (
+    32, -1, None, None, None, False, False, False, ValueError
+), (
+    32, 0, -1, None, None, False, False, False, ValueError
+), (
+    2.2, 0, 0, None, None, False, False, False, ValueError
+), (
+    'some string', 0, 0, None, None, False, False, False, ValueError
+)))
+def test_fail_firefox_version_constructor(major_number, minor_number, patch_number, beta_number, build_number, is_nightly, is_aurora_or_devedition, is_esr, ExpectedErrorType):
+    with pytest.raises(ExpectedErrorType):
+        FirefoxVersion(
+            major_number=major_number,
+            minor_number=minor_number,
+            patch_number=patch_number,
+            beta_number=beta_number,
+            build_number=build_number,
+            is_nightly=is_nightly,
+            is_aurora_or_devedition=is_aurora_or_devedition,
+            is_esr=is_esr
+        )
+
+
+def test_firefox_version_constructor_minimum_kwargs():
+    assert str(FirefoxVersion(32, 0)) == '32.0'
+    assert str(FirefoxVersion(32, 0, 1)) == '32.0.1'
+    assert str(FirefoxVersion(32, 1, 0)) == '32.1.0'
+    assert str(FirefoxVersion(32, 0, 1, 1)) == '32.0.1build1'
+    assert str(FirefoxVersion(32, 0, beta_number=1)) == '32.0b1'
+    assert str(FirefoxVersion(32, 0, is_nightly=True)) == '32.0a1'
+    assert str(FirefoxVersion(32, 0, is_aurora_or_devedition=True)) == '32.0a2'
+    assert str(FirefoxVersion(32, 0, is_esr=True)) == '32.0esr'
+    assert str(FirefoxVersion(32, 0, 1, is_esr=True)) == '32.0.1esr'
+
+
+@pytest.mark.parametrize('version_string, ExpectedErrorType', (
+    ('32', PatternNotMatchedError),
+    ('32.b2', PatternNotMatchedError),
+    ('.1', PatternNotMatchedError),
+    ('32.0.0', PatternNotMatchedError),
+    ('32.2', PatternNotMatchedError),
+    ('32.02', PatternNotMatchedError),
+    ('32.0a0', ValueError),
+    ('32.0b0', ValueError),
+    ('32.0.1a1', PatternNotMatchedError),
+    ('32.0.1a2', PatternNotMatchedError),
+    ('32.0.1b2', PatternNotMatchedError),
+    ('32.0build0', ValueError),
+    ('32.0a1a2', PatternNotMatchedError),
+    ('32.0a1b2', PatternNotMatchedError),
+    ('32.0b2esr', PatternNotMatchedError),
+    ('32.0esrb2', PatternNotMatchedError),
+))
+def test_firefox_version_raises_when_invalid_version_is_given(version_string, ExpectedErrorType):
+    with pytest.raises(ExpectedErrorType):
+        FirefoxVersion.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string, expected_type', VALID_VERSIONS.items())
+def test_firefox_version_is_of_a_defined_type(version_string, expected_type):
+    release = FirefoxVersion.parse(version_string)
+    assert getattr(release, 'is_{}'.format(expected_type))
+
+
+@pytest.mark.parametrize('previous, next', (
+    ('32.0', '33.0'),
+    ('32.0', '32.1.0'),
+    ('32.0', '32.0.1'),
+    ('32.0build1', '32.0build2'),
+
+    ('32.0.1', '33.0'),
+    ('32.0.1', '32.1.0'),
+    ('32.0.1', '32.0.2'),
+    ('32.0.1build1', '32.0.1build2'),
+
+    ('32.1.0', '33.0'),
+    ('32.1.0', '32.2.0'),
+    ('32.1.0', '32.1.1'),
+    ('32.1.0build1', '32.1.0build2'),
+
+    ('32.0b1', '33.0b1'),
+    ('32.0b1', '32.0b2'),
+    ('32.0b1build1', '32.0b1build2'),
+
+    ('32.0a1', '32.0a2'),
+    ('32.0a1', '32.0b1'),
+    ('32.0a1', '32.0'),
+    ('32.0a1', '32.0esr'),
+
+    ('32.0a2', '32.0b1'),
+    ('32.0a2', '32.0'),
+    ('32.0a2', '32.0esr'),
+
+    ('32.0b1', '32.0'),
+    ('32.0b1', '32.0esr'),
+
+    ('32.0', '32.0esr'),
+
+    ('2.0', '10.0'),
+    ('10.2.0', '10.10.0'),
+    ('10.0.2', '10.0.10'),
+    ('10.10.1', '10.10.10'),
+    ('10.0build2', '10.0build10'),
+    ('10.0b2', '10.0b10'),
+))
+def test_firefox_version_implements_lt_operator(previous, next):
+    assert FirefoxVersion.parse(previous) < FirefoxVersion.parse(next)
+
+
+@pytest.mark.parametrize('equivalent_version_string', (
+    '32.0', '032.0', '32.0build1', '32.0build01', '32.0-build1', '32.0build2',
+))
+def test_firefox_version_implements_eq_operator(equivalent_version_string):
+    assert FirefoxVersion.parse('32.0') == FirefoxVersion.parse(equivalent_version_string)
+    # raw strings are also converted
+    assert FirefoxVersion.parse('32.0') == equivalent_version_string
+
+
+@pytest.mark.parametrize('wrong_type', (
+    32,
+    32.0,
+    ('32', '0', '1'),
+    ['32', '0', '1'],
+    LooseVersion('32.0'),
+    StrictVersion('32.0'),
+))
+def test_firefox_version_raises_eq_operator(wrong_type):
+    with pytest.raises(ValueError):
+        assert FirefoxVersion.parse('32.0') == wrong_type
+    # AttributeError is raised by LooseVersion and StrictVersion
+    with pytest.raises((ValueError, AttributeError)):
+        assert wrong_type == FirefoxVersion.parse('32.0')
+
+
+def test_firefox_version_implements_remaining_comparision_operators():
+    assert FirefoxVersion.parse('32.0') <= FirefoxVersion.parse('32.0')
+    assert FirefoxVersion.parse('32.0') <= FirefoxVersion.parse('33.0')
+
+    assert FirefoxVersion.parse('33.0') >= FirefoxVersion.parse('32.0')
+    assert FirefoxVersion.parse('33.0') >= FirefoxVersion.parse('33.0')
+
+    assert FirefoxVersion.parse('33.0') > FirefoxVersion.parse('32.0')
+    assert not FirefoxVersion.parse('33.0') > FirefoxVersion.parse('33.0')
+
+    assert not FirefoxVersion.parse('32.0') < FirefoxVersion.parse('32.0')
+
+    assert FirefoxVersion.parse('33.0') != FirefoxVersion.parse('32.0')
+
+
+@pytest.mark.parametrize('version_string, expected_output', (
+    ('32.0', '32.0'),
+    ('032.0', '32.0'),
+    ('32.0build1', '32.0build1'),
+    ('32.0build01', '32.0build1'),
+    ('32.0.1', '32.0.1'),
+    ('32.0a1', '32.0a1'),
+    ('32.0a2', '32.0a2'),
+    ('32.0b1', '32.0b1'),
+    ('32.0b01', '32.0b1'),
+    ('32.0esr', '32.0esr'),
+    ('32.0.1esr', '32.0.1esr'),
+))
+def test_firefox_version_implements_str_operator(version_string, expected_output):
+    assert str(FirefoxVersion.parse(version_string)) == expected_output
+
+
+_SUPER_PERMISSIVE_PATTERN = re.compile(r"""
+(?P<major_number>\d+)\.(?P<minor_number>\d+)(\.(\d+))*
+(?P<is_nightly>a1)?(?P<is_aurora_or_devedition>a2)?(b(?P<beta_number>\d+))?
+(?P<is_esr>esr)?
+""", re.VERBOSE)
+
+
+@pytest.mark.parametrize('version_string', (
+    '32.0a1a2', '32.0a1b2', '32.0b2esr'
+))
+def test_firefox_version_ensures_it_does_not_have_multiple_type(monkeypatch, version_string):
+    # Let's make sure the sanity checks detect a broken regular expression
+    original_pattern = FirefoxVersion._VALID_ENOUGH_VERSION_PATTERN
+    FirefoxVersion._VALID_ENOUGH_VERSION_PATTERN = _SUPER_PERMISSIVE_PATTERN
+
+    with pytest.raises(TooManyTypesError):
+        FirefoxVersion.parse(version_string)
+
+    FirefoxVersion._VALID_ENOUGH_VERSION_PATTERN = original_pattern
+
+
+def test_firefox_version_ensures_a_new_added_release_type_is_caught(monkeypatch):
+    # Let's make sure the sanity checks detect a broken regular expression
+    original_pattern = FirefoxVersion._VALID_ENOUGH_VERSION_PATTERN
+    FirefoxVersion._VALID_ENOUGH_VERSION_PATTERN = _SUPER_PERMISSIVE_PATTERN
+
+    # And a broken type detection
+    original_is_release = FirefoxVersion.is_release
+    FirefoxVersion.is_release = False
+
+    with pytest.raises(NoVersionTypeError):
+        mozilla_version.gecko.FirefoxVersion.parse('32.0.0.0')
+
+    FirefoxVersion.is_release = original_is_release
+    FirefoxVersion._VALID_ENOUGH_VERSION_PATTERN = original_pattern
+
+
+@pytest.mark.parametrize('version_string', (
+    '33.1', '33.1build1', '33.1build2', '33.1build3',
+    '38.0.5b1', '38.0.5b1build1', '38.0.5b1build2',
+    '38.0.5b2', '38.0.5b2build1',
+    '38.0.5b3', '38.0.5b3build1',
+))
+def test_firefox_version_supports_released_edge_cases(version_string):
+    assert str(FirefoxVersion.parse(version_string)) == version_string
+    for Class in (DeveditionVersion, FennecVersion, ThunderbirdVersion):
+        if Class == FennecVersion and version_string in ('33.1', '33.1build1', '33.1build2'):
+            # These edge cases also exist in Fennec
+            continue
+        with pytest.raises(PatternNotMatchedError):
+            Class.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string', (
+    '54.0b11', '54.0b12', '55.0b1'
+))
+def test_devedition_version(version_string):
+    DeveditionVersion.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string', (
+    '53.0a1', '53.0b1', '54.0b10', '55.0', '55.0a1', '60.0esr'
+))
+def test_devedition_version_bails_on_wrong_version(version_string):
+    with pytest.raises(PatternNotMatchedError):
+        DeveditionVersion.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string', (
+    '33.1', '33.1build1', '33.1build2',
+    '38.0.5b4', '38.0.5b4build1'
+))
+def test_fennec_version_supports_released_edge_cases(version_string):
+    assert str(FennecVersion.parse(version_string)) == version_string
+    for Class in (FirefoxVersion, DeveditionVersion, ThunderbirdVersion):
+        if Class == FirefoxVersion and version_string in ('33.1', '33.1build1', '33.1build2'):
+            # These edge cases also exist in Firefox
+            continue
+        with pytest.raises(PatternNotMatchedError):
+            Class.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string', (
+    '45.1b1', '45.1b1build1',
+    '45.2', '45.2build1', '45.2build2',
+    '45.2b1', '45.2b1build2',
+))
+def test_thunderbird_version_supports_released_edge_cases(version_string):
+    assert str(ThunderbirdVersion.parse(version_string)) == version_string
+    for Class in (FirefoxVersion, DeveditionVersion, FennecVersion):
+        with pytest.raises(PatternNotMatchedError):
+            Class.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string', (
+    '63.0b7-1', '63.0b7-2',
+    '62.0-1', '62.0-2',
+    '60.2.1esr-1', '60.2.0esr-2',
+    '60.0esr-1', '60.0esr-13',
+    # TODO Bug 1451694: Figure out what nightlies version numbers looks like
+))
+def test_gecko_snap_version(version_string):
+    GeckoSnapVersion.parse(version_string)
+
+
+@pytest.mark.parametrize('version_string', (
+    '32.0a2', '32.0esr1', '32.0-build1',
+))
+def test_gecko_snap_version_bails_on_wrong_version(version_string):
+    with pytest.raises(PatternNotMatchedError):
+        GeckoSnapVersion.parse(version_string)
+
+
+def test_gecko_snap_version_implements_its_own_string():
+    str(GeckoSnapVersion.parse('63.0b7-1')) == '63.0b7-1'
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/test/test_version.py
@@ -0,0 +1,54 @@
+import pytest
+
+from mozilla_version.version import VersionType
+
+
+@pytest.mark.parametrize('previous, next', (
+    (VersionType.NIGHTLY, VersionType.AURORA_OR_DEVEDITION),
+    (VersionType.NIGHTLY, VersionType.BETA),
+    (VersionType.NIGHTLY, VersionType.RELEASE),
+    (VersionType.NIGHTLY, VersionType.ESR),
+
+    (VersionType.AURORA_OR_DEVEDITION, VersionType.BETA),
+    (VersionType.AURORA_OR_DEVEDITION, VersionType.RELEASE),
+    (VersionType.AURORA_OR_DEVEDITION, VersionType.ESR),
+
+    (VersionType.BETA, VersionType.RELEASE),
+    (VersionType.BETA, VersionType.ESR),
+
+    (VersionType.RELEASE, VersionType.ESR),
+))
+def test_version_type_implements_lt_operator(previous, next):
+    assert previous < next
+
+
+@pytest.mark.parametrize('first, second', (
+    (VersionType.NIGHTLY, VersionType.NIGHTLY),
+    (VersionType.AURORA_OR_DEVEDITION, VersionType.AURORA_OR_DEVEDITION),
+    (VersionType.BETA, VersionType.BETA),
+    (VersionType.RELEASE, VersionType.RELEASE),
+    (VersionType.ESR, VersionType.ESR),
+))
+def test_version_type_implements_eq_operator(first, second):
+    assert first == second
+
+
+def test_version_type_implements_remaining_comparision_operators():
+    assert VersionType.NIGHTLY <= VersionType.NIGHTLY
+    assert VersionType.NIGHTLY <= VersionType.BETA
+
+    assert VersionType.NIGHTLY >= VersionType.NIGHTLY
+    assert VersionType.BETA >= VersionType.NIGHTLY
+
+    assert not VersionType.NIGHTLY > VersionType.NIGHTLY
+    assert VersionType.BETA > VersionType.NIGHTLY
+
+    assert not VersionType.BETA < VersionType.NIGHTLY
+
+    assert VersionType.NIGHTLY != VersionType.BETA
+
+
+def test_version_type_compare():
+    assert VersionType.NIGHTLY.compare(VersionType.NIGHTLY) == 0
+    assert VersionType.NIGHTLY.compare(VersionType.BETA) < 0
+    assert VersionType.BETA.compare(VersionType.NIGHTLY) > 0
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/mozilla_version/version.py
@@ -0,0 +1,61 @@
+"""Defines common characteristics of a version at Mozilla."""
+
+from enum import Enum
+
+
+class VersionType(Enum):
+    """Enum that sorts types of versions (e.g.: nightly, beta, release, esr).
+
+    Supports comparison. `ESR` is considered higher than `RELEASE` (even if they technically have
+    the same codebase). For instance: 60.0.1 < 60.0.1esr but 61.0 > 60.0.1esr.
+    This choice has a practical use case: if you have a list of Release and ESR version, you can
+    easily extract one kind or the other thanks to the VersionType.
+
+    Examples:
+        .. code-block:: python
+
+            assert VersionType.NIGHTLY == VersionType.NIGHTLY
+            assert VersionType.ESR > VersionType.RELEASE
+
+    """
+
+    NIGHTLY = 1
+    AURORA_OR_DEVEDITION = 2
+    BETA = 3
+    RELEASE = 4
+    ESR = 5
+
+    def __eq__(self, other):
+        """Implement `==` operator."""
+        return self.compare(other) == 0
+
+    def __ne__(self, other):
+        """Implement `!=` operator."""
+        return self.compare(other) != 0
+
+    def __lt__(self, other):
+        """Implement `<` operator."""
+        return self.compare(other) < 0
+
+    def __le__(self, other):
+        """Implement `<=` operator."""
+        return self.compare(other) <= 0
+
+    def __gt__(self, other):
+        """Implement `>` operator."""
+        return self.compare(other) > 0
+
+    def __ge__(self, other):
+        """Implement `>=` operator."""
+        return self.compare(other) >= 0
+
+    def compare(self, other):
+        """Compare this `VersionType` with anotherself.
+
+        Returns:
+            0 if equal
+            < 0 is this precedes the other
+            > 0 if the other precedes this
+
+        """
+        return self.value - other.value
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/requirements-coveralls.txt
@@ -0,0 +1,61 @@
+# Generated from dephash.py + hashin.py
+certifi==2018.8.24 \
+    --hash=sha512:bb0af24570dd4806bc80cd438cae42db82f820de815a6148b184f58c19cd91a03f9275815559817a84220dcbd7c87a4a7d12e35cf4a95a1dbad286a1dfecf907 \
+    --hash=sha512:e6a6272bac21007738a0e694da3ab4480f9e522cc6b71ce61a12eb21545c9417c87baea4e2eec6df1be9bd3ac2de8403583f8b7d3efcd7ed9a6b53f53cfb1072
+chardet==3.0.4 \
+    --hash=sha512:61a03b23447a2bfe52ceed4dd1b9afdb5784da1933a623776883ee9f297e341f633e27f0ce0230bd5fdc5fdb5382105ab42736a74a417ddeb9f83af57455dba5 \
+    --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
+coverage==4.5.1 \
+    --hash=sha512:0d656cb22528744a8ef0853f2fa7b96f20fdd49f3c439c3211076e6aac1509c216138e31e24ba92e3945ad4dc81d58ef4b147c092e888a1acbc840d8187617e8 \
+    --hash=sha512:1bed7f91763ae47fc7a0548766029328f059c25fbdd292f2b313d867b93f161cb35b9e612894682b1f486d52c91e06ff7173a2274d6d098657e69de35768f854 \
+    --hash=sha512:2520e0ba3adf1485e7d583ff1f14643bdbe531b4062353780b80737d08330adddb7ab6ad869a2542a261760b4be1d050182fe0f91061610463ee972298fe518e \
+    --hash=sha512:2b5ad59f6570e22021ab96a9cba9bfad78469a248dec032a227a2fccd1b110ab7f38a5076a44a1a471b37077e7100b0f9c51fe370b8467f9dbedae498a042581 \
+    --hash=sha512:309fe4c70de41671afc4fefbc959f0d16d54df3ca95c80b11f9ac8b86b6ea117a714f1c36f6f9976c1951a6760747e9b2de9de60943adc8b39482892f5ce5f7b \
+    --hash=sha512:338a31e1a1856073501a02eaefaae2c18df27140598472d34a8432c9c25225296eab81567729a2cc165a4c9d8faeabb1e3bbb52ce840d394ba0f0c5f6baf2528 \
+    --hash=sha512:3d84ef3f2078ced6010f7192fb5fbe1c0f12c11118cb756ff8b46c453327ec61639f47799b8338a0565033641abbcc6e3c5f394020cab2ffdddec04bb489bf1f \
+    --hash=sha512:45126acc107f5a7fcb015c9b866a3b0d0101e9211fdecc5843f334fea28a2d80db1048e752d1d6f860687c9f289e03678cf2d92a77779a355b9b186a772d3353 \
+    --hash=sha512:45abe32fa09505e436c8cae010e3866399dee551c13fb5a35da57cd296502b9f266332d278671ebdb7ccab941326daeb1e27118821862e249ee985ef4a6d0d35 \
+    --hash=sha512:4757a11361a9d4f757082f885512e6b4de24b60344646ad09741c84e4fb4f08813f05aa18a7acb3451a331a91fb9d27ef411bf9348b54d3d7cd3074e4645f41b \
+    --hash=sha512:4ae653511277a5bb984142286e01157e1a068c01a16c825adf06287e569cc516ca056e6fefcb05b2038f31ca261b5fa8ef345a6c0274650b3de175a694188b95 \
+    --hash=sha512:55decd6e6d369682b67c9a82ccf5772538e004279a657d6d9f75482ff20896360b37b4841d08d79996ce16956bb863d2a2bb33e2ee7d5b06fd3e79adf96548df \
+    --hash=sha512:5ad7ec5b9be01298957c22704a2689e2e9c9c3dc12e11570fbc4a5f382432a854bcea75e4419e953175da3aa2ee1d2ec2f244886db5188f7cc8d9e231b29cc2e \
+    --hash=sha512:7927c89140e844f56917746f41a866755ffcb72d8028bd23ab055446f90231d0c1f4ccc29d67fada8155a5c927b4559f3e263e49659755dc847ebd4b7aa6e392 \
+    --hash=sha512:798fd2eb99a17cdcfd7b2f2d95fc319fb860e041b982b4e7c9d1ccbff41c35f19d5ca16e3605e2222cd30a4ed3cddfe94149b1b2ec27a54efd1ea6474b9ccb1b \
+    --hash=sha512:82742a572549400778cad99057b1ced4c36b61e917983148eccc86bfa6340de8cfefc4f743e79ff876b641e0b9d21307dd6bde78638a6b20dd8ad215068dda25 \
+    --hash=sha512:8510d0846aa24b5fbbdd74c0e38ab29c8fce7cc96ff473f4b52fa8044fcafcfb54449e8aaab29efe7c73a3530167231412b14ccf10e6cb34c9838adf63826019 \
+    --hash=sha512:89e12e4b7ee82e2f2f92c75697ac30bdbc214d9b8fb0bd5aa21e657eac1800f8fb1b44ae9c1d8ae07666d0b0d9a5a1f681b555fcc2d141f1ad15ab589e005243 \
+    --hash=sha512:980260b63b49b697a543184e0233996ba6fa4df8c6976227ae6c20dd650f1a094f4115a3e999222e73806f191bf7ce2a2848bd60a690dd1737b102928f25f3bc \
+    --hash=sha512:9b24bda59e374374dbd48973d118e039fdeeff3d193411533aed90e3d425ddd5ad5419903e2cc7a719a4ff04bbd066eb3e11e153ee8df339dae03dcc8c25b9c0 \
+    --hash=sha512:9c0b13abc5376eaff78c4e7f2a945bb86965969ea1ab3571d2209f91df078c1a779ee42f47bf96e62fe9bd63668e49edfaa0b00525c18d2cbbd155781fa2201b \
+    --hash=sha512:9dd03ce95b1a107337b226d46dcd9e6fdf3b388426ac0508cb1b6c5c43423f22b8b35828e9d89dcc08483525b3bc2de1334fe7e85bfaae9d64f64933ef9fe3ce \
+    --hash=sha512:a5cbfd1cd7e5212285c28dec4e8b8354b6b189cde501f078746e5dc45216f4d24479eef18a612c00bfe94192ab48deb79150cd261ff42254ab83205dc58ff77b \
+    --hash=sha512:a7892f7106be5ab0670d32b4e8d10ec9e1336e4706d94666c084be17f4519672aa84c2dbfa4028b43433ea51ed29ffe175ab3908b91f5329578c7a6bbde7df91 \
+    --hash=sha512:ac9dc454f928a9e7fbd8e4a98bfce0511415cac69e80cf0a098afa504d9c6585c2eeac0553f20d3edd297770f5744e8f03ddb0a17f62bde70caf3779d11e32b9 \
+    --hash=sha512:b0b004871cfd6f1b949a4ddc37544a73c1446602109b671c6752baa3776e4f08dfe340c1c75299c62612c43fba652ee92e3481bb39ecb7e9b02643a041becac4 \
+    --hash=sha512:b202a7278ebb3c87d6fcd2f1cff7dc88d78fa7eae709a73e6207c6212f962e706f84f22c14dad6c5c3e8be7eef7c1806c3f66e8f1e557c6127aea0a97a797b0f \
+    --hash=sha512:b388092fe437bfc8bc10205ffaa5a43e418e39e1f0e3509ef03721149c5904455142eadfc28d414007f93e5d0deb39231b59b35cba82ea884809c60020b1cfa3 \
+    --hash=sha512:c718f78a365a5b610cb2f1fe8ca646b83d6439912cfb9f787e57cc84dc84eea6f3150660231acd093dfa84d2f4f8135f7f2512fb05a1978e1b12c3fd8b23f505 \
+    --hash=sha512:d0662ad14ebb76454571ed5b083e62bd35e57cd801f627dd1d286ccfce7c271109a814f73e26fb402d60c23ae08ac96f29ca62a43cbbcb027e72b349ec9f296f \
+    --hash=sha512:d329341278272c48270cb8eb712257ce9970c665606351064d02ce37424c42407e833e4688e938c3e88804451e15cb5287dedc91e0fe5a66cb5cc201f791ab1c \
+    --hash=sha512:d4b9569efa13c9a316a0bc99e7c163a474ed7022df0445ed5811e0ad4e53f0316472e3c8b8540f995d0d6abaae5025796d876f5322e63819fb469a7f1a5419a9 \
+    --hash=sha512:d6788611162a863a8a274473b6dcdaefadccd0f42f6732431cdb12ebd5300987eb3db29df0f305528bbe9766b87baba9a6dca0460089cab5aa11b1ca727d3af8 \
+    --hash=sha512:d987c1fee355cfc7d6bdc89234295b7dd35941449f14dc5c8efac28bb98894f9a78d558dd02a05457a97916de070ae386127be7197efc3b511c4f7a7f056f189 \
+    --hash=sha512:dba0935c4079bfce694866d6598408db305b82063ed4b0dcaf5d4260a70b25118aa3d72ff81ba3326dcb6377734c4fa5811740543331ce6eb1c9886e621217ac \
+    --hash=sha512:e01685335bd44d0c767429568adc5fb3669417354b62d634bb06fcdfaa959102ff1105f3b108046047a4dffd24bbce5bf81fcdd61b49a45f2aad32a83034dc9c \
+    --hash=sha512:f673f86b32764a023d32626939217d56c53029a4de83f7ef018f8a76e5ee2d0090f3395e6c79098c73f3dbe66ae14355ca3e3bb799275b7229bf198dc4864236 \
+    --hash=sha512:f96a049d5dda5b2ca4e135a561d8ad9a1a511603e553996e07ee6e46a3573afea1ffe895d2897e3c2347ecf186588b62b6d3d821d1f87109319081777d1c0fd8 \
+    --hash=sha512:fee28f472b893d8531b7b6b65681cc59e39627de4c9c6d91c043943793eb586f1427a8b18f8b50cc507fab30c5652ddad5d01d9c368e5488254e36bae1627ac4
+coveralls==1.5.0 \
+    --hash=sha512:6379e9f493b0711c1290f1e4ed535dc4e97c6c6f3ecaee78c3d04efb978232eb78ffcac4ab4122e2a2b8c9d81c33c7a48421765939c84acae25ead1d10bd216f \
+    --hash=sha512:8cbe7a258b5b0800968a11cb4bcfa37f2317b676841ad4026de795f005854de898103cf810bfd8a23356319e7c0168a558454f15701261fdcd9a3e5735b55be2
+docopt==0.6.2 \
+    --hash=sha512:af138feccf8c37b374ee44fcda4938a88107d434df13c173214021b1a3348b152a595095a86982b66ac03a11db8e0f1e9e6a3a65c98deea92330311daeb831a3
+idna==2.7 \
+    --hash=sha512:34ba985862e386243f43616586e53830177cf4ba0925b6054198dfa63c085ec5c6c5b54c2b3c0989bc768aacceeef76b84471e58a16183b960dc4b8812cd1c61 \
+    --hash=sha512:ed1fe279142f8fc9604b12fadc6e7cda78b822194d481dcb23c4474e9d584d170b45a410c553a1625c319c3d1b242e29aca86c7c2f13b73f19bbf0607e9fdcbd
+requests==2.19.1 \
+    --hash=sha512:bb4ba9411a3ab7e81169ada77c9f6fe89913418f8f249e4a1e36839dcc2728422517101e12e02fe3ec454c56f2e7bf6f1599b2901c08f88b86185fb52b925f00 \
+    --hash=sha512:deef2662227497b472f6e7eb7f0ee468c772069fc66552f71d80e5a12af62a187f8bc6a8a78531915e824c7f277d0db61c1534c89d13d473f68504f0bd6edf47
+urllib3==1.23 \
+    --hash=sha512:10d6c15f41e5e6738bec8ac167f7138123f4396205bc9bc556cac4246e13e943e6f4d569d63d5a9acab899580b1df680012efa42b9d2c16b28cbb6babdbabe26 \
+    --hash=sha512:6baa76ad3bebc639d7ec0a042d809fba7ef3110de6164a321756389c250e218728d178708611049d91f39d17f24b8b08585edb2f2c260c987bd4c7204e0c0b5f
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/requirements-docs.txt
@@ -0,0 +1,66 @@
+# Generated from dephash.py + hashin.py
+Babel==2.6.0 \
+    --hash=sha512:2633c7782cc9ed54383a2d054c8be2c52bee1e6c0b0ecce10e61ec95cf68e0a8cf265f063089c31901aedb87835ade643b618d8604812ddf694ee9a599c4c41b \
+    --hash=sha512:a87f26c25f1e48a64d1e7ee112bc0c325a6268e0e716a81cb6814a53f0add8f3d579babd2ba064f160298ebe3ac9711aa5d28f112582ee287767b6ed8427b6e0
+Jinja2==2.10 \
+    --hash=sha512:0ea7371be67ffcf19e46dfd06523a45a0806e678a407d54f5f2f3e573982f0959cf82ec5d07b203670309928a62ef71109701ab16547a9bba2ebcdc178cb67f2 \
+    --hash=sha512:672c1a112f76f399600a069c5ee882d5fdf065ff25f6b729ec12a266d7ef6f638c26d5cc680db7b3a375d9e1ae7323aed3c2a49eb03fc39dd1a1ca8b0d658b63
+MarkupSafe==1.0 \
+    --hash=sha512:7437a02cb1b9c1d6d6c20b13d394f24a1cd1cb8c743f832d8b1cbb6ab8846f70f54d924dd693423b33c4d592e772983ae38fd4a35961e233457c48bd3584ecb8
+Pygments==2.2.0 \
+    --hash=sha512:2ecf535b32d727473cfb8b15b73f67dbe15189ed0319b99cc6e9ae222ceee5fffe32b4b6dee8e0aae91338bd5add27bbb3cb1429977ec120ab341818526993c0 \
+    --hash=sha512:cc0a4f73e19fa6cbf46314de2e809460c807c631e39ba05cbe5edb5f40db1a687aafcd9715585a0ed45f791710eb6038305e273f282f8682df76f30e63710b29
+Sphinx==1.8.0 \
+    --hash=sha256:c091dbdd5cc5aac6eb95d591a819fd18bccec90ffb048ec465b165a48b839b45 \
+    --hash=sha256:95acd6648902333647a0e0564abdb28a74b0a76d2333148aa35e5ed1f56d3c4b
+alabaster==0.7.11 \
+    --hash=sha512:0dc70eacacab471f7bfe28fd18ff22edb0059dc3ac46d1c42b24783fcc863da5bace4654dc5dbd2e5b420a0ed9230a1aaf0e7c9df9363196f84645c759f50a00 \
+    --hash=sha512:3b3762cc9f0a04296d07bdadeb7e77ae007e307bfad81d8ddfa8e883dcf7577bf544957b3664ec4cc354749fa249627071474466dc771c5883c75571c86d8f3b
+certifi==2018.8.24 \
+    --hash=sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a \
+    --hash=sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638
+chardet==3.0.4 \
+    --hash=sha512:61a03b23447a2bfe52ceed4dd1b9afdb5784da1933a623776883ee9f297e341f633e27f0ce0230bd5fdc5fdb5382105ab42736a74a417ddeb9f83af57455dba5 \
+    --hash=sha512:bfae58c8ea19c87cc9c9bf3d0b6146bfdb3630346bd954fe8e9f7da1f09da1fc0d6943ff04802798a665ea3b610ee2d65658ce84fe5a89f9e93625ea396a17f4
+docutils==0.14 \
+    --hash=sha512:1ed72c2ef7d2ca38d1c6f3154b7986ea46f599d9bd826405a5257fdd7740c8626957d6298aa95fb0edea8a24515de22f1ad9b2ecbd59341a1ab7a2bab30f500c \
+    --hash=sha512:391177f1f87d837463b9bc135805ef102c8221b52eabf7c2309ac8d9635db3698f6d43e79d2ce9a53c8a2fa2ed1b21dafdad98d5ba1a8d1672b68741854caa9a \
+    --hash=sha512:c3b303121066fb1a67c5984f7e00edda81f3e3a47e84729e4480c86777df7bb236c2965bf6d3f15c72c17de329137a3d20ce35ecef410801539ca9a9328f9260
+idna==2.7 \
+    --hash=sha512:34ba985862e386243f43616586e53830177cf4ba0925b6054198dfa63c085ec5c6c5b54c2b3c0989bc768aacceeef76b84471e58a16183b960dc4b8812cd1c61 \
+    --hash=sha512:ed1fe279142f8fc9604b12fadc6e7cda78b822194d481dcb23c4474e9d584d170b45a410c553a1625c319c3d1b242e29aca86c7c2f13b73f19bbf0607e9fdcbd
+imagesize==1.1.0 \
+    --hash=sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8 \
+    --hash=sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5
+m2r==0.2.0 \
+    --hash=sha512:165e20c68dc4dadad3fa5ebb7c4ebfd9e8971e3529e30aa964cbc820bf8f01c7cf9bfbfedd35ffe7e9651d20e5e5a4e09a4a3f130cdd07b5524edebce88618b1
+mistune==0.8.3 \
+    --hash=sha512:f3691c1b940d827164e110f0ddf366e881625c96d7b3cc353e8805b07205496fd1e37cef3a55557b490d3d5257f86e8515c4155b192fb4b9297159bc609e3c2d \
+    --hash=sha512:fc0b6d53e7a62712b752bff77c2529750628e9e1eeec02603bd2c793f9b0a46efe31fe7a749c5d6aae3c591d74e43c45e450179650bfb57128fb0ec366de6aa3
+packaging==17.1 \
+    --hash=sha512:49b15957d84e0b42ae83c88b1397871ad49c34f72f61e5fc34579165dffe5cd243494285ae89f14a496b94f3b715f135deba4044bf496c9c8a5124121442a0e0 \
+    --hash=sha512:52d2f482ae5cd0f8e7b3ac35c4b270ca736bc4f7ebaa569631d1dcb8ea024ee6f75127ca2aa3c95e686ebbd427de2ef4bc01bcd839591f7b4c1e26f9b8d9acb0
+pyparsing==2.2.1 \
+    --hash=sha256:905d8090c335314568b5faee0025b1829f27bb974604a5762a6cdef3a7dfc3b7 \
+    --hash=sha256:f493ee323be1e94929416b3585eefcc04943115cecbaaa35a8c86d1a2368af19
+pytz==2018.5 \
+    --hash=sha512:35b6bdd25b8e57c693da4379c2032401ef3cf290a57d8448c67dcaf1491a21d27ff25d932ef3ec3a51e31fbb7541e081073f292867a7d1ad47904b69dc7e4863 \
+    --hash=sha512:655836fe203e5aaf066e85133d7e3aa680201651a80f887bebeadb46a0ac8c0aaf0ee2f717d6203e438b14ec49b35f346ed0bdea7ed81cec9447f46f94d5423d
+readthedocs-sphinx-ext==0.5.14 \
+    --hash=sha512:0462bcd7408ee6483823014f1c77bd7e46f5908e793c1b115ea5a3861e8072b96f46f183deb5083057bca8dd6684b6520e04c84a69b1892a13e86c2ef7826d26 \
+    --hash=sha512:6480cb5967a7c192d2f3a27613320ebf37fe7f161ddd437a6bfdec88de708e88c0f294fcf04e41b814ef284a74d6f8d56f75824ade8d74c8a39f6f403f3a2fd3
+requests==2.19.1 \
+    --hash=sha512:bb4ba9411a3ab7e81169ada77c9f6fe89913418f8f249e4a1e36839dcc2728422517101e12e02fe3ec454c56f2e7bf6f1599b2901c08f88b86185fb52b925f00 \
+    --hash=sha512:deef2662227497b472f6e7eb7f0ee468c772069fc66552f71d80e5a12af62a187f8bc6a8a78531915e824c7f277d0db61c1534c89d13d473f68504f0bd6edf47
+six==1.11.0 \
+    --hash=sha512:33f246a2e987141e17e5edad9d3537cf3aba0cbdd0bc2a907ea52ce0d674b1474f29c3dd5cc26605fd960396054b189ca5f501708333cad234c223131483fe24 \
+    --hash=sha512:b71248ef493ac12b115fdf06206090078fac147ec6ab6efb67b87e2b9c07a69d55bf8e70fde713d81735a99e560a17da714274e2ecbd7b0200d2e9a0f39970a7
+snowballstemmer==1.2.1 \
+    --hash=sha512:09f860f383d84d12a83c87ef6654fba4ac10bca07e8d2ce88dd428c72754110d56a4b698e125a18818699a289455bf61cf67ea68e349ee8a12d6dfff0a3fbed9 \
+    --hash=sha512:c97bb3d293ffbe16d4c553f8a8ad1dbb5f659441cde93e407f71419f82b8e16b3090df2c0294d64f73382a648cd8cc62bca407124a8e2d60e4c424b78e10b6b8
+sphinxcontrib-websupport==1.1.0 \
+    --hash=sha512:5c13433d9804feadb0783116648731a21d44f803e73b88bd0ec7a46948bae68284111d33e44bab1723f8224ea06c1efa5afb88fa390fbb127821761f0a990076 \
+    --hash=sha512:adbd7db06150a4424a881a22442c2535ed823c7adcc295ef9c3af5ae38d823349830a114e8a7af2138498d0e68b37189707c1bb5a28b45c76031bcf94210fb89
+urllib3==1.23 \
+    --hash=sha512:10d6c15f41e5e6738bec8ac167f7138123f4396205bc9bc556cac4246e13e943e6f4d569d63d5a9acab899580b1df680012efa42b9d2c16b28cbb6babdbabe26 \
+--hash=sha512:6baa76ad3bebc639d7ec0a042d809fba7ef3110de6164a321756389c250e218728d178708611049d91f39d17f24b8b08585edb2f2c260c987bd4c7204e0c0b5f
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/requirements-test.txt
@@ -0,0 +1,122 @@
+# Generated from dephash.py + hashin.py
+attrs==18.2.0 \
+    --hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb \
+    --hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69
+atomicwrites==1.2.0 \
+    --hash=sha256:6b5282987b21cd79151f51caccead7a09d0a32e89c568bd9e3c4aaa7bbdf3f3a \
+    --hash=sha256:e16334d50fe0f90919ef7339c24b9b62e6abaa78cd2d226f3d94eb067eb89043
+configparser==3.5.0 \
+    --hash=sha512:490b9f7807bce02667f41a48389b51f550818d2bd4296b528833d65d4b04bdbe5b906e7584e55eee4495405267a697ba26a056e6504fe6b3f8cf07ea8f55f7d3 \
+    ; python_version < "3.2"
+coverage==4.5.1 \
+    --hash=sha512:0d656cb22528744a8ef0853f2fa7b96f20fdd49f3c439c3211076e6aac1509c216138e31e24ba92e3945ad4dc81d58ef4b147c092e888a1acbc840d8187617e8 \
+    --hash=sha512:1bed7f91763ae47fc7a0548766029328f059c25fbdd292f2b313d867b93f161cb35b9e612894682b1f486d52c91e06ff7173a2274d6d098657e69de35768f854 \
+    --hash=sha512:2520e0ba3adf1485e7d583ff1f14643bdbe531b4062353780b80737d08330adddb7ab6ad869a2542a261760b4be1d050182fe0f91061610463ee972298fe518e \
+    --hash=sha512:2b5ad59f6570e22021ab96a9cba9bfad78469a248dec032a227a2fccd1b110ab7f38a5076a44a1a471b37077e7100b0f9c51fe370b8467f9dbedae498a042581 \
+    --hash=sha512:309fe4c70de41671afc4fefbc959f0d16d54df3ca95c80b11f9ac8b86b6ea117a714f1c36f6f9976c1951a6760747e9b2de9de60943adc8b39482892f5ce5f7b \
+    --hash=sha512:338a31e1a1856073501a02eaefaae2c18df27140598472d34a8432c9c25225296eab81567729a2cc165a4c9d8faeabb1e3bbb52ce840d394ba0f0c5f6baf2528 \
+    --hash=sha512:3d84ef3f2078ced6010f7192fb5fbe1c0f12c11118cb756ff8b46c453327ec61639f47799b8338a0565033641abbcc6e3c5f394020cab2ffdddec04bb489bf1f \
+    --hash=sha512:45126acc107f5a7fcb015c9b866a3b0d0101e9211fdecc5843f334fea28a2d80db1048e752d1d6f860687c9f289e03678cf2d92a77779a355b9b186a772d3353 \
+    --hash=sha512:45abe32fa09505e436c8cae010e3866399dee551c13fb5a35da57cd296502b9f266332d278671ebdb7ccab941326daeb1e27118821862e249ee985ef4a6d0d35 \
+    --hash=sha512:4757a11361a9d4f757082f885512e6b4de24b60344646ad09741c84e4fb4f08813f05aa18a7acb3451a331a91fb9d27ef411bf9348b54d3d7cd3074e4645f41b \
+    --hash=sha512:4ae653511277a5bb984142286e01157e1a068c01a16c825adf06287e569cc516ca056e6fefcb05b2038f31ca261b5fa8ef345a6c0274650b3de175a694188b95 \
+    --hash=sha512:55decd6e6d369682b67c9a82ccf5772538e004279a657d6d9f75482ff20896360b37b4841d08d79996ce16956bb863d2a2bb33e2ee7d5b06fd3e79adf96548df \
+    --hash=sha512:5ad7ec5b9be01298957c22704a2689e2e9c9c3dc12e11570fbc4a5f382432a854bcea75e4419e953175da3aa2ee1d2ec2f244886db5188f7cc8d9e231b29cc2e \
+    --hash=sha512:7927c89140e844f56917746f41a866755ffcb72d8028bd23ab055446f90231d0c1f4ccc29d67fada8155a5c927b4559f3e263e49659755dc847ebd4b7aa6e392 \
+    --hash=sha512:798fd2eb99a17cdcfd7b2f2d95fc319fb860e041b982b4e7c9d1ccbff41c35f19d5ca16e3605e2222cd30a4ed3cddfe94149b1b2ec27a54efd1ea6474b9ccb1b \
+    --hash=sha512:82742a572549400778cad99057b1ced4c36b61e917983148eccc86bfa6340de8cfefc4f743e79ff876b641e0b9d21307dd6bde78638a6b20dd8ad215068dda25 \
+    --hash=sha512:8510d0846aa24b5fbbdd74c0e38ab29c8fce7cc96ff473f4b52fa8044fcafcfb54449e8aaab29efe7c73a3530167231412b14ccf10e6cb34c9838adf63826019 \
+    --hash=sha512:89e12e4b7ee82e2f2f92c75697ac30bdbc214d9b8fb0bd5aa21e657eac1800f8fb1b44ae9c1d8ae07666d0b0d9a5a1f681b555fcc2d141f1ad15ab589e005243 \
+    --hash=sha512:980260b63b49b697a543184e0233996ba6fa4df8c6976227ae6c20dd650f1a094f4115a3e999222e73806f191bf7ce2a2848bd60a690dd1737b102928f25f3bc \
+    --hash=sha512:9b24bda59e374374dbd48973d118e039fdeeff3d193411533aed90e3d425ddd5ad5419903e2cc7a719a4ff04bbd066eb3e11e153ee8df339dae03dcc8c25b9c0 \
+    --hash=sha512:9c0b13abc5376eaff78c4e7f2a945bb86965969ea1ab3571d2209f91df078c1a779ee42f47bf96e62fe9bd63668e49edfaa0b00525c18d2cbbd155781fa2201b \
+    --hash=sha512:9dd03ce95b1a107337b226d46dcd9e6fdf3b388426ac0508cb1b6c5c43423f22b8b35828e9d89dcc08483525b3bc2de1334fe7e85bfaae9d64f64933ef9fe3ce \
+    --hash=sha512:a5cbfd1cd7e5212285c28dec4e8b8354b6b189cde501f078746e5dc45216f4d24479eef18a612c00bfe94192ab48deb79150cd261ff42254ab83205dc58ff77b \
+    --hash=sha512:a7892f7106be5ab0670d32b4e8d10ec9e1336e4706d94666c084be17f4519672aa84c2dbfa4028b43433ea51ed29ffe175ab3908b91f5329578c7a6bbde7df91 \
+    --hash=sha512:ac9dc454f928a9e7fbd8e4a98bfce0511415cac69e80cf0a098afa504d9c6585c2eeac0553f20d3edd297770f5744e8f03ddb0a17f62bde70caf3779d11e32b9 \
+    --hash=sha512:b0b004871cfd6f1b949a4ddc37544a73c1446602109b671c6752baa3776e4f08dfe340c1c75299c62612c43fba652ee92e3481bb39ecb7e9b02643a041becac4 \
+    --hash=sha512:b202a7278ebb3c87d6fcd2f1cff7dc88d78fa7eae709a73e6207c6212f962e706f84f22c14dad6c5c3e8be7eef7c1806c3f66e8f1e557c6127aea0a97a797b0f \
+    --hash=sha512:b388092fe437bfc8bc10205ffaa5a43e418e39e1f0e3509ef03721149c5904455142eadfc28d414007f93e5d0deb39231b59b35cba82ea884809c60020b1cfa3 \
+    --hash=sha512:c718f78a365a5b610cb2f1fe8ca646b83d6439912cfb9f787e57cc84dc84eea6f3150660231acd093dfa84d2f4f8135f7f2512fb05a1978e1b12c3fd8b23f505 \
+    --hash=sha512:d0662ad14ebb76454571ed5b083e62bd35e57cd801f627dd1d286ccfce7c271109a814f73e26fb402d60c23ae08ac96f29ca62a43cbbcb027e72b349ec9f296f \
+    --hash=sha512:d329341278272c48270cb8eb712257ce9970c665606351064d02ce37424c42407e833e4688e938c3e88804451e15cb5287dedc91e0fe5a66cb5cc201f791ab1c \
+    --hash=sha512:d4b9569efa13c9a316a0bc99e7c163a474ed7022df0445ed5811e0ad4e53f0316472e3c8b8540f995d0d6abaae5025796d876f5322e63819fb469a7f1a5419a9 \
+    --hash=sha512:d6788611162a863a8a274473b6dcdaefadccd0f42f6732431cdb12ebd5300987eb3db29df0f305528bbe9766b87baba9a6dca0460089cab5aa11b1ca727d3af8 \
+    --hash=sha512:d987c1fee355cfc7d6bdc89234295b7dd35941449f14dc5c8efac28bb98894f9a78d558dd02a05457a97916de070ae386127be7197efc3b511c4f7a7f056f189 \
+    --hash=sha512:dba0935c4079bfce694866d6598408db305b82063ed4b0dcaf5d4260a70b25118aa3d72ff81ba3326dcb6377734c4fa5811740543331ce6eb1c9886e621217ac \
+    --hash=sha512:e01685335bd44d0c767429568adc5fb3669417354b62d634bb06fcdfaa959102ff1105f3b108046047a4dffd24bbce5bf81fcdd61b49a45f2aad32a83034dc9c \
+    --hash=sha512:f673f86b32764a023d32626939217d56c53029a4de83f7ef018f8a76e5ee2d0090f3395e6c79098c73f3dbe66ae14355ca3e3bb799275b7229bf198dc4864236 \
+    --hash=sha512:f96a049d5dda5b2ca4e135a561d8ad9a1a511603e553996e07ee6e46a3573afea1ffe895d2897e3c2347ecf186588b62b6d3d821d1f87109319081777d1c0fd8 \
+    --hash=sha512:fee28f472b893d8531b7b6b65681cc59e39627de4c9c6d91c043943793eb586f1427a8b18f8b50cc507fab30c5652ddad5d01d9c368e5488254e36bae1627ac4
+enum34==1.1.6 \
+    --hash=sha512:3671dc8c0c3cf2a3cbafd22feb85bcc66f4262722b59683429af0865642929622fd3213113703c0b811f7e3bf9cee0e30c7403f6971585150fc013ec01333873 \
+    --hash=sha512:4f916143fe35d41b28efb7958ffa7e10d430d66697f8150b98aae3797a058378fd9a4c201969c155f30f88c962f78e4e75e684889d5138cd2fe60406924363e9 \
+    --hash=sha512:51652525adc37bd1af1d81933f965dba9c508838d9f759c80ca1392991515a29c2c0263264a4e175b37a6ba11dca68c354774e448b19ef1bdba96be5474d93ec \
+    --hash=sha512:ac5eedcd9425b06f44c3e9029cfc3e93a266a5b797c1b8bd3ed81cbbe6c90a163245c15f1ab5ebedf03f885edeb61ebc6b341c61e31789072f29f2a3e1b4bece \
+    ; python_version < '3.4'
+flake8==3.5.0 \
+    --hash=sha512:3eae909818050481e37a701d33ac4efc0c573233ae533b7d72414696f522e115e13945cc52bba22b0172d6e815d1dc34b369f2853cbc60328024c0851161184f \
+    --hash=sha512:ac5d1bd9c44ed76c9e5cbd941b24bb552aa76e156b24ccda6cc8440ee90f3a574fdb393665adc75298e4f57111d7ab3115ef21ef9088fb327d0a9703d442450c
+flake8-docstrings==1.3.0 \
+    --hash=sha512:b2807bd0b68071f5ea8f1de545826692d8c97c14f5f27132a2c5348a4f0599773345305208d80e6302d6ae7c714e1575c30012d50e0996ec86549df7c3278a1c \
+    --hash=sha512:c90bdcafc84f2940c401c8b22a9cfb96007e6c0b63465e611f5031735a85f1fa7f65f5d9ca898bb98e69966758e9e3ef5b907577ea0b1e925285572ed6f2f954
+flake8-polyfill==1.0.2 \
+    --hash=sha512:5d5d9c487e3fcfa3a51d226ca81b8c559e2dffaa657f902d4afc47551677c0e889bf168297a56e816eead2aa45e8a9f4ca1674e8f7c9dc653bb2a01a95132e28 \
+    --hash=sha512:767a599aacbe87328b3e36bd85e0841870760ed9bd95dd3a4a9084edc6f0ae89f8203d565c2b075f16f1db21b647c17b2aa59b08e4702109d7e5f79f36d9f3fa
+funcsigs==1.0.2 \
+    --hash=sha512:4e583bb7081bb1d6c0fe5a6935ca03032d562b93ef5c11b51a72ce9e7ac12902451cc2233c7e6f70440629d88d8e6e9625965ee408100b80b0024f3a6204afda \
+    --hash=sha512:8f3d521413af262085cecbe12a41cf1ad36d42efe4c7c892aca58240e1412c9380da72ec159a499a8eaa18bf5013a5892cff58b240b49f3cf4d9428caca9cfe2 \
+    ; python_version < "3.0"
+mccabe==0.6.1 \
+    --hash=sha512:9cebc6bd1c8c0201072c8c18fac5e250544ec61d6421e75e7db31a526e3f5daa5e9afae8e1ac85ed7df23d3e3216950ee311051230c685044432dabd81a3e14a \
+    --hash=sha512:d8fc251a29790887c14c5932c5172b4cd578cd37ccf14cb96e80f0b97f27023427ea032d14e1e2a99d72627b055eb285f60db69e679ecd79d90a34b0255703d8
+more-itertools==4.3.0 \
+    --hash=sha512:5fee3d99fd796b0f0f6b5aa99a8db22e219d3dced71fef6f4b16286fe67061dce280389b3638c55c6e5f6c828ef1286cee845d76a6861d2af66d90e995d5aa4e \
+    --hash=sha512:6b551ae04729c6b6531121384efcc9e3db9de028d121da27cf10fae862ea28e8e73e8bcb1e3d4f6f5f0bfac5a4774bdd731f877639a4dc685bf64b207d6b69ab \
+    --hash=sha512:a01f0070312c1cfdfbd5ff56c2e98779b7cd67b824885a49a4cd9ea99b6dc1462bacb56f2ac6132aa8febb0ef9263546ecb5f57566f091d84d69a821af7faaa1
+pathlib2==2.3.2 \
+    --hash=sha512:2bc072ab13d311f54ad8b1bcfb826f56ba62572dcb3b1788594429e5e47e08488f69d3ddae99b5278306d90717565ac75bd2bfb3e4b8c5cd82b1fe359649c9cd \
+    --hash=sha512:46ba0cc8b26006bc4cb914118b7c453dc49cc8a80147ea7a4b3d5a17e97d5538c5d73a3029bd7e5b59f42f256baba30ea273382e57468df1a459ac6f7c237ddc \
+    ; python_version < "3.6"
+pluggy==0.7.1 \
+    --hash=sha512:8d111f37894239974af6daff28d3fadc0f5b8c25cf9544391538e1992341720de056c23a98754c5646447020f2adbf974b9524954c57c93558d232d9de67c078 \
+    --hash=sha512:e19f6c1b3f2e711df1c8b075a2b802751afd218f1b692f08b2434c01d71c402331ff8c937550fbdfece2098f6d6856422d485f193e188916b365ded2e4087697
+py==1.6.0 \
+    --hash=sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6 \
+    --hash=sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1
+pycodestyle==2.3.1  \
+    --hash=sha512:276bc21b4e6898e379be88f3582135a21314460ad20d4b6c3e12825ac1a72082b2c08bb62099e704c3222f879a1098d0b305132fac1095aaa60ca239b9763a2d \
+    --hash=sha512:e58e10d1514edacfa5a844935a0c4643614dc8b656c9b900d8a56928659cc06c30b2b25ad231261d90837bd53495f8d7ba9748b5480902eadd83124cd262e0ac \
+    # pyup: <2.4.0
+pydocstyle==2.1.1 \
+    --hash=sha512:7bf9e7f35af02cf2da7090316e4c42bd21015604ef6388e88feae7322843b407097bbbb7d12eadbdf179f94db2f7502a8eafc17464e27e5262ccb310be5ef2cf \
+    --hash=sha512:815c604c81e6cf7824dd29af24797534e078860d4bbbaa35962beebc7284fe27e2c4238fa806a57225680f0d96c2bef544e0243a98efdc61ac59253aba7d5e5c \
+    --hash=sha512:e3cc922df3fa2c581d9086dbfc7fc2618dc4b2ea5d9b41b7b15625c02a1110e6390b771f696086b32f2c0635f2c0d4ced779211dcaf960d9660e0f52d9269c1d 
+pyflakes==1.6.0 \
+    --hash=sha512:340f63ed287bfc0bcbe924a5692b7dad1694c874ffe9ce279f744db0726efe42c57573b4416cd5d4cd1fb1a5b3262fb76ba7b8cfab1f00b65771cdb67b7c0062 \
+    --hash=sha512:7e9c2aad6ebed638a1354cef51c7e1f68b25e59f8caf4694997a9afecd7cd8baa629a9363297ac0d961430f007fd22dcae7dae1bcbd7838a3b5d4285063bc7c5
+    # pyup: <1.7.0
+pytest==3.8.0 \
+    --hash=sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823 \
+    --hash=sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d
+pytest-cov==2.6.0 \
+    --hash=sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7 \
+    --hash=sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762
+scandir==1.9.0 \
+    --hash=sha512:0b673afcd993095a3a5b56f501b533f5ca5732362b200551faa7d9a26b22814c288082747b548752b98588ca20f131cbfaf52eeada3e8054c4c70084176a868a \
+    --hash=sha512:1d1ae30e4adbe07c4f54c2319c71e782f07a64bc3f59b620f6d43ef71e0e12a012f33bcd8de6bada06131e0be5185a930502919093adb12c42f4798084dec410 \
+    --hash=sha512:3ae68b5e6d9e22c354cc587cf62afb971601dd76168f7e8d701ce37de28579dd68f4e1958dc61ae18759d03238b795036130b1968088838ff13ad5c02b31cf35 \
+    --hash=sha512:41dacf42734ac9a53bb5bd5d783023236d68bb4883c853fb2ee34224206c577a4a277d4a8cf8f9be6fd5a8b59d9b4fe4568a0c815a62ec84ce99ded933961ec3 \
+    --hash=sha512:a34fc8b5a28711f796bd806210ce8fc5d72c604f84c8cbf20bc62c916105932152cb627b875b0b2b9127f930c5cad7d628f9c4ee061d126339a84aaad9fd810a \
+    --hash=sha512:a6d927cb63a3f39e10493428e1bde0f73b7f0716ef9d014e9cfc6c3e68934dbac98825bc3965de803f05abdb4e685942a0fbc3a962c511bbbbc19765f96749f8 \
+    --hash=sha512:bbeeb756a65d0cf43cb742e7b3bcde9cfb697b86ca48f803f497d3cb99f448ea8714bb50e69abcf17a0049bad0c399e03949f7bc52d13a13b6d673a824b3fc94 \
+    --hash=sha512:c29009141ae5353d485500c4bb33b66c1e2164b333ddcdf7c4b72da67063844f15a2a02c30d30507b6bb1b04b79761045073fd1d243b459bf5108db923bf4d96 \
+    --hash=sha512:c36caf67b3703d83be53d4351957b17fcf3bc8eef8e0326ef800840985a18f5370babd8ac3cb26d21a62fa2d5fb4abda014c59de4f35904b7c596a22501bd118 \
+    --hash=sha512:d73cad3a09fcc5dd3dcecacd4cc77d5571c9d593f96331c028870e7c5130c81773a6e32b7cfdc1ff666e6819694b538262fe786b88c1a46277aa3b2892f0e35c \
+    --hash=sha512:ea7fb1d9488450ac0cd6e640582a90089a3d7fcf9844abecb4f37f253732f295cb628913e3aec147173b7019317cfafe4791b3b04a3759f7c0474f7f2c214c2a
+six==1.11.0 \
+    --hash=sha512:33f246a2e987141e17e5edad9d3537cf3aba0cbdd0bc2a907ea52ce0d674b1474f29c3dd5cc26605fd960396054b189ca5f501708333cad234c223131483fe24 \
+    --hash=sha512:b71248ef493ac12b115fdf06206090078fac147ec6ab6efb67b87e2b9c07a69d55bf8e70fde713d81735a99e560a17da714274e2ecbd7b0200d2e9a0f39970a7
+snowballstemmer==1.2.1 \
+    --hash=sha512:09f860f383d84d12a83c87ef6654fba4ac10bca07e8d2ce88dd428c72754110d56a4b698e125a18818699a289455bf61cf67ea68e349ee8a12d6dfff0a3fbed9 \
+    --hash=sha512:c97bb3d293ffbe16d4c553f8a8ad1dbb5f659441cde93e407f71419f82b8e16b3090df2c0294d64f73382a648cd8cc62bca407124a8e2d60e4c424b78e10b6b8
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/requirements.txt
@@ -0,0 +1,10 @@
+# Generated from dephash.py + hashin.py
+attrs==18.2.0 \
+    --hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb \
+    --hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69
+enum34==1.1.6 \
+    --hash=sha512:3671dc8c0c3cf2a3cbafd22feb85bcc66f4262722b59683429af0865642929622fd3213113703c0b811f7e3bf9cee0e30c7403f6971585150fc013ec01333873 \
+    --hash=sha512:4f916143fe35d41b28efb7958ffa7e10d430d66697f8150b98aae3797a058378fd9a4c201969c155f30f88c962f78e4e75e684889d5138cd2fe60406924363e9 \
+    --hash=sha512:51652525adc37bd1af1d81933f965dba9c508838d9f759c80ca1392991515a29c2c0263264a4e175b37a6ba11dca68c354774e448b19ef1bdba96be5474d93ec \
+    --hash=sha512:ac5eedcd9425b06f44c3e9029cfc3e93a266a5b797c1b8bd3ed81cbbe6c90a163245c15f1ab5ebedf03f885edeb61ebc6b341c61e31789072f29f2a3e1b4bece \
+    ; python_version < '3.4'
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/requirements.txt.in
@@ -0,0 +1,3 @@
+# pyup: ignore file
+enum34; python_version < '3.4'
+attrs
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/setup.py
@@ -0,0 +1,38 @@
+import os
+import re
+from setuptools import setup, find_packages
+
+
+project_dir = os.path.abspath(os.path.dirname(__file__))
+
+with open(os.path.join(project_dir, 'version.txt')) as f:
+    version = f.read().rstrip()
+
+# We use the .in file because a library shouldn't pin versions, it breaks consumers' updates.
+# We allow commented lines in this file
+with open(os.path.join(project_dir, 'requirements.txt.in')) as f:
+    requirements_raw = f.readlines()
+
+requirements_without_comments = [
+    line for line in requirements_raw if line and not line.startswith('#')
+]
+
+setup(
+    name='mozilla-version',
+    version=version,
+    description="""Process Firefox versions numbers. Tells whether they are valid or not, whether \
+they are nightlies or regular releases, whether this version precedes that other.
+    """,
+    author='Mozilla Release Engineering',
+    author_email='release+python@mozilla.com',
+    url='https://github.com/mozilla-releng/mozilla-version',
+    packages=find_packages(),
+    include_package_data=True,
+    zip_safe=False,
+    license='MPL2',
+    install_requires=requirements_without_comments,
+    classifiers=(
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+    ),
+)
new file mode 100644
--- /dev/null
+++ b/third_party/python/mozilla-version/version.txt
@@ -0,0 +1,1 @@
+0.3.0
--- a/third_party/python/requirements.in
+++ b/third_party/python/requirements.in
@@ -1,12 +1,13 @@
 attrs==18.1.0
 blessings==1.7
 jsmin==2.1.0
 json-e==2.7.0
+mozilla-version==0.3.0
 pip-tools==3.0.0
 pipenv==2018.5.18
 psutil==5.4.3
 pytest==3.6.2
 python-hglib==2.4
 requests==2.9.1
 six==1.10.0
 virtualenv==15.2.0
--- a/third_party/python/requirements.txt
+++ b/third_party/python/requirements.txt
@@ -12,29 +12,37 @@ blessings==1.7 \
 certifi==2018.4.16 \
     --hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
     --hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0 \
     # via pipenv
 click==7.0 \
     --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \
     --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 \
     # via pip-tools
+enum34==1.1.6 \
+    --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \
+    --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \
+    --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \
+    --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 \
+    # via mozilla-version
 funcsigs==1.0.2 \
     --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \
     --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 \
     # via pytest
 jsmin==2.1.0 \
     --hash=sha256:5d07bf0251a4128e5e8e8eef603849b6b5741c337bff087731a248f9cc774f56
 json-e==2.7.0 \
     --hash=sha256:d8c1ec3f5bbc7728c3a504ebe58829f283c64eca230871e4eefe974b4cdaae4a
 more-itertools==4.3.0 \
     --hash=sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092 \
     --hash=sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e \
     --hash=sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d \
     # via pytest
+mozilla-version==0.3.0 \
+    --hash=sha256:97f428f6a87f1a0569e03c39e446eeed87c3ec5d8300319d41e8348ef832e8ea
 pip-tools==3.0.0 \
     --hash=sha256:4a94997602848f77ff02f660c0fcdfeaf316924ebb236c865f9742ce212aa6f9 \
     --hash=sha256:e45e5198ce3799068642ebb0e7c9be5520bcff944c0186f79c1199a2759c970a
 pipenv==2018.5.18 \
     --hash=sha256:04b9a8b02a3ff12a5502b335850cfdb192adcfd1d6bbdb7a7c47cae9ab9ddece \
     --hash=sha256:e96d5bfa6822a17b2200d455aa5f9002c14361c50df1b1e51921479d7c09e741
 pluggy==0.6.0 \
     --hash=sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff \