bpo-15450: Allow subclassing of dircmp (GH-23424) (#23424)

Co-authored-by: Chris Jerdonek <chris.jerdonek@gmail.com>
This commit is contained in:
Nick Crews 2020-11-23 09:29:37 -07:00 committed by GitHub
parent ff420f0e08
commit 2f2f9d0b5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 13 deletions

View File

@ -173,7 +173,13 @@ The :class:`dircmp` class
.. attribute:: subdirs .. attribute:: subdirs
A dictionary mapping names in :attr:`common_dirs` to :class:`dircmp` A dictionary mapping names in :attr:`common_dirs` to :class:`dircmp`
objects. instances (or MyDirCmp instances if this instance is of type MyDirCmp, a
subclass of :class:`dircmp`).
.. versionchanged:: 3.10
Previously entries were always :class:`dircmp` instances. Now entries
are the same type as *self*, if *self* is a subclass of
:class:`dircmp`.
.. attribute:: DEFAULT_IGNORES .. attribute:: DEFAULT_IGNORES

View File

@ -115,7 +115,9 @@ class dircmp:
same_files: list of identical files. same_files: list of identical files.
diff_files: list of filenames which differ. diff_files: list of filenames which differ.
funny_files: list of files which could not be compared. funny_files: list of files which could not be compared.
subdirs: a dictionary of dircmp objects, keyed by names in common_dirs. subdirs: a dictionary of dircmp instances (or MyDirCmp instances if this
object is of type MyDirCmp, a subclass of dircmp), keyed by names
in common_dirs.
""" """
def __init__(self, a, b, ignore=None, hide=None): # Initialize def __init__(self, a, b, ignore=None, hide=None): # Initialize
@ -185,14 +187,15 @@ class dircmp:
self.same_files, self.diff_files, self.funny_files = xx self.same_files, self.diff_files, self.funny_files = xx
def phase4(self): # Find out differences between common subdirectories def phase4(self): # Find out differences between common subdirectories
# A new dircmp object is created for each common subdirectory, # A new dircmp (or MyDirCmp if dircmp was subclassed) object is created
# for each common subdirectory,
# these are stored in a dictionary indexed by filename. # these are stored in a dictionary indexed by filename.
# The hide and ignore properties are inherited from the parent # The hide and ignore properties are inherited from the parent
self.subdirs = {} self.subdirs = {}
for x in self.common_dirs: for x in self.common_dirs:
a_x = os.path.join(self.left, x) a_x = os.path.join(self.left, x)
b_x = os.path.join(self.right, x) b_x = os.path.join(self.right, x)
self.subdirs[x] = dircmp(a_x, b_x, self.ignore, self.hide) self.subdirs[x] = self.__class__(a_x, b_x, self.ignore, self.hide)
def phase4_closure(self): # Recursively call phase4() on subdirectories def phase4_closure(self): # Recursively call phase4() on subdirectories
self.phase4() self.phase4()

View File

@ -66,6 +66,8 @@ class DirCompareTestCase(unittest.TestCase):
for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored): for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored):
shutil.rmtree(dir, True) shutil.rmtree(dir, True)
os.mkdir(dir) os.mkdir(dir)
subdir_path = os.path.join(dir, 'subdir')
os.mkdir(subdir_path)
if self.caseinsensitive and dir is self.dir_same: if self.caseinsensitive and dir is self.dir_same:
fn = 'FiLe' # Verify case-insensitive comparison fn = 'FiLe' # Verify case-insensitive comparison
else: else:
@ -110,6 +112,11 @@ class DirCompareTestCase(unittest.TestCase):
"Comparing mismatched directories fails") "Comparing mismatched directories fails")
def _assert_lists(self, actual, expected):
"""Assert that two lists are equal, up to ordering."""
self.assertEqual(sorted(actual), sorted(expected))
def test_dircmp(self): def test_dircmp(self):
# Check attributes for comparison of two identical directories # Check attributes for comparison of two identical directories
left_dir, right_dir = self.dir, self.dir_same left_dir, right_dir = self.dir, self.dir_same
@ -117,10 +124,13 @@ class DirCompareTestCase(unittest.TestCase):
self.assertEqual(d.left, left_dir) self.assertEqual(d.left, left_dir)
self.assertEqual(d.right, right_dir) self.assertEqual(d.right, right_dir)
if self.caseinsensitive: if self.caseinsensitive:
self.assertEqual([d.left_list, d.right_list],[['file'], ['FiLe']]) self._assert_lists(d.left_list, ['file', 'subdir'])
self._assert_lists(d.right_list, ['FiLe', 'subdir'])
else: else:
self.assertEqual([d.left_list, d.right_list],[['file'], ['file']]) self._assert_lists(d.left_list, ['file', 'subdir'])
self.assertEqual(d.common, ['file']) self._assert_lists(d.right_list, ['file', 'subdir'])
self._assert_lists(d.common, ['file', 'subdir'])
self._assert_lists(d.common_dirs, ['subdir'])
self.assertEqual(d.left_only, []) self.assertEqual(d.left_only, [])
self.assertEqual(d.right_only, []) self.assertEqual(d.right_only, [])
self.assertEqual(d.same_files, ['file']) self.assertEqual(d.same_files, ['file'])
@ -128,6 +138,7 @@ class DirCompareTestCase(unittest.TestCase):
expected_report = [ expected_report = [
"diff {} {}".format(self.dir, self.dir_same), "diff {} {}".format(self.dir, self.dir_same),
"Identical files : ['file']", "Identical files : ['file']",
"Common subdirectories : ['subdir']",
] ]
self._assert_report(d.report, expected_report) self._assert_report(d.report, expected_report)
@ -136,9 +147,10 @@ class DirCompareTestCase(unittest.TestCase):
d = filecmp.dircmp(left_dir, right_dir) d = filecmp.dircmp(left_dir, right_dir)
self.assertEqual(d.left, left_dir) self.assertEqual(d.left, left_dir)
self.assertEqual(d.right, right_dir) self.assertEqual(d.right, right_dir)
self.assertEqual(d.left_list, ['file']) self._assert_lists(d.left_list, ['file', 'subdir'])
self.assertEqual(d.right_list, ['file', 'file2']) self._assert_lists(d.right_list, ['file', 'file2', 'subdir'])
self.assertEqual(d.common, ['file']) self._assert_lists(d.common, ['file', 'subdir'])
self._assert_lists(d.common_dirs, ['subdir'])
self.assertEqual(d.left_only, []) self.assertEqual(d.left_only, [])
self.assertEqual(d.right_only, ['file2']) self.assertEqual(d.right_only, ['file2'])
self.assertEqual(d.same_files, ['file']) self.assertEqual(d.same_files, ['file'])
@ -147,6 +159,7 @@ class DirCompareTestCase(unittest.TestCase):
"diff {} {}".format(self.dir, self.dir_diff), "diff {} {}".format(self.dir, self.dir_diff),
"Only in {} : ['file2']".format(self.dir_diff), "Only in {} : ['file2']".format(self.dir_diff),
"Identical files : ['file']", "Identical files : ['file']",
"Common subdirectories : ['subdir']",
] ]
self._assert_report(d.report, expected_report) self._assert_report(d.report, expected_report)
@ -159,9 +172,9 @@ class DirCompareTestCase(unittest.TestCase):
d = filecmp.dircmp(left_dir, right_dir) d = filecmp.dircmp(left_dir, right_dir)
self.assertEqual(d.left, left_dir) self.assertEqual(d.left, left_dir)
self.assertEqual(d.right, right_dir) self.assertEqual(d.right, right_dir)
self.assertEqual(d.left_list, ['file', 'file2']) self._assert_lists(d.left_list, ['file', 'file2', 'subdir'])
self.assertEqual(d.right_list, ['file']) self._assert_lists(d.right_list, ['file', 'subdir'])
self.assertEqual(d.common, ['file']) self._assert_lists(d.common, ['file', 'subdir'])
self.assertEqual(d.left_only, ['file2']) self.assertEqual(d.left_only, ['file2'])
self.assertEqual(d.right_only, []) self.assertEqual(d.right_only, [])
self.assertEqual(d.same_files, ['file']) self.assertEqual(d.same_files, ['file'])
@ -170,6 +183,7 @@ class DirCompareTestCase(unittest.TestCase):
"diff {} {}".format(self.dir, self.dir_diff), "diff {} {}".format(self.dir, self.dir_diff),
"Only in {} : ['file2']".format(self.dir), "Only in {} : ['file2']".format(self.dir),
"Identical files : ['file']", "Identical files : ['file']",
"Common subdirectories : ['subdir']",
] ]
self._assert_report(d.report, expected_report) self._assert_report(d.report, expected_report)
@ -183,24 +197,45 @@ class DirCompareTestCase(unittest.TestCase):
"diff {} {}".format(self.dir, self.dir_diff), "diff {} {}".format(self.dir, self.dir_diff),
"Identical files : ['file']", "Identical files : ['file']",
"Differing files : ['file2']", "Differing files : ['file2']",
"Common subdirectories : ['subdir']",
] ]
self._assert_report(d.report, expected_report) self._assert_report(d.report, expected_report)
def test_dircmp_subdirs_type(self):
"""Check that dircmp.subdirs respects subclassing."""
class MyDirCmp(filecmp.dircmp):
pass
d = MyDirCmp(self.dir, self.dir_diff)
sub_dirs = d.subdirs
self.assertEqual(list(sub_dirs.keys()), ['subdir'])
sub_dcmp = sub_dirs['subdir']
self.assertEqual(type(sub_dcmp), MyDirCmp)
def test_report_partial_closure(self): def test_report_partial_closure(self):
left_dir, right_dir = self.dir, self.dir_same left_dir, right_dir = self.dir, self.dir_same
d = filecmp.dircmp(left_dir, right_dir) d = filecmp.dircmp(left_dir, right_dir)
left_subdir = os.path.join(left_dir, 'subdir')
right_subdir = os.path.join(right_dir, 'subdir')
expected_report = [ expected_report = [
"diff {} {}".format(self.dir, self.dir_same), "diff {} {}".format(self.dir, self.dir_same),
"Identical files : ['file']", "Identical files : ['file']",
"Common subdirectories : ['subdir']",
'',
"diff {} {}".format(left_subdir, right_subdir),
] ]
self._assert_report(d.report_partial_closure, expected_report) self._assert_report(d.report_partial_closure, expected_report)
def test_report_full_closure(self): def test_report_full_closure(self):
left_dir, right_dir = self.dir, self.dir_same left_dir, right_dir = self.dir, self.dir_same
d = filecmp.dircmp(left_dir, right_dir) d = filecmp.dircmp(left_dir, right_dir)
left_subdir = os.path.join(left_dir, 'subdir')
right_subdir = os.path.join(right_dir, 'subdir')
expected_report = [ expected_report = [
"diff {} {}".format(self.dir, self.dir_same), "diff {} {}".format(self.dir, self.dir_same),
"Identical files : ['file']", "Identical files : ['file']",
"Common subdirectories : ['subdir']",
'',
"diff {} {}".format(left_subdir, right_subdir),
] ]
self._assert_report(d.report_full_closure, expected_report) self._assert_report(d.report_full_closure, expected_report)

View File

@ -368,6 +368,7 @@ Ryan Coyner
Christopher A. Craig Christopher A. Craig
Jeremy Craven Jeremy Craven
Laura Creighton Laura Creighton
Nick Crews
Tyler Crompton Tyler Crompton
Simon Cross Simon Cross
Felipe Cruz Felipe Cruz

View File

@ -0,0 +1,2 @@
Make :class:`filecmp.dircmp` respect subclassing. Now the
:attr:`filecmp.dircmp.subdirs` behaves as expected when subclassing dircmp.