├── flufl ├── enum │ ├── docs │ │ ├── __init__.py │ │ └── using.rst │ ├── tests │ │ ├── __init__.py │ │ ├── fruit.py │ │ ├── test_documentation.py │ │ └── test_enum.py │ ├── __init__.py │ ├── NEWS.rst │ ├── README.rst │ ├── conf.py │ └── _enum.py └── __init__.py ├── flufl.enum.egg-info ├── top_level.txt ├── dependency_links.txt ├── namespace_packages.txt ├── PKG-INFO └── SOURCES.txt ├── MANIFEST.in ├── setup.cfg ├── PKG-INFO ├── template.py ├── README.rst ├── setup.py ├── setup_helpers.py └── distribute_setup.py /flufl/enum/docs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flufl/enum/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flufl.enum.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | flufl 2 | -------------------------------------------------------------------------------- /flufl.enum.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flufl.enum.egg-info/namespace_packages.txt: -------------------------------------------------------------------------------- 1 | flufl 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py 2 | global-include *.txt *.rst 3 | prune build 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source_dir = flufl/enum 3 | 4 | [upload_docs] 5 | upload_dir = build/sphinx/html 6 | 7 | [egg_info] 8 | tag_build = 9 | tag_date = 0 10 | tag_svn_revision = 0 11 | 12 | -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: flufl.enum 3 | Version: 3.3.1 4 | Summary: A Python enumeration package. 5 | Home-page: http://launchpad.net/flufl.enum 6 | Author: Barry Warsaw 7 | Author-email: barry@python.org 8 | License: LGPLv3 9 | Download-URL: https://launchpad.net/flufl.enum/+download 10 | Description: flufl/enum/README.rst 11 | 12 | flufl/enum/NEWS.rst 13 | 14 | Platform: UNKNOWN 15 | -------------------------------------------------------------------------------- /flufl.enum.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: flufl.enum 3 | Version: 3.3.1 4 | Summary: A Python enumeration package. 5 | Home-page: http://launchpad.net/flufl.enum 6 | Author: Barry Warsaw 7 | Author-email: barry@python.org 8 | License: LGPLv3 9 | Download-URL: https://launchpad.net/flufl.enum/+download 10 | Description: flufl/enum/README.rst 11 | 12 | flufl/enum/NEWS.rst 13 | 14 | Platform: UNKNOWN 15 | -------------------------------------------------------------------------------- /flufl.enum.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | MANIFEST.in 2 | README.rst 3 | distribute_setup.py 4 | setup.cfg 5 | setup.py 6 | setup_helpers.py 7 | template.py 8 | flufl/__init__.py 9 | flufl.enum.egg-info/PKG-INFO 10 | flufl.enum.egg-info/SOURCES.txt 11 | flufl.enum.egg-info/dependency_links.txt 12 | flufl.enum.egg-info/namespace_packages.txt 13 | flufl.enum.egg-info/top_level.txt 14 | flufl/enum/NEWS.rst 15 | flufl/enum/README.rst 16 | flufl/enum/__init__.py 17 | flufl/enum/_enum.py 18 | flufl/enum/conf.py 19 | flufl/enum/docs/__init__.py 20 | flufl/enum/docs/using.rst 21 | flufl/enum/tests/__init__.py 22 | flufl/enum/tests/fruit.py 23 | flufl/enum/tests/test_documentation.py 24 | flufl/enum/tests/test_enum.py -------------------------------------------------------------------------------- /template.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, version 3 of the License. 8 | # 9 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 12 | # for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with flufl.enum. If not, see . 16 | 17 | """Module contents.""" 18 | 19 | from __future__ import absolute_import, print_function, unicode_literals 20 | 21 | __metaclass__ = type 22 | __all__ = [ 23 | ] 24 | -------------------------------------------------------------------------------- /flufl/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2004-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum. 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation, version 3 of the License. 8 | # 9 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 12 | # License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with flufl.enum. If not, see . 16 | 17 | # this is a namespace package 18 | try: 19 | import pkg_resources 20 | pkg_resources.declare_namespace(__name__) 21 | except ImportError: 22 | import pkgutil 23 | __path__ = pkgutil.extend_path(__path__, __name__) 24 | -------------------------------------------------------------------------------- /flufl/enum/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2004-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, version 3 of the License. 8 | # 9 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 12 | # for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with flufl.enum. If not, see . 16 | 17 | """Package init.""" 18 | 19 | from __future__ import absolute_import, print_function, unicode_literals 20 | 21 | __metaclass__ = type 22 | __all__ = [ 23 | 'Enum', 24 | '__version__', 25 | 'make_enum', 26 | ] 27 | 28 | 29 | __version__ = '3.3.1' 30 | 31 | 32 | from ._enum import Enum, make, make_enum 33 | -------------------------------------------------------------------------------- /flufl/enum/tests/fruit.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, version 3 of the License. 8 | # 9 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 12 | # for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with flufl.enum. If not, see . 16 | 17 | """A class for testing pickling.""" 18 | 19 | from __future__ import absolute_import, print_function, unicode_literals 20 | 21 | __metaclass__ = type 22 | __all__ = [ 23 | 'Fruit', 24 | ] 25 | 26 | 27 | from flufl.enum import Enum 28 | 29 | 30 | class Fruit(Enum): 31 | kiwi = 1 32 | banana = 2 33 | tomato = 3 34 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | A Python enumeration package. 2 | 3 | .. 4 | This file is part of flufl.enum. 5 | 6 | flufl.enum is free software: you can redistribute it and/or modify it 7 | under the terms of the GNU Lesser General Public License as published by 8 | the Free Software Foundation, version 3 of the License. 9 | 10 | flufl.enum is distributed in the hope that it will be useful, but 11 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 13 | License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with flufl.enum. If not, see . 17 | 18 | 19 | ========== 20 | flufl.enum 21 | ========== 22 | 23 | The ``flufl.enum`` library is yet another Python enumeration package. Its 24 | goal is to provide simple, specific, concise semantics in an easy to read and 25 | write syntax. ``flufl.enum`` has just enough of the features needed to make 26 | enumerations useful, but without a lot of extra baggage to weigh them down. 27 | This work grew out of the Mailman 3.0 project. 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2004-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum. 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with flufl.enum. If not, see . 17 | 18 | import distribute_setup 19 | distribute_setup.use_setuptools() 20 | 21 | from setup_helpers import ( 22 | description, get_version, long_description, require_python) 23 | from setuptools import setup, find_packages 24 | 25 | 26 | require_python(0x20600f0) 27 | __version__ = get_version('flufl/enum/__init__.py') 28 | 29 | 30 | setup( 31 | name='flufl.enum', 32 | version=__version__, 33 | namespace_packages=['flufl'], 34 | packages=find_packages(), 35 | include_package_data=True, 36 | maintainer='Barry Warsaw', 37 | maintainer_email='barry@python.org', 38 | description=description('README.rst'), 39 | long_description=long_description( 40 | 'flufl/enum/README.rst', 41 | 'flufl/enum/NEWS.rst'), 42 | license='LGPLv3', 43 | url='http://launchpad.net/flufl.enum', 44 | download_url='https://launchpad.net/flufl.enum/+download', 45 | test_suite='flufl.enum.tests', 46 | ) 47 | -------------------------------------------------------------------------------- /flufl/enum/NEWS.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | NEWS for flufl.enum 3 | =================== 4 | 5 | 3.3.1 (2012-01-19) 6 | ================== 7 | * Fix Python 3 compatibility with Sphinx's conf.py ($python setup.py install). 8 | 9 | 10 | 3.3 (2012-01-19) 11 | ================ 12 | * Remove the dependency on 2to3 for Python 3 support; support Python 3 13 | directly with a single code base. 14 | * flufl.enum.make_enum() is deprecated in favor of flufl.enum.make() which 15 | provides a better API. (LP: #839529) 16 | * Updated to distribute 0.6.19. 17 | * Moved all documentation to .rst suffix. 18 | * Make test_deprecations() compatible with Python 3 and Python 2. 19 | * Removed markup for pylint. 20 | * Improve documentation to illustrate that enum values with similar names and 21 | integer representations still do not hash equally. (Found by Jeroen 22 | Vermeulen). 23 | 24 | 25 | 3.2 (2011-08-19) 26 | ================ 27 | * make_enum() accepts an optional `iterable` argument to provide the values 28 | for the enums. 29 | * The .enumclass and .enumname attributes are deprecated. Use .enum and 30 | .name instead, respectively. 31 | * Improve the documentation regarding ordered comparisons and equality 32 | tests. (LP: #794853) 33 | * make_enum() now enforces the use of valid Python identifiers. (LP: #803570) 34 | 35 | 36 | 3.1 (2011-03-01) 37 | ================ 38 | * New convenience function `make_enum()`. (Contributed by Michael Foord) 39 | * Fix `from flufl.enum import *`. 40 | * Enums created with the class syntax can be pickled and unpickled. 41 | (Suggestion and basic implementation idea by Phillip Eby). 42 | 43 | 44 | 3.0.1 (2010-06-07) 45 | ================== 46 | * Fixed typo which caused the package to break. 47 | 48 | 49 | 3.0 (2010-04-24) 50 | ================ 51 | * Package renamed to flufl.enum. 52 | 53 | 54 | 2.0.2 (2010-01-29) 55 | ================== 56 | * Fixed some test failures when running under 2to3. 57 | 58 | 59 | 2.0.1 (2010-01-08) 60 | ================== 61 | * Fix the manifest and clarify license. 62 | 63 | 64 | 2.0 (2010-01-07) 65 | ================ 66 | * Use Sphinx to build the documentation. 67 | * Updates to better package Debian/Ubuntu. 68 | * Use distribute_setup instead of ez_setup. 69 | * Rename pep-xxxx.txt; this won't be submitted as a PEP. 70 | * Remove dependencies on nose and setuptools_bzr 71 | * Support Python 3 via 2to3. 72 | 73 | 74 | Earlier 75 | ======= 76 | 77 | Try `bzr log` for details. 78 | -------------------------------------------------------------------------------- /flufl/enum/tests/test_documentation.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2004-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum. 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with flufl.enum. If not, see . 17 | 18 | """Test harness for doctests.""" 19 | 20 | from __future__ import absolute_import, print_function, unicode_literals 21 | 22 | __metaclass__ = type 23 | __all__ = [ 24 | 'additional_tests', 25 | ] 26 | 27 | 28 | import os 29 | import atexit 30 | import doctest 31 | import unittest 32 | 33 | from pkg_resources import ( 34 | resource_filename, resource_exists, resource_listdir, cleanup_resources) 35 | 36 | 37 | COMMASPACE = ', ' 38 | DOT = '.' 39 | DOCTEST_FLAGS = ( 40 | doctest.ELLIPSIS | 41 | doctest.NORMALIZE_WHITESPACE | 42 | doctest.REPORT_NDIFF) 43 | 44 | 45 | 46 | def stop(): 47 | """Call into pdb.set_trace()""" 48 | # Do the import here so that you get the wacky special hacked pdb instead 49 | # of Python's normal pdb. 50 | import pdb 51 | pdb.set_trace() 52 | 53 | 54 | 55 | def setup(testobj): 56 | """Test setup.""" 57 | # Make sure future statements in our doctests match the Python code. When 58 | # run with 2to3, the future import gets removed and these names are not 59 | # defined. 60 | try: 61 | testobj.globs['absolute_import'] = absolute_import 62 | testobj.globs['print_function'] = print_function 63 | testobj.globs['unicode_literals'] = unicode_literals 64 | except NameError: 65 | pass 66 | testobj.globs['stop'] = stop 67 | 68 | 69 | 70 | def additional_tests(): 71 | "Run the doc tests (README.rst and docs/*, if any exist)" 72 | doctest_files = [ 73 | os.path.abspath(resource_filename('flufl.enum', 'README.rst'))] 74 | if resource_exists('flufl.enum', 'docs'): 75 | for name in resource_listdir('flufl.enum', 'docs'): 76 | if name.endswith('.rst'): 77 | doctest_files.append( 78 | os.path.abspath( 79 | resource_filename('flufl.enum', 'docs/%s' % name))) 80 | kwargs = dict(module_relative=False, 81 | optionflags=DOCTEST_FLAGS, 82 | setUp=setup, 83 | ) 84 | atexit.register(cleanup_resources) 85 | return unittest.TestSuite(( 86 | doctest.DocFileSuite(*doctest_files, **kwargs))) 87 | -------------------------------------------------------------------------------- /flufl/enum/README.rst: -------------------------------------------------------------------------------- 1 | ========================================= 2 | flufl.enum - A Python enumeration package 3 | ========================================= 4 | 5 | This package is called ``flufl.enum``. It is yet another Python enumeration 6 | package, but with a slightly different take on syntax and semantics than 7 | earlier such packages. 8 | 9 | The goals of ``flufl.enum`` are to produce simple, specific, concise semantics 10 | in an easy to read and write syntax. ``flufl.enum`` has just enough of the 11 | features needed to make enumerations useful, but without a lot of extra 12 | baggage to weigh them down. This work grew out of the Mailman 3.0 project and 13 | it is the enum package used there. Until version 3.0, this package was called 14 | ``munepy``. 15 | 16 | 17 | Requirements 18 | ============ 19 | 20 | ``flufl.enum`` requires Python 2.6 or newer, and is compatible with Python 3 21 | when used with ``2to3``. 22 | 23 | 24 | Documentation 25 | ============= 26 | 27 | A `simple guide`_ to using the library is available within this package, in 28 | the form of doctests. The manual is also available online in the Cheeseshop 29 | at: 30 | 31 | http://package.python.org/flufl.enum 32 | 33 | 34 | Project details 35 | =============== 36 | 37 | The project home page is: 38 | 39 | http://launchpad.net/flufl.enum 40 | 41 | You should report bugs at: 42 | 43 | http://bugs.launchpad.net/flufl.enum 44 | 45 | You can download the latest version of the package either from the Cheeseshop: 46 | 47 | http://pypi.python.org/pypi/flufl.enum 48 | 49 | or from the Launchpad page above. Of course you can also just install it with 50 | ``pip`` or ``easy_install`` from the command line:: 51 | 52 | % sudo pip flufl.enum 53 | % sudo easy_install flufl.enum 54 | 55 | You can grab the latest development copy of the code using Bazaar, from the 56 | Launchpad home page above. See http://bazaar-vcs.org for details on the 57 | Bazaar distributed revision control system. If you have Bazaar installed, you 58 | can grab your own branch of the code like this:: 59 | 60 | bzr branch lp:flufl.enum 61 | 62 | You may contact the author via barry@python.org. 63 | 64 | 65 | Copyright 66 | ========= 67 | 68 | Copyright (C) 2004-2011 Barry A. Warsaw 69 | 70 | This file is part of flufl.enum. 71 | 72 | flufl.enum is free software: you can redistribute it and/or modify it under 73 | the terms of the GNU Lesser General Public License as published by the Free 74 | Software Foundation, either version 3 of the License, or (at your option) any 75 | later version. 76 | 77 | flufl.enum is distributed in the hope that it will be useful, but WITHOUT ANY 78 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 79 | A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 80 | details. 81 | 82 | You should have received a copy of the GNU Lesser General Public License along 83 | with flufl.enum. If not, see . 84 | 85 | 86 | Table of Contents 87 | ================= 88 | 89 | .. toctree:: 90 | 91 | docs/using.rst 92 | NEWS.rst 93 | 94 | .. _`simple guide`: docs/using.html 95 | -------------------------------------------------------------------------------- /flufl/enum/tests/test_enum.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum. 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) 8 | # any later version. 9 | # 10 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | # for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with flufl.enum. If not, see . 17 | 18 | """Additional package tests.""" 19 | 20 | from __future__ import absolute_import, print_function, unicode_literals 21 | 22 | __metaclass__ = type 23 | __all__ = [ 24 | 'TestEnum', 25 | ] 26 | 27 | 28 | import unittest 29 | import warnings 30 | 31 | from flufl.enum import Enum, make, make_enum 32 | 33 | 34 | 35 | class TestEnum(unittest.TestCase): 36 | """Additional unit tests.""" 37 | 38 | def test_deprecations(self): 39 | # Enum.enumclass and Enum.enumname are deprecated. 40 | class Animals(Enum): 41 | ant = 1 42 | bee = 2 43 | cat = 3 44 | with warnings.catch_warnings(record=True) as seen: 45 | # Cause all warnings to always be triggered. 46 | warnings.simplefilter('always') 47 | Animals.ant.enumclass 48 | self.assertEqual(len(seen), 1) 49 | self.assertTrue(issubclass(seen[0].category, DeprecationWarning)) 50 | self.assertEqual(seen[0].message.args[0], 51 | '.enumclass is deprecated; use .enum instead') 52 | with warnings.catch_warnings(record=True) as seen: 53 | # Cause all warnings to always be triggered. 54 | warnings.simplefilter('always') 55 | Animals.ant.enumname 56 | self.assertEqual(len(seen), 1) 57 | self.assertTrue(issubclass(seen[0].category, DeprecationWarning)) 58 | self.assertEqual(seen[0].message.args[0], 59 | '.enumname is deprecated; use .name instead') 60 | 61 | def test_make_enum_identifiers_bug_803570(self): 62 | # LP: #803570 describes that make_enum() allows non-identifiers as 63 | # enum value names. 64 | try: 65 | make_enum('Foo', '1 2 3') 66 | except ValueError as exc: 67 | self.assertEqual(exc.args[0], 'non-identifiers: 1 2 3') 68 | else: 69 | raise AssertionError('Expected a ValueError') 70 | 71 | def test_make_enum_deprecated(self): 72 | # LP: #839529: deprecate the make_enum() API and use the much better 73 | # make() API. 74 | with warnings.catch_warnings(record=True) as seen: 75 | # Cause all warnings to always be triggered. 76 | warnings.simplefilter('always') 77 | make_enum('Foo', 'a b c') 78 | self.assertEqual(len(seen), 1) 79 | self.assertTrue(issubclass(seen[0].category, DeprecationWarning)) 80 | self.assertEqual(seen[0].message.args[0], 81 | 'make_enum() is deprecated; use make() instead') 82 | 83 | def test_enum_make_not_all_2_tuples(self): 84 | # If 2-tuples are used, all items must be 2-tuples. 85 | self.assertRaises(ValueError, make, 'Animals', ( 86 | ('ant', 1), 87 | ('bee', 2), 88 | 'cat', 89 | ('dog', 4), 90 | )) 91 | self.assertRaises(ValueError, make, 'Animals', ( 92 | ('ant', 1), 93 | ('bee', 2), 94 | ('cat',), 95 | ('dog', 4), 96 | )) 97 | self.assertRaises(ValueError, make, 'Animals', ( 98 | ('ant', 1), 99 | ('bee', 2), 100 | ('cat', 3, 'oops'), 101 | ('dog', 4), 102 | )) 103 | 104 | def test_make_identifiers(self): 105 | # Ensure that the make() interface also enforces identifiers. 106 | try: 107 | make('Foo', ('1', '2', '3')) 108 | except ValueError as exc: 109 | self.assertEqual(exc.args[0], 'non-identifiers: 1 2 3') 110 | else: 111 | raise AssertionError('Expected a ValueError') 112 | try: 113 | make('Foo', (('ant', 1), ('bee', 2), ('3', 'cat'))) 114 | except ValueError as exc: 115 | self.assertEqual(exc.args[0], 'non-identifiers: 3') 116 | else: 117 | raise AssertionError('Expected a ValueError') 118 | -------------------------------------------------------------------------------- /setup_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, version 3 of the License. 8 | # 9 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 12 | # for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with flufl.enum. If not, see . 16 | 17 | """setup.py helper functions.""" 18 | 19 | from __future__ import absolute_import, unicode_literals 20 | from __future__ import print_function 21 | 22 | 23 | __metaclass__ = type 24 | __all__ = [ 25 | 'description', 26 | 'find_doctests', 27 | 'get_version', 28 | 'long_description', 29 | 'require_python', 30 | ] 31 | 32 | 33 | import os 34 | import re 35 | import sys 36 | 37 | 38 | DEFAULT_VERSION_RE = re.compile(r'(?P\d+\.\d+(?:\.\d+)?)') 39 | NL = '\n' 40 | 41 | __version__ = '2.0' 42 | 43 | 44 | 45 | def require_python(minimum): 46 | """Require at least a minimum Python version. 47 | 48 | The version number is expressed in terms of `sys.hexversion`. E.g. to 49 | require a minimum of Python 2.6, use:: 50 | 51 | >>> require_python(0x206000f0) 52 | 53 | :param minimum: Minimum Python version supported. 54 | :type minimum: integer 55 | """ 56 | if sys.hexversion < minimum: 57 | hversion = hex(minimum)[2:] 58 | if len(hversion) % 2 != 0: 59 | hversion = '0' + hversion 60 | split = list(hversion) 61 | parts = [] 62 | while split: 63 | parts.append(int(''.join((split.pop(0), split.pop(0))), 16)) 64 | major, minor, micro, release = parts 65 | if release == 0xf0: 66 | print('Python {0}.{1}.{2} or better is required'.format( 67 | major, minor, micro)) 68 | else: 69 | print('Python {0}.{1}.{2} ({3}) or better is required'.format( 70 | major, minor, micro, hex(release)[2:])) 71 | sys.exit(1) 72 | 73 | 74 | 75 | def get_version(filename, pattern=None): 76 | """Extract the __version__ from a file without importing it. 77 | 78 | While you could get the __version__ by importing the module, the very act 79 | of importing can cause unintended consequences. For example, Distribute's 80 | automatic 2to3 support will break. Instead, this searches the file for a 81 | line that starts with __version__, and extract the version number by 82 | regular expression matching. 83 | 84 | By default, two or three dot-separated digits are recognized, but by 85 | passing a pattern parameter, you can recognize just about anything. Use 86 | the `version` group name to specify the match group. 87 | 88 | :param filename: The name of the file to search. 89 | :type filename: string 90 | :param pattern: Optional alternative regular expression pattern to use. 91 | :type pattern: string 92 | :return: The version that was extracted. 93 | :rtype: string 94 | """ 95 | if pattern is None: 96 | cre = DEFAULT_VERSION_RE 97 | else: 98 | cre = re.compile(pattern) 99 | with open(filename) as fp: 100 | for line in fp: 101 | if line.startswith('__version__'): 102 | mo = cre.search(line) 103 | assert mo, 'No valid __version__ string found' 104 | return mo.group('version') 105 | raise AssertionError('No __version__ assignment found') 106 | 107 | 108 | 109 | def find_doctests(start='.', extension='.rst'): 110 | """Find separate-file doctests in the package. 111 | 112 | This is useful for Distribute's automatic 2to3 conversion support. The 113 | `setup()` keyword argument `convert_2to3_doctests` requires file names, 114 | which may be difficult to track automatically as you add new doctests. 115 | 116 | :param start: Directory to start searching in (default is cwd) 117 | :type start: string 118 | :param extension: Doctest file extension (default is .txt) 119 | :type extension: string 120 | :return: The doctest files found. 121 | :rtype: list 122 | """ 123 | doctests = [] 124 | for dirpath, dirnames, filenames in os.walk(start): 125 | doctests.extend(os.path.join(dirpath, filename) 126 | for filename in filenames 127 | if filename.endswith(extension)) 128 | return doctests 129 | 130 | 131 | 132 | def long_description(*filenames): 133 | """Provide a long description.""" 134 | res = [] 135 | for value in filenames: 136 | if value.endswith('.txt'): 137 | with open(value) as fp: 138 | value = fp.read() 139 | res.append(value) 140 | if not value.endswith(NL): 141 | res.append('') 142 | return NL.join(res) 143 | 144 | 145 | def description(filename): 146 | """Provide a short description.""" 147 | with open(filename) as fp: 148 | for line in fp: 149 | return line.strip() 150 | -------------------------------------------------------------------------------- /flufl/enum/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # flufl.enum documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Jan 7 18:41:30 2010. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | from __future__ import print_function 15 | import sys, os 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | #sys.path.append(os.path.abspath('.')) 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be extensions 25 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 26 | extensions = ['sphinx.ext.autodoc'] 27 | 28 | # Add any paths that contain templates here, relative to this directory. 29 | templates_path = ['../../_templates'] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The encoding of source files. 35 | #source_encoding = 'utf-8' 36 | 37 | # The master toctree document. 38 | master_doc = 'README' 39 | 40 | # General information about the project. 41 | project = 'flufl.enum' 42 | copyright = '2004-2012, Barry A. Warsaw' 43 | 44 | # The version info for the project you're documenting, acts as replacement for 45 | # |version| and |release|, also used in various other places throughout the 46 | # built documents. 47 | # 48 | from flufl.enum import __version__ 49 | # The short X.Y version. 50 | version = __version__ 51 | # The full version, including alpha/beta/rc tags. 52 | release = __version__ 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | #language = None 57 | 58 | # There are two options for replacing |today|: either, you set today to some 59 | # non-false value, then it is used: 60 | #today = '' 61 | # Else, today_fmt is used as the format for a strftime call. 62 | #today_fmt = '%B %d, %Y' 63 | 64 | # List of documents that shouldn't be included in the build. 65 | #unused_docs = [] 66 | 67 | # List of directories, relative to source directory, that shouldn't be searched 68 | # for source files. 69 | exclude_trees = ['_build', 'build', 'flufl.enum.egg-info', 'distribute-0.6.10'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. Major themes that come with 95 | # Sphinx are currently 'default' and 'sphinxdoc'. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['../../_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_use_modindex = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, an OpenSearch description file will be output, and all pages will 155 | # contain a tag referring to it. The value of this option must be the 156 | # base URL from which the finished HTML is served. 157 | #html_use_opensearch = '' 158 | 159 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 160 | #html_file_suffix = '' 161 | 162 | # Output file base name for HTML help builder. 163 | htmlhelp_basename = 'fluflenumdoc' 164 | 165 | 166 | # -- Options for LaTeX output -------------------------------------------------- 167 | 168 | # The paper size ('letter' or 'a4'). 169 | #latex_paper_size = 'letter' 170 | 171 | # The font size ('10pt', '11pt' or '12pt'). 172 | #latex_font_size = '10pt' 173 | 174 | # Grouping the document tree into LaTeX files. List of tuples 175 | # (source start file, target name, title, author, documentclass [howto/manual]). 176 | latex_documents = [ 177 | ('README.txt', 'fluflenum.tex', 'flufl.enum Documentation', 178 | 'Barry A. Warsaw', 'manual'), 179 | ] 180 | 181 | # The name of an image file (relative to this directory) to place at the top of 182 | # the title page. 183 | #latex_logo = None 184 | 185 | # For "manual" documents, if this is true, then toplevel headings are parts, 186 | # not chapters. 187 | #latex_use_parts = False 188 | 189 | # Additional stuff for the LaTeX preamble. 190 | #latex_preamble = '' 191 | 192 | # Documents to append as an appendix to all manuals. 193 | #latex_appendices = [] 194 | 195 | # If false, no module index is generated. 196 | #latex_use_modindex = True 197 | 198 | import errno 199 | def index_html(): 200 | cwd = os.getcwd() 201 | try: 202 | os.chdir('build/sphinx/html') 203 | try: 204 | os.symlink('README.html', 'index.html') 205 | except OSError as error: 206 | if error.errno != errno.EEXIST: 207 | raise 208 | print('index.html -> README.html') 209 | finally: 210 | os.chdir(cwd) 211 | 212 | import atexit 213 | atexit.register(index_html) 214 | -------------------------------------------------------------------------------- /flufl/enum/_enum.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2004-2012 by Barry A. Warsaw 2 | # 3 | # This file is part of flufl.enum 4 | # 5 | # flufl.enum is free software: you can redistribute it and/or modify it under 6 | # the terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, version 3 of the License. 8 | # 9 | # flufl.enum is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 12 | # for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with flufl.enum. If not, see . 16 | 17 | """Python enumerations.""" 18 | 19 | from __future__ import absolute_import, print_function, unicode_literals 20 | 21 | __metaclass__ = type 22 | __all__ = [ 23 | 'Enum', 24 | 'make_enum', 25 | ] 26 | 27 | 28 | import re 29 | import warnings 30 | 31 | 32 | COMMASPACE = ', ' 33 | SPACE = ' ' 34 | IDENTIFIER_RE = r'[a-zA-Z_][a-zA-Z0-0_]*' 35 | 36 | 37 | 38 | class EnumMetaclass(type): 39 | """Meta class for Enums.""" 40 | 41 | def __init__(cls, name, bases, attributes): 42 | """Create an Enum class. 43 | 44 | :param cls: The class being defined. 45 | :param name: The name of the class. 46 | :param bases: The class's base classes. 47 | :param attributes: The class attributes. 48 | """ 49 | super(EnumMetaclass, cls).__init__(name, bases, attributes) 50 | # Store EnumValues here for easy access. 51 | cls._enums = {} 52 | # Figure out the set of enum values on the base classes, to ensure 53 | # that we don't get any duplicate values (which would screw up 54 | # conversion from integer). 55 | for basecls in cls.__mro__: 56 | if hasattr(basecls, '_enums'): 57 | cls._enums.update(basecls._enums) 58 | # For each class attribute, create an EnumValue and store that back on 59 | # the class instead of the int. Skip Python reserved names. Also add 60 | # a mapping from the integer to the instance so we can return the same 61 | # object on conversion. 62 | for attr in attributes: 63 | if not (attr.startswith('__') and attr.endswith('__')): 64 | intval = attributes[attr] 65 | enumval = EnumValue(cls, intval, attr) 66 | if intval in cls._enums: 67 | raise TypeError('Multiple enum values: %s' % intval) 68 | # Store as an attribute on the class, and save the attr name 69 | setattr(cls, attr, enumval) 70 | cls._enums[intval] = attr 71 | 72 | def __getattr__(cls, name): 73 | if name == '__members__': 74 | return cls._enums.values() 75 | raise AttributeError(name) 76 | 77 | def __repr__(cls): 78 | enums = ['{0}: {1:d}'.format( 79 | cls._enums[k], k) for k in sorted(cls._enums)] 80 | return '<{0} {{{1}}}>'.format(cls.__name__, COMMASPACE.join(enums)) 81 | 82 | def __iter__(cls): 83 | for i in sorted(cls._enums): 84 | yield getattr(cls, cls._enums[i]) 85 | 86 | def __getitem__(cls, i): 87 | # i can be an integer or a string 88 | attr = cls._enums.get(i) 89 | if attr is None: 90 | # It wasn't an integer -- try attribute name 91 | try: 92 | return getattr(cls, i) 93 | except (AttributeError, TypeError): 94 | raise ValueError(i) 95 | return getattr(cls, attr) 96 | 97 | # Support both MyEnum[i] and MyEnum(i) 98 | __call__ = __getitem__ 99 | 100 | 101 | 102 | class EnumValue: 103 | """Class to represent an enumeration value. 104 | 105 | EnumValue('Color', 'red', 12) prints as 'Color.red' and can be converted 106 | to the integer 12. 107 | """ 108 | def __init__(self, cls, value, name): 109 | self._enum = cls 110 | self._value = value 111 | self._name = name 112 | 113 | def __repr__(self): 114 | return ''.format( 115 | self._enum.__name__, self._name, self._value) 116 | 117 | def __str__(self): 118 | return '{0}.{1}'.format(self._enum.__name__, self._name) 119 | 120 | def __int__(self): 121 | return self._value 122 | 123 | def __reduce__(self): 124 | return getattr, (self._enum, self._name) 125 | 126 | @property 127 | def enum(self): 128 | """Return the class associated with the enum value.""" 129 | return self._enum 130 | 131 | @property 132 | def name(self): 133 | """Return the name of the enum value.""" 134 | return self._name 135 | 136 | @property 137 | def enumclass(self): 138 | """Return the class associated with the enum value.""" 139 | warnings.warn('.enumclass is deprecated; use .enum instead', 140 | DeprecationWarning) 141 | return self._enum 142 | 143 | @property 144 | def enumname(self): 145 | """Return the name of the enum value.""" 146 | warnings.warn('.enumname is deprecated; use .name instead', 147 | DeprecationWarning) 148 | return self._name 149 | 150 | # Support only comparison by identity and equality. Ordered comparisions 151 | # are not supported. 152 | def __eq__(self, other): 153 | return self is other 154 | 155 | def __ne__(self, other): 156 | return self is not other 157 | 158 | def __lt__(self, other): 159 | raise NotImplementedError 160 | 161 | __gt__ = __lt__ 162 | __le__ = __lt__ 163 | __ge__ = __lt__ 164 | __hash__ = object.__hash__ 165 | 166 | 167 | 168 | # Define the Enum class using metaclass syntax compatible with both Python 2 169 | # and Python 3. 170 | Enum = EnumMetaclass(str('Enum'), (), { 171 | '__doc__': 'The public API Enum class.', 172 | }) 173 | 174 | 175 | 176 | def make(name, source): 177 | """Return an Enum class from a name and source. 178 | 179 | This is a convenience function for defining a new enumeration given an 180 | existing sequence. When an sequence is used, it is iterated over to get 181 | the enumeration value items. The sequence iteration can either return 182 | strings or 2-tuples. When strings are used, values are automatically 183 | assigned starting from 1. When 2-tuples are used, the first item of the 184 | tuple is a string and the second item is the integer value. 185 | 186 | `source` must be homogeneous. You cannot mix string-only and 2-tuple 187 | items in the sequence. 188 | 189 | :param name: The resulting enum's class name. 190 | :type name: byte string (or ASCII-only unicode string) 191 | :param source: An object giving the enumeration value items. 192 | :type source: A sequence of strings or 2-tuples. 193 | :return: The new enumeration class. 194 | :rtype: instance of `EnumMetaClass` 195 | :raises ValueError: when a heterogeneous source is given, or when 196 | non-identifiers are used as enumeration value names. 197 | """ 198 | namespace = {} 199 | illegals = [] 200 | have_strings = None 201 | try: 202 | # Python 2 203 | string_type = basestring 204 | except NameError: 205 | # Python 3 206 | string_type = str 207 | for i, item in enumerate(source, start=1): 208 | if isinstance(item, string_type): 209 | if have_strings is None: 210 | have_strings = True 211 | elif not have_strings: 212 | raise ValueError('heterogeneous source') 213 | namespace[item] = i 214 | if re.match(IDENTIFIER_RE, item) is None: 215 | illegals.append(item) 216 | else: 217 | if have_strings is None: 218 | have_strings = False 219 | elif have_strings: 220 | raise ValueError('heterogeneous souce') 221 | item_name, item_value = item 222 | namespace[item_name] = item_value 223 | if re.match(IDENTIFIER_RE, item_name) is None: 224 | illegals.append(item_name) 225 | if len(illegals) > 0: 226 | raise ValueError('non-identifiers: {0}'.format(SPACE.join(illegals))) 227 | return EnumMetaclass(str(name), (Enum,), namespace) 228 | 229 | 230 | 231 | def make_enum(name, value_string, iterable=None): 232 | """Return an Enum class from a name and a value string. 233 | 234 | *This function is deprecated; use `make()` instead.* 235 | 236 | This is a convenience function for defining a new enumeration when you 237 | don't care about the values of the items. The values are automatically 238 | created by splitting the value string on spaces. 239 | 240 | Normally, values are assigned to sequentially increasing integers starting 241 | at one. With optional `iterable`, integer values are extracted one at a 242 | time and assigned to the values. 243 | 244 | :param name: The resulting enum's class name. 245 | :type name: byte string (or ASCII-only unicode string) 246 | :param value_string: A string of enumeration item names, separated by 247 | spaces, e.g. 'one two three'. 248 | :type value_string: byte string (or ASCII-only unicode string) 249 | :param iterable: A sequence of integers. 250 | :type iterable: iterator over int 251 | :return: The new enumeration class. 252 | :rtype: instance of `EnumMetaClass` 253 | """ 254 | warnings.warn('make_enum() is deprecated; use make() instead', 255 | DeprecationWarning) 256 | value_names = value_string.split() 257 | illegals = [value for value in value_names 258 | if re.match(IDENTIFIER_RE, value) is None] 259 | if len(illegals) > 0: 260 | raise ValueError('non-identifiers: {0}'.format(SPACE.join(illegals))) 261 | if iterable is None: 262 | namespace = dict((str(value), i) 263 | for i, value in enumerate(value_names, 1)) 264 | else: 265 | namespace = dict((str(value), i) 266 | for i, value in zip(iterable, value_names)) 267 | return EnumMetaclass(str(name), (Enum,), namespace) 268 | -------------------------------------------------------------------------------- /flufl/enum/docs/using.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Using the flufl.enum library 3 | ============================ 4 | 5 | The ``flufl.enum`` package provides yet another enumeration data type for 6 | Python. While this is similar intent to the reject `PEP 354`_, this package 7 | defines an alternative syntax and semantics. 8 | 9 | An enumeration is a set of symbolic names bound to unique, constant integer 10 | values. Within an enumeration, the values can be compared by identity, and 11 | the enumeration itself can be iterated over. Enumeration items can be 12 | converted to and from their integer equivalents, supporting use cases such as 13 | storing enumeration values in a database. 14 | 15 | 16 | Motivation 17 | ========== 18 | 19 | [Lifted from PEP 354] 20 | 21 | The properties of an enumeration are useful for defining an immutable, related 22 | set of constant values that have a defined sequence but no inherent semantic 23 | meaning. Classic examples are days of the week (Sunday through Saturday) and 24 | school assessment grades ('A' through 'D', and 'F'). Other examples include 25 | error status values and states within a defined process. 26 | 27 | It is possible to simply define a sequence of values of some other basic type, 28 | such as ``int`` or ``str``, to represent discrete arbitrary values. However, 29 | an enumeration ensures that such values are distinct from any others, and that 30 | operations without meaning ("Wednesday times two") are not defined for these 31 | values. 32 | 33 | 34 | Creating an Enum 35 | ================ 36 | 37 | Enumerations are created using the class syntax, which makes them easy to read 38 | and write. Every enumeration value must have a unique integer value and the 39 | only restriction on their names is that they must be valid Python identifiers. 40 | To define an enumeration, derive from the Enum class and add attributes with 41 | assignment to their integer values. 42 | 43 | >>> from flufl.enum import Enum 44 | >>> class Colors(Enum): 45 | ... red = 1 46 | ... green = 2 47 | ... blue = 3 48 | 49 | Enumeration values are compared by identity. 50 | 51 | >>> Colors.red is Colors.red 52 | True 53 | >>> Colors.blue is Colors.blue 54 | True 55 | >>> Colors.red is not Colors.blue 56 | True 57 | >>> Colors.blue is Colors.red 58 | False 59 | 60 | Enumeration values have nice, human readable string representations... 61 | 62 | >>> print(Colors.red) 63 | Colors.red 64 | 65 | ...while their repr has more information. 66 | 67 | >>> print(repr(Colors.red)) 68 | 69 | 70 | The enumeration value names are available through the class members. 71 | 72 | >>> for member in Colors.__members__: 73 | ... print(member) 74 | red 75 | green 76 | blue 77 | 78 | Let's say you wanted to encode an enumeration value in a database. You might 79 | want to get the enumeration class object from an enumeration value. 80 | 81 | >>> cls = Colors.red.enum 82 | >>> print(cls.__name__) 83 | Colors 84 | 85 | Enums also have a property that contains just their item name. 86 | 87 | >>> print(Colors.red.name) 88 | red 89 | >>> print(Colors.green.name) 90 | green 91 | >>> print(Colors.blue.name) 92 | blue 93 | 94 | The str and repr of the enumeration class also provides useful information. 95 | 96 | >>> print(Colors) 97 | 98 | >>> print(repr(Colors)) 99 | 100 | 101 | You can extend previously defined Enums by subclassing. 102 | 103 | >>> class MoreColors(Colors): 104 | ... pink = 4 105 | ... cyan = 5 106 | 107 | When extended in this way, the base enumeration's values are identical to the 108 | same named values in the derived class. 109 | 110 | >>> Colors.red is MoreColors.red 111 | True 112 | >>> Colors.blue is MoreColors.blue 113 | True 114 | 115 | However, these are not doing comparisons against the integer equivalent 116 | values, because if you define an enumeration with similar item names and 117 | integer values, they will not be identical. 118 | 119 | >>> class OtherColors(Enum): 120 | ... red = 1 121 | ... blue = 2 122 | ... yellow = 3 123 | >>> Colors.red is OtherColors.red 124 | False 125 | >>> Colors.blue is not OtherColors.blue 126 | True 127 | 128 | These enumeration values are not equal, nor do they hash equally. 129 | 130 | >>> Colors.red == OtherColors.red 131 | False 132 | >>> len(set((Colors.red, OtherColors.red))) 133 | 2 134 | 135 | Ordered comparisons between enumeration values are *not* supported. Enums are 136 | not integers! 137 | 138 | >>> Colors.red < Colors.blue 139 | Traceback (most recent call last): 140 | ... 141 | NotImplementedError 142 | >>> Colors.red <= Colors.blue 143 | Traceback (most recent call last): 144 | ... 145 | NotImplementedError 146 | >>> Colors.blue > Colors.green 147 | Traceback (most recent call last): 148 | ... 149 | NotImplementedError 150 | >>> Colors.blue >= Colors.green 151 | Traceback (most recent call last): 152 | ... 153 | NotImplementedError 154 | 155 | Equality comparisons are defined though. 156 | 157 | >>> Colors.blue == Colors.blue 158 | True 159 | >>> Colors.green != Colors.blue 160 | True 161 | 162 | Enumeration values do not support ordered comparisons. 163 | 164 | >>> Colors.red < Colors.blue 165 | Traceback (most recent call last): 166 | ... 167 | NotImplementedError 168 | >>> Colors.red < 3 169 | Traceback (most recent call last): 170 | ... 171 | NotImplementedError 172 | >>> Colors.red <= 3 173 | Traceback (most recent call last): 174 | ... 175 | NotImplementedError 176 | >>> Colors.blue > 2 177 | Traceback (most recent call last): 178 | ... 179 | NotImplementedError 180 | >>> Colors.blue >= 2 181 | Traceback (most recent call last): 182 | ... 183 | NotImplementedError 184 | 185 | While equality comparisons are allowed, comparisons against non-enumeration 186 | values will always compare not equal. 187 | 188 | >>> Colors.green == 2 189 | False 190 | >>> Colors.blue == 3 191 | False 192 | >>> Colors.green != 3 193 | True 194 | >>> Colors.green == 'green' 195 | False 196 | 197 | If you really want the integer equivalent values, you can convert enumeration 198 | values explicitly using the ``int()`` built-in. This is quite convenient for 199 | storing enums in a database for example. 200 | 201 | >>> int(Colors.red) 202 | 1 203 | >>> int(Colors.green) 204 | 2 205 | >>> int(Colors.blue) 206 | 3 207 | 208 | You can also convert back to the enumeration value by calling the Enum class, 209 | passing in the integer value for the item you want. 210 | 211 | >>> Colors(1) 212 | 213 | >>> Colors(2) 214 | 215 | >>> Colors(3) 216 | 217 | >>> Colors(1) is Colors.red 218 | True 219 | 220 | The Enum class also accepts the string name of the enumeration value. 221 | 222 | >>> Colors('red') 223 | 224 | >>> Colors('blue') is Colors.blue 225 | True 226 | 227 | You get exceptions though, if you try to use invalid arguments. 228 | 229 | >>> Colors('magenta') 230 | Traceback (most recent call last): 231 | ... 232 | ValueError: magenta 233 | >>> Colors(99) 234 | Traceback (most recent call last): 235 | ... 236 | ValueError: 99 237 | 238 | The Enum base class also supports getitem syntax, exactly equivalent to the 239 | class's call semantics. 240 | 241 | >>> Colors[1] 242 | 243 | >>> Colors[2] 244 | 245 | >>> Colors[3] 246 | 247 | >>> Colors[1] is Colors.red 248 | True 249 | >>> Colors['red'] 250 | 251 | >>> Colors['blue'] is Colors.blue 252 | True 253 | >>> Colors['magenta'] 254 | Traceback (most recent call last): 255 | ... 256 | ValueError: magenta 257 | >>> Colors[99] 258 | Traceback (most recent call last): 259 | ... 260 | ValueError: 99 261 | 262 | The integer equivalent values serve another purpose. You may not define two 263 | enumeration values with the same integer value. 264 | 265 | >>> class Bad(Enum): 266 | ... cartman = 1 267 | ... stan = 2 268 | ... kyle = 3 269 | ... kenny = 3 # Oops! 270 | ... butters = 4 271 | Traceback (most recent call last): 272 | ... 273 | TypeError: Multiple enum values: 3 274 | 275 | You also may not duplicate values in derived enumerations. 276 | 277 | >>> class BadColors(Colors): 278 | ... yellow = 4 279 | ... chartreuse = 2 # Oops! 280 | Traceback (most recent call last): 281 | ... 282 | TypeError: Multiple enum values: 2 283 | 284 | The Enum class support iteration. Enumeration values are returned in the 285 | sorted order of their integer equivalent values. 286 | 287 | >>> [v.name for v in MoreColors] 288 | ['red', 'green', 'blue', 'pink', 'cyan'] 289 | >>> [int(v) for v in MoreColors] 290 | [1, 2, 3, 4, 5] 291 | 292 | Enumeration values are hashable, so they can be used in dictionaries and sets. 293 | 294 | >>> apples = {} 295 | >>> apples[Colors.red] = 'red delicious' 296 | >>> apples[Colors.green] = 'granny smith' 297 | >>> for color in sorted(apples, key=int): 298 | ... print(color.name, '->', apples[color]) 299 | red -> red delicious 300 | green -> granny smith 301 | 302 | 303 | Pickling 304 | ======== 305 | 306 | Enumerations created with the class syntax can also be pickled and unpickled: 307 | 308 | >>> from flufl.enum.tests.fruit import Fruit 309 | >>> from pickle import dumps, loads 310 | >>> Fruit.tomato is loads(dumps(Fruit.tomato)) 311 | True 312 | 313 | 314 | Alternative API 315 | =============== 316 | 317 | You can also create enumerations using the convenience function `make()`, 318 | which takes an iterable object or dictionary to provide the item names and 319 | values. `make()` is a static method. 320 | 321 | .. note:: 322 | 323 | The `make_enum()` function from earlier releases is deprecated. Use 324 | `make()` instead. 325 | 326 | The first argument to `make()` is the name of the enumeration, and it returns 327 | the so-named `Enum` subclass. The second argument is a `source` which can be 328 | either an iterable or a dictionary. In the most basic usage, `source` returns 329 | a sequence of strings which name the enumeration items. In this case, the 330 | values are automatically assigned starting from 1:: 331 | 332 | >>> from flufl.enum import make 333 | >>> make('Animals', ('ant', 'bee', 'cat', 'dog')) 334 | 335 | 336 | The items in source can also be 2-tuples, where the first item is the 337 | enumeration value name and the second is the integer value to assign to the 338 | value. If 2-tuples are used, all items must be 2-tuples. 339 | 340 | >>> def enumiter(): 341 | ... start = 1 342 | ... while True: 343 | ... yield start 344 | ... start <<= 1 345 | >>> make('Flags', zip(list('abcdefg'), enumiter())) 346 | 347 | 348 | 349 | Differences from PEP 354 350 | ======================== 351 | 352 | Unlike PEP 354, enumeration values are not defined as a sequence of strings, 353 | but as attributes of a class. This design was chosen because it was felt that 354 | class syntax is more readable. 355 | 356 | Unlike PEP 354, enumeration values require an explicit integer value. This 357 | difference recognizes that enumerations often represent real-world values, or 358 | must interoperate with external real-world systems. For example, to store an 359 | enumeration in a database, it is better to convert it to an integer on the way 360 | in and back to an enumeration on the way out. Providing an integer value also 361 | provides an explicit ordering. However, there is no automatic conversion to 362 | and from the integer values, because explicit is better than implicit. 363 | 364 | Unlike PEP 354, this implementation does use a metaclass to define the 365 | enumeration's syntax, and allows for extended base-enumerations so that the 366 | common values in derived classes are identical (a singleton model). While PEP 367 | 354 dismisses this approach for its complexity, in practice any perceived 368 | complexity, though minimal, is hidden from users of the enumeration. 369 | 370 | Unlike PEP 354, enumeration values can only be tested by identity comparison. 371 | This is to emphasis the fact that enumeration values are singletons, much like 372 | ``None``. 373 | 374 | 375 | Acknowledgments 376 | =============== 377 | 378 | The ``flufl.enum`` implementation is based on an example by Jeremy Hylton. It 379 | has been modified and extended by Barry Warsaw for use in the `GNU Mailman`_ 380 | project. Ben Finney is the author of the earlier enumeration PEP 354. 381 | 382 | 383 | .. _`PEP 354`: http://www.python.org/dev/peps/pep-0354/ 384 | 385 | .. _enum: http://cheeseshop.python.org/pypi/enum/ 386 | 387 | .. _`GNU Mailman`: http://www.list.org 388 | -------------------------------------------------------------------------------- /distribute_setup.py: -------------------------------------------------------------------------------- 1 | #!python 2 | """Bootstrap distribute installation 3 | 4 | If you want to use setuptools in your package's setup.py, just include this 5 | file in the same directory with it, and add this to the top of your setup.py:: 6 | 7 | from distribute_setup import use_setuptools 8 | use_setuptools() 9 | 10 | If you want to require a specific version of setuptools, set a download 11 | mirror, or use an alternate download directory, you can do so by supplying 12 | the appropriate options to ``use_setuptools()``. 13 | 14 | This file can also be run as a script to install or upgrade setuptools. 15 | """ 16 | import os 17 | import sys 18 | import time 19 | import fnmatch 20 | import tempfile 21 | import tarfile 22 | from distutils import log 23 | 24 | try: 25 | from site import USER_SITE 26 | except ImportError: 27 | USER_SITE = None 28 | 29 | try: 30 | import subprocess 31 | 32 | def _python_cmd(*args): 33 | args = (sys.executable,) + args 34 | return subprocess.call(args) == 0 35 | 36 | except ImportError: 37 | # will be used for python 2.3 38 | def _python_cmd(*args): 39 | args = (sys.executable,) + args 40 | # quoting arguments if windows 41 | if sys.platform == 'win32': 42 | def quote(arg): 43 | if ' ' in arg: 44 | return '"%s"' % arg 45 | return arg 46 | args = [quote(arg) for arg in args] 47 | return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 48 | 49 | DEFAULT_VERSION = "0.6.19" 50 | DEFAULT_URL = "https://pypi.python.org/packages/source/d/distribute/" 51 | SETUPTOOLS_FAKED_VERSION = "0.6c11" 52 | 53 | SETUPTOOLS_PKG_INFO = """\ 54 | Metadata-Version: 1.0 55 | Name: setuptools 56 | Version: %s 57 | Summary: xxxx 58 | Home-page: xxx 59 | Author: xxx 60 | Author-email: xxx 61 | License: xxx 62 | Description: xxx 63 | """ % SETUPTOOLS_FAKED_VERSION 64 | 65 | 66 | def _install(tarball): 67 | # extracting the tarball 68 | tmpdir = tempfile.mkdtemp() 69 | log.warn('Extracting in %s', tmpdir) 70 | old_wd = os.getcwd() 71 | try: 72 | os.chdir(tmpdir) 73 | tar = tarfile.open(tarball) 74 | _extractall(tar) 75 | tar.close() 76 | 77 | # going in the directory 78 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 79 | os.chdir(subdir) 80 | log.warn('Now working in %s', subdir) 81 | 82 | # installing 83 | log.warn('Installing Distribute') 84 | if not _python_cmd('setup.py', 'install'): 85 | log.warn('Something went wrong during the installation.') 86 | log.warn('See the error message above.') 87 | finally: 88 | os.chdir(old_wd) 89 | 90 | 91 | def _build_egg(egg, tarball, to_dir): 92 | # extracting the tarball 93 | tmpdir = tempfile.mkdtemp() 94 | log.warn('Extracting in %s', tmpdir) 95 | old_wd = os.getcwd() 96 | try: 97 | os.chdir(tmpdir) 98 | tar = tarfile.open(tarball) 99 | _extractall(tar) 100 | tar.close() 101 | 102 | # going in the directory 103 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 104 | os.chdir(subdir) 105 | log.warn('Now working in %s', subdir) 106 | 107 | # building an egg 108 | log.warn('Building a Distribute egg in %s', to_dir) 109 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) 110 | 111 | finally: 112 | os.chdir(old_wd) 113 | # returning the result 114 | log.warn(egg) 115 | if not os.path.exists(egg): 116 | raise IOError('Could not build the egg.') 117 | 118 | 119 | def _do_download(version, download_base, to_dir, download_delay): 120 | egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' 121 | % (version, sys.version_info[0], sys.version_info[1])) 122 | if not os.path.exists(egg): 123 | tarball = download_setuptools(version, download_base, 124 | to_dir, download_delay) 125 | _build_egg(egg, tarball, to_dir) 126 | sys.path.insert(0, egg) 127 | import setuptools 128 | setuptools.bootstrap_install_from = egg 129 | 130 | 131 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 132 | to_dir=os.curdir, download_delay=15, no_fake=True): 133 | # making sure we use the absolute path 134 | to_dir = os.path.abspath(to_dir) 135 | was_imported = 'pkg_resources' in sys.modules or \ 136 | 'setuptools' in sys.modules 137 | try: 138 | try: 139 | import pkg_resources 140 | if not hasattr(pkg_resources, '_distribute'): 141 | if not no_fake: 142 | _fake_setuptools() 143 | raise ImportError 144 | except ImportError: 145 | return _do_download(version, download_base, to_dir, download_delay) 146 | try: 147 | pkg_resources.require("distribute>="+version) 148 | return 149 | except pkg_resources.VersionConflict: 150 | e = sys.exc_info()[1] 151 | if was_imported: 152 | sys.stderr.write( 153 | "The required version of distribute (>=%s) is not available,\n" 154 | "and can't be installed while this script is running. Please\n" 155 | "install a more recent version first, using\n" 156 | "'easy_install -U distribute'." 157 | "\n\n(Currently using %r)\n" % (version, e.args[0])) 158 | sys.exit(2) 159 | else: 160 | del pkg_resources, sys.modules['pkg_resources'] # reload ok 161 | return _do_download(version, download_base, to_dir, 162 | download_delay) 163 | except pkg_resources.DistributionNotFound: 164 | return _do_download(version, download_base, to_dir, 165 | download_delay) 166 | finally: 167 | if not no_fake: 168 | _create_fake_setuptools_pkg_info(to_dir) 169 | 170 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 171 | to_dir=os.curdir, delay=15): 172 | """Download distribute from a specified location and return its filename 173 | 174 | `version` should be a valid distribute version number that is available 175 | as an egg for download under the `download_base` URL (which should end 176 | with a '/'). `to_dir` is the directory where the egg will be downloaded. 177 | `delay` is the number of seconds to pause before an actual download 178 | attempt. 179 | """ 180 | # making sure we use the absolute path 181 | to_dir = os.path.abspath(to_dir) 182 | try: 183 | from urllib.request import urlopen 184 | except ImportError: 185 | from urllib2 import urlopen 186 | tgz_name = "distribute-%s.tar.gz" % version 187 | url = download_base + tgz_name 188 | saveto = os.path.join(to_dir, tgz_name) 189 | src = dst = None 190 | if not os.path.exists(saveto): # Avoid repeated downloads 191 | try: 192 | log.warn("Downloading %s", url) 193 | src = urlopen(url) 194 | # Read/write all in one block, so we don't create a corrupt file 195 | # if the download is interrupted. 196 | data = src.read() 197 | dst = open(saveto, "wb") 198 | dst.write(data) 199 | finally: 200 | if src: 201 | src.close() 202 | if dst: 203 | dst.close() 204 | return os.path.realpath(saveto) 205 | 206 | def _no_sandbox(function): 207 | def __no_sandbox(*args, **kw): 208 | try: 209 | from setuptools.sandbox import DirectorySandbox 210 | if not hasattr(DirectorySandbox, '_old'): 211 | def violation(*args): 212 | pass 213 | DirectorySandbox._old = DirectorySandbox._violation 214 | DirectorySandbox._violation = violation 215 | patched = True 216 | else: 217 | patched = False 218 | except ImportError: 219 | patched = False 220 | 221 | try: 222 | return function(*args, **kw) 223 | finally: 224 | if patched: 225 | DirectorySandbox._violation = DirectorySandbox._old 226 | del DirectorySandbox._old 227 | 228 | return __no_sandbox 229 | 230 | def _patch_file(path, content): 231 | """Will backup the file then patch it""" 232 | existing_content = open(path).read() 233 | if existing_content == content: 234 | # already patched 235 | log.warn('Already patched.') 236 | return False 237 | log.warn('Patching...') 238 | _rename_path(path) 239 | f = open(path, 'w') 240 | try: 241 | f.write(content) 242 | finally: 243 | f.close() 244 | return True 245 | 246 | _patch_file = _no_sandbox(_patch_file) 247 | 248 | def _same_content(path, content): 249 | return open(path).read() == content 250 | 251 | def _rename_path(path): 252 | new_name = path + '.OLD.%s' % time.time() 253 | log.warn('Renaming %s into %s', path, new_name) 254 | os.rename(path, new_name) 255 | return new_name 256 | 257 | def _remove_flat_installation(placeholder): 258 | if not os.path.isdir(placeholder): 259 | log.warn('Unkown installation at %s', placeholder) 260 | return False 261 | found = False 262 | for file in os.listdir(placeholder): 263 | if fnmatch.fnmatch(file, 'setuptools*.egg-info'): 264 | found = True 265 | break 266 | if not found: 267 | log.warn('Could not locate setuptools*.egg-info') 268 | return 269 | 270 | log.warn('Removing elements out of the way...') 271 | pkg_info = os.path.join(placeholder, file) 272 | if os.path.isdir(pkg_info): 273 | patched = _patch_egg_dir(pkg_info) 274 | else: 275 | patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) 276 | 277 | if not patched: 278 | log.warn('%s already patched.', pkg_info) 279 | return False 280 | # now let's move the files out of the way 281 | for element in ('setuptools', 'pkg_resources.py', 'site.py'): 282 | element = os.path.join(placeholder, element) 283 | if os.path.exists(element): 284 | _rename_path(element) 285 | else: 286 | log.warn('Could not find the %s element of the ' 287 | 'Setuptools distribution', element) 288 | return True 289 | 290 | _remove_flat_installation = _no_sandbox(_remove_flat_installation) 291 | 292 | def _after_install(dist): 293 | log.warn('After install bootstrap.') 294 | placeholder = dist.get_command_obj('install').install_purelib 295 | _create_fake_setuptools_pkg_info(placeholder) 296 | 297 | def _create_fake_setuptools_pkg_info(placeholder): 298 | if not placeholder or not os.path.exists(placeholder): 299 | log.warn('Could not find the install location') 300 | return 301 | pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) 302 | setuptools_file = 'setuptools-%s-py%s.egg-info' % \ 303 | (SETUPTOOLS_FAKED_VERSION, pyver) 304 | pkg_info = os.path.join(placeholder, setuptools_file) 305 | if os.path.exists(pkg_info): 306 | log.warn('%s already exists', pkg_info) 307 | return 308 | 309 | log.warn('Creating %s', pkg_info) 310 | f = open(pkg_info, 'w') 311 | try: 312 | f.write(SETUPTOOLS_PKG_INFO) 313 | finally: 314 | f.close() 315 | 316 | pth_file = os.path.join(placeholder, 'setuptools.pth') 317 | log.warn('Creating %s', pth_file) 318 | f = open(pth_file, 'w') 319 | try: 320 | f.write(os.path.join(os.curdir, setuptools_file)) 321 | finally: 322 | f.close() 323 | 324 | _create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) 325 | 326 | def _patch_egg_dir(path): 327 | # let's check if it's already patched 328 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') 329 | if os.path.exists(pkg_info): 330 | if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): 331 | log.warn('%s already patched.', pkg_info) 332 | return False 333 | _rename_path(path) 334 | os.mkdir(path) 335 | os.mkdir(os.path.join(path, 'EGG-INFO')) 336 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') 337 | f = open(pkg_info, 'w') 338 | try: 339 | f.write(SETUPTOOLS_PKG_INFO) 340 | finally: 341 | f.close() 342 | return True 343 | 344 | _patch_egg_dir = _no_sandbox(_patch_egg_dir) 345 | 346 | def _before_install(): 347 | log.warn('Before install bootstrap.') 348 | _fake_setuptools() 349 | 350 | 351 | def _under_prefix(location): 352 | if 'install' not in sys.argv: 353 | return True 354 | args = sys.argv[sys.argv.index('install')+1:] 355 | for index, arg in enumerate(args): 356 | for option in ('--root', '--prefix'): 357 | if arg.startswith('%s=' % option): 358 | top_dir = arg.split('root=')[-1] 359 | return location.startswith(top_dir) 360 | elif arg == option: 361 | if len(args) > index: 362 | top_dir = args[index+1] 363 | return location.startswith(top_dir) 364 | if arg == '--user' and USER_SITE is not None: 365 | return location.startswith(USER_SITE) 366 | return True 367 | 368 | 369 | def _fake_setuptools(): 370 | log.warn('Scanning installed packages') 371 | try: 372 | import pkg_resources 373 | except ImportError: 374 | # we're cool 375 | log.warn('Setuptools or Distribute does not seem to be installed.') 376 | return 377 | ws = pkg_resources.working_set 378 | try: 379 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', 380 | replacement=False)) 381 | except TypeError: 382 | # old distribute API 383 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) 384 | 385 | if setuptools_dist is None: 386 | log.warn('No setuptools distribution found') 387 | return 388 | # detecting if it was already faked 389 | setuptools_location = setuptools_dist.location 390 | log.warn('Setuptools installation detected at %s', setuptools_location) 391 | 392 | # if --root or --preix was provided, and if 393 | # setuptools is not located in them, we don't patch it 394 | if not _under_prefix(setuptools_location): 395 | log.warn('Not patching, --root or --prefix is installing Distribute' 396 | ' in another location') 397 | return 398 | 399 | # let's see if its an egg 400 | if not setuptools_location.endswith('.egg'): 401 | log.warn('Non-egg installation') 402 | res = _remove_flat_installation(setuptools_location) 403 | if not res: 404 | return 405 | else: 406 | log.warn('Egg installation') 407 | pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') 408 | if (os.path.exists(pkg_info) and 409 | _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): 410 | log.warn('Already patched.') 411 | return 412 | log.warn('Patching...') 413 | # let's create a fake egg replacing setuptools one 414 | res = _patch_egg_dir(setuptools_location) 415 | if not res: 416 | return 417 | log.warn('Patched done.') 418 | _relaunch() 419 | 420 | 421 | def _relaunch(): 422 | log.warn('Relaunching...') 423 | # we have to relaunch the process 424 | # pip marker to avoid a relaunch bug 425 | if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: 426 | sys.argv[0] = 'setup.py' 427 | args = [sys.executable] + sys.argv 428 | sys.exit(subprocess.call(args)) 429 | 430 | 431 | def _extractall(self, path=".", members=None): 432 | """Extract all members from the archive to the current working 433 | directory and set owner, modification time and permissions on 434 | directories afterwards. `path' specifies a different directory 435 | to extract to. `members' is optional and must be a subset of the 436 | list returned by getmembers(). 437 | """ 438 | import copy 439 | import operator 440 | from tarfile import ExtractError 441 | directories = [] 442 | 443 | if members is None: 444 | members = self 445 | 446 | for tarinfo in members: 447 | if tarinfo.isdir(): 448 | # Extract directories with a safe mode. 449 | directories.append(tarinfo) 450 | tarinfo = copy.copy(tarinfo) 451 | tarinfo.mode = 448 # decimal for oct 0700 452 | self.extract(tarinfo, path) 453 | 454 | # Reverse sort directories. 455 | if sys.version_info < (2, 4): 456 | def sorter(dir1, dir2): 457 | return cmp(dir1.name, dir2.name) 458 | directories.sort(sorter) 459 | directories.reverse() 460 | else: 461 | directories.sort(key=operator.attrgetter('name'), reverse=True) 462 | 463 | # Set correct owner, mtime and filemode on directories. 464 | for tarinfo in directories: 465 | dirpath = os.path.join(path, tarinfo.name) 466 | try: 467 | self.chown(tarinfo, dirpath) 468 | self.utime(tarinfo, dirpath) 469 | self.chmod(tarinfo, dirpath) 470 | except ExtractError: 471 | e = sys.exc_info()[1] 472 | if self.errorlevel > 1: 473 | raise 474 | else: 475 | self._dbg(1, "tarfile: %s" % e) 476 | 477 | 478 | def main(argv, version=DEFAULT_VERSION): 479 | """Install or upgrade setuptools and EasyInstall""" 480 | tarball = download_setuptools() 481 | _install(tarball) 482 | 483 | 484 | if __name__ == '__main__': 485 | main(sys.argv[1:]) 486 | --------------------------------------------------------------------------------