Apply pyupgrade changes

This commit is contained in:
Andrew Senetar 2022-04-27 20:53:12 -05:00
parent e0061d7bc1
commit 63dd4d4561
Signed by: arsenetar
GPG Key ID: C63300DCE48AB2F1
36 changed files with 89 additions and 92 deletions

View File

@ -61,7 +61,7 @@ def parse_args():
def build_one_help(language): def build_one_help(language):
print("Generating Help in {}".format(language)) print(f"Generating Help in {language}")
current_path = Path(".").absolute() current_path = Path(".").absolute()
changelog_path = current_path.joinpath("help", "changelog") changelog_path = current_path.joinpath("help", "changelog")
tixurl = "https://github.com/arsenetar/dupeguru/issues/{}" tixurl = "https://github.com/arsenetar/dupeguru/issues/{}"
@ -139,7 +139,7 @@ def build_normal():
print("Building localizations") print("Building localizations")
build_localizations() build_localizations()
print("Building Qt stuff") print("Building Qt stuff")
print_and_do("pyrcc5 {0} > {1}".format(Path("qt", "dg.qrc"), Path("qt", "dg_rc.py"))) print_and_do("pyrcc5 {} > {}".format(Path("qt", "dg.qrc"), Path("qt", "dg_rc.py")))
fix_qt_resource_file(Path("qt", "dg_rc.py")) fix_qt_resource_file(Path("qt", "dg_rc.py"))
build_help() build_help()

View File

@ -264,7 +264,7 @@ class DupeGuru(Broadcaster):
try: try:
f._read_all_info(attrnames=self.METADATA_TO_READ) f._read_all_info(attrnames=self.METADATA_TO_READ)
return f return f
except EnvironmentError: except OSError:
return None return None
def _get_export_data(self): def _get_export_data(self):

View File

