· 5 years ago · Jul 20, 2020, 08:54 PM
1diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py
2index b13f2638..35d8827e 100755
3--- a/beets/dbcore/db.py
4+++ b/beets/dbcore/db.py
5@@ -30,6 +30,7 @@ from beets.util import py3_path
6 from beets.dbcore import types
7 from .query import MatchQuery, NullSort, TrueQuery
8 import six
9+
10 if six.PY2:
11 from collections import Mapping
12 else:
13@@ -80,10 +81,10 @@ class FormattedMapping(Mapping):
14 def _get_formatted(self, model, key):
15 value = model._type(key).format(model.get(key))
16 if isinstance(value, bytes):
17- value = value.decode('utf-8', 'ignore')
18+ value = value.decode("utf-8", "ignore")
19
20 if self.for_path:
21- sep_repl = beets.config['path_sep_replace'].as_str()
22+ sep_repl = beets.config["path_sep_replace"].as_str()
23 for sep in (os.path.sep, os.path.altsep):
24 if sep:
25 value = value.replace(sep, sep_repl)
26@@ -187,6 +188,7 @@ class LazyConvertDict(object):
27
28 # Abstract base for model classes.
29
30+
31 class Model(object):
32 """An abstract object representing an object in the database. Model
33 objects act like dictionaries (i.e., they allow subscript access like
34@@ -296,9 +298,9 @@ class Model(object):
35 return obj
36
37 def __repr__(self):
38- return '{0}({1})'.format(
39+ return "{0}({1})".format(
40 type(self).__name__,
41- ', '.join('{0}={1!r}'.format(k, v) for k, v in dict(self).items()),
42+ ", ".join("{0}={1!r}".format(k, v) for k, v in dict(self).items()),
43 )
44
45 def clear_dirty(self):
46@@ -313,11 +315,9 @@ class Model(object):
47 exception is raised otherwise.
48 """
49 if not self._db:
50- raise ValueError(
51- u'{0} has no database'.format(type(self).__name__)
52- )
53+ raise ValueError(u"{0} has no database".format(type(self).__name__))
54 if need_id and not self.id:
55- raise ValueError(u'{0} has no id'.format(type(self).__name__))
56+ raise ValueError(u"{0} has no id".format(type(self).__name__))
57
58 def copy(self):
59 """Create a copy of the model object.
60@@ -398,9 +398,9 @@ class Model(object):
61 elif key in self._fields: # Fixed
62 setattr(self, key, self._type(key).null)
63 elif key in self._getters(): # Computed.
64- raise KeyError(u'computed field {0} cannot be deleted'.format(key))
65+ raise KeyError(u"computed field {0} cannot be deleted".format(key))
66 else:
67- raise KeyError(u'no such field {0}'.format(key))
68+ raise KeyError(u"no such field {0}".format(key))
69
70 def keys(self, computed=False):
71 """Get a list of available field names for this object. The
72@@ -458,22 +458,22 @@ class Model(object):
73 # Convenient attribute access.
74
75 def __getattr__(self, key):
76- if key.startswith('_'):
77- raise AttributeError(u'model has no attribute {0!r}'.format(key))
78+ if key.startswith("_"):
79+ raise AttributeError(u"model has no attribute {0!r}".format(key))
80 else:
81 try:
82 return self[key]
83 except KeyError:
84- raise AttributeError(u'no such field {0!r}'.format(key))
85+ raise AttributeError(u"no such field {0!r}".format(key))
86
87 def __setattr__(self, key, value):
88- if key.startswith('_'):
89+ if key.startswith("_"):
90 super(Model, self).__setattr__(key, value)
91 else:
92 self[key] = value
93
94 def __delattr__(self, key):
95- if key.startswith('_'):
96+ if key.startswith("_"):
97 super(Model, self).__delattr__(key)
98 else:
99 del self[key]
100@@ -493,19 +493,17 @@ class Model(object):
101 assignments = []
102 subvars = []
103 for key in fields:
104- if key != 'id' and key in self._dirty:
105+ if key != "id" and key in self._dirty:
106 self._dirty.remove(key)
107- assignments.append(key + '=?')
108+ assignments.append(key + "=?")
109 value = self._type(key).to_sql(self[key])
110 subvars.append(value)
111- assignments = ','.join(assignments)
112+ assignments = ",".join(assignments)
113
114 with self._db.transaction() as tx:
115 # Main table update.
116 if assignments:
117- query = 'UPDATE {0} SET {1} WHERE id=?'.format(
118- self._table, assignments
119- )
120+ query = "UPDATE {0} SET {1} WHERE id=?".format(self._table, assignments)
121 subvars.append(self.id)
122 tx.mutate(query, subvars)
123
124@@ -514,18 +512,18 @@ class Model(object):
125 if key in self._dirty:
126 self._dirty.remove(key)
127 tx.mutate(
128- 'INSERT INTO {0} '
129- '(entity_id, key, value) '
130- 'VALUES (?, ?, ?);'.format(self._flex_table),
131+ "INSERT INTO {0} "
132+ "(entity_id, key, value) "
133+ "VALUES (?, ?, ?);".format(self._flex_table),
134 (self.id, key, value),
135 )
136
137 # Deleted flexible attributes.
138 for key in self._dirty:
139 tx.mutate(
140- 'DELETE FROM {0} '
141- 'WHERE entity_id=? AND key=?'.format(self._flex_table),
142- (self.id, key)
143+ "DELETE FROM {0} "
144+ "WHERE entity_id=? AND key=?".format(self._flex_table),
145+ (self.id, key),
146 )
147
148 self.clear_dirty()
149@@ -546,13 +544,9 @@ class Model(object):
150 """
151 self._check_db()
152 with self._db.transaction() as tx:
153+ tx.mutate("DELETE FROM {0} WHERE id=?".format(self._table), (self.id,))
154 tx.mutate(
155- 'DELETE FROM {0} WHERE id=?'.format(self._table),
156- (self.id,)
157- )
158- tx.mutate(
159- 'DELETE FROM {0} WHERE entity_id=?'.format(self._flex_table),
160- (self.id,)
161+ "DELETE FROM {0} WHERE entity_id=?".format(self._flex_table), (self.id,)
162 )
163
164 def add(self, db=None):
165@@ -568,9 +562,7 @@ class Model(object):
166 self._check_db(False)
167
168 with self._db.transaction() as tx:
169- new_id = tx.mutate(
170- 'INSERT INTO {0} DEFAULT VALUES'.format(self._table)
171- )
172+ new_id = tx.mutate("INSERT INTO {0} DEFAULT VALUES".format(self._table))
173 self.id = new_id
174 self.added = time.time()
175
176@@ -598,8 +590,7 @@ class Model(object):
177 # Perform substitution.
178 if isinstance(template, six.string_types):
179 template = functemplate.template(template)
180- return template.substitute(self.formatted(for_path),
181- self._template_funcs())
182+ return template.substitute(self.formatted(for_path), self._template_funcs())
183
184 # Parsing.
185
186@@ -620,12 +611,13 @@ class Model(object):
187
188 # Database controller and supporting interfaces.
189
190+
191 class Results(object):
192 """An item query result set. Iterating over the collection lazily
193 constructs LibModel objects that reflect database rows.
194 """
195- def __init__(self, model_class, rows, db, flex_rows,
196- query=None, sort=None):
197+
198+ def __init__(self, model_class, rows, db, flex_rows, query=None, sort=None):
199 """Create a result set that will construct objects of type
200 `model_class`.
201
202@@ -683,7 +675,7 @@ class Results(object):
203 else:
204 while self._rows:
205 row = self._rows.pop(0)
206- obj = self._make_model(row, flex_attrs.get(row['id'], {}))
207+ obj = self._make_model(row, flex_attrs.get(row["id"], {}))
208 # If there is a slow-query predicate, ensurer that the
209 # object passes it.
210 if not self.query or self.query.match(obj):
211@@ -710,10 +702,10 @@ class Results(object):
212 """
213 flex_values = dict()
214 for row in self.flex_rows:
215- if row['entity_id'] not in flex_values:
216- flex_values[row['entity_id']] = dict()
217+ if row["entity_id"] not in flex_values:
218+ flex_values[row["entity_id"]] = dict()
219
220- flex_values[row['entity_id']][row['key']] = row['value']
221+ flex_values[row["entity_id"]][row["key"]] = row["value"]
222
223 return flex_values
224
225@@ -721,8 +713,7 @@ class Results(object):
226 """ Create a Model object for the given row
227 """
228 cols = dict(row)
229- values = dict((k, v) for (k, v) in cols.items()
230- if not k[:4] == 'flex')
231+ values = dict((k, v) for (k, v) in cols.items() if not k[:4] == "flex")
232
233 # Construct the Python object
234 obj = self.model_class._awaken(self.db, values, flex_values)
235@@ -771,7 +762,7 @@ class Results(object):
236 next(it)
237 return next(it)
238 except StopIteration:
239- raise IndexError(u'result index {0} out of range'.format(n))
240+ raise IndexError(u"result index {0} out of range".format(n))
241
242 def get(self):
243 """Return the first matching object, or None if no objects
244@@ -788,6 +779,7 @@ class Transaction(object):
245 """A context manager for safe, concurrent access to the database.
246 All SQL commands should be executed through a transaction.
247 """
248+
249 def __init__(self, db):
250 self.db = db
251
252@@ -835,8 +827,10 @@ class Transaction(object):
253 # In two specific cases, SQLite reports an error while accessing
254 # the underlying database file. We surface these exceptions as
255 # DBAccessError so the application can abort.
256- if e.args[0] in ("attempt to write a readonly database",
257- "unable to open database file"):
258+ if e.args[0] in (
259+ "attempt to write a readonly database",
260+ "unable to open database file",
261+ ):
262 raise DBAccessError(e.args[0])
263 else:
264 raise
265@@ -855,7 +849,7 @@ class Database(object):
266 """The Model subclasses representing tables in this database.
267 """
268
269- supports_extensions = hasattr(sqlite3.Connection, 'enable_load_extension')
270+ supports_extensions = hasattr(sqlite3.Connection, "enable_load_extension")
271 """Whether or not the current version of SQLite supports extensions"""
272
273 def __init__(self, path, timeout=5.0):
274@@ -910,9 +904,7 @@ class Database(object):
275 # Make a new connection. The `sqlite3` module can't use
276 # bytestring paths here on Python 3, so we need to
277 # provide a `str` using `py3_path`.
278- conn = sqlite3.connect(
279- py3_path(self.path), timeout=self.timeout
280- )
281+ conn = sqlite3.connect(py3_path(self.path), timeout=self.timeout)
282
283 if self.supports_extensions:
284 conn.enable_load_extension(True)
285@@ -952,8 +944,7 @@ class Database(object):
286 def load_extension(self, path):
287 """Load an SQLite extension into all open connections."""
288 if not self.supports_extensions:
289- raise ValueError(
290- 'this sqlite3 installation does not support extensions')
291+ raise ValueError("this sqlite3 installation does not support extensions")
292
293 self._extensions.append(path)
294
295@@ -969,7 +960,7 @@ class Database(object):
296 """
297 # Get current schema.
298 with self.transaction() as tx:
299- rows = tx.query('PRAGMA table_info(%s)' % table)
300+ rows = tx.query("PRAGMA table_info(%s)" % table)
301 current_fields = set([row[1] for row in rows])
302
303 field_names = set(fields.keys())
304@@ -981,17 +972,16 @@ class Database(object):
305 # No table exists.
306 columns = []
307 for name, typ in fields.items():
308- columns.append('{0} {1}'.format(name, typ.sql))
309- setup_sql = 'CREATE TABLE {0} ({1});\n'.format(table,
310- ', '.join(columns))
311+ columns.append("{0} {1}".format(name, typ.sql))
312+ setup_sql = "CREATE TABLE {0} ({1});\n".format(table, ", ".join(columns))
313
314 else:
315 # Table exists does not match the field set.
316- setup_sql = ''
317+ setup_sql = ""
318 for name, typ in fields.items():
319 if name in current_fields:
320 continue
321- setup_sql += 'ALTER TABLE {0} ADD COLUMN {1} {2};\n'.format(
322+ setup_sql += "ALTER TABLE {0} ADD COLUMN {1} {2};\n".format(
323 table, name, typ.sql
324 )
325
326@@ -1003,7 +993,8 @@ class Database(object):
327 for the given entity (if they don't exist).
328 """
329 with self.transaction() as tx:
330- tx.script("""
331+ tx.script(
332+ """
333 CREATE TABLE IF NOT EXISTS {0} (
334 id INTEGER PRIMARY KEY,
335 entity_id INTEGER,
336@@ -1012,7 +1003,10 @@ class Database(object):
337 UNIQUE(entity_id, key) ON CONFLICT REPLACE);
338 CREATE INDEX IF NOT EXISTS {0}_by_entity
339 ON {0} (entity_id);
340- """.format(flex_table))
341+ """.format(
342+ flex_table
343+ )
344+ )
345
346 # Querying.
347
348@@ -1029,21 +1023,18 @@ class Database(object):
349
350 sql = ("SELECT * FROM {0} WHERE {1} {2}").format(
351 model_cls._table,
352- where or '1',
353- "ORDER BY {0}".format(order_by) if order_by else '',
354+ where or "1",
355+ "ORDER BY {0}".format(order_by) if order_by else "",
356 )
357
358 # Fetch flexible attributes for items matching the main query.
359 # Doing the per-item filtering in python is faster than issuing
360 # one query per item to sqlite.
361- flex_sql = ("""
362+ flex_sql = """
363 SELECT * FROM {0} WHERE entity_id IN
364 (SELECT id FROM {1} WHERE {2});
365 """.format(
366- model_cls._flex_table,
367- model_cls._table,
368- where or '1',
369- )
370+ model_cls._flex_table, model_cls._table, where or "1",
371 )
372
373 with self.transaction() as tx:
374@@ -1051,7 +1042,10 @@ class Database(object):
375 flex_rows = tx.query(flex_sql, subvals)
376
377 return Results(
378- model_cls, rows, self, flex_rows,
379+ model_cls,
380+ rows,
381+ self,
382+ flex_rows,
383 None if where else query, # Slow query component.
384 sort if sort.is_slow() else None, # Slow sort component.
385 )
386@@ -1060,4 +1054,4 @@ class Database(object):
387 """Get a Model object by its id or None if the id does not
388 exist.
389 """
390- return self._fetch(model_cls, MatchQuery('id', id)).get()
391+ return self._fetch(model_cls, MatchQuery("id", id)).get()
392diff --git a/beets/util/__init__.py b/beets/util/__init__.py
393index bb84aedc..1ed6ab62 100644
394--- a/beets/util/__init__.py
395+++ b/beets/util/__init__.py
396@@ -37,7 +37,7 @@ from enum import Enum
397
398
399 MAX_FILENAME_LENGTH = 200
400-WINDOWS_MAGIC_PREFIX = u'\\\\?\\'
401+WINDOWS_MAGIC_PREFIX = u"\\\\?\\"
402 SNI_SUPPORTED = sys.version_info >= (2, 7, 9)
403
404
405@@ -54,7 +54,8 @@ class HumanReadableException(Exception):
406 associated exception. (Note that this is not necessary in Python 3.x
407 and should be removed when we make the transition.)
408 """
409- error_kind = 'Error' # Human-readable description of error type.
410+
411+ error_kind = "Error" # Human-readable description of error type.
412
413 def __init__(self, reason, verb, tb=None):
414 self.reason = reason
415@@ -65,10 +66,10 @@ class HumanReadableException(Exception):
416 def _gerund(self):
417 """Generate a (likely) gerund form of the English verb.
418 """
419- if u' ' in self.verb:
420+ if u" " in self.verb:
421 return self.verb
422- gerund = self.verb[:-1] if self.verb.endswith(u'e') else self.verb
423- gerund += u'ing'
424+ gerund = self.verb[:-1] if self.verb.endswith(u"e") else self.verb
425+ gerund += u"ing"
426 return gerund
427
428 def _reasonstr(self):
429@@ -76,8 +77,8 @@ class HumanReadableException(Exception):
430 if isinstance(self.reason, six.text_type):
431 return self.reason
432 elif isinstance(self.reason, bytes):
433- return self.reason.decode('utf-8', 'ignore')
434- elif hasattr(self.reason, 'strerror'): # i.e., EnvironmentError
435+ return self.reason.decode("utf-8", "ignore")
436+ elif hasattr(self.reason, "strerror"): # i.e., EnvironmentError
437 return self.reason.strerror
438 else:
439 return u'"{0}"'.format(six.text_type(self.reason))
440@@ -94,7 +95,7 @@ class HumanReadableException(Exception):
441 """
442 if self.tb:
443 logger.debug(self.tb)
444- logger.error(u'{0}: {1}', self.error_kind, self.args[0])
445+ logger.error(u"{0}: {1}", self.error_kind, self.args[0])
446
447
448 class FilesystemError(HumanReadableException):
449@@ -102,34 +103,35 @@ class FilesystemError(HumanReadableException):
450 via a function in this module. The `paths` field is a sequence of
451 pathnames involved in the operation.
452 """
453+
454 def __init__(self, reason, verb, paths, tb=None):
455 self.paths = paths
456 super(FilesystemError, self).__init__(reason, verb, tb)
457
458 def get_message(self):
459 # Use a nicer English phrasing for some specific verbs.
460- if self.verb in ('move', 'copy', 'rename'):
461- clause = u'while {0} {1} to {2}'.format(
462+ if self.verb in ("move", "copy", "rename"):
463+ clause = u"while {0} {1} to {2}".format(
464 self._gerund(),
465 displayable_path(self.paths[0]),
466- displayable_path(self.paths[1])
467+ displayable_path(self.paths[1]),
468 )
469- elif self.verb in ('delete', 'write', 'create', 'read'):
470- clause = u'while {0} {1}'.format(
471- self._gerund(),
472- displayable_path(self.paths[0])
473+ elif self.verb in ("delete", "write", "create", "read"):
474+ clause = u"while {0} {1}".format(
475+ self._gerund(), displayable_path(self.paths[0])
476 )
477 else:
478- clause = u'during {0} of paths {1}'.format(
479- self.verb, u', '.join(displayable_path(p) for p in self.paths)
480+ clause = u"during {0} of paths {1}".format(
481+ self.verb, u", ".join(displayable_path(p) for p in self.paths)
482 )
483
484- return u'{0} {1}'.format(self._reasonstr(), clause)
485+ return u"{0} {1}".format(self._reasonstr(), clause)
486
487
488 class MoveOperation(Enum):
489 """The file operations that e.g. various move functions can carry out.
490 """
491+
492 MOVE = 0
493 COPY = 1
494 LINK = 2
495@@ -184,9 +186,11 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None):
496 contents = os.listdir(syspath(path))
497 except OSError as exc:
498 if logger:
499- logger.warning(u'could not list directory {0}: {1}'.format(
500- displayable_path(path), exc.strerror
501- ))
502+ logger.warning(
503+ u"could not list directory {0}: {1}".format(
504+ displayable_path(path), exc.strerror
505+ )
506+ )
507 return
508 dirs = []
509 files = []
510@@ -227,7 +231,7 @@ def path_as_posix(path):
511 """Return the string representation of the path with forward (/)
512 slashes.
513 """
514- return path.replace(b'\\', b'/')
515+ return path.replace(b"\\", b"/")
516
517
518 def mkdirall(path):
519@@ -239,8 +243,9 @@ def mkdirall(path):
520 try:
521 os.mkdir(syspath(ancestor))
522 except (OSError, IOError) as exc:
523- raise FilesystemError(exc, 'create', (ancestor,),
524- traceback.format_exc())
525+ raise FilesystemError(
526+ exc, "create", (ancestor,), traceback.format_exc()
527+ )
528
529
530 def fnmatch_all(names, patterns):
531@@ -258,7 +263,7 @@ def fnmatch_all(names, patterns):
532 return True
533
534
535-def prune_dirs(path, root=None, clutter=('.DS_Store', 'Thumbs.db')):
536+def prune_dirs(path, root=None, clutter=(".DS_Store", "Thumbs.db")):
537 """If path is an empty directory, then remove it. Recursively remove
538 path's ancestry up to root (which is never removed) where there are
539 empty directories. If path is not contained in root, then nothing is
540@@ -276,7 +281,7 @@ def prune_dirs(path, root=None, clutter=('.DS_Store', 'Thumbs.db')):
541 ancestors = []
542 elif root in ancestors:
543 # Only remove directories below the root.
544- ancestors = ancestors[ancestors.index(root) + 1:]
545+ ancestors = ancestors[ancestors.index(root) + 1 :]
546 else:
547 # Remove nothing.
548 return
549@@ -330,11 +335,11 @@ def arg_encoding():
550 locale-sensitive strings).
551 """
552 try:
553- return locale.getdefaultlocale()[1] or 'utf-8'
554+ return locale.getdefaultlocale()[1] or "utf-8"
555 except ValueError:
556 # Invalid locale environment variable setting. To avoid
557 # failing entirely for no good reason, assume UTF-8.
558- return 'utf-8'
559+ return "utf-8"
560
561
562 def _fsencoding():
563@@ -342,13 +347,13 @@ def _fsencoding():
564 UTF-8 (not MBCS).
565 """
566 encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
567- if encoding == 'mbcs':
568+ if encoding == "mbcs":
569 # On Windows, a broken encoding known to Python as "MBCS" is
570 # used for the filesystem. However, we only use the Unicode API
571 # for Windows paths, so the encoding is actually immaterial so
572 # we can avoid dealing with this nastiness. We arbitrarily
573 # choose UTF-8.
574- encoding = 'utf-8'
575+ encoding = "utf-8"
576 return encoding
577
578
579@@ -363,20 +368,20 @@ def bytestring_path(path):
580 # On Windows, remove the magic prefix added by `syspath`. This makes
581 # ``bytestring_path(syspath(X)) == X``, i.e., we can safely
582 # round-trip through `syspath`.
583- if os.path.__name__ == 'ntpath' and path.startswith(WINDOWS_MAGIC_PREFIX):
584- path = path[len(WINDOWS_MAGIC_PREFIX):]
585+ if os.path.__name__ == "ntpath" and path.startswith(WINDOWS_MAGIC_PREFIX):
586+ path = path[len(WINDOWS_MAGIC_PREFIX) :]
587
588 # Try to encode with default encodings, but fall back to utf-8.
589 try:
590 return path.encode(_fsencoding())
591 except (UnicodeError, LookupError):
592- return path.encode('utf-8')
593+ return path.encode("utf-8")
594
595
596 PATH_SEP = bytestring_path(os.sep)
597
598
599-def displayable_path(path, separator=u'; '):
600+def displayable_path(path, separator=u"; "):
601 """Attempts to decode a bytestring path to a unicode object for the
602 purpose of displaying it to the user. If the `path` argument is a
603 list or a tuple, the elements are joined with `separator`.
604@@ -390,9 +395,9 @@ def displayable_path(path, separator=u'; '):
605 return six.text_type(path)
606
607 try:
608- return path.decode(_fsencoding(), 'ignore')
609+ return path.decode(_fsencoding(), "ignore")
610 except (UnicodeError, LookupError):
611- return path.decode('utf-8', 'ignore')
612+ return path.decode("utf-8", "ignore")
613
614
615 def syspath(path, prefix=True):
616@@ -403,7 +408,7 @@ def syspath(path, prefix=True):
617 *really* know what you're doing.
618 """
619 # Don't do anything if we're not on windows
620- if os.path.__name__ != 'ntpath':
621+ if os.path.__name__ != "ntpath":
622 return path
623
624 if not isinstance(path, six.text_type):
625@@ -411,19 +416,19 @@ def syspath(path, prefix=True):
626 # arbitrarily. But earlier versions used MBCS because it is
627 # reported as the FS encoding by Windows. Try both.
628 try:
629- path = path.decode('utf-8')
630+ path = path.decode("utf-8")
631 except UnicodeError:
632 # The encoding should always be MBCS, Windows' broken
633 # Unicode representation.
634 encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
635- path = path.decode(encoding, 'replace')
636+ path = path.decode(encoding, "replace")
637
638 # Add the magic prefix if it isn't already there.
639 # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
640 if prefix and not path.startswith(WINDOWS_MAGIC_PREFIX):
641- if path.startswith(u'\\\\'):
642+ if path.startswith(u"\\\\"):
643 # UNC path. Final path should look like \\?\UNC\...
644- path = u'UNC' + path[1:]
645+ path = u"UNC" + path[1:]
646 path = WINDOWS_MAGIC_PREFIX + path
647
648 return path
649@@ -446,7 +451,7 @@ def remove(path, soft=True):
650 try:
651 os.remove(path)
652 except (OSError, IOError) as exc:
653- raise FilesystemError(exc, 'delete', (path,), traceback.format_exc())
654+ raise FilesystemError(exc, "delete", (path,), traceback.format_exc())
655
656
657 def copy(path, dest, replace=False):
658@@ -460,12 +465,11 @@ def copy(path, dest, replace=False):
659 path = syspath(path)
660 dest = syspath(dest)
661 if not replace and os.path.exists(dest):
662- raise FilesystemError(u'file exists', 'copy', (path, dest))
663+ raise FilesystemError(u"file exists", "copy", (path, dest))
664 try:
665 shutil.copyfile(path, dest)
666 except (OSError, IOError) as exc:
667- raise FilesystemError(exc, 'copy', (path, dest),
668- traceback.format_exc())
669+ raise FilesystemError(exc, "copy", (path, dest), traceback.format_exc())
670
671
672 def move(path, dest, replace=False):
673@@ -481,7 +485,7 @@ def move(path, dest, replace=False):
674 path = syspath(path)
675 dest = syspath(dest)
676 if os.path.exists(dest) and not replace:
677- raise FilesystemError(u'file exists', 'rename', (path, dest))
678+ raise FilesystemError(u"file exists", "rename", (path, dest))
679
680 # First, try renaming the file.
681 try:
682@@ -492,8 +496,7 @@ def move(path, dest, replace=False):
683 shutil.copyfile(path, dest)
684 os.remove(path)
685 except (OSError, IOError) as exc:
686- raise FilesystemError(exc, 'move', (path, dest),
687- traceback.format_exc())
688+ raise FilesystemError(exc, "move", (path, dest), traceback.format_exc())
689
690
691 def link(path, dest, replace=False):
692@@ -505,20 +508,22 @@ def link(path, dest, replace=False):
693 return
694
695 if os.path.exists(syspath(dest)) and not replace:
696- raise FilesystemError(u'file exists', 'rename', (path, dest))
697+ raise FilesystemError(u"file exists", "rename", (path, dest))
698 try:
699 os.symlink(syspath(path), syspath(dest))
700 except NotImplementedError:
701 # raised on python >= 3.2 and Windows versions before Vista
702- raise FilesystemError(u'OS does not support symbolic links.'
703- 'link', (path, dest), traceback.format_exc())
704+ raise FilesystemError(
705+ u"OS does not support symbolic links." "link",
706+ (path, dest),
707+ traceback.format_exc(),
708+ )
709 except OSError as exc:
710 # TODO: Windows version checks can be removed for python 3
711- if hasattr('sys', 'getwindowsversion'):
712+ if hasattr("sys", "getwindowsversion"):
713 if sys.getwindowsversion()[0] < 6: # is before Vista
714- exc = u'OS does not support symbolic links.'
715- raise FilesystemError(exc, 'link', (path, dest),
716- traceback.format_exc())
717+ exc = u"OS does not support symbolic links."
718+ raise FilesystemError(exc, "link", (path, dest), traceback.format_exc())
719
720
721 def hardlink(path, dest, replace=False):
722@@ -530,19 +535,24 @@ def hardlink(path, dest, replace=False):
723 return
724
725 if os.path.exists(syspath(dest)) and not replace:
726- raise FilesystemError(u'file exists', 'rename', (path, dest))
727+ raise FilesystemError(u"file exists", "rename", (path, dest))
728 try:
729 os.link(syspath(path), syspath(dest))
730 except NotImplementedError:
731- raise FilesystemError(u'OS does not support hard links.'
732- 'link', (path, dest), traceback.format_exc())
733+ raise FilesystemError(
734+ u"OS does not support hard links." "link",
735+ (path, dest),
736+ traceback.format_exc(),
737+ )
738 except OSError as exc:
739 if exc.errno == errno.EXDEV:
740- raise FilesystemError(u'Cannot hard link across devices.'
741- 'link', (path, dest), traceback.format_exc())
742+ raise FilesystemError(
743+ u"Cannot hard link across devices." "link",
744+ (path, dest),
745+ traceback.format_exc(),
746+ )
747 else:
748- raise FilesystemError(exc, 'link', (path, dest),
749- traceback.format_exc())
750+ raise FilesystemError(exc, "link", (path, dest), traceback.format_exc())
751
752
753 def unique_path(path):
754@@ -554,30 +564,31 @@ def unique_path(path):
755 return path
756
757 base, ext = os.path.splitext(path)
758- match = re.search(br'\.(\d)+$', base)
759+ match = re.search(br"\.(\d)+$", base)
760 if match:
761 num = int(match.group(1))
762- base = base[:match.start()]
763+ base = base[: match.start()]
764 else:
765 num = 0
766 while True:
767 num += 1
768- suffix = u'.{}'.format(num).encode() + ext
769+ suffix = u".{}".format(num).encode() + ext
770 new_path = base + suffix
771 if not os.path.exists(new_path):
772 return new_path
773
774+
775 # Note: The Windows "reserved characters" are, of course, allowed on
776 # Unix. They are forbidden here because they cause problems on Samba
777 # shares, which are sufficiently common as to cause frequent problems.
778 # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
779 CHAR_REPLACE = [
780- (re.compile(r'[\\/]'), u'_'), # / and \ -- forbidden everywhere.
781- (re.compile(r'^\.'), u'_'), # Leading dot (hidden files on Unix).
782- (re.compile(r'[\x00-\x1f]'), u''), # Control characters.
783- (re.compile(r'[<>:"\?\*\|]'), u'_'), # Windows "reserved characters".
784- (re.compile(r'\.$'), u'_'), # Trailing dots.
785- (re.compile(r'\s+$'), u''), # Trailing whitespace.
786+ (re.compile(r"[\\/]"), u"_"), # / and \ -- forbidden everywhere.
787+ (re.compile(r"^\."), u"_"), # Leading dot (hidden files on Unix).
788+ (re.compile(r"[\x00-\x1f]"), u""), # Control characters.
789+ (re.compile(r'[<>:"\?\*\|]'), u"_"), # Windows "reserved characters".
790+ (re.compile(r"\.$"), u"_"), # Trailing dots.
791+ (re.compile(r"\s+$"), u""), # Trailing whitespace.
792 ]
793
794
795@@ -594,7 +605,7 @@ def sanitize_path(path, replacements=None):
796
797 comps = components(path)
798 if not comps:
799- return ''
800+ return ""
801 for i, comp in enumerate(comps):
802 for regex, repl in replacements:
803 comp = regex.sub(repl, comp)
804@@ -613,7 +624,7 @@ def truncate_path(path, length=MAX_FILENAME_LENGTH):
805 base, ext = os.path.splitext(comps[-1])
806 if ext:
807 # Last component has an extension.
808- base = base[:length - len(ext)]
809+ base = base[: length - len(ext)]
810 out[-1] = base + ext
811
812 return os.path.join(*out)
813@@ -667,7 +678,7 @@ def legalize_path(path, replacements, length, extension, fragment):
814
815 if fragment:
816 # Outputting Unicode.
817- extension = extension.decode('utf-8', 'ignore')
818+ extension = extension.decode("utf-8", "ignore")
819
820 first_stage_path, _ = _legalize_stage(
821 path, replacements, length, extension, fragment
822@@ -711,7 +722,7 @@ def py3_path(path):
823
824 def str2bool(value):
825 """Returns a boolean reflecting a human-entered string."""
826- return value.lower() in (u'yes', u'1', u'true', u't', u'y')
827+ return value.lower() in (u"yes", u"1", u"true", u"t", u"y")
828
829
830 def as_string(value):
831@@ -724,16 +735,16 @@ def as_string(value):
832 buffer_types = memoryview
833
834 if value is None:
835- return u''
836+ return u""
837 elif isinstance(value, buffer_types):
838- return bytes(value).decode('utf-8', 'ignore')
839+ return bytes(value).decode("utf-8", "ignore")
840 elif isinstance(value, bytes):
841- return value.decode('utf-8', 'ignore')
842+ return value.decode("utf-8", "ignore")
843 else:
844 return six.text_type(value)
845
846
847-def text_string(value, encoding='utf-8'):
848+def text_string(value, encoding="utf-8"):
849 """Convert a string, which can either be bytes or unicode, to
850 unicode.
851
852@@ -753,7 +764,7 @@ def plurality(objs):
853 """
854 c = Counter(objs)
855 if not c:
856- raise ValueError(u'sequence must be non-empty')
857+ raise ValueError(u"sequence must be non-empty")
858 return c.most_common(1)[0]
859
860
861@@ -763,23 +774,19 @@ def cpu_count():
862 """
863 # Adapted from the soundconverter project:
864 # https://github.com/kassoulet/soundconverter
865- if sys.platform == 'win32':
866+ if sys.platform == "win32":
867 try:
868- num = int(os.environ['NUMBER_OF_PROCESSORS'])
869+ num = int(os.environ["NUMBER_OF_PROCESSORS"])
870 except (ValueError, KeyError):
871 num = 0
872- elif sys.platform == 'darwin':
873+ elif sys.platform == "darwin":
874 try:
875- num = int(command_output([
876- '/usr/sbin/sysctl',
877- '-n',
878- 'hw.ncpu',
879- ]).stdout)
880+ num = int(command_output(["/usr/sbin/sysctl", "-n", "hw.ncpu",]).stdout)
881 except (ValueError, OSError, subprocess.CalledProcessError):
882 num = 0
883 else:
884 try:
885- num = os.sysconf('SC_NPROCESSORS_ONLN')
886+ num = os.sysconf("SC_NPROCESSORS_ONLN")
887 except (ValueError, OSError, AttributeError):
888 num = 0
889 if num >= 1:
890@@ -799,7 +806,7 @@ def convert_command_args(args):
891 arg = arg.encode(arg_encoding())
892 else:
893 if isinstance(arg, bytes):
894- arg = arg.decode(arg_encoding(), 'surrogateescape')
895+ arg = arg.decode(arg_encoding(), "surrogateescape")
896 return arg
897
898 return [convert(a) for a in args]
899@@ -832,22 +839,20 @@ def command_output(cmd, shell=False):
900 try: # python >= 3.3
901 devnull = subprocess.DEVNULL
902 except AttributeError:
903- devnull = open(os.devnull, 'r+b')
904+ devnull = open(os.devnull, "r+b")
905
906 proc = subprocess.Popen(
907 cmd,
908 stdout=subprocess.PIPE,
909 stderr=subprocess.PIPE,
910 stdin=devnull,
911- close_fds=platform.system() != 'Windows',
912- shell=shell
913+ close_fds=platform.system() != "Windows",
914+ shell=shell,
915 )
916 stdout, stderr = proc.communicate()
917 if proc.returncode:
918 raise subprocess.CalledProcessError(
919- returncode=proc.returncode,
920- cmd=' '.join(cmd),
921- output=stdout + stderr,
922+ returncode=proc.returncode, cmd=" ".join(cmd), output=stdout + stderr,
923 )
924 return CommandOutput(stdout, stderr)
925
926@@ -859,7 +864,7 @@ def max_filename_length(path, limit=MAX_FILENAME_LENGTH):
927 misreports its capacity). If it cannot be determined (e.g., on
928 Windows), return `limit`.
929 """
930- if hasattr(os, 'statvfs'):
931+ if hasattr(os, "statvfs"):
932 try:
933 res = os.statvfs(path)
934 except OSError:
935@@ -874,12 +879,12 @@ def open_anything():
936 program.
937 """
938 sys_name = platform.system()
939- if sys_name == 'Darwin':
940- base_cmd = 'open'
941- elif sys_name == 'Windows':
942- base_cmd = 'start'
943+ if sys_name == "Darwin":
944+ base_cmd = "open"
945+ elif sys_name == "Windows":
946+ base_cmd = "start"
947 else: # Assume Unix
948- base_cmd = 'xdg-open'
949+ base_cmd = "xdg-open"
950 return base_cmd
951
952
953@@ -890,7 +895,7 @@ def editor_command():
954 present, fall back to `open_anything()`, the platform-specific tool
955 for opening files in general.
956 """
957- editor = os.environ.get('EDITOR')
958+ editor = os.environ.get("EDITOR")
959 if editor:
960 return editor
961 return open_anything()
962@@ -908,11 +913,11 @@ def shlex_split(s):
963 elif isinstance(s, six.text_type):
964 # Work around a Python bug.
965 # http://bugs.python.org/issue6988
966- bs = s.encode('utf-8')
967- return [c.decode('utf-8') for c in shlex.split(bs)]
968+ bs = s.encode("utf-8")
969+ return [c.decode("utf-8") for c in shlex.split(bs)]
970
971 else:
972- raise TypeError(u'shlex_split called with non-string')
973+ raise TypeError(u"shlex_split called with non-string")
974
975
976 def interactive_open(targets, command):
977@@ -945,6 +950,7 @@ def _windows_long_path_name(short_path):
978 short_path = short_path.decode(_fsencoding())
979
980 import ctypes
981+
982 buf = ctypes.create_unicode_buffer(260)
983 get_long_path_name_w = ctypes.windll.kernel32.GetLongPathNameW
984 return_value = get_long_path_name_w(short_path, buf, 260)
985@@ -956,7 +962,7 @@ def _windows_long_path_name(short_path):
986 long_path = buf.value
987 # GetLongPathNameW does not change the case of the drive
988 # letter.
989- if len(long_path) > 1 and long_path[1] == ':':
990+ if len(long_path) > 1 and long_path[1] == ":":
991 long_path = long_path[0].upper() + long_path[1:]
992 return long_path
993
994@@ -971,21 +977,21 @@ def case_sensitive(path):
995 # A fallback in case the path does not exist.
996 if not os.path.exists(syspath(path)):
997 # By default, the case sensitivity depends on the platform.
998- return platform.system() != 'Windows'
999+ return platform.system() != "Windows"
1000
1001 # If an upper-case version of the path exists but a lower-case
1002 # version does not, then the filesystem must be case-sensitive.
1003 # (Otherwise, we have more work to do.)
1004- if not (os.path.exists(syspath(path.lower())) and
1005- os.path.exists(syspath(path.upper()))):
1006+ if not (
1007+ os.path.exists(syspath(path.lower())) and os.path.exists(syspath(path.upper()))
1008+ ):
1009 return True
1010
1011 # Both versions of the path exist on the file system. Check whether
1012 # they refer to different files by their inodes. Alas,
1013 # `os.path.samefile` is only available on Unix systems on Python 2.
1014- if platform.system() != 'Windows':
1015- return not os.path.samefile(syspath(path.lower()),
1016- syspath(path.upper()))
1017+ if platform.system() != "Windows":
1018+ return not os.path.samefile(syspath(path.lower()), syspath(path.upper()))
1019
1020 # On Windows, we check whether the canonical, long filenames for the
1021 # files are the same.
1022@@ -1000,9 +1006,9 @@ def raw_seconds_short(string):
1023 Raises ValueError if the conversion cannot take place due to `string` not
1024 being in the right format.
1025 """
1026- match = re.match(r'^(\d+):([0-5]\d)$', string)
1027+ match = re.match(r"^(\d+):([0-5]\d)$", string)
1028 if not match:
1029- raise ValueError(u'String not in M:SS format')
1030+ raise ValueError(u"String not in M:SS format")
1031 minutes, seconds = map(int, match.groups())
1032 return float(minutes * 60 + seconds)
1033
1034@@ -1024,10 +1030,7 @@ def asciify_path(path, sep_replace):
1035 for index, item in enumerate(path_components):
1036 path_components[index] = unidecode(item).replace(os.sep, sep_replace)
1037 if os.altsep:
1038- path_components[index] = unidecode(item).replace(
1039- os.altsep,
1040- sep_replace
1041- )
1042+ path_components[index] = unidecode(item).replace(os.altsep, sep_replace)
1043 return os.sep.join(path_components)
1044
1045
1046@@ -1060,7 +1063,7 @@ def lazy_property(func):
1047 This behaviour is useful when `func` is expensive to evaluate, and it is
1048 not certain that the result will be needed.
1049 """
1050- field_name = '_' + func.__name__
1051+ field_name = "_" + func.__name__
1052
1053 @property
1054 @functools.wraps(func)