[Python-modules-commits] [flufl.testing] 01/03: import flufl.testing_0.5.orig.tar.gz

Barry Warsaw barry at moszumanska.debian.org
Mon Dec 12 20:21:21 UTC 2016


This is an automated email from the git hooks/post-receive script.

barry pushed a commit to branch master
in repository flufl.testing.

commit d614f811f2d2a67a0f5df1d05304e5e064ae68b4
Author: Barry Warsaw <barry at python.org>
Date:   Mon Dec 12 14:39:46 2016 -0500

    import flufl.testing_0.5.orig.tar.gz
---
 MANIFEST.in                                 |   5 +
 NEWS.rst                                    |  28 +++++
 PKG-INFO                                    |  16 +++
 README.rst                                  | 177 ++++++++++++++++++++++++++++
 flufl.testing.egg-info/PKG-INFO             |  16 +++
 flufl.testing.egg-info/SOURCES.txt          |  14 +++
 flufl.testing.egg-info/dependency_links.txt |   1 +
 flufl.testing.egg-info/entry_points.txt     |   3 +
 flufl.testing.egg-info/top_level.txt        |   1 +
 flufl/__init__.py                           |   1 +
 flufl/testing/__init__.py                   |   1 +
 flufl/testing/imports.py                    | 136 +++++++++++++++++++++
 flufl/testing/nose.py                       | 132 +++++++++++++++++++++
 setup.cfg                                   |   5 +
 setup.py                                    |  44 +++++++
 setup_helpers.py                            | 138 ++++++++++++++++++++++
 16 files changed, 718 insertions(+)

diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..73b67b1
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,5 @@
+include *.py MANIFEST.in
+global-include *.txt *.rst *.po *.mo *.ini
+exclude .gitignore
+prune build
+prune .tox
diff --git a/NEWS.rst b/NEWS.rst
new file mode 100644
index 0000000..f450b2d
--- /dev/null
+++ b/NEWS.rst
@@ -0,0 +1,28 @@
+===============
+ flufl.testing
+===============
+
+Copyright (C) 2016 Barry A. Warsaw
+
+
+0.5 (2016-12-02)
+================
+* Fix namespace package compatibility.
+
+0.4 (2016-11-30)
+================
+* More fixes and documentation updates.
+
+0.3 (2016-11-29)
+================
+* Rename the ``unittest.cfg`` section to ``[flufl.testing]``.
+* Improve the documentation.
+
+0.2 (2016-11-28)
+================
+* Re-enable Python 3.4.
+* Update README.
+
+0.1 (2016-11-17)
+================
+* Initial release.
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..ebffb35
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,16 @@
+Metadata-Version: 1.1
+Name: flufl.testing
+Version: 0.5
+Summary: A small collection of test tool plugins
+Home-page: https://gitlab.com/warsaw/flufl.testing
+Author: Barry Warsaw
+Author-email: barry at python.org
+License: ASLv2
+Download-URL: https://pypi.python.org/pypi/flufl.testing
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Plugins
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: 3
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..069796f
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,177 @@
+===============
+ flufl.testing
+===============
+
+This is a small collection of test helpers that I use in almost all my
+packages.  Specifically, plugins for the following test tools are provided:
+
+* nose2_
+* flake8_
+
+Python 3.4 is the minimum supported version.
+
+
+Using test helpers
+==================
+
+You can use each of the plugins independently.  For example, if you use flake8
+but you don't use nose2, just enable the flake8 plugin and ignore the rest.
+
+
+flake8 import order plugin
+--------------------------
+
+This flake8_ plugin enables import order checks as are used in the `GNU
+Mailman`_ project.  Specifically, it enforces the following rules:
+
+* Non-``from`` imports must precede ``from``-imports.
+* Exactly one line must separate the block of non-``from`` imports from the
+  block of ``from`` imports.
+* Import exactly one module per non-``from`` import line.
+* Lines in the non-``from`` import block are sorted by length first, then
+  alphabetically.  Dotted imports count toward both restrictions.
+* Lines in the ``from`` import block are sorted alphabetically.
+* Multiple names can be imported in a ``from`` import line, but those names
+  must be sorted alphabetically.
+* Names imported from the same module in a ``from`` import must appear in the
+  same import statement.
+
+It's so much easier to see an example::
+
+    import copy
+    import socket
+    import logging
+    import smtplib
+
+    from mailman import public
+    from mailman.config import config
+    from mailman.interfaces.mta import IMailTransportAgentDelivery
+    from mailman.mta.connection import Connection
+    from zope.interface import implementer
+
+To enable this plugin [#]_, add the following to your ``tox.ini`` or any other
+`flake8 recognized configuration file`_::
+
+    [flake8]
+    enable-extensions = U4
+
+
+nose2 plugin
+------------
+
+The `nose2`_ plugin enables a few helpful things for folks who use that test
+runner:
+
+* Implements better support for doctests, including supporting layers.
+* Enables sophisticated test pattern matching.
+* Provides test tracing.
+* A *log to stderr* flag that you can check. [#]_
+* Pluggable doctest setup/teardowns.
+
+To enable this plugin, add the following to your ``unittest.cfg`` file, in the
+``[unittest]`` section::
+
+    plugins = flufl.testing.nose
+
+You also need to add this section to ``unittest.cfg``, where *<package>* names
+the top-level package you want to test::
+
+    [flufl.testing]
+    always-on = True
+    package = <package>
+
+Now when you run your tests, you can include one or more ``-P`` options, which
+provide patterns to match your tests against.  If given, only tests matching
+the given pattern are run.  This is especially helpful if your test suite is
+huge.  These patterns can match a test name, class, module, or filename, and
+follow Python's regexp syntax.
+
+The following options are also available by setting configuration variables in
+your ``unittest.cfg`` file, under the ``[flufl.testing]`` section.
+
+
+Doctests
+~~~~~~~~
+
+The plugin also provides some useful features for doctests.  If you make a
+directory a package in your source tree (i.e. by adding an `__init__.py`), you
+can optionally also add specify a `nose2 layer`_ to use for the doctest.  Bind
+the layer object you want to the ``layer`` attribute in the ``__init__.py``
+and it will be automatically assigned to the doctest's ``layer`` attribute for
+nose2 to find.
+
+Also for doctests, you can specify the ``setUp()`` and ``tearDown()`` methods
+you want by adding the following::
+
+    setup = my.package.namespace.setup
+    teardown = my.package.other.namespace.teardown
+
+The named packages will be imported, with the last path component naming an
+attribute in the module.  This attribute should be a function taking a single
+argument, in the style used by the stdlib ``doctest.DocFileTest`` class [#]_.
+
+You can also name a default layer by setting::
+
+    default_layer = my.package.layers.DefaultLayer.
+
+This has the same format as the ``setup`` and ``teardown settings, except that
+it should name a class.
+
+
+Pre-test initialization
+~~~~~~~~~~~~~~~~~~~~~~~
+
+If you need to do anything before the tests starts, such as initialize
+database connections or acquire resources, set this::
+
+    start_run = my.package.initializer
+
+This has the same format as the ``setup`` and ``teardown`` settings, except
+that it takes a single argument which is the plugin instance.  You can use
+this plugin instance for example to check if the ``-E`` option was given on
+the command line.  This flag sets the ``stderr`` attribute to True on the
+plugin instance.
+
+
+Tracing
+~~~~~~~
+
+If you add this the plugin will also print some additional tracers to stderr
+for ever test as it starts and stops::
+
+    trace = True
+
+
+
+Author
+======
+
+``flufl.testing`` is Copyright (C) 2013-2016 Barry Warsaw <barry at python.org>
+
+Licensed under the terms of the Apache License, Version 2.0.
+
+
+Project details
+===============
+
+ * Project home: https://gitlab.com/warsaw/flufl.testing
+ * Report bugs at: https://gitlab.com/warsaw/flufl.testing/issues
+ * Code hosting: https://gitlab.com/warsaw/flufl.testing.git
+ * Documentation: https://gitlab.com/warsaw/flufl.testing/tree/master
+
+
+Footnotes
+=========
+
+.. [#] Note that flake8 3.1 or newer is required.
+.. [#] It's up to your application to do something with this flag.
+.. [#] This class is undocumented, so use the doctest_ module source to grok
+       the details.
+
+
+.. _flake8: http://flake8.pycqa.org/en/latest/index.html
+.. _`GNU Mailman`: http://www.list.org
+.. _`flake8 recognized configuration file`: http://flake8.pycqa.org/en/latest/user/configuration.html
+.. _nose2: http://nose2.readthedocs.io/en/latest/index.html
+.. _`nose2 layer`: http://nose2.readthedocs.io/en/latest/plugins/layers.html
+.. _doctest: https://docs.python.org/3/library/doctest.html
diff --git a/flufl.testing.egg-info/PKG-INFO b/flufl.testing.egg-info/PKG-INFO
new file mode 100644
index 0000000..ebffb35
--- /dev/null
+++ b/flufl.testing.egg-info/PKG-INFO
@@ -0,0 +1,16 @@
+Metadata-Version: 1.1
+Name: flufl.testing
+Version: 0.5
+Summary: A small collection of test tool plugins
+Home-page: https://gitlab.com/warsaw/flufl.testing
+Author: Barry Warsaw
+Author-email: barry at python.org
+License: ASLv2
+Download-URL: https://pypi.python.org/pypi/flufl.testing
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Plugins
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: 3
diff --git a/flufl.testing.egg-info/SOURCES.txt b/flufl.testing.egg-info/SOURCES.txt
new file mode 100644
index 0000000..501a889
--- /dev/null
+++ b/flufl.testing.egg-info/SOURCES.txt
@@ -0,0 +1,14 @@
+MANIFEST.in
+NEWS.rst
+README.rst
+setup.py
+setup_helpers.py
+flufl/__init__.py
+flufl.testing.egg-info/PKG-INFO
+flufl.testing.egg-info/SOURCES.txt
+flufl.testing.egg-info/dependency_links.txt
+flufl.testing.egg-info/entry_points.txt
+flufl.testing.egg-info/top_level.txt
+flufl/testing/__init__.py
+flufl/testing/imports.py
+flufl/testing/nose.py
\ No newline at end of file
diff --git a/flufl.testing.egg-info/dependency_links.txt b/flufl.testing.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/flufl.testing.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/flufl.testing.egg-info/entry_points.txt b/flufl.testing.egg-info/entry_points.txt
new file mode 100644
index 0000000..1641056
--- /dev/null
+++ b/flufl.testing.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[flake8.extension]
+U4 = flufl.testing.imports:ImportOrder
+
diff --git a/flufl.testing.egg-info/top_level.txt b/flufl.testing.egg-info/top_level.txt
new file mode 100644
index 0000000..9ab661d
--- /dev/null
+++ b/flufl.testing.egg-info/top_level.txt
@@ -0,0 +1 @@
+flufl
diff --git a/flufl/__init__.py b/flufl/__init__.py
new file mode 100644
index 0000000..de40ea7
--- /dev/null
+++ b/flufl/__init__.py
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)
diff --git a/flufl/testing/__init__.py b/flufl/testing/__init__.py
new file mode 100644
index 0000000..5a6f84c
--- /dev/null
+++ b/flufl/testing/__init__.py
@@ -0,0 +1 @@
+__version__ = '0.5'
diff --git a/flufl/testing/imports.py b/flufl/testing/imports.py
new file mode 100644
index 0000000..88e19e3
--- /dev/null
+++ b/flufl/testing/imports.py
@@ -0,0 +1,136 @@
+# Copyright (C) 2016 Barry A. Warsaw
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License.  You may obtain a copy
+# of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ast import NodeVisitor
+from collections import namedtuple
+from enum import Enum
+
+
+class ImportType(Enum):
+    non_from = 0
+    from_import = 1
+
+
+ImportRecord = namedtuple('ImportRecord', 'itype lineno colno, module, names')
+
+
+NONFROM_FOLLOWS_FROM = 'U401 Non-from import follows from-import'
+NONFROM_MULTIPLE_NAMES = 'U402 Multiple names on non-from import'
+NONFROM_SHORTER_FOLLOWS = 'U403 Shorter non-from import follows longer'
+NONFROM_ALPHA_UNSORTED = (
+    'U404 Same-length non-from imports not sorted alphabetically')
+NONFROM_EXTRA_BLANK_LINE = (
+    'U405 Unexpected blank line since last non-from import')
+NONFROM_DOTTED_UNSORTED = (
+    'U406 Dotted non-from import not sorted alphabetically')
+
+FROMIMPORT_MISSING_BLANK_LINE = (
+    'U411 Expected one blank line since last non-from import')
+FROMIMPORT_ALPHA_UNSORTED = 'U412 from-import not sorted alphabetically'
+FROMIMPORT_MULTIPLE = 'U413 Multiple from-imports of same module'
+FROMIMPORT_NAMES_UNSORTED = (
+    'U414 from-imported names are not sorted alphabetically')
+
+
+class ImportVisitor(NodeVisitor):
+    def __init__(self):
+        self.imports = []
+
+    def visit_Import(self, node):
+        if node.col_offset != 0:
+            # Ignore nested imports.
+            return
+        names = [alias.name for alias in node.names]
+        self.imports.append(
+            ImportRecord(ImportType.non_from, node.lineno, node.col_offset,
+                         None, names))
+
+    def visit_ImportFrom(self, node):
+        if node.col_offset != 0:
+            # Ignore nested imports.
+            return
+        names = [alias.name for alias in node.names]
+        self.imports.append(
+            ImportRecord(ImportType.from_import, node.lineno, node.col_offset,
+                         node.module, names))
+
+
+class ImportOrder:
+    name = 'flufl-import-order'
+    version = '0.2'
+    off_by_default = True
+
+    def __init__(self, tree, filename):
+        self.tree = tree
+        self.filename = filename
+
+    def _error(self, record, error):
+        code, space, text = error.partition(' ')
+        return (record.lineno, record.colno,
+                '{} {}'.format(code, text), ImportOrder)
+
+    def run(self):
+        visitor = ImportVisitor()
+        visitor.visit(self.tree)
+        last_import = None
+        for record in visitor.imports:
+            if last_import is None:
+                last_import = record
+                continue
+            if record.itype is ImportType.non_from:
+                if len(record.names) != 1:
+                    yield self._error(record, NONFROM_MULTIPLE_NAMES)
+                if last_import.itype is ImportType.from_import:
+                    yield self._error(record, NONFROM_FOLLOWS_FROM)
+                # Shorter imports should always precede longer import *except*
+                # when they are dotted imports and everything but the last
+                # path component are the same.  In that case, they should be
+                # sorted alphabetically.
+                last_name = last_import.names[0]
+                this_name = record.names[0]
+                if '.' in last_name and '.' in this_name:
+                    last_parts = last_name.split('.')
+                    this_parts = this_name.split('.')
+                    if (last_parts[:-1] == this_parts[:-1] and
+                            last_parts[-1] > this_parts[-1]):
+                        yield self._error(record, NONFROM_DOTTED_UNSORTED)
+                elif len(last_name) > len(this_name):
+                    yield self._error(record, NONFROM_SHORTER_FOLLOWS)
+                # It's also possible that the imports are the same length, in
+                # which case they must be sorted alphabetically.
+                if (len(last_import.names[0]) == len(record.names[0]) and
+                        last_import.names[0] > record.names[0]):
+                    yield self._error(record, NONFROM_ALPHA_UNSORTED)
+                if last_import.lineno + 1 != record.lineno:
+                    yield self._error(record, NONFROM_DOTTED_UNSORTED)
+            else:
+                assert record.itype is ImportType.from_import
+                if (last_import.itype is ImportType.non_from and
+                        record.lineno != last_import.lineno + 2):
+                    yield self._error(record, FROMIMPORT_MISSING_BLANK_LINE)
+                if last_import.itype is ImportType.non_from:
+                    last_import = record
+                    continue
+                if last_import.module > record.module:
+                    yield self._error(record, FROMIMPORT_ALPHA_UNSORTED)
+                # All imports from the same module should show up in the same
+                # multiline import.
+                if last_import.module == record.module:
+                    yield self._error(record, FROMIMPORT_MULTIPLE)
+                # Check the sort order of the imported names.
+                if sorted(record.names) != record.names:
+                    yield self._error(record, FROMIMPORT_NAMES_UNSORTED)
+                # How to check for no blank lines between from imports?
+            # Update the last import.
+            last_import = record
diff --git a/flufl/testing/nose.py b/flufl/testing/nose.py
new file mode 100644
index 0000000..dc45354
--- /dev/null
+++ b/flufl/testing/nose.py
@@ -0,0 +1,132 @@
+# Copyright (C) 2013-2016 Barry A. Warsaw
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License.  You may obtain a copy
+# of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""nose2 test infrastructure."""
+
+import os
+import re
+import sys
+import doctest
+import importlib
+
+from nose2.events import Plugin
+
+
+DOT = '.'
+FLAGS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF
+
+
+def as_object(path):
+    if path is None:
+        return None
+    # mod.ule.object -> import(mod.ule); mod.ule.object
+    module_name, dot, object_name = path.rpartition('.')
+    if dot != '.' or len(object_name) == 0:
+        return None
+    module = importlib.import_module(module_name)
+    return getattr(module, object_name, None)
+
+
+class NosePlugin(Plugin):
+    configSection = 'flufl.testing'
+
+    def __init__(self):
+        super().__init__()
+        self.patterns = []
+        self.stderr = False
+        def set_stderr(ignore):                                  # noqa: E306
+            self.stderr = True
+        self.addArgument(self.patterns, 'P', 'pattern',
+                         'Add a test matching pattern')
+        self.addFlag(set_stderr, 'E', 'stderr',
+                     'Enable stderr logging to sub-runners')
+        # Get the topdir out of the plugin configuration file.
+        self.package = self.config.as_str('package')
+        if self.package is None:
+            raise RuntimeError('flufl.nose2 plugin missing "package" setting')
+
+    def startTestRun(self, event):
+        callback = as_object(self.config.get('start_run'))
+        if callback is not None:
+            callback(self)
+
+    def getTestCaseNames(self, event):
+        if len(self.patterns) == 0:
+            # No filter patterns, so everything should be tested.
+            return
+        # Does the pattern match the fully qualified class name?
+        for pattern in self.patterns:
+            full_class_name = '{}.{}'.format(
+                event.testCase.__module__, event.testCase.__name__)
+            if re.search(pattern, full_class_name):
+                # Don't suppress this test class.
+                return
+        names = filter(event.isTestMethod, dir(event.testCase))
+        for name in names:
+            full_test_name = '{}.{}.{}'.format(
+                event.testCase.__module__,
+                event.testCase.__name__,
+                name)
+            for pattern in self.patterns:
+                if re.search(pattern, full_test_name):
+                    break
+            else:
+                event.excludedNames.append(name)
+
+    def handleFile(self, event):
+        package = importlib.import_module(self.package)
+        path = event.path[len(os.path.dirname(package.__file__))+1:]
+        if len(self.patterns) > 0:
+            for pattern in self.patterns:
+                if re.search(pattern, path):
+                    break
+            else:
+                # Skip this doctest.
+                return
+        base, ext = os.path.splitext(path)
+        if ext != '.rst':
+            return
+        # Look to see if the package defines a test layer, otherwise use the
+        # default layer.  First turn the file system path into a dotted Python
+        # module path.
+        parent = os.path.dirname(path)
+        dotted = '{}.{}'.format(
+            self.package, DOT.join(parent.split(os.path.sep)))
+        layer = None
+        default_layer = as_object(self.config.get('default_layer'))
+        try:
+            module = importlib.import_module(dotted)
+        except ImportError:
+            layer = default_layer
+        else:
+            layer = getattr(module, 'layer', default_layer)
+        setup = as_object(self.config.get('setup'))
+        teardown = as_object(self.config.get('teardown'))
+        test = doctest.DocFileTest(
+            path, package=self.package,
+            optionflags=FLAGS,
+            setUp=setup,
+            tearDown=teardown)
+        test.layer = layer
+        # Suppress the extra "Doctest: ..." line.
+        test.shortDescription = lambda: None
+        event.extraTests.append(test)
+
+    def startTest(self, event):
+        if self.config.as_bool('trace', False):
+            print('vvvvv', event.test, file=sys.stderr)
+
+    def stopTest(self, event):
+        if self.config.as_bool('trace', False):
+            print('^^^^^', event.test, file=sys.stderr)
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..755e299
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2016 Barry A. Warsaw
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License.  You may obtain a copy
+# of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from setup_helpers import get_version, require_python
+from setuptools import setup, find_packages
+
+
+require_python(0x030400f0)
+__version__ = get_version('flufl/testing/__init__.py')
+
+
+setup(
+    name='flufl.testing',
+    version=__version__,
+    packages=find_packages(),
+    include_package_data=True,
+    maintainer='Barry Warsaw',
+    maintainer_email='barry at python.org',
+    description='A small collection of test tool plugins',
+    license='ASLv2',
+    url='https://gitlab.com/warsaw/flufl.testing',
+    download_url='https://pypi.python.org/pypi/flufl.testing',
+    entry_points={
+        'flake8.extension': ['U4 = flufl.testing.imports:ImportOrder'],
+        },
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Environment :: Plugins',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        'Programming Language :: Python :: 3',
+        ]
+    )
diff --git a/setup_helpers.py b/setup_helpers.py
new file mode 100644
index 0000000..11327e3
--- /dev/null
+++ b/setup_helpers.py
@@ -0,0 +1,138 @@
+# Copyright (C) 2016 Barry A. Warsaw
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License.  You may obtain a copy
+# of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""setup.py helper functions."""
+
+import os
+import re
+import sys
+
+
+DEFAULT_VERSION_RE = re.compile(
+    r'(?P<version>\d+\.\d+(?:\.\d+)?(?:(?:a|b|rc)\d+)?)')
+EMPTYSTRING = ''
+
+__version__ = '2.3'
+
+
+def require_python(minimum):
+    """Require at least a minimum Python version.
+
+    The version number is expressed in terms of `sys.hexversion`.  E.g. to
+    require a minimum of Python 2.6, use::
+
+    >>> require_python(0x206000f0)
+
+    :param minimum: Minimum Python version supported.
+    :type minimum: integer
+    """
+    if sys.hexversion < minimum:
+        hversion = hex(minimum)[2:]
+        if len(hversion) % 2 != 0:
+            hversion = '0' + hversion
+        split = list(hversion)
+        parts = []
+        while split:
+            parts.append(int(''.join((split.pop(0), split.pop(0))), 16))
+        major, minor, micro, release = parts
+        if release == 0xf0:
+            print('Python {0}.{1}.{2} or better is required'.format(
+                major, minor, micro))
+        else:
+            print('Python {0}.{1}.{2} ({3}) or better is required'.format(
+                major, minor, micro, hex(release)[2:]))
+        sys.exit(1)
+
+
+def get_version(filename, pattern=None):
+    """Extract the __version__ from a file without importing it.
+
+    While you could get the __version__ by importing the module, the very act
+    of importing can cause unintended consequences.  For example, Distribute's
+    automatic 2to3 support will break.  Instead, this searches the file for a
+    line that starts with __version__, and extract the version number by
+    regular expression matching.
+
+    By default, two or three dot-separated digits are recognized, but by
+    passing a pattern parameter, you can recognize just about anything.  Use
+    the `version` group name to specify the match group.
+
+    :param filename: The name of the file to search.
+    :type filename: string
+    :param pattern: Optional alternative regular expression pattern to use.
+    :type pattern: string
+    :return: The version that was extracted.
+    :rtype: string
+    """
+    if pattern is None:
+        cre = DEFAULT_VERSION_RE
+    else:
+        cre = re.compile(pattern)
+    with open(filename) as fp:
+        for line in fp:
+            if line.startswith('__version__'):
+                mo = cre.search(line)
+                assert mo, 'No valid __version__ string found'
+                return mo.group('version')
+    raise AssertionError('No __version__ assignment found')
+
+
+def find_doctests(start='.', extension='.rst'):
+    """Find separate-file doctests in the package.
+
+    This is useful for Distribute's automatic 2to3 conversion support.  The
+    `setup()` keyword argument `convert_2to3_doctests` requires file names,
+    which may be difficult to track automatically as you add new doctests.
+
+    :param start: Directory to start searching in (default is cwd)
+    :type start: string
+    :param extension: Doctest file extension (default is .txt)
+    :type extension: string
+    :return: The doctest files found.
+    :rtype: list
+    """
+    doctests = []
+    for dirpath, dirnames, filenames in os.walk(start):
+        doctests.extend(os.path.join(dirpath, filename)
+                        for filename in filenames
+                        if filename.endswith(extension))
+    return doctests
+
+
+def long_description(*filenames):
+    """Provide a long description."""
+    res = ['']
+    for filename in filenames:
+        with open(filename) as fp:
+            for line in fp:
+                res.append('   ' + line)
+            res.append('')
+        res.append('\n')
+    return EMPTYSTRING.join(res)
+
+
+def description(filename):
+    """Provide a short description."""
+    # This ends up in the Summary header for PKG-INFO and it should be a
+    # one-liner.  It will get rendered on the package page just below the
+    # package version header but above the long_description, which ironically
+    # gets stuff into the Description header.  It should not include reST, so
+    # pick out the first single line after the double header.
+    with open(filename) as fp:
+        for lineno, line in enumerate(fp):
+            if lineno < 3:
+                continue
+            line = line.strip()
+            if len(line) > 0:
+                return line

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/flufl.testing.git



More information about the Python-modules-commits mailing list