@ -120,7 +120,7 @@ class Directories:
file.is_ref = state == DirectoryState.REFERENCE file.is_ref = state == DirectoryState.REFERENCE
count += 1 count += 1
yield file yield file
except (EnvironmentError, OSError, fs.InvalidPath): except (OSError, fs.InvalidPath):
pass pass
logging.debug( logging.debug(
"Collected %d files in folder %s", "Collected %d files in folder %s",
@ -134,14 +134,13 @@ class Directories:
j.check_if_cancelled() j.check_if_cancelled()
try: try:
for subfolder in from_folder.subfolders: for subfolder in from_folder.subfolders:
for folder in self._get_folders(subfolder, j): yield from self._get_folders(subfolder, j)
yield folder
state = self.get_state(from_folder.path) state = self.get_state(from_folder.path)
if state != DirectoryState.EXCLUDED: if state != DirectoryState.EXCLUDED:
from_folder.is_ref = state == DirectoryState.REFERENCE from_folder.is_ref = state == DirectoryState.REFERENCE
logging.debug("Yielding Folder %r state: %d", from_folder, state) logging.debug("Yielding Folder %r state: %d", from_folder, state)
yield from_folder yield from_folder
except (EnvironmentError, fs.InvalidPath): except (OSError, fs.InvalidPath):
pass pass
# ---Public # ---Public
@ -173,7 +172,7 @@ class Directories:
subpaths = [p for p in path.glob("*") if p.is_dir()] subpaths = [p for p in path.glob("*") if p.is_dir()]
subpaths.sort(key=lambda x: x.name.lower()) subpaths.sort(key=lambda x: x.name.lower())
return subpaths return subpaths
except EnvironmentError: except OSError:
return [] return []
def get_files(self, fileclasses=None, j=job.nulljob): def get_files(self, fileclasses=None, j=job.nulljob):

View File

@ -166,7 +166,7 @@ def reduce_common_words(word_dict, threshold):
The exception to this removal are the objects where all the words of the object are common. The exception to this removal are the objects where all the words of the object are common.
Because if we remove them, we will miss some duplicates! Because if we remove them, we will miss some duplicates!
""" """
uncommon_words = set(word for word, objects in word_dict.items() if len(objects) < threshold) uncommon_words = {word for word, objects in word_dict.items() if len(objects) < threshold}
for word, objects in list(word_dict.items()): for word, objects in list(word_dict.items()):
if len(objects) < threshold: if len(objects) < threshold:
continue continue
@ -409,7 +409,7 @@ class Group:
You can call this after the duplicate scanning process to free a bit of memory. You can call this after the duplicate scanning process to free a bit of memory.
""" """
discarded = set(m for m in self.matches if not all(obj in self.unordered for obj in [m.first, m.second])) discarded = {m for m in self.matches if not all(obj in self.unordered for obj in [m.first, m.second])}
self.matches -= discarded self.matches -= discarded
self.candidates = defaultdict(set) self.candidates = defaultdict(set)
return discarded return discarded
@ -456,7 +456,7 @@ class Group:
self._matches_for_ref = None self._matches_for_ref = None
if (len(self) > 1) and any(not getattr(item, "is_ref", False) for item in self): if (len(self) > 1) and any(not getattr(item, "is_ref", False) for item in self):
if discard_matches: if discard_matches:
self.matches = set(m for m in self.matches if item not in m) self.matches = {m for m in self.matches if item not in m}
else: else:
self._clear() self._clear()
except ValueError: except ValueError:
@ -529,7 +529,7 @@ def get_groups(matches):
del dupe2group del dupe2group
del matches del matches
# should free enough memory to continue # should free enough memory to continue
logging.warning("Memory Overflow. Groups: {0}".format(len(groups))) logging.warning(f"Memory Overflow. Groups: {len(groups)}")
# Now that we have a group, we have to discard groups' matches and see if there're any "orphan" # Now that we have a group, we have to discard groups' matches and see if there're any "orphan"
# matches, that is, matches that were candidate in a group but that none of their 2 files were # matches, that is, matches that were candidate in a group but that none of their 2 files were
# accepted in the group. With these orphan groups, it's safe to build additional groups # accepted in the group. With these orphan groups, it's safe to build additional groups

View File

@ -197,7 +197,7 @@ class File:
self.path = path self.path = path
def __repr__(self): def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, str(self.path)) return f"<{self.__class__.__name__} {str(self.path)}>"
def __getattribute__(self, attrname): def __getattribute__(self, attrname):
result = object.__getattribute__(self, attrname) result = object.__getattribute__(self, attrname)
@ -317,7 +317,7 @@ class File:
raise AlreadyExistsError(newname, self.path.parent) raise AlreadyExistsError(newname, self.path.parent)
try: try:
self.path.rename(destpath) self.path.rename(destpath)
except EnvironmentError: except OSError:
raise OperationError(self) raise OperationError(self)
if not destpath.exists(): if not destpath.exists():
raise OperationError(self) raise OperationError(self)
@ -421,5 +421,5 @@ def get_files(path, fileclasses=[File]):
if file is not None: if file is not None:
result.append(file) result.append(file)
return result return result
except EnvironmentError: except OSError:
raise InvalidPath(path) raise InvalidPath(path)

View File

@ -13,7 +13,7 @@ def colors_to_string(colors):
[(0,100,255)] --> 0064ff [(0,100,255)] --> 0064ff
[(1,2,3),(4,5,6)] --> 010203040506 [(1,2,3),(4,5,6)] --> 010203040506
""" """
return "".join("%02x%02x%02x" % (r, g, b) for r, g, b in colors) return "".join("{:02x}{:02x}{:02x}".format(r, g, b) for r, g, b in colors)
# This function is an important bottleneck of dupeGuru PE. It has been converted to C. # This function is an important bottleneck of dupeGuru PE. It has been converted to C.

View File

@ -14,7 +14,7 @@ from .cache import string_to_colors, colors_to_string
def wrap_path(path): def wrap_path(path):
return "path:{}".format(path) return f"path:{path}"
def unwrap_path(key): def unwrap_path(key):
@ -22,7 +22,7 @@ def unwrap_path(key):
def wrap_id(path): def wrap_id(path):
return "id:{}".format(path) return f"id:{path}"
def unwrap_id(key): def unwrap_id(key):

View File

@ -87,7 +87,7 @@ def prepare_pictures(pictures, cache_path, with_dimensions, j=job.nulljob):
blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE) blocks = picture.get_blocks(BLOCK_COUNT_PER_SIDE)
cache[picture.unicode_path] = blocks cache[picture.unicode_path] = blocks
prepared.append(picture) prepared.append(picture)
except (IOError, ValueError) as e: except (OSError, ValueError) as e:
logging.warning(str(e)) logging.warning(str(e))
except MemoryError: except MemoryError:
logging.warning( logging.warning(

View File

@ -43,7 +43,7 @@ class Criterion:
@property @property
def display(self): def display(self):
return "{} ({})".format(self.category.NAME, self.display_value) return f"{self.category.NAME} ({self.display_value})"
class ValueListCategory(CriterionCategory): class ValueListCategory(CriterionCategory):

View File

@ -191,7 +191,7 @@ class Results(Markable):
self.__filters.append(filter_str) self.__filters.append(filter_str)
if self.__filtered_dupes is None: if self.__filtered_dupes is None:
self.__filtered_dupes = flatten(g[:] for g in self.groups) self.__filtered_dupes = flatten(g[:] for g in self.groups)
self.__filtered_dupes = set(dupe for dupe in self.__filtered_dupes if filter_re.search(str(dupe.path))) self.__filtered_dupes = {dupe for dupe in self.__filtered_dupes if filter_re.search(str(dupe.path))}
filtered_groups = set() filtered_groups = set()
for dupe in self.__filtered_dupes: for dupe in self.__filtered_dupes:
filtered_groups.add(self.get_group_of_duplicate(dupe)) filtered_groups.add(self.get_group_of_duplicate(dupe))
@ -301,7 +301,7 @@ class Results(Markable):
try: try:
func(dupe) func(dupe)
to_remove.append(dupe) to_remove.append(dupe)
except (EnvironmentError, UnicodeEncodeError) as e: except (OSError, UnicodeEncodeError) as e:
self.problems.append((dupe, str(e))) self.problems.append((dupe, str(e)))
if remove_from_results: if remove_from_results:
self.remove_duplicates(to_remove) self.remove_duplicates(to_remove)
@ -374,8 +374,8 @@ class Results(Markable):
try: try:
do_write(outfile) do_write(outfile)
except IOError as e: except OSError as e:
# If our IOError is because dest is already a directory, we want to handle that. 21 is # If our OSError is because dest is already a directory, we want to handle that. 21 is
# the code we get on OS X and Linux, 13 is what we get on Windows. # the code we get on OS X and Linux, 13 is what we get on Windows.
if e.errno in {21, 13}: if e.errno in {21, 13}:
p = str(outfile) p = str(outfile)

View File

@ -95,7 +95,7 @@ class TestCaseDupeGuru:
# At some point, any() was used in a wrong way that made Scan() wrongly return 1 # At some point, any() was used in a wrong way that made Scan() wrongly return 1
app = TestApp().app app = TestApp().app
f1, f2 = [FakeFile("foo") for _ in range(2)] f1, f2 = (FakeFile("foo") for _ in range(2))
f1.is_ref, f2.is_ref = (False, False) f1.is_ref, f2.is_ref = (False, False)
assert not (bool(f1) and bool(f2)) assert not (bool(f1) and bool(f2))
add_fake_files_to_directories(app.directories, [f1, f2]) add_fake_files_to_directories(app.directories, [f1, f2])

View File

@ -271,9 +271,9 @@ class TestCaseBuildWordDict:
class TestCaseMergeSimilarWords: class TestCaseMergeSimilarWords:
def test_some_similar_words(self): def test_some_similar_words(self):
d = { d = {
"foobar": set([1]), "foobar": {1},
"foobar1": set([2]), "foobar1": {2},
"foobar2": set([3]), "foobar2": {3},
} }
merge_similar_words(d) merge_similar_words(d)
eq_(1, len(d)) eq_(1, len(d))
@ -283,8 +283,8 @@ class TestCaseMergeSimilarWords:
class TestCaseReduceCommonWords: class TestCaseReduceCommonWords:
def test_typical(self): def test_typical(self):
d = { d = {
"foo": set([NamedObject("foo bar", True) for _ in range(50)]), "foo": {NamedObject("foo bar", True) for _ in range(50)},
"bar": set([NamedObject("foo bar", True) for _ in range(49)]), "bar": {NamedObject("foo bar", True) for _ in range(49)},
} }
reduce_common_words(d, 50) reduce_common_words(d, 50)
assert "foo" not in d assert "foo" not in d
@ -293,7 +293,7 @@ class TestCaseReduceCommonWords:
def test_dont_remove_objects_with_only_common_words(self): def test_dont_remove_objects_with_only_common_words(self):
d = { d = {
"common": set([NamedObject("common uncommon", True) for _ in range(50)] + [NamedObject("common", True)]), "common": set([NamedObject("common uncommon", True) for _ in range(50)] + [NamedObject("common", True)]),
"uncommon": set([NamedObject("common uncommon", True)]), "uncommon": {NamedObject("common uncommon", True)},
} }
reduce_common_words(d, 50) reduce_common_words(d, 50)
eq_(1, len(d["common"])) eq_(1, len(d["common"]))
@ -302,7 +302,7 @@ class TestCaseReduceCommonWords:
def test_values_still_are_set_instances(self): def test_values_still_are_set_instances(self):
d = { d = {
"common": set([NamedObject("common uncommon", True) for _ in range(50)] + [NamedObject("common", True)]), "common": set([NamedObject("common uncommon", True) for _ in range(50)] + [NamedObject("common", True)]),
"uncommon": set([NamedObject("common uncommon", True)]), "uncommon": {NamedObject("common uncommon", True)},
} }
reduce_common_words(d, 50) reduce_common_words(d, 50)
assert isinstance(d["common"], set) assert isinstance(d["common"], set)
@ -312,9 +312,9 @@ class TestCaseReduceCommonWords:
# If a word has been removed by the reduce, an object in a subsequent common word that # If a word has been removed by the reduce, an object in a subsequent common word that
# contains the word that has been removed would cause a KeyError. # contains the word that has been removed would cause a KeyError.
d = { d = {
"foo": set([NamedObject("foo bar baz", True) for _ in range(50)]), "foo": {NamedObject("foo bar baz", True) for _ in range(50)},
"bar": set([NamedObject("foo bar baz", True) for _ in range(50)]), "bar": {NamedObject("foo bar baz", True) for _ in range(50)},
"baz": set([NamedObject("foo bar baz", True) for _ in range(49)]), "baz": {NamedObject("foo bar baz", True) for _ in range(49)},
} }
try: try:
reduce_common_words(d, 50) reduce_common_words(d, 50)
@ -328,7 +328,7 @@ class TestCaseReduceCommonWords:
o.words = [["foo", "bar"], ["baz"]] o.words = [["foo", "bar"], ["baz"]]
return o return o
d = {"foo": set([create_it() for _ in range(50)])} d = {"foo": {create_it() for _ in range(50)}}
try: try:
reduce_common_words(d, 50) reduce_common_words(d, 50)
except TypeError: except TypeError:
@ -343,7 +343,7 @@ class TestCaseReduceCommonWords:
d = { d = {
"foo": set([NamedObject("foo bar baz", True) for _ in range(49)] + [only_common]), "foo": set([NamedObject("foo bar baz", True) for _ in range(49)] + [only_common]),
"bar": set([NamedObject("foo bar baz", True) for _ in range(49)] + [only_common]), "bar": set([NamedObject("foo bar baz", True) for _ in range(49)] + [only_common]),
"baz": set([NamedObject("foo bar baz", True) for _ in range(49)]), "baz": {NamedObject("foo bar baz", True) for _ in range(49)},
} }
reduce_common_words(d, 50) reduce_common_words(d, 50)
eq_(1, len(d["foo"])) eq_(1, len(d["foo"]))
@ -884,7 +884,7 @@ class TestCaseGetGroups:
# If, with a (A, B, C, D) set, all match with A, but C and D don't match with B and that the # If, with a (A, B, C, D) set, all match with A, but C and D don't match with B and that the
# (A, B) match is the highest (thus resulting in an (A, B) group), still match C and D # (A, B) match is the highest (thus resulting in an (A, B) group), still match C and D
# in a separate group instead of discarding them. # in a separate group instead of discarding them.
A, B, C, D = [NamedObject() for _ in range(4)] A, B, C, D = (NamedObject() for _ in range(4))
m1 = Match(A, B, 90) # This is the strongest "A" match m1 = Match(A, B, 90) # This is the strongest "A" match
m2 = Match(A, C, 80) # Because C doesn't match with B, it won't be in the group m2 = Match(A, C, 80) # Because C doesn't match with B, it won't be in the group
m3 = Match(A, D, 80) # Same thing for D m3 = Match(A, D, 80) # Same thing for D

View File

@ -289,8 +289,8 @@ class TestCaseListEmptyUnion(TestCaseListEmpty):
compiled = [x for x in self.exclude_list.compiled] compiled = [x for x in self.exclude_list.compiled]
assert regex not in compiled assert regex not in compiled
# Need to escape both to get the same strings after compilation # Need to escape both to get the same strings after compilation
compiled_escaped = set([x.encode("unicode-escape").decode() for x in compiled[0].pattern.split("|")]) compiled_escaped = {x.encode("unicode-escape").decode() for x in compiled[0].pattern.split("|")}
default_escaped = set([x.encode("unicode-escape").decode() for x in default_regexes]) default_escaped = {x.encode("unicode-escape").decode() for x in default_regexes}
assert compiled_escaped == default_escaped assert compiled_escaped == default_escaped
eq_(len(default_regexes), len(compiled[0].pattern.split("|"))) eq_(len(default_regexes), len(compiled[0].pattern.split("|")))
@ -366,8 +366,8 @@ class TestCaseDictEmptyUnion(TestCaseDictEmpty):
compiled = [x for x in self.exclude_list.compiled] compiled = [x for x in self.exclude_list.compiled]
assert regex not in compiled assert regex not in compiled
# Need to escape both to get the same strings after compilation # Need to escape both to get the same strings after compilation
compiled_escaped = set([x.encode("unicode-escape").decode() for x in compiled[0].pattern.split("|")]) compiled_escaped = {x.encode("unicode-escape").decode() for x in compiled[0].pattern.split("|")}
default_escaped = set([x.encode("unicode-escape").decode() for x in default_regexes]) default_escaped = {x.encode("unicode-escape").decode() for x in default_regexes}
assert compiled_escaped == default_escaped assert compiled_escaped == default_escaped
eq_(len(default_regexes), len(compiled[0].pattern.split("|"))) eq_(len(default_regexes), len(compiled[0].pattern.split("|")))

View File

@ -337,7 +337,7 @@ class TestCaseResultsMarkings:
def log_object(o): def log_object(o):
log.append(o) log.append(o)
if o is self.objects[1]: if o is self.objects[1]:
raise EnvironmentError("foobar") raise OSError("foobar")
log = [] log = []
self.results.mark_all() self.results.mark_all()
@ -464,7 +464,7 @@ class TestCaseResultsXML:
eq_(6, len(g1)) eq_(6, len(g1))
eq_(3, len([c for c in g1 if c.tag == "file"])) eq_(3, len([c for c in g1 if c.tag == "file"]))
eq_(3, len([c for c in g1 if c.tag == "match"])) eq_(3, len([c for c in g1 if c.tag == "match"]))
d1, d2, d3 = [c for c in g1 if c.tag == "file"] d1, d2, d3 = (c for c in g1 if c.tag == "file")
eq_(op.join("basepath", "foo bar"), d1.get("path")) eq_(op.join("basepath", "foo bar"), d1.get("path"))
eq_(op.join("basepath", "bar bleh"), d2.get("path")) eq_(op.join("basepath", "bar bleh"), d2.get("path"))
eq_(op.join("basepath", "foo bleh"), d3.get("path")) eq_(op.join("basepath", "foo bleh"), d3.get("path"))
@ -477,7 +477,7 @@ class TestCaseResultsXML:
eq_(3, len(g2)) eq_(3, len(g2))
eq_(2, len([c for c in g2 if c.tag == "file"])) eq_(2, len([c for c in g2 if c.tag == "file"]))
eq_(1, len([c for c in g2 if c.tag == "match"])) eq_(1, len([c for c in g2 if c.tag == "match"]))
d1, d2 = [c for c in g2 if c.tag == "file"] d1, d2 = (c for c in g2 if c.tag == "file")
eq_(op.join("basepath", "ibabtu"), d1.get("path")) eq_(op.join("basepath", "ibabtu"), d1.get("path"))
eq_(op.join("basepath", "ibabtu"), d2.get("path")) eq_(op.join("basepath", "ibabtu"), d2.get("path"))
eq_("n", d1.get("is_ref")) eq_("n", d1.get("is_ref"))

View File

@ -29,7 +29,7 @@ class NamedObject:
self.words = getwords(name) self.words = getwords(name)
def __repr__(self): def __repr__(self):
return "<NamedObject %r %r>" % (self.name, self.path) return "<NamedObject {!r} {!r}>".format(self.name, self.path)
no = NamedObject no = NamedObject
@ -336,7 +336,7 @@ def test_tag_scan(fake_fileexists):
def test_tag_with_album_scan(fake_fileexists): def test_tag_with_album_scan(fake_fileexists):
s = Scanner() s = Scanner()
s.scan_type = ScanType.TAG s.scan_type = ScanType.TAG
s.scanned_tags = set(["artist", "album", "title"]) s.scanned_tags = {"artist", "album", "title"}
o1 = no("foo") o1 = no("foo")
o2 = no("bar") o2 = no("bar")
o3 = no("bleh") o3 = no("bleh")
@ -356,7 +356,7 @@ def test_tag_with_album_scan(fake_fileexists):
def test_that_dash_in_tags_dont_create_new_fields(fake_fileexists): def test_that_dash_in_tags_dont_create_new_fields(fake_fileexists):
s = Scanner() s = Scanner()
s.scan_type = ScanType.TAG s.scan_type = ScanType.TAG
s.scanned_tags = set(["artist", "album", "title"]) s.scanned_tags = {"artist", "album", "title"}
s.min_match_percentage = 50 s.min_match_percentage = 50
o1 = no("foo") o1 = no("foo")
o2 = no("bar") o2 = no("bar")
@ -373,7 +373,7 @@ def test_that_dash_in_tags_dont_create_new_fields(fake_fileexists):
def test_tag_scan_with_different_scanned(fake_fileexists): def test_tag_scan_with_different_scanned(fake_fileexists):
s = Scanner() s = Scanner()
s.scan_type = ScanType.TAG s.scan_type = ScanType.TAG
s.scanned_tags = set(["track", "year"]) s.scanned_tags = {"track", "year"}
o1 = no("foo") o1 = no("foo")
o2 = no("bar") o2 = no("bar")
o1.artist = "The White Stripes" o1.artist = "The White Stripes"
@ -391,7 +391,7 @@ def test_tag_scan_with_different_scanned(fake_fileexists):
def test_tag_scan_only_scans_existing_tags(fake_fileexists): def test_tag_scan_only_scans_existing_tags(fake_fileexists):
s = Scanner() s = Scanner()
s.scan_type = ScanType.TAG s.scan_type = ScanType.TAG
s.scanned_tags = set(["artist", "foo"]) s.scanned_tags = {"artist", "foo"}
o1 = no("foo") o1 = no("foo")
o2 = no("bar") o2 = no("bar")
o1.artist = "The White Stripes" o1.artist = "The White Stripes"
@ -405,7 +405,7 @@ def test_tag_scan_only_scans_existing_tags(fake_fileexists):
def test_tag_scan_converts_to_str(fake_fileexists): def test_tag_scan_converts_to_str(fake_fileexists):
s = Scanner() s = Scanner()
s.scan_type = ScanType.TAG s.scan_type = ScanType.TAG
s.scanned_tags = set(["track"]) s.scanned_tags = {"track"}
o1 = no("foo") o1 = no("foo")
o2 = no("bar") o2 = no("bar")
o1.track = 42 o1.track = 42
@ -420,7 +420,7 @@ def test_tag_scan_converts_to_str(fake_fileexists):
def test_tag_scan_non_ascii(fake_fileexists): def test_tag_scan_non_ascii(fake_fileexists):
s = Scanner() s = Scanner()
s.scan_type = ScanType.TAG s.scan_type = ScanType.TAG
s.scanned_tags = set(["title"]) s.scanned_tags = {"title"}
o1 = no("foo") o1 = no("foo")
o2 = no("bar") o2 = no("bar")
o1.title = "foobar\u00e9" o1.title = "foobar\u00e9"

View File

@ -40,7 +40,7 @@ def _perform(src, dst, action, actionname):
shutil.rmtree(dst) shutil.rmtree(dst)
else: else:
os.remove(dst) os.remove(dst)
print("%s %s --> %s" % (actionname, src, dst)) print("{} {} --> {}".format(actionname, src, dst))
action(src, dst) action(src, dst)
@ -95,12 +95,12 @@ def filereplace(filename, outfilename=None, **kwargs):
"""Reads `filename`, replaces all {variables} in kwargs, and writes the result to `outfilename`.""" """Reads `filename`, replaces all {variables} in kwargs, and writes the result to `outfilename`."""
if outfilename is None: if outfilename is None:
outfilename = filename outfilename = filename
fp = open(filename, "rt", encoding="utf-8") fp = open(filename, encoding="utf-8")
contents = fp.read() contents = fp.read()
fp.close() fp.close()
# We can't use str.format() because in some files, there might be {} characters that mess with it. # We can't use str.format() because in some files, there might be {} characters that mess with it.
for key, item in kwargs.items(): for key, item in kwargs.items():
contents = contents.replace("{{{}}}".format(key), item) contents = contents.replace(f"{{{key}}}", item)
fp = open(outfilename, "wt", encoding="utf-8") fp = open(outfilename, "wt", encoding="utf-8")
fp.write(contents) fp.write(contents)
fp.close() fp.close()
@ -143,8 +143,8 @@ def package_cocoa_app_in_dmg(app_path, destfolder, args):
# phase because running the app before packaging can modify it and we want to be sure to have # phase because running the app before packaging can modify it and we want to be sure to have
# a valid signature. # a valid signature.
if args.sign_identity: if args.sign_identity:
sign_identity = "Developer ID Application: {}".format(args.sign_identity) sign_identity = f"Developer ID Application: {args.sign_identity}"
result = print_and_do('codesign --force --deep --sign "{}" "{}"'.format(sign_identity, app_path)) result = print_and_do(f'codesign --force --deep --sign "{sign_identity}" "{app_path}"')
if result != 0: if result != 0:
print("ERROR: Signing failed. Aborting packaging.") print("ERROR: Signing failed. Aborting packaging.")
return return
@ -164,15 +164,15 @@ def build_dmg(app_path, destfolder):
workpath = tempfile.mkdtemp() workpath = tempfile.mkdtemp()
dmgpath = op.join(workpath, plist["CFBundleName"]) dmgpath = op.join(workpath, plist["CFBundleName"])
os.mkdir(dmgpath) os.mkdir(dmgpath)
print_and_do('cp -R "%s" "%s"' % (app_path, dmgpath)) print_and_do('cp -R "{}" "{}"'.format(app_path, dmgpath))
print_and_do('ln -s /Applications "%s"' % op.join(dmgpath, "Applications")) print_and_do('ln -s /Applications "%s"' % op.join(dmgpath, "Applications"))
dmgname = "%s_osx_%s.dmg" % ( dmgname = "{}_osx_{}.dmg".format(
plist["CFBundleName"].lower().replace(" ", "_"), plist["CFBundleName"].lower().replace(" ", "_"),
plist["CFBundleVersion"].replace(".", "_"), plist["CFBundleVersion"].replace(".", "_"),
) )
print("Building %s" % dmgname) print("Building %s" % dmgname)
# UDBZ = bzip compression. UDZO (zip compression) was used before, but it compresses much less. # UDBZ = bzip compression. UDZO (zip compression) was used before, but it compresses much less.
print_and_do('hdiutil create "%s" -format UDBZ -nocrossdev -srcdir "%s"' % (op.join(destfolder, dmgname), dmgpath)) print_and_do('hdiutil create "{}" -format UDBZ -nocrossdev -srcdir "{}"'.format(op.join(destfolder, dmgname), dmgpath))
print("Build Complete") print("Build Complete")
@ -216,7 +216,7 @@ def copy_packages(packages_names, dest, create_links=False, extra_ignores=None):
os.unlink(dest_path) os.unlink(dest_path)
else: else:
shutil.rmtree(dest_path) shutil.rmtree(dest_path)
print("Copying package at {0} to {1}".format(source_path, dest_path)) print(f"Copying package at {source_path} to {dest_path}")
if create_links: if create_links:
os.symlink(op.abspath(source_path), dest_path) os.symlink(op.abspath(source_path), dest_path)
else: else:
@ -297,7 +297,7 @@ def read_changelog_file(filename):
return return
yield version, date, description yield version, date, description
with open(filename, "rt", encoding="utf-8") as fp: with open(filename, encoding="utf-8") as fp:
contents = fp.read() contents = fp.read()
splitted = re_changelog_header.split(contents)[1:] # the first item is empty splitted = re_changelog_header.split(contents)[1:] # the first item is empty
result = [] result = []

View File

@ -18,7 +18,7 @@ def get_parser():
def main(): def main():
args = get_parser().parse_args() args = get_parser().parse_args()
print("Building {}...".format(args.name[0])) print(f"Building {args.name[0]}...")
ext = Extension(args.name[0], args.source_files) ext = Extension(args.name[0], args.source_files)
setup( setup(
script_args=["build_ext", "--inplace"], script_args=["build_ext", "--inplace"],

View File

@ -73,7 +73,7 @@ def smart_copy(source_path, dest_path):
"""Copies ``source_path`` to ``dest_path``, recursively and with conflict resolution.""" """Copies ``source_path`` to ``dest_path``, recursively and with conflict resolution."""
try: try:
_smart_move_or_copy(shutil.copy, source_path, dest_path) _smart_move_or_copy(shutil.copy, source_path, dest_path)
except IOError as e: except OSError as e:
if e.errno in { if e.errno in {
21, 21,
13, 13,

View File

@ -216,7 +216,7 @@ class Columns(GUIObject):
self.view.restore_columns() self.view.restore_columns()
return return
for col in self.column_list: for col in self.column_list:
pref_name = "{}.Columns.{}".format(self.savename, col.name) pref_name = f"{self.savename}.Columns.{col.name}"
coldata = self.prefaccess.get_default(pref_name, fallback_value={}) coldata = self.prefaccess.get_default(pref_name, fallback_value={})
if "index" in coldata: if "index" in coldata:
col.ordered_index = coldata["index"] col.ordered_index = coldata["index"]
@ -231,7 +231,7 @@ class Columns(GUIObject):
if not (self.prefaccess and self.savename and self.coldata): if not (self.prefaccess and self.savename and self.coldata):
return return
for col in self.column_list: for col in self.column_list:
pref_name = "{}.Columns.{}".format(self.savename, col.name) pref_name = f"{self.savename}.Columns.{col.name}"
coldata = {"index": col.ordered_index, "width": col.width} coldata = {"index": col.ordered_index, "width": col.width}
if col.optional: if col.optional:
coldata["visible"] = col.visible coldata["visible"] = col.visible

View File

@ -451,7 +451,7 @@ class Row:
""" """
def __init__(self, table): def __init__(self, table):
super(Row, self).__init__() super().__init__()
self.table = table self.table = table
def _edit(self): def _edit(self):

View File

@ -77,8 +77,7 @@ class Node(MutableSequence):
if include_self and predicate(self): if include_self and predicate(self):
yield self yield self
for child in self: for child in self:
for found in child.findall(predicate, include_self=True): yield from child.findall(predicate, include_self=True)
yield found
def get_node(self, index_path): def get_node(self, index_path):
"""Returns the node at ``index_path``. """Returns the node at ``index_path``.

View File

@ -47,7 +47,7 @@ def log_io_error(func):
def wrapper(path, *args, **kwargs): def wrapper(path, *args, **kwargs):
try: try:
return func(path, *args, **kwargs) return func(path, *args, **kwargs)
except (IOError, OSError) as e: except OSError as e:
msg = 'Error "{0}" during operation "{1}" on "{2}": "{3}"' msg = 'Error "{0}" during operation "{1}" on "{2}": "{3}"'
classname = e.__class__.__name__ classname = e.__class__.__name__
funcname = func.__name__ funcname = func.__name__

View File

@ -374,7 +374,7 @@ def main(source_files, outpath, keywords=None):
fp = open(options.excludefilename, encoding="utf-8") fp = open(options.excludefilename, encoding="utf-8")
options.toexclude = fp.readlines() options.toexclude = fp.readlines()
fp.close() fp.close()
except IOError: except OSError:
print( print(
"Can't read --exclude-file: %s" % options.excludefilename, "Can't read --exclude-file: %s" % options.excludefilename,
file=sys.stderr, file=sys.stderr,

View File

@ -24,7 +24,7 @@ def tixgen(tixurl):
""" """
urlpattern = tixurl.format("\\1") # will be replaced buy the content of the first group in re urlpattern = tixurl.format("\\1") # will be replaced buy the content of the first group in re
R = re.compile(r"#(\d+)") R = re.compile(r"#(\d+)")
repl = "`#\\1 <{}>`__".format(urlpattern) repl = f"`#\\1 <{urlpattern}>`__"
return lambda text: R.sub(repl, text) return lambda text: R.sub(repl, text)

View File

@ -113,7 +113,7 @@ def test_repeater_with_repeated_notifications():
# If REPEATED_NOTIFICATIONS is not empty, only notifs in this set are repeated (but they're # If REPEATED_NOTIFICATIONS is not empty, only notifs in this set are repeated (but they're
# still dispatched locally). # still dispatched locally).
class MyRepeater(HelloRepeater): class MyRepeater(HelloRepeater):
REPEATED_NOTIFICATIONS = set(["hello"]) REPEATED_NOTIFICATIONS = {"hello"}
def __init__(self, broadcaster): def __init__(self, broadcaster):
HelloRepeater.__init__(self, broadcaster) HelloRepeater.__init__(self, broadcaster)

View File

@ -98,7 +98,7 @@ def test_selection_override():
def test_findall(): def test_findall():
t = tree_with_some_nodes() t = tree_with_some_nodes()
r = t.findall(lambda n: n.name.startswith("sub")) r = t.findall(lambda n: n.name.startswith("sub"))
eq_(set(r), set([t[0][0], t[0][1]])) eq_(set(r), {t[0][0], t[0][1]})
def test_findall_dont_include_self(): def test_findall_dont_include_self():
@ -106,7 +106,7 @@ def test_findall_dont_include_self():
t = tree_with_some_nodes() t = tree_with_some_nodes()
del t._name # so that if the predicate is called on `t`, we crash del t._name # so that if the predicate is called on `t`, we crash
r = t.findall(lambda n: not n.name.startswith("sub"), include_self=False) # no crash r = t.findall(lambda n: not n.name.startswith("sub"), include_self=False) # no crash
eq_(set(r), set([t[0], t[1], t[2]])) eq_(set(r), {t[0], t[1], t[2]})
def test_find_dont_include_self(): def test_find_dont_include_self():

View File

@ -14,7 +14,7 @@ import py.path
def eq_(a, b, msg=None): def eq_(a, b, msg=None):
__tracebackhide__ = True __tracebackhide__ = True
assert a == b, msg or "%r != %r" % (a, b) assert a == b, msg or "{!r} != {!r}".format(a, b)
def eq_sorted(a, b, msg=None): def eq_sorted(a, b, msg=None):
@ -97,17 +97,17 @@ class CallLogger:
__tracebackhide__ = True __tracebackhide__ = True
if expected is not None: if expected is not None:
not_called = set(expected) - set(self.calls) not_called = set(expected) - set(self.calls)
assert not not_called, "These calls haven't been made: {0}".format(not_called) assert not not_called, f"These calls haven't been made: {not_called}"
if verify_order: if verify_order:
max_index = 0 max_index = 0
for call in expected: for call in expected:
index = self.calls.index(call) index = self.calls.index(call)
if index < max_index: if index < max_index:
raise AssertionError("The call {0} hasn't been made in the correct order".format(call)) raise AssertionError(f"The call {call} hasn't been made in the correct order")
max_index = index max_index = index
if not_expected is not None: if not_expected is not None:
called = set(not_expected) & set(self.calls) called = set(not_expected) & set(self.calls)
assert not called, "These calls shouldn't have been made: {0}".format(called) assert not called, f"These calls shouldn't have been made: {called}"
self.clear_calls() self.clear_calls()
@ -133,7 +133,7 @@ class TestApp:
parent = self.default_parent parent = self.default_parent
if holder is None: if holder is None:
holder = self holder = self
setattr(holder, "{0}_gui".format(name), view) setattr(holder, f"{name}_gui", view)
gui = class_(parent) gui = class_(parent)
gui.view = view gui.view = view
setattr(holder, name, gui) setattr(holder, name, gui)

View File

@ -112,7 +112,7 @@ def install_gettext_trans(base_folder, lang):
return lambda s: s return lambda s: s
try: try:
return gettext.translation(domain, localedir=base_folder, languages=[lang]).gettext return gettext.translation(domain, localedir=base_folder, languages=[lang]).gettext
except IOError: except OSError:
return lambda s: s return lambda s: s
default_gettext = gettext_trget("core") default_gettext = gettext_trget("core")

View File

@ -270,7 +270,7 @@ def format_size(size, decimal=0, forcepower=-1, showdesc=True):
_valid_xml_range = "\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD" _valid_xml_range = "\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD"
if sys.maxunicode > 0x10000: if sys.maxunicode > 0x10000:
_valid_xml_range += "%s-%s" % (chr(0x10000), chr(min(sys.maxunicode, 0x10FFFF))) _valid_xml_range += "{}-{}".format(chr(0x10000), chr(min(sys.maxunicode, 0x10FFFF)))
RE_INVALID_XML_SUB = re.compile("[^%s]" % _valid_xml_range, re.U).sub RE_INVALID_XML_SUB = re.compile("[^%s]" % _valid_xml_range, re.U).sub
@ -328,11 +328,11 @@ def modified_after(first_path: Path, second_path: Path):
""" """
try: try:
first_mtime = first_path.stat().st_mtime first_mtime = first_path.stat().st_mtime
except (EnvironmentError, AttributeError): except (OSError, AttributeError):
return False return False
try: try:
second_mtime = second_path.stat().st_mtime second_mtime = second_path.stat().st_mtime
except (EnvironmentError, AttributeError): except (OSError, AttributeError):
return True return True
return first_mtime > second_mtime return first_mtime > second_mtime

View File

@ -442,6 +442,6 @@ class DupeGuru(QObject):
def select_dest_file(self, prompt, extension): def select_dest_file(self, prompt, extension):
files = tr("{} file (*.{})").format(extension.upper(), extension) files = tr("{} file (*.{})").format(extension.upper(), extension)
destination, chosen_filter = QFileDialog.getSaveFileName(self.resultWindow, prompt, "", files) destination, chosen_filter = QFileDialog.getSaveFileName(self.resultWindow, prompt, "", files)
if not destination.endswith(".{}".format(extension)): if not destination.endswith(f".{extension}"):
destination = "{}.{}".format(destination, extension) destination = f"{destination}.{extension}"
return destination return destination

View File

@ -347,7 +347,7 @@ class DirectoriesDialog(QMainWindow):
destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files) destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files)
if destination: if destination:
if not destination.endswith(".dupegurudirs"): if not destination.endswith(".dupegurudirs"):
destination = "{}.dupegurudirs".format(destination) destination = f"{destination}.dupegurudirs"
self.app.model.save_directories_as(destination) self.app.model.save_directories_as(destination)
def scanButtonClicked(self): def scanButtonClicked(self):

View File

@ -22,7 +22,7 @@ class File(PhotoBase):
return (size.width(), size.height()) return (size.width(), size.height())
else: else:
return (0, 0) return (0, 0)
except EnvironmentError: except OSError:
logging.warning("Could not read image '%s'", str(self.path)) logging.warning("Could not read image '%s'", str(self.path))
return (0, 0) return (0, 0)

View File

@ -470,7 +470,7 @@ class ResultWindow(QMainWindow):
destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files) destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files)
if destination: if destination:
if not destination.endswith(".dupeguru"): if not destination.endswith(".dupeguru"):
destination = "{}.dupeguru".format(destination) destination = f"{destination}.dupeguru"
self.app.model.save_as(destination) self.app.model.save_as(destination)
self.app.recentResults.insertItem(destination) self.app.recentResults.insertItem(destination)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras # Created By: Virgil Dupras
# Created On: 2009-10-16 # Created On: 2009-10-16
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net) # Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

View File

@ -38,7 +38,7 @@ class ClearableEdit(QLineEdit):
self._clearButton = LineEditButton(self) self._clearButton = LineEditButton(self)
frame_width = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) frame_width = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
padding_right = self._clearButton.sizeHint().width() + frame_width + 1 padding_right = self._clearButton.sizeHint().width() + frame_width + 1
stylesheet = "QLineEdit {{ padding-right:{0}px; }}".format(padding_right) stylesheet = f"QLineEdit {{ padding-right:{padding_right}px; }}"
self.setStyleSheet(stylesheet) self.setStyleSheet(stylesheet)
self._updateClearButton() self._updateClearButton()

2
run.py
View File

@ -71,7 +71,7 @@ def main():
# has been installed # has been installed
from qt.app import DupeGuru from qt.app import DupeGuru
app.setWindowIcon(QIcon(QPixmap(":/{0}".format(DupeGuru.LOGO_NAME)))) app.setWindowIcon(QIcon(QPixmap(f":/{DupeGuru.LOGO_NAME}")))
global dgapp global dgapp
dgapp = DupeGuru() dgapp = DupeGuru()
install_excepthook("https://github.com/hsoft/dupeguru/issues") install_excepthook("https://github.com/hsoft/dupeguru/issues")