mirror of
https://github.com/beetbox/beets.git
synced 2026-01-09 17:33:51 +01:00
pyupgrade util dir
This commit is contained in:
parent
432fa55725
commit
af102c3e2f
8 changed files with 137 additions and 155 deletions
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
|
|
@ -15,7 +14,6 @@
|
|||
|
||||
"""Miscellaneous utility functions."""
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
import os
|
||||
import sys
|
||||
import errno
|
||||
|
|
@ -37,7 +35,7 @@ from enum import Enum
|
|||
|
||||
|
||||
MAX_FILENAME_LENGTH = 200
|
||||
WINDOWS_MAGIC_PREFIX = u'\\\\?\\'
|
||||
WINDOWS_MAGIC_PREFIX = '\\\\?\\'
|
||||
SNI_SUPPORTED = sys.version_info >= (2, 7, 9)
|
||||
|
||||
|
||||
|
|
@ -60,27 +58,27 @@ class HumanReadableException(Exception):
|
|||
self.reason = reason
|
||||
self.verb = verb
|
||||
self.tb = tb
|
||||
super(HumanReadableException, self).__init__(self.get_message())
|
||||
super().__init__(self.get_message())
|
||||
|
||||
def _gerund(self):
|
||||
"""Generate a (likely) gerund form of the English verb.
|
||||
"""
|
||||
if u' ' in self.verb:
|
||||
if ' ' in self.verb:
|
||||
return self.verb
|
||||
gerund = self.verb[:-1] if self.verb.endswith(u'e') else self.verb
|
||||
gerund += u'ing'
|
||||
gerund = self.verb[:-1] if self.verb.endswith('e') else self.verb
|
||||
gerund += 'ing'
|
||||
return gerund
|
||||
|
||||
def _reasonstr(self):
|
||||
"""Get the reason as a string."""
|
||||
if isinstance(self.reason, six.text_type):
|
||||
if isinstance(self.reason, str):
|
||||
return self.reason
|
||||
elif isinstance(self.reason, bytes):
|
||||
return self.reason.decode('utf-8', 'ignore')
|
||||
elif hasattr(self.reason, 'strerror'): # i.e., EnvironmentError
|
||||
return self.reason.strerror
|
||||
else:
|
||||
return u'"{0}"'.format(six.text_type(self.reason))
|
||||
return '"{}"'.format(str(self.reason))
|
||||
|
||||
def get_message(self):
|
||||
"""Create the human-readable description of the error, sans
|
||||
|
|
@ -94,7 +92,7 @@ class HumanReadableException(Exception):
|
|||
"""
|
||||
if self.tb:
|
||||
logger.debug(self.tb)
|
||||
logger.error(u'{0}: {1}', self.error_kind, self.args[0])
|
||||
logger.error('{0}: {1}', self.error_kind, self.args[0])
|
||||
|
||||
|
||||
class FilesystemError(HumanReadableException):
|
||||
|
|
@ -104,27 +102,27 @@ class FilesystemError(HumanReadableException):
|
|||
"""
|
||||
def __init__(self, reason, verb, paths, tb=None):
|
||||
self.paths = paths
|
||||
super(FilesystemError, self).__init__(reason, verb, tb)
|
||||
super().__init__(reason, verb, tb)
|
||||
|
||||
def get_message(self):
|
||||
# Use a nicer English phrasing for some specific verbs.
|
||||
if self.verb in ('move', 'copy', 'rename'):
|
||||
clause = u'while {0} {1} to {2}'.format(
|
||||
clause = 'while {} {} to {}'.format(
|
||||
self._gerund(),
|
||||
displayable_path(self.paths[0]),
|
||||
displayable_path(self.paths[1])
|
||||
)
|
||||
elif self.verb in ('delete', 'write', 'create', 'read'):
|
||||
clause = u'while {0} {1}'.format(
|
||||
clause = 'while {} {}'.format(
|
||||
self._gerund(),
|
||||
displayable_path(self.paths[0])
|
||||
)
|
||||
else:
|
||||
clause = u'during {0} of paths {1}'.format(
|
||||
self.verb, u', '.join(displayable_path(p) for p in self.paths)
|
||||
clause = 'during {} of paths {}'.format(
|
||||
self.verb, ', '.join(displayable_path(p) for p in self.paths)
|
||||
)
|
||||
|
||||
return u'{0} {1}'.format(self._reasonstr(), clause)
|
||||
return f'{self._reasonstr()} {clause}'
|
||||
|
||||
|
||||
class MoveOperation(Enum):
|
||||
|
|
@ -186,7 +184,7 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None):
|
|||
contents = os.listdir(syspath(path))
|
||||
except OSError as exc:
|
||||
if logger:
|
||||
logger.warning(u'could not list directory {0}: {1}'.format(
|
||||
logger.warning('could not list directory {}: {}'.format(
|
||||
displayable_path(path), exc.strerror
|
||||
))
|
||||
return
|
||||
|
|
@ -200,7 +198,7 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None):
|
|||
for pat in ignore:
|
||||
if fnmatch.fnmatch(base, pat):
|
||||
if logger:
|
||||
logger.debug(u'ignoring {0} due to ignore rule {1}'.format(
|
||||
logger.debug('ignoring {} due to ignore rule {}'.format(
|
||||
base, pat
|
||||
))
|
||||
skip = True
|
||||
|
|
@ -225,8 +223,7 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None):
|
|||
for base in dirs:
|
||||
cur = os.path.join(path, base)
|
||||
# yield from sorted_walk(...)
|
||||
for res in sorted_walk(cur, ignore, ignore_hidden, logger):
|
||||
yield res
|
||||
yield from sorted_walk(cur, ignore, ignore_hidden, logger)
|
||||
|
||||
|
||||
def path_as_posix(path):
|
||||
|
|
@ -244,7 +241,7 @@ def mkdirall(path):
|
|||
if not os.path.isdir(syspath(ancestor)):
|
||||
try:
|
||||
os.mkdir(syspath(ancestor))
|
||||
except (OSError, IOError) as exc:
|
||||
except OSError as exc:
|
||||
raise FilesystemError(exc, 'create', (ancestor,),
|
||||
traceback.format_exc())
|
||||
|
||||
|
|
@ -382,18 +379,18 @@ def bytestring_path(path):
|
|||
PATH_SEP = bytestring_path(os.sep)
|
||||
|
||||
|
||||
def displayable_path(path, separator=u'; '):
|
||||
def displayable_path(path, separator='; '):
|
||||
"""Attempts to decode a bytestring path to a unicode object for the
|
||||
purpose of displaying it to the user. If the `path` argument is a
|
||||
list or a tuple, the elements are joined with `separator`.
|
||||
"""
|
||||
if isinstance(path, (list, tuple)):
|
||||
return separator.join(displayable_path(p) for p in path)
|
||||
elif isinstance(path, six.text_type):
|
||||
elif isinstance(path, str):
|
||||
return path
|
||||
elif not isinstance(path, bytes):
|
||||
# A non-string object: just get its unicode representation.
|
||||
return six.text_type(path)
|
||||
return str(path)
|
||||
|
||||
try:
|
||||
return path.decode(_fsencoding(), 'ignore')
|
||||
|
|
@ -412,7 +409,7 @@ def syspath(path, prefix=True):
|
|||
if os.path.__name__ != 'ntpath':
|
||||
return path
|
||||
|
||||
if not isinstance(path, six.text_type):
|
||||
if not isinstance(path, str):
|
||||
# Beets currently represents Windows paths internally with UTF-8
|
||||
# arbitrarily. But earlier versions used MBCS because it is
|
||||
# reported as the FS encoding by Windows. Try both.
|
||||
|
|
@ -427,9 +424,9 @@ def syspath(path, prefix=True):
|
|||
# Add the magic prefix if it isn't already there.
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
|
||||
if prefix and not path.startswith(WINDOWS_MAGIC_PREFIX):
|
||||
if path.startswith(u'\\\\'):
|
||||
if path.startswith('\\\\'):
|
||||
# UNC path. Final path should look like \\?\UNC\...
|
||||
path = u'UNC' + path[1:]
|
||||
path = 'UNC' + path[1:]
|
||||
path = WINDOWS_MAGIC_PREFIX + path
|
||||
|
||||
return path
|
||||
|
|
@ -451,7 +448,7 @@ def remove(path, soft=True):
|
|||
return
|
||||
try:
|
||||
os.remove(path)
|
||||
except (OSError, IOError) as exc:
|
||||
except OSError as exc:
|
||||
raise FilesystemError(exc, 'delete', (path,), traceback.format_exc())
|
||||
|
||||
|
||||
|
|
@ -466,10 +463,10 @@ def copy(path, dest, replace=False):
|
|||
path = syspath(path)
|
||||
dest = syspath(dest)
|
||||
if not replace and os.path.exists(dest):
|
||||
raise FilesystemError(u'file exists', 'copy', (path, dest))
|
||||
raise FilesystemError('file exists', 'copy', (path, dest))
|
||||
try:
|
||||
shutil.copyfile(path, dest)
|
||||
except (OSError, IOError) as exc:
|
||||
except OSError as exc:
|
||||
raise FilesystemError(exc, 'copy', (path, dest),
|
||||
traceback.format_exc())
|
||||
|
||||
|
|
@ -487,7 +484,7 @@ def move(path, dest, replace=False):
|
|||
path = syspath(path)
|
||||
dest = syspath(dest)
|
||||
if os.path.exists(dest) and not replace:
|
||||
raise FilesystemError(u'file exists', 'rename', (path, dest))
|
||||
raise FilesystemError('file exists', 'rename', (path, dest))
|
||||
|
||||
# First, try renaming the file.
|
||||
try:
|
||||
|
|
@ -497,7 +494,7 @@ def move(path, dest, replace=False):
|
|||
try:
|
||||
shutil.copyfile(path, dest)
|
||||
os.remove(path)
|
||||
except (OSError, IOError) as exc:
|
||||
except OSError as exc:
|
||||
raise FilesystemError(exc, 'move', (path, dest),
|
||||
traceback.format_exc())
|
||||
|
||||
|
|
@ -511,18 +508,18 @@ def link(path, dest, replace=False):
|
|||
return
|
||||
|
||||
if os.path.exists(syspath(dest)) and not replace:
|
||||
raise FilesystemError(u'file exists', 'rename', (path, dest))
|
||||
raise FilesystemError('file exists', 'rename', (path, dest))
|
||||
try:
|
||||
os.symlink(syspath(path), syspath(dest))
|
||||
except NotImplementedError:
|
||||
# raised on python >= 3.2 and Windows versions before Vista
|
||||
raise FilesystemError(u'OS does not support symbolic links.'
|
||||
raise FilesystemError('OS does not support symbolic links.'
|
||||
'link', (path, dest), traceback.format_exc())
|
||||
except OSError as exc:
|
||||
# TODO: Windows version checks can be removed for python 3
|
||||
if hasattr('sys', 'getwindowsversion'):
|
||||
if sys.getwindowsversion()[0] < 6: # is before Vista
|
||||
exc = u'OS does not support symbolic links.'
|
||||
exc = 'OS does not support symbolic links.'
|
||||
raise FilesystemError(exc, 'link', (path, dest),
|
||||
traceback.format_exc())
|
||||
|
||||
|
|
@ -536,15 +533,15 @@ def hardlink(path, dest, replace=False):
|
|||
return
|
||||
|
||||
if os.path.exists(syspath(dest)) and not replace:
|
||||
raise FilesystemError(u'file exists', 'rename', (path, dest))
|
||||
raise FilesystemError('file exists', 'rename', (path, dest))
|
||||
try:
|
||||
os.link(syspath(path), syspath(dest))
|
||||
except NotImplementedError:
|
||||
raise FilesystemError(u'OS does not support hard links.'
|
||||
raise FilesystemError('OS does not support hard links.'
|
||||
'link', (path, dest), traceback.format_exc())
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EXDEV:
|
||||
raise FilesystemError(u'Cannot hard link across devices.'
|
||||
raise FilesystemError('Cannot hard link across devices.'
|
||||
'link', (path, dest), traceback.format_exc())
|
||||
else:
|
||||
raise FilesystemError(exc, 'link', (path, dest),
|
||||
|
|
@ -568,7 +565,7 @@ def reflink(path, dest, replace=False, fallback=False):
|
|||
return
|
||||
|
||||
if os.path.exists(syspath(dest)) and not replace:
|
||||
raise FilesystemError(u'file exists', 'rename', (path, dest))
|
||||
raise FilesystemError('file exists', 'rename', (path, dest))
|
||||
|
||||
try:
|
||||
pyreflink.reflink(path, dest)
|
||||
|
|
@ -576,7 +573,7 @@ def reflink(path, dest, replace=False, fallback=False):
|
|||
if fallback:
|
||||
copy(path, dest, replace)
|
||||
else:
|
||||
raise FilesystemError(u'OS/filesystem does not support reflinks.',
|
||||
raise FilesystemError('OS/filesystem does not support reflinks.',
|
||||
'link', (path, dest), traceback.format_exc())
|
||||
|
||||
|
||||
|
|
@ -597,7 +594,7 @@ def unique_path(path):
|
|||
num = 0
|
||||
while True:
|
||||
num += 1
|
||||
suffix = u'.{}'.format(num).encode() + ext
|
||||
suffix = f'.{num}'.encode() + ext
|
||||
new_path = base + suffix
|
||||
if not os.path.exists(new_path):
|
||||
return new_path
|
||||
|
|
@ -607,12 +604,12 @@ def unique_path(path):
|
|||
# shares, which are sufficiently common as to cause frequent problems.
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
|
||||
CHAR_REPLACE = [
|
||||
(re.compile(r'[\\/]'), u'_'), # / and \ -- forbidden everywhere.
|
||||
(re.compile(r'^\.'), u'_'), # Leading dot (hidden files on Unix).
|
||||
(re.compile(r'[\x00-\x1f]'), u''), # Control characters.
|
||||
(re.compile(r'[<>:"\?\*\|]'), u'_'), # Windows "reserved characters".
|
||||
(re.compile(r'\.$'), u'_'), # Trailing dots.
|
||||
(re.compile(r'\s+$'), u''), # Trailing whitespace.
|
||||
(re.compile(r'[\\/]'), '_'), # / and \ -- forbidden everywhere.
|
||||
(re.compile(r'^\.'), '_'), # Leading dot (hidden files on Unix).
|
||||
(re.compile(r'[\x00-\x1f]'), ''), # Control characters.
|
||||
(re.compile(r'[<>:"\?\*\|]'), '_'), # Windows "reserved characters".
|
||||
(re.compile(r'\.$'), '_'), # Trailing dots.
|
||||
(re.compile(r'\s+$'), ''), # Trailing whitespace.
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -736,7 +733,7 @@ def py3_path(path):
|
|||
it is. So this function helps us "smuggle" the true bytes data
|
||||
through APIs that took Python 3's Unicode mandate too seriously.
|
||||
"""
|
||||
if isinstance(path, six.text_type):
|
||||
if isinstance(path, str):
|
||||
return path
|
||||
assert isinstance(path, bytes)
|
||||
if six.PY2:
|
||||
|
|
@ -746,7 +743,7 @@ def py3_path(path):
|
|||
|
||||
def str2bool(value):
|
||||
"""Returns a boolean reflecting a human-entered string."""
|
||||
return value.lower() in (u'yes', u'1', u'true', u't', u'y')
|
||||
return value.lower() in ('yes', '1', 'true', 't', 'y')
|
||||
|
||||
|
||||
def as_string(value):
|
||||
|
|
@ -754,13 +751,13 @@ def as_string(value):
|
|||
None becomes the empty string. Bytestrings are silently decoded.
|
||||
"""
|
||||
if value is None:
|
||||
return u''
|
||||
return ''
|
||||
elif isinstance(value, memoryview):
|
||||
return bytes(value).decode('utf-8', 'ignore')
|
||||
elif isinstance(value, bytes):
|
||||
return value.decode('utf-8', 'ignore')
|
||||
else:
|
||||
return six.text_type(value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def text_string(value, encoding='utf-8'):
|
||||
|
|
@ -783,7 +780,7 @@ def plurality(objs):
|
|||
"""
|
||||
c = Counter(objs)
|
||||
if not c:
|
||||
raise ValueError(u'sequence must be non-empty')
|
||||
raise ValueError('sequence must be non-empty')
|
||||
return c.most_common(1)[0]
|
||||
|
||||
|
||||
|
|
@ -948,7 +945,7 @@ def _windows_long_path_name(short_path):
|
|||
"""Use Windows' `GetLongPathNameW` via ctypes to get the canonical,
|
||||
long path given a short filename.
|
||||
"""
|
||||
if not isinstance(short_path, six.text_type):
|
||||
if not isinstance(short_path, str):
|
||||
short_path = short_path.decode(_fsencoding())
|
||||
|
||||
import ctypes
|
||||
|
|
@ -1009,7 +1006,7 @@ def raw_seconds_short(string):
|
|||
"""
|
||||
match = re.match(r'^(\d+):([0-5]\d)$', string)
|
||||
if not match:
|
||||
raise ValueError(u'String not in M:SS format')
|
||||
raise ValueError('String not in M:SS format')
|
||||
minutes, seconds = map(int, match.groups())
|
||||
return float(minutes * 60 + seconds)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Fabrice Laporte
|
||||
#
|
||||
|
|
@ -16,7 +15,6 @@
|
|||
"""Abstraction layer to resize images using PIL, ImageMagick, or a
|
||||
public resizing proxy if neither is available.
|
||||
"""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
|
|
@ -52,7 +50,7 @@ def resize_url(url, maxwidth, quality=0):
|
|||
if quality > 0:
|
||||
params['q'] = quality
|
||||
|
||||
return '{0}?{1}'.format(PROXY_URL, urlencode(params))
|
||||
return '{}?{}'.format(PROXY_URL, urlencode(params))
|
||||
|
||||
|
||||
def temp_file_for(path):
|
||||
|
|
@ -71,7 +69,7 @@ def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0):
|
|||
path_out = path_out or temp_file_for(path_in)
|
||||
from PIL import Image
|
||||
|
||||
log.debug(u'artresizer: PIL resizing {0} to {1}',
|
||||
log.debug('artresizer: PIL resizing {0} to {1}',
|
||||
util.displayable_path(path_in), util.displayable_path(path_out))
|
||||
|
||||
try:
|
||||
|
|
@ -95,7 +93,7 @@ def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0):
|
|||
for i in range(5):
|
||||
# 5 attempts is an abitrary choice
|
||||
filesize = os.stat(util.syspath(path_out)).st_size
|
||||
log.debug(u"PIL Pass {0} : Output size: {1}B", i, filesize)
|
||||
log.debug("PIL Pass {0} : Output size: {1}B", i, filesize)
|
||||
if filesize <= max_filesize:
|
||||
return path_out
|
||||
# The relationship between filesize & quality will be
|
||||
|
|
@ -108,14 +106,14 @@ def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0):
|
|||
im.save(
|
||||
util.py3_path(path_out), quality=lower_qual, optimize=True
|
||||
)
|
||||
log.warning(u"PIL Failed to resize file to below {0}B",
|
||||
log.warning("PIL Failed to resize file to below {0}B",
|
||||
max_filesize)
|
||||
return path_out
|
||||
|
||||
else:
|
||||
return path_out
|
||||
except IOError:
|
||||
log.error(u"PIL cannot create thumbnail for '{0}'",
|
||||
except OSError:
|
||||
log.error("PIL cannot create thumbnail for '{0}'",
|
||||
util.displayable_path(path_in))
|
||||
return path_in
|
||||
|
||||
|
|
@ -127,7 +125,7 @@ def im_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0):
|
|||
the output path of resized image.
|
||||
"""
|
||||
path_out = path_out or temp_file_for(path_in)
|
||||
log.debug(u'artresizer: ImageMagick resizing {0} to {1}',
|
||||
log.debug('artresizer: ImageMagick resizing {0} to {1}',
|
||||
util.displayable_path(path_in), util.displayable_path(path_out))
|
||||
|
||||
# "-resize WIDTHx>" shrinks images with the width larger
|
||||
|
|
@ -135,23 +133,23 @@ def im_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0):
|
|||
# with regards to the height.
|
||||
cmd = ArtResizer.shared.im_convert_cmd + [
|
||||
util.syspath(path_in, prefix=False),
|
||||
'-resize', '{0}x>'.format(maxwidth),
|
||||
'-resize', f'{maxwidth}x>',
|
||||
]
|
||||
|
||||
if quality > 0:
|
||||
cmd += ['-quality', '{0}'.format(quality)]
|
||||
cmd += ['-quality', f'{quality}']
|
||||
|
||||
# "-define jpeg:extent=SIZEb" sets the target filesize for imagemagick to
|
||||
# SIZE in bytes.
|
||||
if max_filesize > 0:
|
||||
cmd += ['-define', 'jpeg:extent={0}b'.format(max_filesize)]
|
||||
cmd += ['-define', f'jpeg:extent={max_filesize}b']
|
||||
|
||||
cmd.append(util.syspath(path_out, prefix=False))
|
||||
|
||||
try:
|
||||
util.command_output(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
log.warning(u'artresizer: IM convert failed for {0}',
|
||||
log.warning('artresizer: IM convert failed for {0}',
|
||||
util.displayable_path(path_in))
|
||||
return path_in
|
||||
|
||||
|
|
@ -170,8 +168,8 @@ def pil_getsize(path_in):
|
|||
try:
|
||||
im = Image.open(util.syspath(path_in))
|
||||
return im.size
|
||||
except IOError as exc:
|
||||
log.error(u"PIL could not read file {}: {}",
|
||||
except OSError as exc:
|
||||
log.error("PIL could not read file {}: {}",
|
||||
util.displayable_path(path_in), exc)
|
||||
|
||||
|
||||
|
|
@ -182,17 +180,17 @@ def im_getsize(path_in):
|
|||
try:
|
||||
out = util.command_output(cmd).stdout
|
||||
except subprocess.CalledProcessError as exc:
|
||||
log.warning(u'ImageMagick size query failed')
|
||||
log.warning('ImageMagick size query failed')
|
||||
log.debug(
|
||||
u'`convert` exited with (status {}) when '
|
||||
u'getting size with command {}:\n{}',
|
||||
'`convert` exited with (status {}) when '
|
||||
'getting size with command {}:\n{}',
|
||||
exc.returncode, cmd, exc.output.strip()
|
||||
)
|
||||
return
|
||||
try:
|
||||
return tuple(map(int, out.split(b' ')))
|
||||
except IndexError:
|
||||
log.warning(u'Could not understand IM output: {0!r}', out)
|
||||
log.warning('Could not understand IM output: {0!r}', out)
|
||||
|
||||
|
||||
BACKEND_GET_SIZE = {
|
||||
|
|
@ -209,7 +207,7 @@ class Shareable(type):
|
|||
"""
|
||||
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(Shareable, cls).__init__(name, bases, dict)
|
||||
super().__init__(name, bases, dict)
|
||||
cls._instance = None
|
||||
|
||||
@property
|
||||
|
|
@ -219,7 +217,7 @@ class Shareable(type):
|
|||
return cls._instance
|
||||
|
||||
|
||||
class ArtResizer(six.with_metaclass(Shareable, object)):
|
||||
class ArtResizer(metaclass=Shareable):
|
||||
"""A singleton class that performs image resizes.
|
||||
"""
|
||||
|
||||
|
|
@ -227,7 +225,7 @@ class ArtResizer(six.with_metaclass(Shareable, object)):
|
|||
"""Create a resizer object with an inferred method.
|
||||
"""
|
||||
self.method = self._check_method()
|
||||
log.debug(u"artresizer: method is {0}", self.method)
|
||||
log.debug("artresizer: method is {0}", self.method)
|
||||
self.can_compare = self._can_compare()
|
||||
|
||||
# Use ImageMagick's magick binary when it's available. If it's
|
||||
|
|
@ -323,7 +321,7 @@ def get_im_version():
|
|||
try:
|
||||
out = util.command_output(cmd).stdout
|
||||
except (subprocess.CalledProcessError, OSError) as exc:
|
||||
log.debug(u'ImageMagick version check failed: {}', exc)
|
||||
log.debug('ImageMagick version check failed: {}', exc)
|
||||
else:
|
||||
if b'imagemagick' in out.lower():
|
||||
pattern = br".+ (\d+)\.(\d+)\.(\d+).*"
|
||||
|
|
@ -341,7 +339,7 @@ def get_pil_version():
|
|||
"""Get the PIL/Pillow version, or None if it is unavailable.
|
||||
"""
|
||||
try:
|
||||
__import__('PIL', fromlist=[str('Image')])
|
||||
__import__('PIL', fromlist=['Image'])
|
||||
return (0,)
|
||||
except ImportError:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Extremely simple pure-Python implementation of coroutine-style
|
||||
asynchronous socket I/O. Inspired by, but inferior to, Eventlet.
|
||||
Bluelet can also be thought of as a less-terrible replacement for
|
||||
|
|
@ -7,7 +5,6 @@ asyncore.
|
|||
|
||||
Bluelet: easy concurrency without all the messy parallelism.
|
||||
"""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import six
|
||||
import socket
|
||||
|
|
@ -22,7 +19,7 @@ import collections
|
|||
|
||||
# Basic events used for thread scheduling.
|
||||
|
||||
class Event(object):
|
||||
class Event:
|
||||
"""Just a base class identifying Bluelet events. An event is an
|
||||
object yielded from a Bluelet thread coroutine to suspend operation
|
||||
and communicate with the scheduler.
|
||||
|
|
@ -201,7 +198,7 @@ class ThreadException(Exception):
|
|||
self.exc_info = exc_info
|
||||
|
||||
def reraise(self):
|
||||
six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2])
|
||||
raise self.exc_info[1].with_traceback(self.exc_info[2])
|
||||
|
||||
|
||||
SUSPENDED = Event() # Special sentinel placeholder for suspended threads.
|
||||
|
|
@ -336,12 +333,12 @@ def run(root_coro):
|
|||
break
|
||||
|
||||
# Wait and fire.
|
||||
event2coro = dict((v, k) for k, v in threads.items())
|
||||
event2coro = {v: k for k, v in threads.items()}
|
||||
for event in _event_select(threads.values()):
|
||||
# Run the IO operation, but catch socket errors.
|
||||
try:
|
||||
value = event.fire()
|
||||
except socket.error as exc:
|
||||
except OSError as exc:
|
||||
if isinstance(exc.args, tuple) and \
|
||||
exc.args[0] == errno.EPIPE:
|
||||
# Broken pipe. Remote host disconnected.
|
||||
|
|
@ -390,7 +387,7 @@ class SocketClosedError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class Listener(object):
|
||||
class Listener:
|
||||
"""A socket wrapper object for listening sockets.
|
||||
"""
|
||||
def __init__(self, host, port):
|
||||
|
|
@ -420,7 +417,7 @@ class Listener(object):
|
|||
self.sock.close()
|
||||
|
||||
|
||||
class Connection(object):
|
||||
class Connection:
|
||||
"""A socket wrapper object for connected sockets.
|
||||
"""
|
||||
def __init__(self, sock, addr):
|
||||
|
|
@ -545,7 +542,7 @@ def spawn(coro):
|
|||
and child coroutines run concurrently.
|
||||
"""
|
||||
if not isinstance(coro, types.GeneratorType):
|
||||
raise ValueError(u'%s is not a coroutine' % coro)
|
||||
raise ValueError('%s is not a coroutine' % coro)
|
||||
return SpawnEvent(coro)
|
||||
|
||||
|
||||
|
|
@ -555,7 +552,7 @@ def call(coro):
|
|||
returns a value using end(), then this event returns that value.
|
||||
"""
|
||||
if not isinstance(coro, types.GeneratorType):
|
||||
raise ValueError(u'%s is not a coroutine' % coro)
|
||||
raise ValueError('%s is not a coroutine' % coro)
|
||||
return DelegationEvent(coro)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016-2019, Adrian Sampson.
|
||||
#
|
||||
|
|
@ -13,7 +12,6 @@
|
|||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import confuse
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
|
|
@ -13,7 +12,6 @@
|
|||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
|
|
@ -27,7 +26,6 @@ This is sort of like a tiny, horrible degeneration of a real templating
|
|||
engine like Jinja2 or Mustache.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import re
|
||||
import ast
|
||||
|
|
@ -37,18 +35,18 @@ import sys
|
|||
import six
|
||||
import functools
|
||||
|
||||
SYMBOL_DELIM = u'$'
|
||||
FUNC_DELIM = u'%'
|
||||
GROUP_OPEN = u'{'
|
||||
GROUP_CLOSE = u'}'
|
||||
ARG_SEP = u','
|
||||
ESCAPE_CHAR = u'$'
|
||||
SYMBOL_DELIM = '$'
|
||||
FUNC_DELIM = '%'
|
||||
GROUP_OPEN = '{'
|
||||
GROUP_CLOSE = '}'
|
||||
ARG_SEP = ','
|
||||
ESCAPE_CHAR = '$'
|
||||
|
||||
VARIABLE_PREFIX = '__var_'
|
||||
FUNCTION_PREFIX = '__func_'
|
||||
|
||||
|
||||
class Environment(object):
|
||||
class Environment:
|
||||
"""Contains the values and functions to be substituted into a
|
||||
template.
|
||||
"""
|
||||
|
|
@ -76,21 +74,21 @@ def ex_literal(val):
|
|||
if sys.version_info[:2] < (3, 4):
|
||||
if val is None:
|
||||
return ast.Name('None', ast.Load())
|
||||
elif isinstance(val, six.integer_types):
|
||||
elif isinstance(val, int):
|
||||
return ast.Num(val)
|
||||
elif isinstance(val, bool):
|
||||
return ast.Name(bytes(val), ast.Load())
|
||||
elif isinstance(val, six.string_types):
|
||||
elif isinstance(val, str):
|
||||
return ast.Str(val)
|
||||
raise TypeError(u'no literal for {0}'.format(type(val)))
|
||||
raise TypeError('no literal for {}'.format(type(val)))
|
||||
elif sys.version_info[:2] < (3, 6):
|
||||
if val in [None, True, False]:
|
||||
return ast.NameConstant(val)
|
||||
elif isinstance(val, six.integer_types):
|
||||
elif isinstance(val, int):
|
||||
return ast.Num(val)
|
||||
elif isinstance(val, six.string_types):
|
||||
elif isinstance(val, str):
|
||||
return ast.Str(val)
|
||||
raise TypeError(u'no literal for {0}'.format(type(val)))
|
||||
raise TypeError('no literal for {}'.format(type(val)))
|
||||
else:
|
||||
return ast.Constant(val)
|
||||
|
||||
|
|
@ -109,7 +107,7 @@ def ex_call(func, args):
|
|||
function may be an expression or the name of a function. Each
|
||||
argument may be an expression or a value to be used as a literal.
|
||||
"""
|
||||
if isinstance(func, six.string_types):
|
||||
if isinstance(func, str):
|
||||
func = ex_rvalue(func)
|
||||
|
||||
args = list(args)
|
||||
|
|
@ -170,14 +168,14 @@ def compile_func(arg_names, statements, name='_the_func', debug=False):
|
|||
|
||||
# AST nodes for the template language.
|
||||
|
||||
class Symbol(object):
|
||||
class Symbol:
|
||||
"""A variable-substitution symbol in a template."""
|
||||
def __init__(self, ident, original):
|
||||
self.ident = ident
|
||||
self.original = original
|
||||
|
||||
def __repr__(self):
|
||||
return u'Symbol(%s)' % repr(self.ident)
|
||||
return 'Symbol(%s)' % repr(self.ident)
|
||||
|
||||
def evaluate(self, env):
|
||||
"""Evaluate the symbol in the environment, returning a Unicode
|
||||
|
|
@ -194,10 +192,10 @@ class Symbol(object):
|
|||
"""Compile the variable lookup."""
|
||||
ident = self.ident
|
||||
expr = ex_rvalue(VARIABLE_PREFIX + ident)
|
||||
return [expr], set([ident]), set()
|
||||
return [expr], {ident}, set()
|
||||
|
||||
|
||||
class Call(object):
|
||||
class Call:
|
||||
"""A function call in a template."""
|
||||
def __init__(self, ident, args, original):
|
||||
self.ident = ident
|
||||
|
|
@ -205,7 +203,7 @@ class Call(object):
|
|||
self.original = original
|
||||
|
||||
def __repr__(self):
|
||||
return u'Call(%s, %s, %s)' % (repr(self.ident), repr(self.args),
|
||||
return 'Call({}, {}, {})'.format(repr(self.ident), repr(self.args),
|
||||
repr(self.original))
|
||||
|
||||
def evaluate(self, env):
|
||||
|
|
@ -219,15 +217,15 @@ class Call(object):
|
|||
except Exception as exc:
|
||||
# Function raised exception! Maybe inlining the name of
|
||||
# the exception will help debug.
|
||||
return u'<%s>' % six.text_type(exc)
|
||||
return six.text_type(out)
|
||||
return '<%s>' % str(exc)
|
||||
return str(out)
|
||||
else:
|
||||
return self.original
|
||||
|
||||
def translate(self):
|
||||
"""Compile the function call."""
|
||||
varnames = set()
|
||||
funcnames = set([self.ident])
|
||||
funcnames = {self.ident}
|
||||
|
||||
arg_exprs = []
|
||||
for arg in self.args:
|
||||
|
|
@ -238,11 +236,11 @@ class Call(object):
|
|||
# Create a subexpression that joins the result components of
|
||||
# the arguments.
|
||||
arg_exprs.append(ex_call(
|
||||
ast.Attribute(ex_literal(u''), 'join', ast.Load()),
|
||||
ast.Attribute(ex_literal(''), 'join', ast.Load()),
|
||||
[ex_call(
|
||||
'map',
|
||||
[
|
||||
ex_rvalue(six.text_type.__name__),
|
||||
ex_rvalue(str.__name__),
|
||||
ast.List(subexprs, ast.Load()),
|
||||
]
|
||||
)],
|
||||
|
|
@ -255,7 +253,7 @@ class Call(object):
|
|||
return [subexpr_call], varnames, funcnames
|
||||
|
||||
|
||||
class Expression(object):
|
||||
class Expression:
|
||||
"""Top-level template construct: contains a list of text blobs,
|
||||
Symbols, and Calls.
|
||||
"""
|
||||
|
|
@ -263,7 +261,7 @@ class Expression(object):
|
|||
self.parts = parts
|
||||
|
||||
def __repr__(self):
|
||||
return u'Expression(%s)' % (repr(self.parts))
|
||||
return 'Expression(%s)' % (repr(self.parts))
|
||||
|
||||
def evaluate(self, env):
|
||||
"""Evaluate the entire expression in the environment, returning
|
||||
|
|
@ -271,11 +269,11 @@ class Expression(object):
|
|||
"""
|
||||
out = []
|
||||
for part in self.parts:
|
||||
if isinstance(part, six.string_types):
|
||||
if isinstance(part, str):
|
||||
out.append(part)
|
||||
else:
|
||||
out.append(part.evaluate(env))
|
||||
return u''.join(map(six.text_type, out))
|
||||
return ''.join(map(str, out))
|
||||
|
||||
def translate(self):
|
||||
"""Compile the expression to a list of Python AST expressions, a
|
||||
|
|
@ -285,7 +283,7 @@ class Expression(object):
|
|||
varnames = set()
|
||||
funcnames = set()
|
||||
for part in self.parts:
|
||||
if isinstance(part, six.string_types):
|
||||
if isinstance(part, str):
|
||||
expressions.append(ex_literal(part))
|
||||
else:
|
||||
e, v, f = part.translate()
|
||||
|
|
@ -301,7 +299,7 @@ class ParseError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class Parser(object):
|
||||
class Parser:
|
||||
"""Parses a template expression string. Instantiate the class with
|
||||
the template source and call ``parse_expression``. The ``pos`` field
|
||||
will indicate the character after the expression finished and
|
||||
|
|
@ -329,7 +327,7 @@ class Parser(object):
|
|||
special_chars = (SYMBOL_DELIM, FUNC_DELIM, GROUP_OPEN, GROUP_CLOSE,
|
||||
ESCAPE_CHAR)
|
||||
special_char_re = re.compile(r'[%s]|\Z' %
|
||||
u''.join(re.escape(c) for c in special_chars))
|
||||
''.join(re.escape(c) for c in special_chars))
|
||||
escapable_chars = (SYMBOL_DELIM, FUNC_DELIM, GROUP_CLOSE, ARG_SEP)
|
||||
terminator_chars = (GROUP_CLOSE,)
|
||||
|
||||
|
|
@ -346,7 +344,7 @@ class Parser(object):
|
|||
if self.in_argument:
|
||||
extra_special_chars = (ARG_SEP,)
|
||||
special_char_re = re.compile(
|
||||
r'[%s]|\Z' % u''.join(
|
||||
r'[%s]|\Z' % ''.join(
|
||||
re.escape(c) for c in
|
||||
self.special_chars + extra_special_chars
|
||||
)
|
||||
|
|
@ -390,7 +388,7 @@ class Parser(object):
|
|||
|
||||
# Shift all characters collected so far into a single string.
|
||||
if text_parts:
|
||||
self.parts.append(u''.join(text_parts))
|
||||
self.parts.append(''.join(text_parts))
|
||||
text_parts = []
|
||||
|
||||
if char == SYMBOL_DELIM:
|
||||
|
|
@ -412,7 +410,7 @@ class Parser(object):
|
|||
|
||||
# If any parsed characters remain, shift them into a string.
|
||||
if text_parts:
|
||||
self.parts.append(u''.join(text_parts))
|
||||
self.parts.append(''.join(text_parts))
|
||||
|
||||
def parse_symbol(self):
|
||||
"""Parse a variable reference (like ``$foo`` or ``${foo}``)
|
||||
|
|
@ -567,7 +565,7 @@ def template(fmt):
|
|||
|
||||
|
||||
# External interface.
|
||||
class Template(object):
|
||||
class Template:
|
||||
"""A string template, including text, Symbols, and Calls.
|
||||
"""
|
||||
def __init__(self, template):
|
||||
|
|
@ -618,7 +616,7 @@ class Template(object):
|
|||
for funcname in funcnames:
|
||||
args[FUNCTION_PREFIX + funcname] = functions[funcname]
|
||||
parts = func(**args)
|
||||
return u''.join(parts)
|
||||
return ''.join(parts)
|
||||
|
||||
return wrapper_func
|
||||
|
||||
|
|
@ -627,9 +625,9 @@ class Template(object):
|
|||
|
||||
if __name__ == '__main__':
|
||||
import timeit
|
||||
_tmpl = Template(u'foo $bar %baz{foozle $bar barzle} $bar')
|
||||
_tmpl = Template('foo $bar %baz{foozle $bar barzle} $bar')
|
||||
_vars = {'bar': 'qux'}
|
||||
_funcs = {'baz': six.text_type.upper}
|
||||
_funcs = {'baz': str.upper}
|
||||
interp_time = timeit.timeit('_tmpl.interpret(_vars, _funcs)',
|
||||
'from __main__ import _tmpl, _vars, _funcs',
|
||||
number=10000)
|
||||
|
|
@ -638,4 +636,4 @@ if __name__ == '__main__':
|
|||
'from __main__ import _tmpl, _vars, _funcs',
|
||||
number=10000)
|
||||
print(comp_time)
|
||||
print(u'Speedup:', interp_time / comp_time)
|
||||
print('Speedup:', interp_time / comp_time)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
|
|
@ -14,7 +13,6 @@
|
|||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Simple library to work out if a file is hidden on different platforms."""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import os
|
||||
import stat
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
|
|
@ -32,7 +31,6 @@ To do so, pass an iterable of coroutines to the Pipeline constructor
|
|||
in place of any single coroutine.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
from six.moves import queue
|
||||
from threading import Thread, Lock
|
||||
|
|
@ -135,7 +133,7 @@ class CountedQueue(queue.Queue):
|
|||
_invalidate_queue(self, POISON, False)
|
||||
|
||||
|
||||
class MultiMessage(object):
|
||||
class MultiMessage:
|
||||
"""A message yielded by a pipeline stage encapsulating multiple
|
||||
values to be sent to the next stage.
|
||||
"""
|
||||
|
|
@ -211,7 +209,7 @@ def _allmsgs(obj):
|
|||
class PipelineThread(Thread):
|
||||
"""Abstract base class for pipeline-stage threads."""
|
||||
def __init__(self, all_threads):
|
||||
super(PipelineThread, self).__init__()
|
||||
super().__init__()
|
||||
self.abort_lock = Lock()
|
||||
self.abort_flag = False
|
||||
self.all_threads = all_threads
|
||||
|
|
@ -242,7 +240,7 @@ class FirstPipelineThread(PipelineThread):
|
|||
The coroutine should just be a generator.
|
||||
"""
|
||||
def __init__(self, coro, out_queue, all_threads):
|
||||
super(FirstPipelineThread, self).__init__(all_threads)
|
||||
super().__init__(all_threads)
|
||||
self.coro = coro
|
||||
self.out_queue = out_queue
|
||||
self.out_queue.acquire()
|
||||
|
|
@ -280,7 +278,7 @@ class MiddlePipelineThread(PipelineThread):
|
|||
last.
|
||||
"""
|
||||
def __init__(self, coro, in_queue, out_queue, all_threads):
|
||||
super(MiddlePipelineThread, self).__init__(all_threads)
|
||||
super().__init__(all_threads)
|
||||
self.coro = coro
|
||||
self.in_queue = in_queue
|
||||
self.out_queue = out_queue
|
||||
|
|
@ -328,7 +326,7 @@ class LastPipelineThread(PipelineThread):
|
|||
should yield nothing.
|
||||
"""
|
||||
def __init__(self, coro, in_queue, all_threads):
|
||||
super(LastPipelineThread, self).__init__(all_threads)
|
||||
super().__init__(all_threads)
|
||||
self.coro = coro
|
||||
self.in_queue = in_queue
|
||||
|
||||
|
|
@ -359,7 +357,7 @@ class LastPipelineThread(PipelineThread):
|
|||
return
|
||||
|
||||
|
||||
class Pipeline(object):
|
||||
class Pipeline:
|
||||
"""Represents a staged pattern of work. Each stage in the pipeline
|
||||
is a coroutine that receives messages from the previous stage and
|
||||
yields messages to be sent to the next stage.
|
||||
|
|
@ -369,7 +367,7 @@ class Pipeline(object):
|
|||
be at least two stages.
|
||||
"""
|
||||
if len(stages) < 2:
|
||||
raise ValueError(u'pipeline must have at least two stages')
|
||||
raise ValueError('pipeline must have at least two stages')
|
||||
self.stages = []
|
||||
for stage in stages:
|
||||
if isinstance(stage, (list, tuple)):
|
||||
|
|
@ -439,7 +437,7 @@ class Pipeline(object):
|
|||
exc_info = thread.exc_info
|
||||
if exc_info:
|
||||
# Make the exception appear as it was raised originally.
|
||||
six.reraise(exc_info[0], exc_info[1], exc_info[2])
|
||||
raise exc_info[1].with_traceback(exc_info[2])
|
||||
|
||||
def pull(self):
|
||||
"""Yield elements from the end of the pipeline. Runs the stages
|
||||
|
|
@ -474,14 +472,14 @@ if __name__ == '__main__':
|
|||
# in parallel.
|
||||
def produce():
|
||||
for i in range(5):
|
||||
print(u'generating %i' % i)
|
||||
print('generating %i' % i)
|
||||
time.sleep(1)
|
||||
yield i
|
||||
|
||||
def work():
|
||||
num = yield
|
||||
while True:
|
||||
print(u'processing %i' % num)
|
||||
print('processing %i' % num)
|
||||
time.sleep(2)
|
||||
num = yield num * 2
|
||||
|
||||
|
|
@ -489,7 +487,7 @@ if __name__ == '__main__':
|
|||
while True:
|
||||
num = yield
|
||||
time.sleep(1)
|
||||
print(u'received %i' % num)
|
||||
print('received %i' % num)
|
||||
|
||||
ts_start = time.time()
|
||||
Pipeline([produce(), work(), consume()]).run_sequential()
|
||||
|
|
@ -498,22 +496,22 @@ if __name__ == '__main__':
|
|||
ts_par = time.time()
|
||||
Pipeline([produce(), (work(), work()), consume()]).run_parallel()
|
||||
ts_end = time.time()
|
||||
print(u'Sequential time:', ts_seq - ts_start)
|
||||
print(u'Parallel time:', ts_par - ts_seq)
|
||||
print(u'Multiply-parallel time:', ts_end - ts_par)
|
||||
print('Sequential time:', ts_seq - ts_start)
|
||||
print('Parallel time:', ts_par - ts_seq)
|
||||
print('Multiply-parallel time:', ts_end - ts_par)
|
||||
print()
|
||||
|
||||
# Test a pipeline that raises an exception.
|
||||
def exc_produce():
|
||||
for i in range(10):
|
||||
print(u'generating %i' % i)
|
||||
print('generating %i' % i)
|
||||
time.sleep(1)
|
||||
yield i
|
||||
|
||||
def exc_work():
|
||||
num = yield
|
||||
while True:
|
||||
print(u'processing %i' % num)
|
||||
print('processing %i' % num)
|
||||
time.sleep(3)
|
||||
if num == 3:
|
||||
raise Exception()
|
||||
|
|
@ -522,6 +520,6 @@ if __name__ == '__main__':
|
|||
def exc_consume():
|
||||
while True:
|
||||
num = yield
|
||||
print(u'received %i' % num)
|
||||
print('received %i' % num)
|
||||
|
||||
Pipeline([exc_produce(), exc_work(), exc_consume()]).run_parallel(1)
|
||||
|
|
|
|||
Loading…
Reference in a new issue