├── 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 |
--------------------------------------------------------------------------------