#22 Refactor the build system and package generator
Closed 5 years ago by bignose. Opened 5 years ago by hberaud.
hberaud/python-daemon refactor-packaging  into  master

file modified
+1
@@ -16,6 +16,7 @@ 

  /dist/

  /.eggs/

  /*.egg-info

+ Pipfile

  

  # Test artifacts.

  /.coverage

README.rst README
file renamed
file was moved with no change to the file
file modified
+8
@@ -192,6 +192,14 @@ 

  development tree when accompanied by corresponding additions or

  changes to the unit tests.

  

+ To launch unit test using the following commands::

+ 

+    $ tox

+ 

+ You can also check specific environment by using::

+ 

+    $ tox -e py37

+ 

  Test-driven development

  -----------------------

  

file removed
-16
@@ -1,16 +0,0 @@ 

- # pyproject.toml

- # Build system requirements for Python code in this code base.

- # Documentation: <URL:https://www.python.org/dev/peps/pep-0518/>.

- 

- [build-system]

- 

- # Minimum requirements for the build system.

- requires = ["setuptools", "wheel", "docutils"]

- 

- 

- # Local-variables:

- # coding: utf-8

- # mode: conf

- # mode: toml

- # End:

- # vim: fileencoding=utf-8 filetype=toml :

file added
+1
@@ -0,0 +1,1 @@ 

+ lockfile>=0.10

file modified
+37 -7
@@ -1,5 +1,42 @@ 

  # setup.cfg

  # Python Distutils configuration options for this distribution.

+ [metadata]

+ name = python-daemon

+ home-page = https://pagure.io/python-daemon/

+ summary = Library to implement a well-behaved Unix daemon process

+ description-file = 

+     README.rst

+ author = Ben Finney

+ author-email = ben+python@benfinney.id.au

+ license = Apache License 2.0

+ classifier =

+     Development Status :: 5 - Production/Stable

+     Intended Audience :: Developers

+     License :: OSI Approved :: Apache Software License

+     Operating System :: POSIX

+     Programming Language :: Python

+     Programming Language :: Python :: 3

+     Programming Language :: Python :: 3.5

+     Programming Language :: Python :: 3.6

+     Programming Language :: Python :: 3.7

+     Topic :: Software Development :: Libraries :: Python Modules

+ keywords =

+     daemon

+     fork

+     unix

+ 

+ [files]

+ packages =

+     daemon

+ 

+ [extras]

+ devel=

+     pbr

+     tox

+ 

+ [pbr]

+     skip_authors = 1

+     skip_changelog = 1

  

  [aliases]

  distribute = register sdist bdist_wheel upload
@@ -13,10 +50,3 @@ 

  

  # Sign distributions, and upload the signing public key?

  sign = true

- 

- 

- # Local variables:

- # coding: utf-8

- # mode: conf

- # End:

- # vim: fileencoding=utf-8 filetype=conf :

file modified
+6 -78
@@ -9,99 +9,27 @@ 

  

  """ Distribution setup for ‘python-daemon’ library. """

  

- from __future__ import (absolute_import, unicode_literals)

  

  import os.path

- import pydoc

- import sys

  import unittest

  

- from setuptools import (setup, find_packages)

+ from setuptools import setup

  

- import version

  

- 

- fromlist_expects_type = str

- if sys.version_info < (3, 0):

-     fromlist_expects_type = bytes

- 

- 

- main_module_name = 'daemon'

- main_module_fromlist = list(map(fromlist_expects_type, [

-         '_metadata']))

- main_module = __import__(

-         main_module_name,

-         level=0, fromlist=main_module_fromlist)

- metadata = main_module._metadata

- 

- (synopsis, long_description) = pydoc.splitdoc(pydoc.getdoc(main_module))

- 

- 

  def test_suite():

      """ Make the test suite for this code base. """

      loader = unittest.TestLoader()

      suite = loader.discover(os.path.curdir, pattern='test_*.py')

      return suite

  

- 

- setup_kwargs = dict(

-         distclass=version.ChangelogAwareDistribution,

-         name=metadata.distribution_name,

-         packages=find_packages(exclude=["test"]),

-         cmdclass={

-             "write_version_info": version.WriteVersionInfoCommand,

-             "egg_info": version.EggInfoCommand,

-             "build": version.BuildCommand,

-             },

- 

-         # Setuptools metadata.

-         zip_safe=False,

-         setup_requires=[

-             "docutils",

-             ],

-         test_suite="setup.test_suite",

-         tests_require=[

-             "unittest2 >=0.5.1",

-             "testtools",

-             "testscenarios >=0.4",

-             "mock >=1.3",

-             "docutils",

-             ],

-         install_requires=[

-             "setuptools",

-             "lockfile >=0.10",

-             ],

  

-         # PyPI metadata.

-         author=metadata.author_name,

-         author_email=metadata.author_email,

-         description=synopsis,

-         license=metadata.license,

-         keywords="daemon fork unix".split(),

-         url=metadata.url,

-         long_description=long_description,

-         classifiers=[

-             # Reference: <URL:https://pypi.org/classifiers/>

-             "Development Status :: 5 - Production/Stable",

-             "License :: OSI Approved :: Apache Software License",

-             "Operating System :: POSIX",

-             "Programming Language :: Python :: 2.7",

-             "Programming Language :: Python :: 3",

-             "Intended Audience :: Developers",

-             "Topic :: Software Development :: Libraries :: Python Modules",

-             ],

-         )

+ setup(

+     setup_requires=["pbr"],

+     pbr=True,

+     test_suite='setup.test_suite'

+ )

  

- # Docutils is only required for building, but Setuptools can't distinguish

- # dependencies properly.

- # See <URL:https://github.com/pypa/setuptools/issues/457>.

- setup_kwargs['install_requires'].append("docutils")

  

- 

- if __name__ == '__main__':

-     setup(**setup_kwargs)

- 

- 

  # Copyright © 2008–2018 Ben Finney <ben+python@benfinney.id.au>

  #

  # This is free software: you may copy, modify, and/or distribute this work

@@ -0,0 +1,5 @@ 

+ unittest2>=0.5.1

+ testtools

+ testscenarios>=0.4

+ mock>=1.3

+ docutils

file removed
-1486
@@ -1,1486 +0,0 @@ 

- # -*- coding: utf-8 -*-

- #

- # test_version.py

- # Part of ‘python-daemon’, an implementation of PEP 3143.

- #

- # This is free software, and you are welcome to redistribute it under

- # certain conditions; see the end of this file for copyright

- # information, grant of license, and disclaimer of warranty.

- 

- """ Unit test for ‘version’ packaging module. """

- 

- from __future__ import (absolute_import, unicode_literals)

- 

- import collections

- import distutils.cmd

- import distutils.dist

- import distutils.errors

- import distutils.fancy_getopt

- import errno

- import functools

- import io

- import json

- import os

- import os.path

- import tempfile

- import textwrap

- 

- import docutils

- import docutils.nodes

- import docutils.writers

- import mock

- import setuptools

- import setuptools.command

- import testscenarios

- import testtools

- 

- import version

- 

- 

- version.ensure_class_bases_begin_with(

-         version.__dict__, str('VersionInfoWriter'), docutils.writers.Writer)

- version.ensure_class_bases_begin_with(

-         version.__dict__, str('VersionInfoTranslator'),

-         docutils.nodes.SparseNodeVisitor)

- 

- __metaclass__ = type

- 

- 

- def make_test_classes_for_ensure_class_bases_begin_with():

-     """ Make test classes for use with ‘ensure_class_bases_begin_with’.

- 

-         :return: Mapping {`name`: `type`} of the custom types created.

- 

-         """

- 

-     class quux_metaclass(type):

-         def __new__(metaclass, name, bases, namespace):

-             return super(quux_metaclass, metaclass).__new__(

-                     metaclass, name, bases, namespace)

- 

-     class Foo(object):

-         __metaclass__ = type

- 

-     class Bar(object):

-         pass

- 

-     class FooInheritingBar(Bar):

-         __metaclass__ = type

- 

-     class FooWithCustomMetaclass(object):

-         __metaclass__ = quux_metaclass

- 

-     result = dict(

-             (name, value) for (name, value) in locals().items()

-             if isinstance(value, type))

- 

-     return result

- 

- 

- class ensure_class_bases_begin_with_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘ensure_class_bases_begin_with’ function. """

- 

-     test_classes = make_test_classes_for_ensure_class_bases_begin_with()

- 

-     scenarios = [

-             ('simple', {

-                 'test_class': test_classes['Foo'],

-                 'base_class': test_classes['Bar'],

-                 }),

-             ('custom metaclass', {

-                 'test_class': test_classes['FooWithCustomMetaclass'],

-                 'base_class': test_classes['Bar'],

-                 'expected_metaclass': test_classes['quux_metaclass'],

-                 }),

-             ]

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(ensure_class_bases_begin_with_TestCase, self).setUp()

- 

-         self.class_name = self.test_class.__name__

-         self.test_module_namespace = {self.class_name: self.test_class}

- 

-         if not hasattr(self, 'expected_metaclass'):

-             self.expected_metaclass = type

- 

-         patcher_metaclass = mock.patch.object(

-             self.test_class, '__metaclass__')

-         patcher_metaclass.start()

-         self.addCleanup(patcher_metaclass.stop)

- 

-         self.fake_new_class = type(object)

-         self.test_class.__metaclass__.return_value = (

-                 self.fake_new_class)

- 

-     def test_module_namespace_contains_new_class(self):

-         """ Specified module namespace should have new class. """

-         version.ensure_class_bases_begin_with(

-                 self.test_module_namespace, self.class_name, self.base_class)

-         self.assertIn(self.fake_new_class, self.test_module_namespace.values())

- 

-     def test_calls_metaclass_with_expected_class_name(self):

-         """ Should call the metaclass with the expected class name. """

-         version.ensure_class_bases_begin_with(

-                 self.test_module_namespace, self.class_name, self.base_class)

-         expected_class_name = self.class_name

-         self.test_class.__metaclass__.assert_called_with(

-                 expected_class_name, mock.ANY, mock.ANY)

- 

-     def test_calls_metaclass_with_expected_bases(self):

-         """ Should call the metaclass with the expected bases. """

-         version.ensure_class_bases_begin_with(

-                 self.test_module_namespace, self.class_name, self.base_class)

-         expected_bases = tuple(

-                 [self.base_class]

-                 + list(self.test_class.__bases__))

-         self.test_class.__metaclass__.assert_called_with(

-                 mock.ANY, expected_bases, mock.ANY)

- 

-     def test_calls_metaclass_with_expected_namespace(self):

-         """ Should call the metaclass with the expected class namespace. """

-         version.ensure_class_bases_begin_with(

-                 self.test_module_namespace, self.class_name, self.base_class)

-         expected_namespace = self.test_class.__dict__.copy()

-         del expected_namespace['__dict__']

-         self.test_class.__metaclass__.assert_called_with(

-                 mock.ANY, mock.ANY, expected_namespace)

- 

- 

- class ensure_class_bases_begin_with_AlreadyHasBase_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘ensure_class_bases_begin_with’ function.

- 

-         These test cases test the conditions where the class's base is

-         already the specified base class.

- 

-         """

- 

-     test_classes = make_test_classes_for_ensure_class_bases_begin_with()

- 

-     scenarios = [

-             ('already Bar subclass', {

-                 'test_class': test_classes['FooInheritingBar'],

-                 'base_class': test_classes['Bar'],

-                 }),

-             ]

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(

-                 ensure_class_bases_begin_with_AlreadyHasBase_TestCase,

-                 self).setUp()

- 

-         self.class_name = self.test_class.__name__

-         self.test_module_namespace = {self.class_name: self.test_class}

- 

-         patcher_metaclass = mock.patch.object(

-             self.test_class, '__metaclass__')

-         patcher_metaclass.start()

-         self.addCleanup(patcher_metaclass.stop)

- 

-     def test_metaclass_not_called(self):

-         """ Should not call metaclass to create a new type. """

-         version.ensure_class_bases_begin_with(

-                 self.test_module_namespace, self.class_name, self.base_class)

-         self.assertFalse(self.test_class.__metaclass__.called)

- 

- 

- class VersionInfoWriter_TestCase(testtools.TestCase):

-     """ Test cases for ‘VersionInfoWriter’ class. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(VersionInfoWriter_TestCase, self).setUp()

- 

-         self.test_instance = version.VersionInfoWriter()

- 

-     def test_declares_version_info_support(self):

-         """ Should declare support for ‘version_info’. """

-         instance = self.test_instance

-         expected_support = "version_info"

-         result = instance.supports(expected_support)

-         self.assertTrue(result)

- 

- 

- class VersionInfoWriter_translate_TestCase(testtools.TestCase):

-     """ Test cases for ‘VersionInfoWriter.translate’ method. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(VersionInfoWriter_translate_TestCase, self).setUp()

- 

-         patcher_translator = mock.patch.object(

-                 version, 'VersionInfoTranslator')

-         self.mock_class_translator = patcher_translator.start()

-         self.addCleanup(patcher_translator.stop)

-         self.mock_translator = self.mock_class_translator.return_value

- 

-         self.test_instance = version.VersionInfoWriter()

-         patcher_document = mock.patch.object(

-                 self.test_instance, 'document')

-         patcher_document.start()

-         self.addCleanup(patcher_document.stop)

- 

-     def test_creates_translator_with_document(self):

-         """ Should create a translator with the writer's document. """

-         instance = self.test_instance

-         expected_document = self.test_instance.document

-         instance.translate()

-         self.mock_class_translator.assert_called_with(expected_document)

- 

-     def test_calls_document_walkabout_with_translator(self):

-         """ Should call document.walkabout with the translator. """

-         instance = self.test_instance

-         instance.translate()

-         instance.document.walkabout.assert_called_with(self.mock_translator)

- 

-     def test_output_from_translator_astext(self):

-         """ Should have output from translator.astext(). """

-         instance = self.test_instance

-         instance.translate()

-         expected_output = self.mock_translator.astext.return_value

-         self.assertEqual(expected_output, instance.output)

- 

- 

- class parse_person_field_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘get_latest_version’ function. """

- 

-     scenarios = [

-             ('simple', {

-                 'test_person': "Foo Bar <foo.bar@example.com>",

-                 'expected_result': ("Foo Bar", "foo.bar@example.com"),

-                 }),

-             ('empty', {

-                 'test_person': "",

-                 'expected_result': (None, None),

-                 }),

-             ('none', {

-                 'test_person': None,

-                 'expected_error': TypeError,

-                 }),

-             ('no email', {

-                 'test_person': "Foo Bar",

-                 'expected_result': ("Foo Bar", None),

-                 }),

-             ]

- 

-     def test_returns_expected_result(self):

-         """ Should return expected result. """

-         if hasattr(self, 'expected_error'):

-             self.assertRaises(

-                     self.expected_error,

-                     version.parse_person_field, self.test_person)

-         else:

-             result = version.parse_person_field(self.test_person)

-             self.assertEqual(self.expected_result, result)

- 

- 

- class NoOpContextManager:

-     """ A context manager with no effect. """

- 

-     def __enter__(self): pass

- 

-     def __exit__(self, exc_type, exc_value, traceback): pass

- 

- 

- class ChangeLogEntry_BaseTestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Base class for ‘ChangeLogEntry’ test case classes. """

- 

-     def expected_error_context(self):

-         """ Make a context manager to expect the nominated error. """

-         context = NoOpContextManager()

-         if hasattr(self, 'expected_error'):

-             context = testtools.ExpectedException(self.expected_error)

-         return context

- 

- 

- class ChangeLogEntry_TestCase(ChangeLogEntry_BaseTestCase):

-     """ Test cases for ‘ChangeLogEntry’ class. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(ChangeLogEntry_TestCase, self).setUp()

- 

-         self.test_instance = version.ChangeLogEntry()

- 

-     def test_instantiate(self):

-         """ New instance of ‘ChangeLogEntry’ should be created. """

-         self.assertIsInstance(

-                 self.test_instance, version.ChangeLogEntry)

- 

-     def test_minimum_zero_arguments(self):

-         """ Initialiser should not require any arguments. """

-         instance = version.ChangeLogEntry()

-         self.assertIsNot(instance, None)

- 

- 

- class ChangeLogEntry_release_date_TestCase(ChangeLogEntry_BaseTestCase):

-     """ Test cases for ‘ChangeLogEntry.release_date’ attribute. """

- 

-     scenarios = [

-             ('default', {

-                 'test_args': {},

-                 'expected_release_date':

-                     version.ChangeLogEntry.default_release_date,

-                 }),

-             ('unknown token', {

-                 'test_args': {'release_date': "UNKNOWN"},

-                 'expected_release_date': "UNKNOWN",

-                 }),

-             ('future token', {

-                 'test_args': {'release_date': "FUTURE"},

-                 'expected_release_date': "FUTURE",

-                 }),

-             ('2001-01-01', {

-                 'test_args': {'release_date': "2001-01-01"},

-                 'expected_release_date': "2001-01-01",

-                 }),

-             ('bogus', {

-                 'test_args': {'release_date': "b0gUs"},

-                 'expected_error': ValueError,

-                 }),

-             ]

- 

-     def test_has_expected_release_date(self):

-         """ Should have default `release_date` attribute. """

-         with self.expected_error_context():

-             instance = version.ChangeLogEntry(**self.test_args)

-         if hasattr(self, 'expected_release_date'):

-             self.assertEqual(self.expected_release_date, instance.release_date)

- 

- 

- class ChangeLogEntry_version_TestCase(ChangeLogEntry_BaseTestCase):

-     """ Test cases for ‘ChangeLogEntry.version’ attribute. """

- 

-     scenarios = [

-             ('default', {

-                 'test_args': {},

-                 'expected_version':

-                     version.ChangeLogEntry.default_version,

-                 }),

-             ('unknown token', {

-                 'test_args': {'version': "UNKNOWN"},

-                 'expected_version': "UNKNOWN",

-                 }),

-             ('next token', {

-                 'test_args': {'version': "NEXT"},

-                 'expected_version': "NEXT",

-                 }),

-             ('0.0', {

-                 'test_args': {'version': "0.0"},

-                 'expected_version': "0.0",

-                 }),

-             ('1.2.3', {

-                 'test_args': {'version': "1.2.3"},

-                 'expected_version': "1.2.3",

-                 }),

-             ('1.23.456', {

-                 'test_args': {'version': "1.23.456"},

-                 'expected_version': "1.23.456",

-                 }),

-             ('1.23.456a5', {

-                 'test_args': {'version': "1.23.456a5"},

-                 'expected_version': "1.23.456a5",

-                 }),

-             ('123.456.789', {

-                 'test_args': {'version': "123.456.789"},

-                 'expected_version': "123.456.789",

-                 }),

-             ('non-number', {

-                 'test_args': {'version': "b0gUs"},

-                 'expected_error': ValueError,

-                 }),

-             ('negative', {

-                 'test_args': {'version': "-1.0"},

-                 'expected_error': ValueError,

-                 }),

-             ('non-number parts', {

-                 'test_args': {'version': "1.b0gUs.0"},

-                 'expected_error': ValueError,

-                 }),

-             ('too many parts', {

-                 'test_args': {'version': "1.2.3.4.5"},

-                 'expected_error': ValueError,

-                 }),

-             ]

- 

-     def test_has_expected_version(self):

-         """ Should have default `version` attribute. """

-         with self.expected_error_context():

-             instance = version.ChangeLogEntry(**self.test_args)

-         if hasattr(self, 'expected_version'):

-             self.assertEqual(self.expected_version, instance.version)

- 

- 

- class ChangeLogEntry_maintainer_TestCase(ChangeLogEntry_BaseTestCase):

-     """ Test cases for ‘ChangeLogEntry.maintainer’ attribute. """

- 

-     scenarios = [

-             ('default', {

-                 'test_args': {},

-                 'expected_maintainer': None,

-                 }),

-             ('person', {

-                 'test_args': {'maintainer': "Foo Bar <foo.bar@example.org>"},

-                 'expected_maintainer': "Foo Bar <foo.bar@example.org>",

-                 }),

-             ('bogus', {

-                 'test_args': {'maintainer': "b0gUs"},

-                 'expected_error': ValueError,

-                 }),

-             ]

- 

-     def test_has_expected_maintainer(self):

-         """ Should have default `maintainer` attribute. """

-         with self.expected_error_context():

-             instance = version.ChangeLogEntry(**self.test_args)

-         if hasattr(self, 'expected_maintainer'):

-             self.assertEqual(self.expected_maintainer, instance.maintainer)

- 

- 

- class ChangeLogEntry_body_TestCase(ChangeLogEntry_BaseTestCase):

-     """ Test cases for ‘ChangeLogEntry.body’ attribute. """

- 

-     scenarios = [

-             ('default', {

-                 'test_args': {},

-                 'expected_body': None,

-                 }),

-             ('simple', {

-                 'test_args': {'body': "Foo bar baz."},

-                 'expected_body': "Foo bar baz.",

-                 }),

-             ]

- 

-     def test_has_expected_body(self):

-         """ Should have default `body` attribute. """

-         instance = version.ChangeLogEntry(**self.test_args)

-         self.assertEqual(self.expected_body, instance.body)

- 

- 

- class ChangeLogEntry_as_version_info_entry_TestCase(

-         ChangeLogEntry_BaseTestCase):

-     """ Test cases for ‘ChangeLogEntry.as_version_info_entry’ attribute. """

- 

-     scenarios = [

-             ('default', {

-                 'test_args': {},

-                 'expected_result': collections.OrderedDict([

-                     (

-                         'release_date',

-                         version.ChangeLogEntry.default_release_date),

-                     ('version', version.ChangeLogEntry.default_version),

-                     ('maintainer', None),

-                     ('body', None),

-                     ]),

-                 }),

-             ]

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(ChangeLogEntry_as_version_info_entry_TestCase, self).setUp()

- 

-         self.test_instance = version.ChangeLogEntry(**self.test_args)

- 

-     def test_returns_result(self):

-         """ Should return expected result. """

-         result = self.test_instance.as_version_info_entry()

-         self.assertEqual(self.expected_result, result)

- 

- 

- def make_mock_field_node(field_name, field_body):

-     """ Make a mock Docutils field node for tests. """

- 

-     mock_field_node = mock.MagicMock(

-             name='field', spec=docutils.nodes.field)

- 

-     mock_field_name_node = mock.MagicMock(

-             name='field_name', spec=docutils.nodes.field_name)

-     mock_field_name_node.parent = mock_field_node

-     mock_field_name_node.children = [field_name]

- 

-     mock_field_body_node = mock.MagicMock(

-             name='field_body', spec=docutils.nodes.field_body)

-     mock_field_body_node.parent = mock_field_node

-     mock_field_body_node.children = [field_body]

- 

-     mock_field_node.children = [mock_field_name_node, mock_field_body_node]

- 

-     def fake_func_first_child_matching_class(node_class):

-         result = None

-         node_class_name = node_class.__name__

-         for (index, node) in enumerate(mock_field_node.children):

-             if node._mock_name == node_class_name:

-                 result = index

-                 break

-         return result

- 

-     mock_field_node.first_child_matching_class.side_effect = (

-             fake_func_first_child_matching_class)

- 

-     return mock_field_node

- 

- 

- class JsonEqual(testtools.matchers.Matcher):

-     """ A matcher to compare the value of JSON streams. """

- 

-     def __init__(self, expected):

-         self.expected_value = expected

- 

-     def match(self, content):

-         """ Assert the JSON `content` matches the `expected_content`. """

-         result = None

-         actual_value = json.loads(content.decode('utf-8'))

-         if actual_value != self.expected_value:

-             result = JsonValueMismatch(self.expected_value, actual_value)

-         return result

- 

- 

- class JsonValueMismatch(testtools.matchers.Mismatch):

-     """ The specified JSON stream does not evaluate to the expected value. """

- 

-     def __init__(self, expected, actual):

-         self.expected_value = expected

-         self.actual_value = actual

- 

-     def describe(self):

-         """ Emit a text description of this mismatch. """

-         expected_json_text = json.dumps(self.expected_value, indent=4)

-         actual_json_text = json.dumps(self.actual_value, indent=4)

-         text = (

-                 "\n"

-                 "reference: {expected}\n"

-                 "actual: {actual}\n").format(

-                     expected=expected_json_text, actual=actual_json_text)

-         return text

- 

- 

- class changelog_to_version_info_collection_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘changelog_to_version_info_collection’ function. """

- 

-     scenarios = [

-             ('single entry', {

-                 'test_input': textwrap.dedent("""\

-                     Version 1.0

-                     ===========

- 

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Lorem ipsum dolor sit amet.

-                     """),

-                 'expected_version_info': [

-                     {

-                         'release_date': "2009-01-01",

-                         'version': "1.0",

-                         'maintainer': "Foo Bar <foo.bar@example.org>",

-                         'body': "* Lorem ipsum dolor sit amet.\n",

-                         },

-                     ],

-                 }),

-             ('multiple entries', {

-                 'test_input': textwrap.dedent("""\

-                     Version 1.0

-                     ===========

- 

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Lorem ipsum dolor sit amet.

- 

- 

-                     Version 0.8

-                     ===========

- 

-                     :Released: 2004-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Donec venenatis nisl aliquam ipsum.

- 

- 

-                     Version 0.7.2

-                     =============

- 

-                     :Released: 2001-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Pellentesque elementum mollis finibus.

-                     """),

-                 'expected_version_info': [

-                     {

-                         'release_date': "2009-01-01",

-                         'version': "1.0",

-                         'maintainer': "Foo Bar <foo.bar@example.org>",

-                         'body': "* Lorem ipsum dolor sit amet.\n",

-                         },

-                     {

-                         'release_date': "2004-01-01",

-                         'version': "0.8",

-                         'maintainer': "Foo Bar <foo.bar@example.org>",

-                         'body': "* Donec venenatis nisl aliquam ipsum.\n",

-                         },

-                     {

-                         'release_date': "2001-01-01",

-                         'version': "0.7.2",

-                         'maintainer': "Foo Bar <foo.bar@example.org>",

-                         'body': "* Pellentesque elementum mollis finibus.\n",

-                         },

-                     ],

-                 }),

-             ('trailing comment', {

-                 'test_input': textwrap.dedent("""\

-                     Version NEXT

-                     ============

- 

-                     :Released: FUTURE

-                     :Maintainer:

- 

-                     * Lorem ipsum dolor sit amet.

- 

-                     ..

-                         Vivamus aliquam felis rutrum rutrum dictum.

-                     """),

-                 'expected_version_info': [

-                     {

-                         'release_date': "FUTURE",

-                         'version': "NEXT",

-                         'maintainer': "",

-                         'body': "* Lorem ipsum dolor sit amet.\n",

-                         },

-                     ],

-                 }),

-             ('inline comment', {

-                 'test_input': textwrap.dedent("""\

-                     Version NEXT

-                     ============

- 

-                     :Released: FUTURE

-                     :Maintainer:

- 

-                     ..

-                         Vivamus aliquam felis rutrum rutrum dictum.

- 

-                     * Lorem ipsum dolor sit amet.

-                     """),

-                 'expected_version_info': [

-                     {

-                         'release_date': "FUTURE",

-                         'version': "NEXT",

-                         'maintainer': "",

-                         'body': "* Lorem ipsum dolor sit amet.\n",

-                         },

-                     ],

-                 }),

-             ('unreleased entry', {

-                 'test_input': textwrap.dedent("""\

-                     Version NEXT

-                     ============

- 

-                     :Released: FUTURE

-                     :Maintainer:

- 

-                     * Lorem ipsum dolor sit amet.

- 

- 

-                     Version 0.8

-                     ===========

- 

-                     :Released: 2001-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Donec venenatis nisl aliquam ipsum.

-                     """),

-                 'expected_version_info': [

-                     {

-                         'release_date': "FUTURE",

-                         'version': "NEXT",

-                         'maintainer': "",

-                         'body': "* Lorem ipsum dolor sit amet.\n",

-                         },

-                     {

-                         'release_date': "2001-01-01",

-                         'version': "0.8",

-                         'maintainer': "Foo Bar <foo.bar@example.org>",

-                         'body': "* Donec venenatis nisl aliquam ipsum.\n",

-                         },

-                     ],

-                 }),

-             ('no section', {

-                 'test_input': textwrap.dedent("""\

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Lorem ipsum dolor sit amet.

-                     """),

-                 'expected_error': version.InvalidFormatError,

-                 }),

-             ('subsection', {

-                 'test_input': textwrap.dedent("""\

-                     Version 1.0

-                     ===========

- 

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Lorem ipsum dolor sit amet.

- 

-                     Ut ultricies fermentum quam

-                     ---------------------------

- 

-                     * In commodo magna facilisis in.

-                     """),

-                 'expected_error': version.InvalidFormatError,

-                 'subsection': True,

-                 }),

-             ('unknown field', {

-                 'test_input': textwrap.dedent("""\

-                     Version 1.0

-                     ===========

- 

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

-                     :Favourite: Spam

- 

-                     * Lorem ipsum dolor sit amet.

-                     """),

-                 'expected_error': version.InvalidFormatError,

-                 }),

-             ('invalid version word', {

-                 'test_input': textwrap.dedent("""\

-                     BoGuS 1.0

-                     =========

- 

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Lorem ipsum dolor sit amet.

-                     """),

-                 'expected_error': version.InvalidFormatError,

-                 }),

-             ('invalid section title', {

-                 'test_input': textwrap.dedent("""\

-                     Lorem Ipsum 1.0

-                     ===============

- 

-                     :Released: 2009-01-01

-                     :Maintainer: Foo Bar <foo.bar@example.org>

- 

-                     * Lorem ipsum dolor sit amet.

-                     """),

-                 'expected_error': version.InvalidFormatError,

-                 }),

-             ]

- 

-     def expected_error_context(self):

-         """ Make a context manager to expect the nominated error. """

-         context = NoOpContextManager()

-         if hasattr(self, 'expected_error'):

-             context = testtools.ExpectedException(self.expected_error)

-         return context

- 

-     def test_returns_expected_version_info(self):

-         """ Should return expected version info mapping. """

-         infile = io.StringIO(self.test_input)

-         with self.expected_error_context():

-             result = version.changelog_to_version_info_collection(infile)

-         if hasattr(self, 'expected_version_info'):

-             self.assertThat(result, JsonEqual(self.expected_version_info))

- 

- 

- try:

-     FileNotFoundError

-     PermissionError

- except NameError:

-     # Python 2 uses OSError.

-     FileNotFoundError = functools.partial(IOError, errno.ENOENT)

-     PermissionError = functools.partial(IOError, errno.EPERM)

- 

- fake_version_info = {

-         'release_date': "2001-01-01", 'version': "2.0",

-         'maintainer': None, 'body': None,

-         }

- 

- @mock.patch.object(

-         version, "get_latest_version", return_value=fake_version_info)

- class generate_version_info_from_changelog_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘generate_version_info_from_changelog’ function. """

- 

-     fake_open_side_effects = {

-             'success': (

-                 lambda *args, **kwargs: io.StringIO()),

-             'file not found': FileNotFoundError(),

-             'permission denied': PermissionError(),

-             }

- 

-     scenarios = [

-             ('simple', {

-                 'open_scenario': 'success',

-                 'fake_versions_json': json.dumps([fake_version_info]),

-                 'expected_result': fake_version_info,

-                 }),

-             ('file not found', {

-                 'open_scenario': 'file not found',

-                 'expected_result': {},

-                 }),

-             ('permission denied', {

-                 'open_scenario': 'permission denied',

-                 'expected_result': {},

-                 }),

-             ]

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(generate_version_info_from_changelog_TestCase, self).setUp()

- 

-         self.fake_changelog_file_path = tempfile.mktemp()

- 

-         def fake_open(filespec, *args, **kwargs):

-             if filespec == self.fake_changelog_file_path:

-                 side_effect = self.fake_open_side_effects[self.open_scenario]

-                 if callable(side_effect):

-                     result = side_effect()

-                 else:

-                     raise side_effect

-             else:

-                 result = io.StringIO()

-             return result

- 

-         func_patcher_io_open = mock.patch.object(

-                 io, "open")

-         func_patcher_io_open.start()

-         self.addCleanup(func_patcher_io_open.stop)

-         io.open.side_effect = fake_open

- 

-         self.file_encoding = "utf-8"

- 

-         func_patcher_changelog_to_version_info_collection = mock.patch.object(

-                 version, "changelog_to_version_info_collection")

-         func_patcher_changelog_to_version_info_collection.start()

-         self.addCleanup(func_patcher_changelog_to_version_info_collection.stop)

-         if hasattr(self, 'fake_versions_json'):

-             version.changelog_to_version_info_collection.return_value = (

-                     self.fake_versions_json.encode(self.file_encoding))

- 

-     def test_returns_empty_collection_on_read_error(

-             self,

-             mock_func_get_latest_version):

-         """ Should return empty collection on error reading changelog. """

-         test_error = PermissionError("Not for you")

-         version.changelog_to_version_info_collection.side_effect = test_error

-         result = version.generate_version_info_from_changelog(

-                 self.fake_changelog_file_path)

-         expected_result = {}

-         self.assertDictEqual(expected_result, result)

- 

-     def test_opens_file_with_expected_encoding(

-             self,

-             mock_func_get_latest_version):

-         """ Should open changelog file in text mode with expected encoding. """

-         version.generate_version_info_from_changelog(

-                 self.fake_changelog_file_path)

-         expected_file_path = self.fake_changelog_file_path

-         expected_open_mode = 'rt'

-         expected_encoding = self.file_encoding

-         (open_args_positional, open_args_kwargs) = io.open.call_args

-         (open_args_filespec, open_args_mode) = open_args_positional[:2]

-         open_args_encoding = open_args_kwargs['encoding']

-         self.assertEqual(expected_file_path, open_args_filespec)

-         self.assertEqual(expected_open_mode, open_args_mode)

-         self.assertEqual(expected_encoding, open_args_encoding)

- 

-     def test_returns_expected_result(

-             self,

-             mock_func_get_latest_version):

-         """ Should return expected result. """

-         result = version.generate_version_info_from_changelog(

-                 self.fake_changelog_file_path)

-         self.assertEqual(self.expected_result, result)

- 

- 

- DefaultNoneDict = functools.partial(collections.defaultdict, lambda: None)

- 

- class get_latest_version_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘get_latest_version’ function. """

- 

-     scenarios = [

-             ('simple', {

-                 'test_versions': [

-                     DefaultNoneDict({'release_date': "LATEST"}),

-                     ],

-                 'expected_result': version.ChangeLogEntry.make_ordered_dict(

-                     DefaultNoneDict({'release_date': "LATEST"})),

-                 }),

-             ('no versions', {

-                 'test_versions': [],

-                 'expected_result': collections.OrderedDict(),

-                 }),

-             ('ordered versions', {

-                 'test_versions': [

-                     DefaultNoneDict({'release_date': "1"}),

-                     DefaultNoneDict({'release_date': "2"}),

-                     DefaultNoneDict({'release_date': "LATEST"}),

-                     ],

-                 'expected_result': version.ChangeLogEntry.make_ordered_dict(

-                     DefaultNoneDict({'release_date': "LATEST"})),

-                 }),

-             ('un-ordered versions', {

-                 'test_versions': [

-                     DefaultNoneDict({'release_date': "2"}),

-                     DefaultNoneDict({'release_date': "LATEST"}),

-                     DefaultNoneDict({'release_date': "1"}),

-                     ],

-                 'expected_result': version.ChangeLogEntry.make_ordered_dict(

-                     DefaultNoneDict({'release_date': "LATEST"})),

-                 }),

-             ]

- 

-     def test_returns_expected_result(self):

-         """ Should return expected result. """

-         result = version.get_latest_version(self.test_versions)

-         self.assertDictEqual(self.expected_result, result)

- 

- 

- @mock.patch.object(json, "dumps", side_effect=json.dumps)

- class serialise_version_info_from_mapping_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘get_latest_version’ function. """

- 

-     scenarios = [

-             ('simple', {

-                 'test_version_info': {'foo': "spam"},

-                 }),

-             ]

- 

-     for (name, scenario) in scenarios:

-         scenario['fake_json_dump'] = json.dumps(scenario['test_version_info'])

-         scenario['expected_value'] = scenario['test_version_info']

- 

-     def test_passes_specified_object(self, mock_func_json_dumps):

-         """ Should pass the specified object to `json.dumps`. """

-         version.serialise_version_info_from_mapping(

-                 self.test_version_info)

-         mock_func_json_dumps.assert_called_with(

-                 self.test_version_info, indent=mock.ANY)

- 

-     def test_returns_expected_result(self, mock_func_json_dumps):

-         """ Should return expected result. """

-         mock_func_json_dumps.return_value = self.fake_json_dump

-         result = version.serialise_version_info_from_mapping(

-                 self.test_version_info)

-         value = json.loads(result)

-         self.assertEqual(self.expected_value, value)

- 

- 

- DistributionMetadata_defaults = {

-         name: None

-         for name in list(collections.OrderedDict.fromkeys(

-             distutils.dist.DistributionMetadata._METHOD_BASENAMES))}

- FakeDistributionMetadata = collections.namedtuple(

-         'FakeDistributionMetadata', DistributionMetadata_defaults.keys())

- 

- Distribution_defaults = {

-         'metadata': None,

-         'version': None,

-         'release_date': None,

-         'maintainer': None,

-         'maintainer_email': None,

-         }

- FakeDistribution = collections.namedtuple(

-         'FakeDistribution', Distribution_defaults.keys())

- 

- def make_fake_distribution(

-         fields_override=None, metadata_fields_override=None):

-     metadata_fields = DistributionMetadata_defaults.copy()

-     if metadata_fields_override is not None:

-         metadata_fields.update(metadata_fields_override)

-     metadata = FakeDistributionMetadata(**metadata_fields)

- 

-     fields = Distribution_defaults.copy()

-     fields['metadata'] = metadata

-     if fields_override is not None:

-         fields.update(fields_override)

-     distribution = FakeDistribution(**fields)

- 

-     return distribution

- 

- 

- class get_changelog_path_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘get_changelog_path’ function. """

- 

-     default_path = ""

-     default_script_filename = "setup.py"

- 

-     scenarios = [

-             ('simple', {}),

-             ('unusual script name', {

-                 'script_filename': "lorem_ipsum",

-                 }),

-             ('relative script path', {

-                 'script_directory': "dolor/sit/amet",

-                 }),

-             ('absolute script path', {

-                 'script_directory': "/dolor/sit/amet",

-                 }),

-             ('specify filename', {

-                 'changelog_filename': "adipiscing",

-                 }),

-             ]

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(get_changelog_path_TestCase, self).setUp()

- 

-         test_distribution = distutils.dist.Distribution()

-         self.test_distribution = mock.MagicMock(test_distribution)

- 

-         if not hasattr(self, 'script_directory'):

-             self.script_directory = self.default_path

-         if not hasattr(self, 'script_filename'):

-             self.script_filename = self.default_script_filename

- 

-         self.test_distribution.packages = None

-         self.test_distribution.package_dir = {'': self.script_directory}

-         self.test_distribution.script_name = self.script_filename

- 

-         changelog_filename = version.changelog_filename

-         if hasattr(self, 'changelog_filename'):

-             changelog_filename = self.changelog_filename

- 

-         self.expected_result = os.path.join(

-                 self.script_directory, changelog_filename)

- 

-     def test_returns_expected_result(self):

-         """ Should return expected result. """

-         args = {

-                 'distribution': self.test_distribution,

-                 }

-         if hasattr(self, 'changelog_filename'):

-             args.update({'filename': self.changelog_filename})

-         result = version.get_changelog_path(**args)

-         self.assertEqual(self.expected_result, result)

- 

- 

- class WriteVersionInfoCommand_BaseTestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Base class for ‘WriteVersionInfoCommand’ test case classes. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(WriteVersionInfoCommand_BaseTestCase, self).setUp()

- 

-         fake_distribution_name = self.getUniqueString()

- 

-         self.test_distribution = distutils.dist.Distribution()

-         self.test_distribution.metadata.name = fake_distribution_name

- 

- 

- class WriteVersionInfoCommand_TestCase(WriteVersionInfoCommand_BaseTestCase):

-     """ Test cases for ‘WriteVersionInfoCommand’ class. """

- 

-     def test_subclass_of_distutils_command(self):

-         """ Should be a subclass of ‘distutils.cmd.Command’. """

-         instance = version.WriteVersionInfoCommand(self.test_distribution)

-         self.assertIsInstance(instance, distutils.cmd.Command)

- 

- 

- class WriteVersionInfoCommand_user_options_TestCase(

-         WriteVersionInfoCommand_BaseTestCase):

-     """ Test cases for ‘WriteVersionInfoCommand.user_options’ attribute. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(WriteVersionInfoCommand_user_options_TestCase, self).setUp()

- 

-         self.test_instance = version.WriteVersionInfoCommand(

-                 self.test_distribution)

-         self.commandline_parser = distutils.fancy_getopt.FancyGetopt(

-                 self.test_instance.user_options)

- 

-     def test_parses_correctly_as_fancy_getopt(self):

-         """ Should parse correctly in ‘FancyGetopt’. """

-         self.assertIsInstance(

-                 self.commandline_parser, distutils.fancy_getopt.FancyGetopt)

- 

-     def test_includes_base_class_user_options(self):

-         """ Should include base class's user_options. """

-         base_command = setuptools.command.egg_info.egg_info

-         expected_user_options = base_command.user_options

-         self.assertThat(

-                 set(expected_user_options),

-                 IsSubset(set(self.test_instance.user_options)))

- 

-     def test_has_option_changelog_path(self):

-         """ Should have a ‘changelog-path’ option. """

-         expected_option_name = "changelog-path="

-         result = self.commandline_parser.has_option(expected_option_name)

-         self.assertTrue(result)

- 

-     def test_has_option_outfile_path(self):

-         """ Should have a ‘outfile-path’ option. """

-         expected_option_name = "outfile-path="

-         result = self.commandline_parser.has_option(expected_option_name)

-         self.assertTrue(result)

- 

- 

- class WriteVersionInfoCommand_initialize_options_TestCase(

-         WriteVersionInfoCommand_BaseTestCase):

-     """ Test cases for ‘WriteVersionInfoCommand.initialize_options’ method. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(

-                 WriteVersionInfoCommand_initialize_options_TestCase, self

-                 ).setUp()

- 

-         patcher_func_egg_info_initialize_options = mock.patch.object(

-                 setuptools.command.egg_info.egg_info, "initialize_options")

-         patcher_func_egg_info_initialize_options.start()

-         self.addCleanup(patcher_func_egg_info_initialize_options.stop)

- 

-     def test_calls_base_class_method(self):

-         """ Should call base class's ‘initialize_options’ method. """

-         version.WriteVersionInfoCommand(self.test_distribution)

-         base_command_class = setuptools.command.egg_info.egg_info

-         base_command_class.initialize_options.assert_called_with()

- 

-     def test_sets_changelog_path_to_none(self):

-         """ Should set ‘changelog_path’ attribute to ``None``. """

-         instance = version.WriteVersionInfoCommand(self.test_distribution)

-         self.assertIs(instance.changelog_path, None)

- 

-     def test_sets_outfile_path_to_none(self):

-         """ Should set ‘outfile_path’ attribute to ``None``. """

-         instance = version.WriteVersionInfoCommand(self.test_distribution)

-         self.assertIs(instance.outfile_path, None)

- 

- 

- class WriteVersionInfoCommand_finalize_options_TestCase(

-         WriteVersionInfoCommand_BaseTestCase):

-     """ Test cases for ‘WriteVersionInfoCommand.finalize_options’ method. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(WriteVersionInfoCommand_finalize_options_TestCase, self).setUp()

- 

-         self.test_instance = version.WriteVersionInfoCommand(

-                 self.test_distribution)

- 

-         patcher_func_egg_info_finalize_options = mock.patch.object(

-                 setuptools.command.egg_info.egg_info, "finalize_options")

-         patcher_func_egg_info_finalize_options.start()

-         self.addCleanup(patcher_func_egg_info_finalize_options.stop)

- 

-         self.fake_script_dir = self.getUniqueString()

-         self.test_distribution.script_name = os.path.join(

-                 self.fake_script_dir, self.getUniqueString())

- 

-         self.fake_egg_dir = self.getUniqueString()

-         self.test_instance.egg_info = self.fake_egg_dir

- 

-         patcher_func_get_changelog_path = mock.patch.object(

-                 version, "get_changelog_path")

-         patcher_func_get_changelog_path.start()

-         self.addCleanup(patcher_func_get_changelog_path.stop)

- 

-         self.fake_changelog_path = self.getUniqueString()

-         version.get_changelog_path.return_value = self.fake_changelog_path

- 

-     def test_calls_base_class_method(self):

-         """ Should call base class's ‘finalize_options’ method. """

-         base_command_class = setuptools.command.egg_info.egg_info

-         self.test_instance.finalize_options()

-         base_command_class.finalize_options.assert_called_with()

- 

-     def test_sets_force_to_none(self):

-         """ Should set ‘force’ attribute to ``None``. """

-         self.test_instance.finalize_options()

-         self.assertIs(self.test_instance.force, None)

- 

-     def test_sets_changelog_path_using_get_changelog_path(self):

-         """ Should set ‘changelog_path’ attribute if it was ``None``. """

-         self.test_instance.changelog_path = None

-         self.test_instance.finalize_options()

-         expected_changelog_path = self.fake_changelog_path

-         self.assertEqual(

-                 expected_changelog_path, self.test_instance.changelog_path)

- 

-     def test_leaves_changelog_path_if_already_set(self):

-         """ Should leave ‘changelog_path’ attribute set. """

-         prior_changelog_path = self.getUniqueString()

-         self.test_instance.changelog_path = prior_changelog_path

-         self.test_instance.finalize_options()

-         expected_changelog_path = prior_changelog_path

-         self.assertEqual(

-                 expected_changelog_path, self.test_instance.changelog_path)

- 

-     def test_sets_outfile_path_to_default(self):

-         """ Should set ‘outfile_path’ attribute to default value. """

-         fake_version_info_filename = self.getUniqueString()

-         with mock.patch.object(

-                 version, "version_info_filename",

-                 new=fake_version_info_filename):

-             self.test_instance.finalize_options()

-         expected_outfile_path = os.path.join(

-                 self.fake_egg_dir, fake_version_info_filename)

-         self.assertEqual(

-                 expected_outfile_path, self.test_instance.outfile_path)

- 

-     def test_leaves_outfile_path_if_already_set(self):

-         """ Should leave ‘outfile_path’ attribute set. """

-         prior_outfile_path = self.getUniqueString()

-         self.test_instance.outfile_path = prior_outfile_path

-         self.test_instance.finalize_options()

-         expected_outfile_path = prior_outfile_path

-         self.assertEqual(

-                 expected_outfile_path, self.test_instance.outfile_path)

- 

- 

- class has_changelog_TestCase(

-         testscenarios.WithScenarios, testtools.TestCase):

-     """ Test cases for ‘has_changelog’ function. """

- 

-     fake_os_path_exists_side_effects = {

-             'true': (lambda path: True),

-             'false': (lambda path: False),

-             }

- 

-     scenarios = [

-             ('no changelog path', {

-                 'changelog_path': None,

-                 'expected_result': False,

-                 }),

-             ('changelog exists', {

-                 'os_path_exists_scenario': 'true',

-                 'expected_result': True,

-                 }),

-             ('changelog not found', {

-                 'os_path_exists_scenario': 'false',

-                 'expected_result': False,

-                 }),

-             ]

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(has_changelog_TestCase, self).setUp()

- 

-         self.test_distribution = distutils.dist.Distribution()

-         self.test_command = version.EggInfoCommand(

-                 self.test_distribution)

- 

-         patcher_func_get_changelog_path = mock.patch.object(

-                 version, "get_changelog_path")

-         patcher_func_get_changelog_path.start()

-         self.addCleanup(patcher_func_get_changelog_path.stop)

- 

-         self.fake_changelog_file_path = self.getUniqueString()

-         if hasattr(self, 'changelog_path'):

-             self.fake_changelog_file_path = self.changelog_path

-         version.get_changelog_path.return_value = self.fake_changelog_file_path

-         self.fake_changelog_file = io.StringIO()

- 

-         def fake_os_path_exists(path):

-             if path == self.fake_changelog_file_path:

-                 side_effect = self.fake_os_path_exists_side_effects[

-                         self.os_path_exists_scenario]

-                 if callable(side_effect):

-                     result = side_effect(path)

-                 else:

-                     raise side_effect

-             else:

-                 result = False

-             return result

- 

-         func_patcher_os_path_exists = mock.patch.object(

-                 os.path, "exists")

-         func_patcher_os_path_exists.start()

-         self.addCleanup(func_patcher_os_path_exists.stop)

-         os.path.exists.side_effect = fake_os_path_exists

- 

-     def test_gets_changelog_path_from_distribution(self):

-         """ Should call ‘get_changelog_path’ with distribution. """

-         version.has_changelog(self.test_command)

-         version.get_changelog_path.assert_called_with(

-                 self.test_distribution)

- 

-     def test_returns_expected_result(self):

-         """ Should be a subclass of ‘distutils.cmd.Command’. """

-         result = version.has_changelog(self.test_command)

-         self.assertEqual(self.expected_result, result)

- 

- 

- class WriteVersionInfoCommand_run_TestCase(

-         WriteVersionInfoCommand_BaseTestCase):

-     """ Test cases for ‘WriteVersionInfoCommand.run’ method. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(WriteVersionInfoCommand_run_TestCase, self).setUp()

- 

-         self.test_instance = version.WriteVersionInfoCommand(

-                 self.test_distribution)

- 

-         self.fake_changelog_path = self.set_changelog_path(self.test_instance)

-         self.fake_outfile_path = self.set_outfile_path(self.test_instance)

- 

-         self.patch_version_info()

-         self.patch_egg_info_write_file()

- 

-     def set_changelog_path(self, instance):

-         """ Set the changelog path for the test instance `instance`. """

-         self.test_instance.changelog_path = self.getUniqueString()

-         return self.test_instance.changelog_path

- 

-     def set_outfile_path(self, instance):

-         """ Set the outfile path for the test instance `instance`. """

-         self.test_instance.outfile_path = self.getUniqueString()

-         return self.test_instance.outfile_path

- 

-     def patch_version_info(self):

-         """ Patch the generation of version info. """

-         self.fake_version_info = self.getUniqueString()

-         func_patcher = mock.patch.object(

-                 version, 'generate_version_info_from_changelog',

-                 return_value=self.fake_version_info)

-         self.mock_func_generate_version_info = func_patcher.start()

-         self.addCleanup(func_patcher.stop)

- 

-         self.fake_version_info_serialised = self.getUniqueString()

-         func_patcher = mock.patch.object(

-                 version, 'serialise_version_info_from_mapping',

-                 return_value=self.fake_version_info_serialised)

-         self.mock_func_serialise_version_info = func_patcher.start()

-         self.addCleanup(func_patcher.stop)

- 

-     def patch_egg_info_write_file(self):

-         """ Patch the command `write_file` method for this test case. """

-         func_patcher = mock.patch.object(

-             version.WriteVersionInfoCommand, 'write_file')

-         self.mock_func_egg_info_write_file = func_patcher.start()

-         self.addCleanup(func_patcher.stop)

- 

-     def test_returns_none(self):

-         """ Should return ``None``. """

-         result = self.test_instance.run()

-         self.assertIs(result, None)

- 

-     def test_generates_version_info_from_changelog(self):

-         """ Should generate version info from specified changelog. """

-         self.test_instance.run()

-         expected_changelog_path = self.test_instance.changelog_path

-         self.mock_func_generate_version_info.assert_called_with(

-                 expected_changelog_path)

- 

-     def test_serialises_version_info_from_mapping(self):

-         """ Should serialise version info from specified mapping. """

-         self.test_instance.run()

-         expected_version_info = self.fake_version_info

-         self.mock_func_serialise_version_info.assert_called_with(

-                 expected_version_info)

- 

-     def test_writes_file_using_command_context(self):

-         """ Should write the metadata file using the command context. """

-         self.test_instance.run()

-         expected_content = self.fake_version_info_serialised

-         self.mock_func_egg_info_write_file.assert_called_with(

-                 "version info", self.fake_outfile_path, expected_content)

- 

- 

- IsSubset = testtools.matchers.MatchesPredicateWithParams(

-         set.issubset, "{0} should be a subset of {1}")

- 

- class EggInfoCommand_TestCase(testtools.TestCase):

-     """ Test cases for ‘EggInfoCommand’ class. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(EggInfoCommand_TestCase, self).setUp()

- 

-         self.test_distribution = distutils.dist.Distribution()

-         self.test_instance = version.EggInfoCommand(self.test_distribution)

- 

-     def test_subclass_of_setuptools_egg_info(self):

-         """ Should be a subclass of Setuptools ‘egg_info’. """

-         self.assertIsInstance(

-                 self.test_instance, setuptools.command.egg_info.egg_info)

- 

-     def test_sub_commands_include_base_class_sub_commands(self):

-         """ Should include base class's sub-commands in this sub_commands. """

-         base_command = setuptools.command.egg_info.egg_info

-         expected_sub_commands = base_command.sub_commands

-         self.assertThat(

-                 set(expected_sub_commands),

-                 IsSubset(set(self.test_instance.sub_commands)))

- 

-     def test_sub_commands_includes_write_version_info_command(self):

-         """ Should include sub-command named ‘write_version_info’. """

-         commands_by_name = dict(self.test_instance.sub_commands)

-         expected_predicate = version.has_changelog

-         expected_item = ('write_version_info', expected_predicate)

-         self.assertIn(expected_item, commands_by_name.items())

- 

- 

- @mock.patch.object(setuptools.command.egg_info.egg_info, "run")

- class EggInfoCommand_run_TestCase(testtools.TestCase):

-     """ Test cases for ‘EggInfoCommand.run’ method. """

- 

-     def setUp(self):

-         """ Set up test fixtures. """

-         super(EggInfoCommand_run_TestCase, self).setUp()

- 

-         self.test_distribution = distutils.dist.Distribution()

-         self.test_instance = version.EggInfoCommand(self.test_distribution)

- 

-         base_command = setuptools.command.egg_info.egg_info

-         patcher_func_egg_info_get_sub_commands = mock.patch.object(

-                 base_command, "get_sub_commands")

-         patcher_func_egg_info_get_sub_commands.start()

-         self.addCleanup(patcher_func_egg_info_get_sub_commands.stop)

- 

-         patcher_func_egg_info_run_command = mock.patch.object(

-                 base_command, "run_command")

-         patcher_func_egg_info_run_command.start()

-         self.addCleanup(patcher_func_egg_info_run_command.stop)

- 

-         self.fake_sub_commands = ["spam", "eggs", "beans"]

-         base_command.get_sub_commands.return_value = self.fake_sub_commands

- 

-     def test_returns_none(self, mock_func_egg_info_run):

-         """ Should return ``None``. """

-         result = self.test_instance.run()

-         self.assertIs(result, None)

- 

-     def test_runs_each_command_in_sub_commands(

-             self, mock_func_egg_info_run):

-         """ Should run each command in ‘self.get_sub_commands()’. """

-         base_command = setuptools.command.egg_info.egg_info

-         self.test_instance.run()

-         expected_calls = [mock.call(name) for name in self.fake_sub_commands]

-         base_command.run_command.assert_has_calls(expected_calls)

- 

-     def test_calls_base_class_run(self, mock_func_egg_info_run):

-         """ Should call base class's ‘run’ method. """

-         self.test_instance.run()

-         mock_func_egg_info_run.assert_called_with()

- 

- 

- # Copyright © 2008–2018 Ben Finney <ben+python@benfinney.id.au>

- #

- # This is free software: you may copy, modify, and/or distribute this work

- # under the terms of the GNU General Public License as published by the

- # Free Software Foundation; version 3 of that license or any later version.

- # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.

- 

- 

- # Local variables:

- # coding: utf-8

- # mode: python

- # End:

- # vim: fileencoding=utf-8 filetype=python :

file added
+13
@@ -0,0 +1,13 @@ 

+ # tox (https://tox.readthedocs.io/) is a tool for running tests

+ # in multiple virtualenvs. This configuration file will run the

+ # test suite on all supported python versions. To use it, "pip install tox"

+ # and then run "tox" from this directory.

+ 

+ [tox]

+ envlist = py35, py36, py37, pep8, docs

+ 

+ [testenv]

+ deps = -r{toxinidir}/test-requirements.txt

+ 

+ commands =

+     python -m unittest discover

file removed
-698
@@ -1,698 +0,0 @@ 

- # -*- coding: utf-8 -*-

- 

- # version.py

- # Part of ‘python-daemon’, an implementation of PEP 3143.

- #

- # This is free software, and you are welcome to redistribute it under

- # certain conditions; see the end of this file for copyright

- # information, grant of license, and disclaimer of warranty.

- 

- """ Version information unified for human- and machine-readable formats.

- 

-     The project ‘ChangeLog’ file is a reStructuredText document, with

-     each section describing a version of the project. The document is

-     intended to be readable as-is by end users.

- 

-     This module handles transformation from the ‘ChangeLog’ to a

-     mapping of version information, serialised as JSON. It also

-     provides functionality for Distutils to use this information.

- 

-     Requires:

- 

-     * Docutils <http://docutils.sourceforge.net/>

-     * JSON <https://docs.python.org/3/reference/json.html>

- 

-     """

- 

- from __future__ import (absolute_import, unicode_literals)

- 

- import collections

- import datetime

- import distutils

- import distutils.cmd

- import distutils.command.build

- import distutils.command.build_py

- import distutils.dist

- import distutils.errors

- import distutils.version

- import functools

- import io

- import json

- import os

- import re

- import sys

- import textwrap

- 

- import setuptools

- import setuptools.command.egg_info

- 

- try:

-     # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text).

-     basestring = basestring

-     unicode = unicode

- except NameError:

-     # Python 3 names the Unicode data type ‘str’.

-     basestring = str

-     unicode = str

- 

- __metaclass__ = type

- 

- 

- def ensure_class_bases_begin_with(namespace, class_name, base_class):

-     """ Ensure the named class's bases start with the base class.

- 

-         :param namespace: The namespace containing the class name.

-         :param class_name: The name of the class to alter.

-         :param base_class: The type to be the first base class for the

-             newly created type.

-         :return: ``None``.

- 

-         This function is a hack to circumvent a circular dependency:

-         using classes from a module which is not installed at the time

-         this module is imported.

- 

-         Call this function after ensuring `base_class` is available,

-         before using the class named by `class_name`.

- 

-         """

-     existing_class = namespace[class_name]

-     assert isinstance(existing_class, type)

- 

-     bases = list(existing_class.__bases__)

-     if base_class is bases[0]:

-         # Already bound to a type with the right bases.

-         return

-     bases.insert(0, base_class)

- 

-     new_class_namespace = existing_class.__dict__.copy()

-     # Type creation will assign the correct ‘__dict__’ attribute.

-     del new_class_namespace['__dict__']

- 

-     metaclass = existing_class.__metaclass__

-     new_class = metaclass(class_name, tuple(bases), new_class_namespace)

- 

-     namespace[class_name] = new_class

- 

- 

- class VersionInfoWriter(object):

-     """ Docutils writer to produce a version info JSON data stream. """

- 

-     # This class needs its base class to be a class from `docutils`.

-     # But that would create a circular dependency: Setuptools cannot

-     # ensure `docutils` is available before importing this module.

-     #

-     # Use `ensure_class_bases_begin_with` after importing `docutils`, to

-     # re-bind the `VersionInfoWriter` name to a new type that inherits

-     # from `docutils.writers.Writer`.

- 

-     __metaclass__ = type

- 

-     supported = ['version_info']

-     """ Formats this writer supports. """

- 

-     def __init__(self):

-         super(VersionInfoWriter, self).__init__()

-         self.translator_class = VersionInfoTranslator

- 

-     def translate(self):

-         visitor = self.translator_class(self.document)

-         self.document.walkabout(visitor)

-         self.output = visitor.astext()

- 

- 

- rfc822_person_regex = re.compile(

-         "^(?P<name>[^<]+) <(?P<email>[^>]+)>$")

- 

- ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email'])

- 

- def parse_person_field(value):

-     """ Parse a person field into name and email address.

- 

-         :param value: The text value specifying a person.

-         :return: A 2-tuple (name, email) for the person's details.

- 

-         If the `value` does not match a standard person with email

-         address, the `email` item is ``None``.

- 

-         """

-     result = ParsedPerson(None, None)

- 

-     match = rfc822_person_regex.match(value)

-     if len(value):

-         if match is not None:

-             result = ParsedPerson(

-                     name=match.group('name'),

-                     email=match.group('email'))

-         else:

-             result = ParsedPerson(name=value, email=None)

- 

-     return result

- 

- 

- class ChangeLogEntry:

-     """ An individual entry from the ‘ChangeLog’ document. """

- 

-     __metaclass__ = type

- 

-     field_names = [

-             'release_date',

-             'version',

-             'maintainer',

-             'body',

-             ]

- 

-     date_format = "%Y-%m-%d"

-     default_version = "UNKNOWN"

-     default_release_date = "UNKNOWN"

- 

-     def __init__(

-             self,

-             release_date=default_release_date, version=default_version,

-             maintainer=None, body=None):

-         self.validate_release_date(release_date)

-         self.release_date = release_date

- 

-         self.validate_version(version)

-         self.version = version

- 

-         self.validate_maintainer(maintainer)

-         self.maintainer = maintainer

-         self.body = body

- 

-     @classmethod

-     def validate_release_date(cls, value):

-         """ Validate the `release_date` value.

- 

-             :param value: The prospective `release_date` value.

-             :return: ``None`` if the value is valid.

-             :raises ValueError: If the value is invalid.

- 

-             """

-         if value in ["UNKNOWN", "FUTURE"]:

-             # A valid non-date value.

-             return None

- 

-         # Raises `ValueError` if parse fails.

-         datetime.datetime.strptime(value, ChangeLogEntry.date_format)

- 

-     @classmethod

-     def validate_version(cls, value):

-         """ Validate the `version` value.

- 

-             :param vaue: The prospective `version` value.

-             :return: ``None`` if the value is valid.

-             :raises ValueError: If the value is invalid.

- 

-             """

-         if value in ["UNKNOWN", "NEXT"]:

-             # A valid non-version value.

-             return None

- 

-         match = distutils.version.StrictVersion.version_re.match(value)

-         if match is None:

-             raise ValueError(

-                     "not a valid version string {value!r}".format(

-                         value=value))

- 

-     @classmethod

-     def validate_maintainer(cls, value):

-         """ Validate the `maintainer` value.

- 

-             :param value: The prospective `maintainer` value.

-             :return: ``None`` if the value is valid.

-             :raises ValueError: If the value is invalid.

- 

-             """

-         valid = False

- 

-         if value is None:

-             valid = True

-         elif rfc822_person_regex.search(value):

-             valid = True

- 

-         if not valid:

-             raise ValueError(

-                     "not a valid person specification {value!r}".format(

-                         value=value))

-         else:

-             return None

- 

-     @classmethod

-     def make_ordered_dict(cls, fields):

-         """ Make an ordered dict of the fields. """

-         result = collections.OrderedDict(

-                 (name, fields[name])

-                 for name in cls.field_names)

-         return result

- 

-     def as_version_info_entry(self):

-         """ Format the changelog entry as a version info entry. """

-         fields = vars(self)

-         entry = self.make_ordered_dict(fields)

- 

-         return entry

- 

- 

- class InvalidFormatError(ValueError):

-     """ Raised when the document is not a valid ‘ChangeLog’ document. """

- 

-     def __init__(self, node, message=None):

-         self.node = node

-         self.message = message

- 

-     def __str__(self):

-         text = "{message}: {source} line {line:d}".format(

-                 message=(

-                     getattr(self, 'message', "(no message)")),

-                 source=(

-                     getattr(self.node, 'source', "(source unknown)")),

-                 line=(

-                     getattr(self.node, 'line', "(unknown)")),

-                 )

- 

-         return text

- 

- 

- class VersionInfoTranslator(object):

-     """ Translator from document nodes to a version info stream. """

- 

-     # This class needs its base class to be a class from `docutils`.

-     # But that would create a circular dependency: Setuptools cannot

-     # ensure `docutils` is available before importing this module.

-     #

-     # Use `ensure_class_bases_begin_with` after importing `docutils`,

-     # to re-bind the `VersionInfoTranslator` name to a new type that

-     # inherits from `docutils.nodes.SparseNodeVisitor`.

- 

-     __metaclass__ = type

- 

-     wrap_width = 78

-     bullet_text = "* "

- 

-     attr_convert_funcs_by_attr_name = {

-             'released': ('release_date', unicode),

-             'version': ('version', unicode),

-             'maintainer': ('maintainer', unicode),

-             }

- 

-     def __init__(self, document):

-         super(VersionInfoTranslator, self).__init__(document)

-         self.settings = document.settings

-         self.current_field_name = None

-         self.content = []

-         self.indent_width = 0

-         self.initial_indent = ""

-         self.subsequent_indent = ""

-         self.current_entry = None

- 

-         # Docutils is not available when this class is defined.

-         # Get the `docutils` module dynamically.

-         self._docutils = sys.modules['docutils']

- 

-     def astext(self):

-         """ Return the translated document as text. """

-         text = json.dumps(self.content, indent=4)

-         return text

- 

-     def append_to_current_entry(self, text):

-         if self.current_entry is not None:

-             if self.current_entry.body is not None:

-                 self.current_entry.body += text

- 

-     def visit_Text(self, node):

-         raw_text = node.astext()

-         text = textwrap.fill(

-                 raw_text,

-                 width=self.wrap_width,

-                 initial_indent=self.initial_indent,

-                 subsequent_indent=self.subsequent_indent)

-         self.append_to_current_entry(text)

- 

-     def depart_Text(self, node):

-         pass

- 

-     def visit_comment(self, node):

-         raise self._docutils.nodes.SkipNode

- 

-     def visit_field_body(self, node):

-         field_list_node = node.parent.parent

-         if not isinstance(field_list_node, self._docutils.nodes.field_list):

-             raise InvalidFormatError(

-                     node,

-                     "Unexpected field within {node!r}".format(

-                         node=field_list_node))

-         if not isinstance(

-                 field_list_node.parent, self._docutils.nodes.section):

-             # Field list is not in a section.

-             raise self._docutils.nodes.SkipNode

-         if self.current_field_name not in self.attr_convert_funcs_by_attr_name:

-             raise InvalidFormatError(

-                     node,

-                     "Unexpected field name {name!r}".format(

-                         name=self.current_field_name))

-         (attr_name, convert_func) = self.attr_convert_funcs_by_attr_name[

-                 self.current_field_name]

-         attr_value = convert_func(node.astext())

-         setattr(self.current_entry, attr_name, attr_value)

- 

-     def depart_field_body(self, node):

-         pass

- 

-     def visit_field_list(self, node):

-         pass

- 

-     def depart_field_list(self, node):

-         self.current_field_name = None

-         self.current_entry.body = ""

- 

-     def visit_field_name(self, node):

-         field_name = node.astext()

-         self.current_field_name = field_name.lower()

-         field_list_node = node.parent

-         if not isinstance(

-                 field_list_node.parent, self._docutils.nodes.section):

-             # Field list is not in a section.

-             raise self._docutils.nodes.SkipNode

-         if not isinstance(

-                 field_list_node.parent.parent, self._docutils.nodes.Root):

-             # The section is not top-level.

-             raise self._docutils.nodes.SkipNode

-         if field_name.lower() not in ["released", "maintainer"]:

-             raise InvalidFormatError(

-                     node,

-                     "Unexpected field name {name!r}".format(name=field_name))

- 

-     def depart_field_name(self, node):

-         pass

- 

-     def visit_bullet_list(self, node):

-         self.current_context = []

- 

-     def depart_bullet_list(self, node):

-         self.current_entry.changes = self.current_context

-         self.current_context = None

- 

-     def adjust_indent_width(self, delta):

-         self.indent_width += delta

-         self.subsequent_indent = " " * self.indent_width

-         self.initial_indent = self.subsequent_indent

- 

-     def visit_list_item(self, node):

-         indent_delta = +len(self.bullet_text)

-         self.adjust_indent_width(indent_delta)

-         self.initial_indent = self.subsequent_indent[:-indent_delta]

-         self.append_to_current_entry(self.initial_indent + self.bullet_text)

- 

-     def depart_list_item(self, node):

-         indent_delta = +len(self.bullet_text)

-         self.adjust_indent_width(-indent_delta)

-         self.append_to_current_entry("\n")

- 

-     def visit_section(self, node):

-         if not isinstance(node.parent, self._docutils.nodes.Root):

-             raise InvalidFormatError(

-                     node, "Subsections not implemented for this writer")

-         self.current_entry = ChangeLogEntry()

- 

-     def depart_section(self, node):

-         self.content.append(

-                 self.current_entry.as_version_info_entry())

-         self.current_entry = None

- 

-     _expected_title_word_length = len("Version FOO".split(" "))

- 

-     def depart_title(self, node):

-         title_text = node.astext()

-         words = title_text.split(" ")

-         version = None

-         if len(words) != self._expected_title_word_length:

-             raise InvalidFormatError(

-                     node,

-                     "Unexpected title text {text!r}".format(text=title_text))

-         if words[0].lower() not in ["version"]:

-             raise InvalidFormatError(

-                     node,

-                     "Unexpected title text {text!r}".format(text=title_text))

-         version = words[-1]

-         self.current_entry.version = version

- 

- 

- def changelog_to_version_info_collection(infile):

-     """ Render the ‘ChangeLog’ document to a version info collection.

- 

-         :param infile: A file-like object containing the changelog.

-         :return: The serialised JSON data of the version info collection.

- 

-         """

- 

-     # Docutils is not available when Setuptools needs this module, so

-     # delay the imports to this function instead.

-     import docutils.core

-     import docutils.nodes

-     import docutils.writers

- 

-     ensure_class_bases_begin_with(

-             globals(), str('VersionInfoWriter'), docutils.writers.Writer)

-     ensure_class_bases_begin_with(

-             globals(), str('VersionInfoTranslator'),

-             docutils.nodes.SparseNodeVisitor)

- 

-     writer = VersionInfoWriter()

-     settings_overrides = {

-             'doctitle_xform': False,

-             }

-     version_info_json = docutils.core.publish_string(

-             infile.read(), writer=writer,

-             settings_overrides=settings_overrides)

- 

-     return version_info_json

- 

- 

- try:

-     lru_cache = functools.lru_cache

- except AttributeError:

-     # Python < 3.2 does not have the `functools.lru_cache` function.

-     # Not essential, so replace it with a no-op.

-     lru_cache = lambda maxsize=None, typed=False: lambda func: func

- 

- 

- @lru_cache(maxsize=128)

- def generate_version_info_from_changelog(infile_path):

-     """ Get the version info for the latest version in the changelog.

- 

-         :param infile_path: Filesystem path to the input changelog file.

-         :return: The generated version info mapping; or ``None`` if the

-             file cannot be read.

- 

-         The document is explicitly opened as UTF-8 encoded text.

- 

-         """

-     version_info = collections.OrderedDict()

- 

-     versions_all_json = None

-     try:

-         with io.open(infile_path, 'rt', encoding="utf-8") as infile:

-             versions_all_json = changelog_to_version_info_collection(infile)

-     except EnvironmentError:

-         # If we can't read the input file, leave the collection empty.

-         pass

- 

-     if versions_all_json is not None:

-         versions_all = json.loads(versions_all_json.decode('utf-8'))

-         # The changelog will have the latest entry first.

-         version_info = versions_all[0]

- 

-     return version_info

- 

- 

- def get_latest_version(versions):

-     """ Get the latest version from a collection of changelog entries.

- 

-         :param versions: A collection of mappings for changelog entries.

-         :return: An ordered mapping of fields for the latest version,

-             if `versions` is non-empty; otherwise, an empty mapping.

- 

-         """

-     version_info = collections.OrderedDict()

- 

-     versions_by_release_date = {

-             item['release_date']: item

-             for item in versions}

-     if versions_by_release_date:

-         latest_release_date = max(versions_by_release_date.keys())

-         version_info = ChangeLogEntry.make_ordered_dict(

-                 versions_by_release_date[latest_release_date])

- 

-     return version_info

- 

- 

- def serialise_version_info_from_mapping(version_info):

-     """ Generate the version info serialised data.

- 

-         :param version_info: Mapping of version info items.

-         :return: The version info serialised to JSON.

- 

-         """

-     content = json.dumps(version_info, indent=4)

- 

-     return content

- 

- 

- changelog_filename = "ChangeLog"

- 

- def get_changelog_path(distribution, filename=changelog_filename):

-     """ Get the changelog file path for the distribution.

- 

-         :param distribution: The distutils.dist.Distribution instance.

-         :param filename: The base filename of the changelog document.

-         :return: Filesystem path of the changelog document, or ``None``

-             if not discoverable.

- 

-         """

-     build_py_command = distutils.command.build_py.build_py(distribution)

-     build_py_command.finalize_options()

-     setup_dirname = build_py_command.get_package_dir("")

-     filepath = os.path.join(setup_dirname, filename)

- 

-     return filepath

- 

- 

- def has_changelog(command):

-     """ Return ``True`` iff the distribution's changelog file exists. """

-     result = False

- 

-     changelog_path = get_changelog_path(command.distribution)

-     if changelog_path is not None:

-         if os.path.exists(changelog_path):

-             result = True

- 

-     return result

- 

- 

- class BuildCommand(distutils.command.build.build, object):

-     """ Custom ‘build’ command for this distribution. """

- 

-     sub_commands = (

-             distutils.command.build.build.sub_commands + [

-                 ('write_version_info', has_changelog),

-             ])

- 

- 

- class EggInfoCommand(setuptools.command.egg_info.egg_info, object):

-     """ Custom ‘egg_info’ command for this distribution. """

- 

-     sub_commands = ([

-             ('write_version_info', has_changelog),

-             ] + setuptools.command.egg_info.egg_info.sub_commands)

- 

-     def run(self):

-         """ Execute this command. """

-         super(EggInfoCommand, self).run()

- 

-         for command_name in self.get_sub_commands():

-             self.run_command(command_name)

- 

- 

- version_info_filename = "version_info.json"

- 

- class WriteVersionInfoCommand(setuptools.command.egg_info.egg_info, object):

-     """ Setuptools command to serialise version info metadata. """

- 

-     user_options = ([

-             ("changelog-path=", None,

-              "Filesystem path to the changelog document."),

-             ("outfile-path=", None,

-              "Filesystem path to the version info file."),

-             ] + setuptools.command.egg_info.egg_info.user_options)

- 

-     def initialize_options(self):

-         """ Initialise command options to defaults. """

-         super(WriteVersionInfoCommand, self).initialize_options()

-         self.changelog_path = None

-         self.outfile_path = None

- 

-     def finalize_options(self):

-         """ Finalise command options before execution. """

-         self.set_undefined_options(

-                 'build',

-                 ('force', 'force'))

- 

-         super(WriteVersionInfoCommand, self).finalize_options()

- 

-         if self.changelog_path is None:

-             self.changelog_path = get_changelog_path(self.distribution)

- 

-         if self.outfile_path is None:

-             egg_dir = self.egg_info

-             self.outfile_path = os.path.join(egg_dir, version_info_filename)

- 

-     def run(self):

-         """ Execute this command. """

-         version_info = generate_version_info_from_changelog(

-                 self.changelog_path)

-         content = serialise_version_info_from_mapping(version_info)

-         self.write_file("version info", self.outfile_path, content)

- 

- 

- class ChangelogAwareDistribution(distutils.dist.Distribution, object):

-     """ A distribution of Python code for installation.

- 

-         This class gets the following attributes instead from the

-         ‘ChangeLog’ document:

- 

-         * version

-         * maintainer

-         * maintainer_email

- 

-         """

- 

-     __metaclass__ = type

- 

-     def __init__(self, *args, **kwargs):

-         super(ChangelogAwareDistribution, self).__init__(*args, **kwargs)

- 

-         if self.script_name is None:

-             self.script_name = sys.argv[1]

- 

-         # Undo the per-instance delegation for these methods.

-         del (

-                 self.get_version,

-                 self.get_maintainer,

-                 self.get_maintainer_email,

-                 )

- 

-     @lru_cache(maxsize=128)

-     def get_version_info(self):

-         changelog_path = get_changelog_path(self)

-         version_info = generate_version_info_from_changelog(changelog_path)

-         return version_info

- 

-     def get_version(self):

-         version_info = self.get_version_info()

-         version_string = version_info['version']

-         return version_string

- 

-     def get_maintainer(self):

-         version_info = self.get_version_info()

-         person = parse_person_field(version_info['maintainer'])

-         return person.name

- 

-     def get_maintainer_email(self):

-         version_info = self.get_version_info()

-         person = parse_person_field(version_info['maintainer'])

-         return person.email

- 

- 

- # Copyright © 2008–2018 Ben Finney <ben+python@benfinney.id.au>

- #

- # This is free software: you may copy, modify, and/or distribute this work

- # under the terms of the GNU General Public License as published by the

- # Free Software Foundation; version 3 of that license or any later version.

- # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.

- 

- 

- # Local variables:

- # coding: utf-8

- # mode: python

- # End:

- # vim: fileencoding=utf-8 filetype=python :

Hey!

This pull request try to simplify packaging and building by using PBR and reduce
the necessary python code to generate a new releases.

In parallel this PR fix issue #18 (https://pagure.io/python-daemon/issue/18)

In second time we want to propose to add:
- pep 8 checks by introducing flake8 in tox
- AST (Abstract Syntax Trees) security checks by introducing bandit with tox

Summary

  • Modernize the packaging by using PBR and setuptools;
  • Introducing metadata inside setup.cfg;
  • Using tox;
  • Unittest on python 3.5, 3.6, 3.7;
  • Remove module version;
  • Remove toml file (pyproject.toml);
  • Using PEP 516 (Build system abstraction for pip/conda etc) instead of PEP 518 (Specifying Minimum Build System Requirements for Python Projects);
  • Introducing requirements files.

How to test it

$ git clone https://pagure.io/fork/hberaud/python-daemon -b refactor-packaging
$ pip install pbr
$ tox # test your unit tests
$ # pbr use git tag to generate release number
$ # you can generate a fake tag to test with it
$ git tag 3.0.0 # generate fake tag
$ python setup.py build # test packaging
$ python setup.py sdist # test packaging

Authors

Co-authored-by: Hobbestigrou hobbestigrou@erakis.eu

Conclusion

Do not hesitate to debate on this topic we guess this feature can improve
maintainability of the project and simplify maintenance.

Thank you for proposing improvements to this library.

Please break this large set of changes into smaller, tightly-focused merge requests. That way each one can be evaluated, and each can be accepted independent of the others.

Pull-Request has been closed by bignose

5 years ago