diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a2aeb014a00..05cd5202658 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,3 +20,9 @@ language_version: 'python3.7' args: - --py3-plus +- repo: https://github.com/asottile/pyupgrade + rev: v2.1.0 + hooks: + - id: pyupgrade + args: + - --py3-plus diff --git a/compose/cli/colors.py b/compose/cli/colors.py index c6a869bf59d..a4983a9f51a 100644 --- a/compose/cli/colors.py +++ b/compose/cli/colors.py @@ -14,16 +14,16 @@ def get_pairs(): for i, name in enumerate(NAMES): - yield(name, str(30 + i)) - yield('intense_' + name, str(30 + i) + ';1') + yield (name, str(30 + i)) + yield ('intense_' + name, str(30 + i) + ';1') def ansi(code): - return '\033[{0}m'.format(code) + return '\033[{}m'.format(code) def ansi_color(code, s): - return '{0}{1}{2}'.format(ansi(code), s, ansi(0)) + return '{}{}{}'.format(ansi(code), s, ansi(0)) def make_color_fn(code): diff --git a/compose/cli/command.py b/compose/cli/command.py index f18f7663995..a1cb38afeae 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -149,15 +149,17 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False, def execution_context_labels(config_details, environment_file): extra_labels = [ - '{0}={1}'.format(LABEL_WORKING_DIR, os.path.abspath(config_details.working_dir)) + '{}={}'.format(LABEL_WORKING_DIR, os.path.abspath(config_details.working_dir)) ] if not use_config_from_stdin(config_details): - extra_labels.append('{0}={1}'.format(LABEL_CONFIG_FILES, config_files_label(config_details))) + extra_labels.append('{}={}'.format(LABEL_CONFIG_FILES, config_files_label(config_details))) if environment_file is not None: - extra_labels.append('{0}={1}'.format(LABEL_ENVIRONMENT_FILE, - os.path.normpath(environment_file))) + extra_labels.append('{}={}'.format( + LABEL_ENVIRONMENT_FILE, + os.path.normpath(environment_file)) + ) return extra_labels @@ -170,7 +172,8 @@ def use_config_from_stdin(config_details): def config_files_label(config_details): return ",".join( - map(str, (os.path.normpath(c.filename) for c in config_details.config_files))) + os.path.normpath(c.filename) for c in config_details.config_files + ) def get_project_name(working_dir, project_name=None, environment=None): diff --git a/compose/cli/docopt_command.py b/compose/cli/docopt_command.py index 856c9234818..d0ba7f67026 100644 --- a/compose/cli/docopt_command.py +++ b/compose/cli/docopt_command.py @@ -11,7 +11,7 @@ def docopt_full_help(docstring, *args, **kwargs): raise SystemExit(docstring) -class DocoptDispatcher(object): +class DocoptDispatcher: def __init__(self, command_class, options): self.command_class = command_class @@ -50,7 +50,7 @@ def get_handler(command_class, command): class NoSuchCommand(Exception): def __init__(self, command, supercommand): - super(NoSuchCommand, self).__init__("No such command: %s" % command) + super().__init__("No such command: %s" % command) self.command = command self.supercommand = supercommand diff --git a/compose/cli/errors.py b/compose/cli/errors.py index d1a47f07833..a807c7d1c78 100644 --- a/compose/cli/errors.py +++ b/compose/cli/errors.py @@ -26,11 +26,9 @@ class UserError(Exception): def __init__(self, msg): self.msg = dedent(msg).strip() - def __unicode__(self): + def __str__(self): return self.msg - __str__ = __unicode__ - class ConnectionError(Exception): pass diff --git a/compose/cli/formatter.py b/compose/cli/formatter.py index a59f0742c6a..ff81ee65163 100644 --- a/compose/cli/formatter.py +++ b/compose/cli/formatter.py @@ -1,15 +1,10 @@ import logging -import shutil +from shutil import get_terminal_size import texttable from compose.cli import colors -if hasattr(shutil, "get_terminal_size"): - from shutil import get_terminal_size -else: - from backports.shutil_get_terminal_size import get_terminal_size - def get_tty_width(): try: @@ -45,15 +40,15 @@ class ConsoleWarningFormatter(logging.Formatter): def get_level_message(self, record): separator = ': ' - if record.levelno == logging.WARNING: - return colors.yellow(record.levelname) + separator - if record.levelno == logging.ERROR: + if record.levelno >= logging.ERROR: return colors.red(record.levelname) + separator + if record.levelno >= logging.WARNING: + return colors.yellow(record.levelname) + separator return '' def format(self, record): if isinstance(record.msg, bytes): record.msg = record.msg.decode('utf-8') - message = super(ConsoleWarningFormatter, self).format(record) - return '{0}{1}'.format(self.get_level_message(record), message) + message = super().format(record) + return '{}{}'.format(self.get_level_message(record), message) diff --git a/compose/cli/log_printer.py b/compose/cli/log_printer.py index 100f3a8250d..cd9f73c2c0c 100644 --- a/compose/cli/log_printer.py +++ b/compose/cli/log_printer.py @@ -2,6 +2,7 @@ import sys from collections import namedtuple from itertools import cycle +from operator import attrgetter from queue import Empty from queue import Queue from threading import Thread @@ -13,7 +14,7 @@ from compose.utils import split_buffer -class LogPresenter(object): +class LogPresenter: def __init__(self, prefix_width, color_func): self.prefix_width = prefix_width @@ -50,7 +51,7 @@ def max_name_width(service_names, max_index_width=3): return max(len(name) for name in service_names) + max_index_width -class LogPrinter(object): +class LogPrinter: """Print logs from many containers to a single output stream.""" def __init__(self, @@ -133,7 +134,7 @@ def build_thread_map(initial_containers, presenters, thread_args): # Container order is unspecified, so they are sorted by name in order to make # container:presenter (log color) assignment deterministic when given a list of containers # with the same names. - for container in sorted(initial_containers, key=lambda c: c.name) + for container in sorted(initial_containers, key=attrgetter('name')) } @@ -194,9 +195,9 @@ def build_log_generator(container, log_args): def wait_on_exit(container): try: exit_code = container.wait() - return "%s exited with code %s\n" % (container.name, exit_code) + return "{} exited with code {}\n".format(container.name, exit_code) except APIError as e: - return "Unexpected API error for %s (HTTP code %s)\nResponse body:\n%s\n" % ( + return "Unexpected API error for {} (HTTP code {})\nResponse body:\n{}\n".format( container.name, e.response.status_code, e.response.text or '[empty]' ) diff --git a/compose/cli/main.py b/compose/cli/main.py index 714592a3236..d9e94020dc3 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -74,7 +74,7 @@ def main(): log.error(e.msg) sys.exit(1) except BuildError as e: - log.error("Service '%s' failed to build: %s" % (e.service.name, e.reason)) + log.error("Service '{}' failed to build: {}".format(e.service.name, e.reason)) sys.exit(1) except StreamOutputError as e: log.error(e) @@ -176,7 +176,7 @@ def parse_doc_section(name, source): return [s.strip() for s in pattern.findall(source)] -class TopLevelCommand(object): +class TopLevelCommand: """Define and run multi-container applications with Docker. Usage: @@ -547,7 +547,7 @@ def images(self, options): key=attrgetter('name')) if options['--quiet']: - for image in set(c.image for c in containers): + for image in {c.image for c in containers}: print(image.split(':')[1]) return @@ -1137,7 +1137,7 @@ def compute_service_exit_code(exit_value_from, attached_containers): attached_containers)) if not candidates: log.error( - 'No containers matching the spec "{0}" ' + 'No containers matching the spec "{}" ' 'were run.'.format(exit_value_from) ) return 2 @@ -1463,10 +1463,7 @@ def call_docker(args, dockeropts, environment): args = [executable_path] + tls_options + args log.debug(" ".join(map(pipes.quote, args))) - filtered_env = {} - for k, v in environment.items(): - if v is not None: - filtered_env[k] = environment[k] + filtered_env = {k: v for k, v in environment.items() if v is not None} return subprocess.call(args, env=filtered_env) diff --git a/compose/cli/utils.py b/compose/cli/utils.py index b91ab6a13c2..6a4615a9660 100644 --- a/compose/cli/utils.py +++ b/compose/cli/utils.py @@ -11,13 +11,6 @@ import compose from ..const import IS_WINDOWS_PLATFORM -# WindowsError is not defined on non-win32 platforms. Avoid runtime errors by -# defining it as OSError (its parent class) if missing. -try: - WindowsError -except NameError: - WindowsError = OSError - def yesno(prompt, default=None): """ @@ -58,7 +51,7 @@ def call_silently(*args, **kwargs): with open(os.devnull, 'w') as shutup: try: return subprocess.call(*args, stdout=shutup, stderr=shutup, **kwargs) - except WindowsError: + except OSError: # On Windows, subprocess.call() can still raise exceptions. Normalize # to POSIXy behaviour by returning a nonzero exit code. return 1 @@ -120,7 +113,7 @@ def generate_user_agent(): try: p_system = platform.system() p_release = platform.release() - except IOError: + except OSError: pass else: parts.append("{}/{}".format(p_system, p_release)) @@ -133,7 +126,7 @@ def human_readable_file_size(size): if order >= len(suffixes): order = len(suffixes) - 1 - return '{0:.4g} {1}'.format( + return '{:.4g} {}'.format( size / pow(10, order * 3), suffixes[order] ) diff --git a/compose/cli/verbose_proxy.py b/compose/cli/verbose_proxy.py index 1d2f28b5c37..c9340c4e0d2 100644 --- a/compose/cli/verbose_proxy.py +++ b/compose/cli/verbose_proxy.py @@ -6,13 +6,13 @@ def format_call(args, kwargs): args = (repr(a) for a in args) - kwargs = ("{0!s}={1!r}".format(*item) for item in kwargs.items()) - return "({0})".format(", ".join(chain(args, kwargs))) + kwargs = ("{!s}={!r}".format(*item) for item in kwargs.items()) + return "({})".format(", ".join(chain(args, kwargs))) def format_return(result, max_lines): if isinstance(result, (list, tuple, set)): - return "({0} with {1} items)".format(type(result).__name__, len(result)) + return "({} with {} items)".format(type(result).__name__, len(result)) if result: lines = pprint.pformat(result).split('\n') @@ -22,7 +22,7 @@ def format_return(result, max_lines): return result -class VerboseProxy(object): +class VerboseProxy: """Proxy all function calls to another class and log method name, arguments and return values for each call. """ diff --git a/compose/config/config.py b/compose/config/config.py index 61fad199f60..fc32579a66f 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -1,12 +1,13 @@ import functools -import io import logging import os import re import string import sys from collections import namedtuple +from itertools import chain from operator import attrgetter +from operator import itemgetter import yaml from cached_property import cached_property @@ -170,7 +171,7 @@ class ConfigDetails(namedtuple('_ConfigDetails', 'working_dir config_files envir def __new__(cls, working_dir, config_files, environment=None): if environment is None: environment = Environment.from_env_file(working_dir) - return super(ConfigDetails, cls).__new__( + return super().__new__( cls, working_dir, config_files, environment ) @@ -304,8 +305,8 @@ def validate_config_version(config_files): if main_file.version != next_file.version: raise ConfigurationError( - "Version mismatch: file {0} specifies version {1} but " - "extension file {2} uses version {3}".format( + "Version mismatch: file {} specifies version {} but " + "extension file {} uses version {}".format( main_file.filename, main_file.version, next_file.filename, @@ -584,7 +585,7 @@ def process_config_file(config_file, environment, service_name=None, interpolate return config_file -class ServiceExtendsResolver(object): +class ServiceExtendsResolver: def __init__(self, service_config, config_file, environment, already_seen=None): self.service_config = service_config self.working_dir = service_config.working_dir @@ -692,7 +693,7 @@ def resolve_build_args(buildargs, environment): def validate_extended_service_dict(service_dict, filename, service): - error_prefix = "Cannot extend service '%s' in %s:" % (service, filename) + error_prefix = "Cannot extend service '{}' in {}:".format(service, filename) if 'links' in service_dict: raise ConfigurationError( @@ -801,9 +802,9 @@ def process_ports(service_dict): def process_depends_on(service_dict): if 'depends_on' in service_dict and not isinstance(service_dict['depends_on'], dict): - service_dict['depends_on'] = dict([ - (svc, {'condition': 'service_started'}) for svc in service_dict['depends_on'] - ]) + service_dict['depends_on'] = { + svc: {'condition': 'service_started'} for svc in service_dict['depends_on'] + } return service_dict @@ -1142,9 +1143,9 @@ def merge_service_dicts(base, override, version): def merge_unique_items_lists(base, override): - override = [str(o) for o in override] - base = [str(b) for b in base] - return sorted(set().union(base, override)) + override = (str(o) for o in override) + base = (str(b) for b in base) + return sorted(set(chain(base, override))) def merge_healthchecks(base, override): @@ -1157,9 +1158,7 @@ def merge_healthchecks(base, override): def merge_ports(md, base, override): def parse_sequence_func(seq): - acc = [] - for item in seq: - acc.extend(ServicePort.parse(item)) + acc = [s for item in seq for s in ServicePort.parse(item)] return to_mapping(acc, 'merge_field') field = 'ports' @@ -1169,7 +1168,7 @@ def parse_sequence_func(seq): merged = parse_sequence_func(md.base.get(field, [])) merged.update(parse_sequence_func(md.override.get(field, []))) - md[field] = [item for item in sorted(merged.values(), key=lambda x: x.target)] + md[field] = [item for item in sorted(merged.values(), key=attrgetter("target"))] def merge_build(output, base, override): @@ -1241,8 +1240,8 @@ def merge_reservations(base, override): def merge_unique_objects_lists(base, override): - result = dict((json_hash(i), i) for i in base + override) - return [i[1] for i in sorted([(k, v) for k, v in result.items()], key=lambda x: x[0])] + result = {json_hash(i): i for i in base + override} + return [i[1] for i in sorted(((k, v) for k, v in result.items()), key=itemgetter(0))] def merge_blkio_config(base, override): @@ -1250,11 +1249,11 @@ def merge_blkio_config(base, override): md.merge_scalar('weight') def merge_blkio_limits(base, override): - index = dict((b['path'], b) for b in base) - for o in override: - index[o['path']] = o + get_path = itemgetter('path') + index = {get_path(b): b for b in base} + index.update((get_path(o), o) for o in override) - return sorted(list(index.values()), key=lambda x: x['path']) + return sorted(index.values(), key=get_path) for field in [ "device_read_bps", "device_read_iops", "device_write_bps", @@ -1375,7 +1374,7 @@ def resolve_volume_path(working_dir, volume): if host_path.startswith('.'): host_path = expand_path(working_dir, host_path) host_path = os.path.expanduser(host_path) - return u"{}:{}{}".format(host_path, container_path, (':' + mode if mode else '')) + return "{}:{}{}".format(host_path, container_path, (':' + mode if mode else '')) return container_path @@ -1518,13 +1517,13 @@ def has_uppercase(name): def load_yaml(filename, encoding=None, binary=True): try: - with io.open(filename, 'rb' if binary else 'r', encoding=encoding) as fh: + with open(filename, 'rb' if binary else 'r', encoding=encoding) as fh: return yaml.safe_load(fh) - except (IOError, yaml.YAMLError, UnicodeDecodeError) as e: + except (OSError, yaml.YAMLError, UnicodeDecodeError) as e: if encoding is None: # Sometimes the user's locale sets an encoding that doesn't match # the YAML files. Im such cases, retry once with the "default" # UTF-8 encoding return load_yaml(filename, encoding='utf-8-sig', binary=False) error_name = getattr(e, '__module__', '') + '.' + e.__class__.__name__ - raise ConfigurationError(u"{}: {}".format(error_name, e)) + raise ConfigurationError("{}: {}".format(error_name, e)) diff --git a/compose/config/environment.py b/compose/config/environment.py index 4526d0b3ef2..1780851fdcb 100644 --- a/compose/config/environment.py +++ b/compose/config/environment.py @@ -43,7 +43,7 @@ def env_vars_from_file(filename, interpolate=True): class Environment(dict): def __init__(self, *args, **kwargs): - super(Environment, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.missing_keys = [] self.silent = False @@ -81,11 +81,11 @@ def from_command_line(cls, parsed_env_opts): def __getitem__(self, key): try: - return super(Environment, self).__getitem__(key) + return super().__getitem__(key) except KeyError: if IS_WINDOWS_PLATFORM: try: - return super(Environment, self).__getitem__(key.upper()) + return super().__getitem__(key.upper()) except KeyError: pass if not self.silent and key not in self.missing_keys: @@ -98,20 +98,20 @@ def __getitem__(self, key): return "" def __contains__(self, key): - result = super(Environment, self).__contains__(key) + result = super().__contains__(key) if IS_WINDOWS_PLATFORM: return ( - result or super(Environment, self).__contains__(key.upper()) + result or super().__contains__(key.upper()) ) return result def get(self, key, *args, **kwargs): if IS_WINDOWS_PLATFORM: - return super(Environment, self).get( + return super().get( key, - super(Environment, self).get(key.upper(), *args, **kwargs) + super().get(key.upper(), *args, **kwargs) ) - return super(Environment, self).get(key, *args, **kwargs) + return super().get(key, *args, **kwargs) def get_boolean(self, key): # Convert a value to a boolean using "common sense" rules. diff --git a/compose/config/errors.py b/compose/config/errors.py index 7db079e9c78..b66433a7998 100644 --- a/compose/config/errors.py +++ b/compose/config/errors.py @@ -1,5 +1,3 @@ - - VERSION_EXPLANATION = ( 'You might be seeing this error because you\'re using the wrong Compose file version. ' 'Either specify a supported version (e.g "2.2" or "3.3") and place ' @@ -40,7 +38,7 @@ def msg(self): class ComposeFileNotFound(ConfigurationError): def __init__(self, supported_filenames): - super(ComposeFileNotFound, self).__init__(""" + super().__init__(""" Can't find a suitable configuration file in this directory or any parent. Are you in the right directory? @@ -51,7 +49,7 @@ def __init__(self, supported_filenames): class DuplicateOverrideFileFound(ConfigurationError): def __init__(self, override_filenames): self.override_filenames = override_filenames - super(DuplicateOverrideFileFound, self).__init__( + super().__init__( "Multiple override files found: {}. You may only use a single " "override file.".format(", ".join(override_filenames)) ) diff --git a/compose/config/interpolation.py b/compose/config/interpolation.py index 9f0fc48472d..a3a4a72d8ef 100644 --- a/compose/config/interpolation.py +++ b/compose/config/interpolation.py @@ -11,7 +11,7 @@ log = logging.getLogger(__name__) -class Interpolator(object): +class Interpolator: def __init__(self, templater, mapping): self.templater = templater @@ -31,15 +31,15 @@ def interpolate_environment_variables(version, config, section, environment): interpolator = Interpolator(TemplateWithDefaults, environment) def process_item(name, config_dict): - return dict( - (key, interpolate_value(name, key, val, section, interpolator)) + return { + key: interpolate_value(name, key, val, section, interpolator) for key, val in (config_dict or {}).items() - ) + } - return dict( - (name, process_item(name, config_dict or {})) + return { + name: process_item(name, config_dict or {}) for name, config_dict in config.items() - ) + } def get_config_path(config_key, section, name): @@ -75,10 +75,10 @@ def append(config_path, key): if isinstance(obj, str): return converter.convert(config_path, interpolator.interpolate(obj)) if isinstance(obj, dict): - return dict( - (key, recursive_interpolate(val, interpolator, append(config_path, key))) - for (key, val) in obj.items() - ) + return { + key: recursive_interpolate(val, interpolator, append(config_path, key)) + for key, val in obj.items() + } if isinstance(obj, list): return [recursive_interpolate(val, interpolator, config_path) for val in obj] return converter.convert(config_path, obj) @@ -135,7 +135,7 @@ def convert(mo): val = mapping[named] if isinstance(val, bytes): val = val.decode('utf-8') - return '%s' % (val,) + return '{}'.format(val) if mo.group('escaped') is not None: return self.delimiter if mo.group('invalid') is not None: @@ -224,7 +224,7 @@ def to_microseconds(v): return int(parse_nanoseconds_int(v) / 1000) -class ConversionMap(object): +class ConversionMap: map = { service_path('blkio_config', 'weight'): to_int, service_path('blkio_config', 'weight_device', 'weight'): to_int, diff --git a/compose/config/serialize.py b/compose/config/serialize.py index fe0d007a60f..3a0fd2643f6 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -123,7 +123,7 @@ def serialize_ns_time_value(value): result = (int(value), stage[1]) else: break - return '{0}{1}'.format(*result) + return '{}{}'.format(*result) def denormalize_service_dict(service_dict, version, image_digest=None): diff --git a/compose/config/sort_services.py b/compose/config/sort_services.py index a600139b2ac..5c7fd506794 100644 --- a/compose/config/sort_services.py +++ b/compose/config/sort_services.py @@ -21,7 +21,7 @@ def get_source_name_from_network_mode(network_mode, source_type): def get_service_names(links): - return [link.split(':')[0] for link in links] + return [link.split(':', 1)[0] for link in links] def get_service_names_from_volumes_from(volumes_from): diff --git a/compose/config/types.py b/compose/config/types.py index 0c654fa6f8d..f52b5654139 100644 --- a/compose/config/types.py +++ b/compose/config/types.py @@ -146,7 +146,7 @@ def normpath(path, win_host=False): return path -class MountSpec(object): +class MountSpec: options_map = { 'volume': { 'nocopy': 'no_copy' @@ -338,9 +338,9 @@ def merge_field(self): return self.source def repr(self): - return dict( - [(k, v) for k, v in zip(self._fields, self) if v is not None] - ) + return { + k: v for k, v in zip(self._fields, self) if v is not None + } class ServiceSecret(ServiceConfigBase): @@ -362,10 +362,7 @@ def __new__(cls, target, published, *args, **kwargs): if published: if isinstance(published, str) and '-' in published: # "x-y:z" format a, b = published.split('-', 1) - try: - int(a) - int(b) - except ValueError: + if not a.isdigit() or not b.isdigit(): raise ConfigurationError('Invalid published port: {}'.format(published)) else: try: @@ -373,7 +370,7 @@ def __new__(cls, target, published, *args, **kwargs): except ValueError: raise ConfigurationError('Invalid published port: {}'.format(published)) - return super(ServicePort, cls).__new__( + return super().__new__( cls, target, published, *args, **kwargs ) @@ -422,9 +419,9 @@ def merge_field(self): return (self.target, self.published, self.external_ip, self.protocol) def repr(self): - return dict( - [(k, v) for k, v in zip(self._fields, self) if v is not None] - ) + return { + k: v for k, v in zip(self._fields, self) if v is not None + } def legacy_repr(self): return normalize_port_dict(self.repr()) @@ -484,9 +481,9 @@ def parse(cls, value): if con[0] == 'seccomp' and con[1] != 'unconfined': try: - with open(unquote_path(con[1]), 'r') as f: + with open(unquote_path(con[1])) as f: seccomp_data = json.load(f) - except (IOError, ValueError) as e: + except (OSError, ValueError) as e: raise ConfigurationError('Error reading seccomp profile: {}'.format(e)) return cls( 'seccomp={}'.format(json.dumps(seccomp_data)), con[1] diff --git a/compose/config/validation.py b/compose/config/validation.py index 3161b239533..c5c58e7093d 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -100,7 +100,7 @@ def match_named_volumes(service_dict, project_volumes): for volume_spec in service_volumes: if volume_spec.is_named_volume and volume_spec.external not in project_volumes: raise ConfigurationError( - 'Named volume "{0}" is used in service "{1}" but no' + 'Named volume "{}" is used in service "{}" but no' ' declaration was found in the volumes section.'.format( volume_spec.repr(), service_dict.get('name') ) @@ -475,14 +475,14 @@ def get_schema_path(): def load_jsonschema(config_file): filename = os.path.join( get_schema_path(), - "config_schema_v{0}.json".format(config_file.version)) + "config_schema_v{}.json".format(config_file.version)) if not os.path.exists(filename): raise ConfigurationError( 'Version in "{}" is unsupported. {}' .format(config_file.filename, VERSION_EXPLANATION)) - with open(filename, "r") as fh: + with open(filename) as fh: return json.load(fh) @@ -502,7 +502,7 @@ def handle_errors(errors, format_error_func, filename): gone wrong. Process each error and pull out relevant information and re-write helpful error messages that are relevant. """ - errors = list(sorted(errors, key=str)) + errors = sorted(errors, key=str) if not errors: return diff --git a/compose/container.py b/compose/container.py index 18deabbda57..e0fb3bcccbf 100644 --- a/compose/container.py +++ b/compose/container.py @@ -12,7 +12,7 @@ from .version import ComposeVersion -class Container(object): +class Container: """ Represents a Docker container, constructed from the output of GET /containers/:id:/json. @@ -78,8 +78,8 @@ def service(self): @property def name_without_project(self): - if self.name.startswith('{0}_{1}'.format(self.project, self.service)): - return '{0}_{1}'.format(self.service, self.number if self.number is not None else self.slug) + if self.name.startswith('{}_{}'.format(self.project, self.service)): + return '{}_{}'.format(self.service, self.number if self.number is not None else self.slug) else: return self.name @@ -91,7 +91,7 @@ def number(self): number = self.labels.get(LABEL_CONTAINER_NUMBER) if not number: - raise ValueError("Container {0} does not have a {1} label".format( + raise ValueError("Container {} does not have a {} label".format( self.short_id, LABEL_CONTAINER_NUMBER)) return int(number) @@ -224,7 +224,7 @@ def get_value(dictionary, key): return reduce(get_value, key.split('.'), self.dictionary) def get_local_port(self, port, protocol='tcp'): - port = self.ports.get("%s/%s" % (port, protocol)) + port = self.ports.get("{}/{}".format(port, protocol)) return "{HostIp}:{HostPort}".format(**port[0]) if port else None def get_mount(self, mount_dest): @@ -266,7 +266,7 @@ def rename_to_tmp_name(self): """ if not self.name.startswith(self.short_id): self.client.rename( - self.id, '{0}_{1}'.format(self.short_id, self.name) + self.id, '{}_{}'.format(self.short_id, self.name) ) def inspect_if_not_inspected(self): @@ -309,7 +309,7 @@ def has_legacy_proj_name(self, project_name): ) def __repr__(self): - return '' % (self.name, self.id[:6]) + return ''.format(self.name, self.id[:6]) def __eq__(self, other): if type(self) != type(other): diff --git a/compose/errors.py b/compose/errors.py index 53065617360..d4fead251ad 100644 --- a/compose/errors.py +++ b/compose/errors.py @@ -1,5 +1,3 @@ - - class OperationFailedError(Exception): def __init__(self, reason): self.msg = reason @@ -17,14 +15,14 @@ def __init__(self, reason): class HealthCheckFailed(HealthCheckException): def __init__(self, container_id): - super(HealthCheckFailed, self).__init__( + super().__init__( 'Container "{}" is unhealthy.'.format(container_id) ) class NoHealthCheckConfigured(HealthCheckException): def __init__(self, service_name): - super(NoHealthCheckConfigured, self).__init__( + super().__init__( 'Service "{}" is missing a healthcheck configuration'.format( service_name ) diff --git a/compose/network.py b/compose/network.py index bc3ade16870..a67c703c01f 100644 --- a/compose/network.py +++ b/compose/network.py @@ -1,6 +1,7 @@ import logging import re from collections import OrderedDict +from operator import itemgetter from docker.errors import NotFound from docker.types import IPAMConfig @@ -24,7 +25,7 @@ ] -class Network(object): +class Network: def __init__(self, client, project, name, driver=None, driver_opts=None, ipam=None, external=False, internal=False, enable_ipv6=False, labels=None, custom_name=False): @@ -51,7 +52,7 @@ def ensure(self): try: self.inspect() log.debug( - 'Network {0} declared as external. No new ' + 'Network {} declared as external. No new ' 'network will be created.'.format(self.name) ) except NotFound: @@ -107,7 +108,7 @@ def inspect(self, legacy=False): def legacy_full_name(self): if self.custom_name: return self.name - return '{0}_{1}'.format( + return '{}_{}'.format( re.sub(r'[_-]', '', self.project), self.name ) @@ -115,7 +116,7 @@ def legacy_full_name(self): def full_name(self): if self.custom_name: return self.name - return '{0}_{1}'.format(self.project, self.name) + return '{}_{}'.format(self.project, self.name) @property def true_name(self): @@ -167,7 +168,7 @@ def create_ipam_config_from_dict(ipam_dict): class NetworkConfigChangedError(ConfigurationError): def __init__(self, net_name, property_name): - super(NetworkConfigChangedError, self).__init__( + super().__init__( 'Network "{}" needs to be recreated - {} has changed'.format( net_name, property_name ) @@ -258,7 +259,7 @@ def build_networks(name, config_data, client): return networks -class ProjectNetworks(object): +class ProjectNetworks: def __init__(self, networks, use_networking): self.networks = networks or {} @@ -299,10 +300,10 @@ def get_network_defs_for_service(service_dict): if 'network_mode' in service_dict: return {} networks = service_dict.get('networks', {'default': None}) - return dict( - (net, (config or {})) + return { + net: (config or {}) for net, config in networks.items() - ) + } def get_network_names_for_service(service_dict): @@ -328,4 +329,4 @@ def get_networks(service_dict, network_definitions): else: # Ensure Compose will pick a consistent primary network if no # priority is set - return OrderedDict(sorted(networks.items(), key=lambda t: t[0])) + return OrderedDict(sorted(networks.items(), key=itemgetter(0))) diff --git a/compose/parallel.py b/compose/parallel.py index 15c3ad572ab..acf9e4a84cf 100644 --- a/compose/parallel.py +++ b/compose/parallel.py @@ -25,7 +25,7 @@ STOP = object() -class GlobalLimit(object): +class GlobalLimit: """Simple class to hold a global semaphore limiter for a project. This class should be treated as a singleton that is instantiated when the project is. """ @@ -114,7 +114,7 @@ def _no_deps(x): return [] -class State(object): +class State: """ Holds the state of a partially-complete parallel operation. @@ -136,7 +136,7 @@ def pending(self): return set(self.objects) - self.started - self.finished - self.failed -class NoLimit(object): +class NoLimit: def __enter__(self): pass @@ -252,7 +252,7 @@ class UpstreamError(Exception): pass -class ParallelStreamWriter(object): +class ParallelStreamWriter: """Write out messages for operations happening in parallel. Each operation has its own line, and ANSI code characters are used diff --git a/compose/progress_stream.py b/compose/progress_stream.py index 8792ff287cb..3c03cc4b5b9 100644 --- a/compose/progress_stream.py +++ b/compose/progress_stream.py @@ -79,19 +79,19 @@ def print_output_event(event, stream, is_terminal): status = event.get('status', '') if 'progress' in event: - write_to_stream("%s %s%s" % (status, event['progress'], terminator), stream) + write_to_stream("{} {}{}".format(status, event['progress'], terminator), stream) elif 'progressDetail' in event: detail = event['progressDetail'] total = detail.get('total') if 'current' in detail and total: percentage = float(detail['current']) / float(total) * 100 - write_to_stream('%s (%.1f%%)%s' % (status, percentage, terminator), stream) + write_to_stream('{} ({:.1f}%){}'.format(status, percentage, terminator), stream) else: - write_to_stream('%s%s' % (status, terminator), stream) + write_to_stream('{}{}'.format(status, terminator), stream) elif 'stream' in event: - write_to_stream("%s%s" % (event['stream'], terminator), stream) + write_to_stream("{}{}".format(event['stream'], terminator), stream) else: - write_to_stream("%s%s\n" % (status, terminator), stream) + write_to_stream("{}{}\n".format(status, terminator), stream) def get_digest_from_pull(events): diff --git a/compose/project.py b/compose/project.py index 4c8b197f287..8a7c5598317 100644 --- a/compose/project.py +++ b/compose/project.py @@ -52,16 +52,16 @@ class OneOffFilter(enum.Enum): @classmethod def update_labels(cls, value, labels): if value == cls.only: - labels.append('{0}={1}'.format(LABEL_ONE_OFF, "True")) + labels.append('{}={}'.format(LABEL_ONE_OFF, "True")) elif value == cls.exclude: - labels.append('{0}={1}'.format(LABEL_ONE_OFF, "False")) + labels.append('{}={}'.format(LABEL_ONE_OFF, "False")) elif value == cls.include: pass else: raise ValueError("Invalid value for one_off: {}".format(repr(value))) -class Project(object): +class Project: """ A collection of services. """ @@ -77,7 +77,7 @@ def labels(self, one_off=OneOffFilter.exclude, legacy=False): name = self.name if legacy: name = re.sub(r'[_-]', '', name) - labels = ['{0}={1}'.format(LABEL_PROJECT, name)] + labels = ['{}={}'.format(LABEL_PROJECT, name)] OneOffFilter.update_labels(one_off, labels) return labels @@ -482,10 +482,10 @@ def build_container_event(event): 'action': event['status'], 'id': event['Actor']['ID'], 'service': container_attrs.get(LABEL_SERVICE), - 'attributes': dict([ - (k, v) for k, v in container_attrs.items() + 'attributes': { + k: v for k, v in container_attrs.items() if not k.startswith('com.docker.compose.') - ]), + }, 'container': container, } @@ -737,7 +737,7 @@ def _find(): return if remove_orphans: for ctnr in orphans: - log.info('Removing orphan container "{0}"'.format(ctnr.name)) + log.info('Removing orphan container "{}"'.format(ctnr.name)) try: ctnr.kill() except APIError: @@ -745,7 +745,7 @@ def _find(): ctnr.remove(force=True) else: log.warning( - 'Found orphan containers ({0}) for this project. If ' + 'Found orphan containers ({}) for this project. If ' 'you removed or renamed this service in your compose ' 'file, you can run this command with the ' '--remove-orphans flag to clean it up.'.format( @@ -816,16 +816,16 @@ def get_secrets(service, service_secrets, secret_defs): .format(service=service, secret=secret.source)) if secret_def.get('external'): - log.warning("Service \"{service}\" uses secret \"{secret}\" which is external. " - "External secrets are not available to containers created by " - "docker-compose.".format(service=service, secret=secret.source)) + log.warning('Service "{service}" uses secret "{secret}" which is external. ' + 'External secrets are not available to containers created by ' + 'docker-compose.'.format(service=service, secret=secret.source)) continue if secret.uid or secret.gid or secret.mode: log.warning( - "Service \"{service}\" uses secret \"{secret}\" with uid, " - "gid, or mode. These fields are not supported by this " - "implementation of the Compose file".format( + 'Service "{service}" uses secret "{secret}" with uid, ' + 'gid, or mode. These fields are not supported by this ' + 'implementation of the Compose file'.format( service=service, secret=secret.source ) ) @@ -833,8 +833,8 @@ def get_secrets(service, service_secrets, secret_defs): secret_file = secret_def.get('file') if not path.isfile(str(secret_file)): log.warning( - "Service \"{service}\" uses an undefined secret file \"{secret_file}\", " - "the following file should be created \"{secret_file}\"".format( + 'Service "{service}" uses an undefined secret file "{secret_file}", ' + 'the following file should be created "{secret_file}"'.format( service=service, secret_file=secret_file ) ) diff --git a/compose/service.py b/compose/service.py index 673b0b335e8..2be2db0ac84 100644 --- a/compose/service.py +++ b/compose/service.py @@ -163,7 +163,7 @@ class BuildAction(enum.Enum): skip = 2 -class Service(object): +class Service: def __init__( self, name, @@ -228,10 +228,10 @@ def get_container(self, number=1): """Return a :class:`compose.container.Container` for this service. The container must be active, and match `number`. """ - for container in self.containers(labels=['{0}={1}'.format(LABEL_CONTAINER_NUMBER, number)]): + for container in self.containers(labels=['{}={}'.format(LABEL_CONTAINER_NUMBER, number)]): return container - raise ValueError("No container found for %s_%s" % (self.name, number)) + raise ValueError("No container found for {}_{}".format(self.name, number)) def start(self, **options): containers = self.containers(stopped=True) @@ -621,7 +621,7 @@ def start_container(self, container, use_network_aliases=True): expl = binarystr_to_unicode(ex.explanation) if "driver failed programming external connectivity" in expl: log.warn("Host is already in use by another container") - raise OperationFailedError("Cannot start service %s: %s" % (self.name, expl)) + raise OperationFailedError("Cannot start service {}: {}".format(self.name, expl)) return container @property @@ -712,12 +712,12 @@ def get_dependency_configs(self): net_name = self.network_mode.service_name pid_namespace = self.pid_mode.service_name - configs = dict( - [(name, None) for name in self.get_linked_service_names()] + configs = { + name: None for name in self.get_linked_service_names() + } + configs.update( + (name, None) for name in self.get_volumes_from_names() ) - configs.update(dict( - [(name, None) for name in self.get_volumes_from_names()] - )) configs.update({net_name: None} if net_name else {}) configs.update({pid_namespace: None} if pid_namespace else {}) configs.update(self.options.get('depends_on', {})) @@ -838,9 +838,9 @@ def _get_container_create_options( add_config_hash = (not one_off and not override_options) slug = generate_random_id() if one_off else None - container_options = dict( - (k, self.options[k]) - for k in DOCKER_CONFIG_KEYS if k in self.options) + container_options = { + k: self.options[k] + for k in DOCKER_CONFIG_KEYS if k in self.options} override_volumes = override_options.pop('volumes', []) container_options.update(override_options) @@ -932,7 +932,7 @@ def _build_container_volume_options(self, previous_container, container_options, ) container_options['environment'].update(affinity) - container_options['volumes'] = dict((v.internal, {}) for v in container_volumes or {}) + container_options['volumes'] = {v.internal: {} for v in container_volumes or {}} if version_gte(self.client.api_version, '1.30'): override_options['mounts'] = [build_mount(v) for v in container_mounts] or None else: @@ -1134,9 +1134,9 @@ def can_be_built(self): def labels(self, one_off=False, legacy=False): proj_name = self.project if not legacy else re.sub(r'[_-]', '', self.project) return [ - '{0}={1}'.format(LABEL_PROJECT, proj_name), - '{0}={1}'.format(LABEL_SERVICE, self.name), - '{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False"), + '{}={}'.format(LABEL_PROJECT, proj_name), + '{}={}'.format(LABEL_SERVICE, self.name), + '{}={}'.format(LABEL_ONE_OFF, "True" if one_off else "False"), ] @property @@ -1153,7 +1153,7 @@ def get_container_name(self, service_name, number, slug=None): ext_links_origins = [link.split(':')[0] for link in self.options.get('external_links', [])] if container_name in ext_links_origins: raise DependencyError( - 'Service {0} has a self-referential external link: {1}'.format( + 'Service {} has a self-referential external link: {}'.format( self.name, container_name ) ) @@ -1208,11 +1208,9 @@ def _do_pull(self, repo, pull_kwargs, silent, ignore_pull_failures): output = self.client.pull(repo, **pull_kwargs) if silent: with open(os.devnull, 'w') as devnull: - for event in stream_output(output, devnull): - yield event + yield from stream_output(output, devnull) else: - for event in stream_output(output, sys.stdout): - yield event + yield from stream_output(output, sys.stdout) except (StreamOutputError, NotFound) as e: if not ignore_pull_failures: raise @@ -1230,7 +1228,7 @@ def pull(self, ignore_pull_failures=False, silent=False, stream=False): 'platform': self.platform, } if not silent: - log.info('Pulling %s (%s%s%s)...' % (self.name, repo, separator, tag)) + log.info('Pulling {} ({}{}{})...'.format(self.name, repo, separator, tag)) if kwargs['platform'] and version_lt(self.client.api_version, '1.35'): raise OperationFailedError( @@ -1248,7 +1246,7 @@ def push(self, ignore_push_failures=False): repo, tag, separator = parse_repository_tag(self.options['image']) tag = tag or 'latest' - log.info('Pushing %s (%s%s%s)...' % (self.name, repo, separator, tag)) + log.info('Pushing {} ({}{}{})...'.format(self.name, repo, separator, tag)) output = self.client.push(repo, tag=tag, stream=True) try: @@ -1310,7 +1308,7 @@ def short_id_alias_exists(container, network): return container.short_id in aliases -class PidMode(object): +class PidMode: def __init__(self, mode): self._mode = mode @@ -1350,7 +1348,7 @@ def __init__(self, container): self._mode = 'container:{}'.format(container.id) -class NetworkMode(object): +class NetworkMode: """A `standard` network mode (ex: host, bridge)""" service_name = None @@ -1365,7 +1363,7 @@ def id(self): mode = id -class ContainerNetworkMode(object): +class ContainerNetworkMode: """A network mode that uses a container's network stack.""" service_name = None @@ -1382,7 +1380,7 @@ def mode(self): return 'container:' + self.container.id -class ServiceNetworkMode(object): +class ServiceNetworkMode: """A network mode that uses a service's network stack.""" def __init__(self, service): @@ -1487,10 +1485,10 @@ def get_container_data_volumes(container, volumes_option, tmpfs_option, mounts_o volumes = [] volumes_option = volumes_option or [] - container_mounts = dict( - (mount['Destination'], mount) + container_mounts = { + mount['Destination']: mount for mount in container.get('Mounts') or {} - ) + } image_volumes = [ VolumeSpec.parse(volume) @@ -1542,9 +1540,9 @@ def get_container_data_volumes(container, volumes_option, tmpfs_option, mounts_o def warn_on_masked_volume(volumes_option, container_volumes, service): - container_volumes = dict( - (volume.internal, volume.external) - for volume in container_volumes) + container_volumes = { + volume.internal: volume.external + for volume in container_volumes} for volume in volumes_option: if ( @@ -1694,7 +1692,7 @@ def convert_blkio_config(blkio_config): continue arr = [] for item in blkio_config[field]: - arr.append(dict([(k.capitalize(), v) for k, v in item.items()])) + arr.append({k.capitalize(): v for k, v in item.items()}) result[field] = arr return result @@ -1706,7 +1704,7 @@ def rewrite_build_path(path): return path -class _CLIBuilder(object): +class _CLIBuilder: def __init__(self, progress): self._progress = progress @@ -1814,7 +1812,7 @@ def build(self, path, tag=None, quiet=False, fileobj=None, yield json.dumps({"stream": "{}{}\n".format(magic_word, image_id)}) -class _CommandBuilder(object): +class _CommandBuilder: def __init__(self): self._args = ["docker", "build"] diff --git a/compose/timeparse.py b/compose/timeparse.py index bdd9f611f3a..47744562519 100644 --- a/compose/timeparse.py +++ b/compose/timeparse.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- ''' timeparse.py (c) Will Roberts 1 February, 2014 @@ -54,14 +53,14 @@ def opt(x): NANO=opt(NANO), ) -MULTIPLIERS = dict([ - ('hours', 60 * 60), - ('mins', 60), - ('secs', 1), - ('milli', 1.0 / 1000), - ('micro', 1.0 / 1000.0 / 1000), - ('nano', 1.0 / 1000.0 / 1000.0 / 1000.0), -]) +MULTIPLIERS = { + 'hours': 60 * 60, + 'mins': 60, + 'secs': 1, + 'milli': 1.0 / 1000, + 'micro': 1.0 / 1000.0 / 1000, + 'nano': 1.0 / 1000.0 / 1000.0 / 1000.0, +} def timeparse(sval): @@ -90,4 +89,4 @@ def timeparse(sval): def cast(value): - return int(value, 10) if value.isdigit() else float(value) + return int(value) if value.isdigit() else float(value) diff --git a/compose/utils.py b/compose/utils.py index 8b5ab38d9a1..060ba50cc36 100644 --- a/compose/utils.py +++ b/compose/utils.py @@ -29,7 +29,7 @@ def stream_as_text(stream): yield data -def line_splitter(buffer, separator=u'\n'): +def line_splitter(buffer, separator='\n'): index = buffer.find(str(separator)) if index == -1: return None @@ -45,7 +45,7 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a): of the input. """ splitter = splitter or line_splitter - buffered = str('') + buffered = '' for data in stream_as_text(stream): buffered += data @@ -116,7 +116,7 @@ def parse_nanoseconds_int(value): def build_string_dict(source_dict): - return dict((k, str(v if v is not None else '')) for k, v in source_dict.items()) + return {k: str(v if v is not None else '') for k, v in source_dict.items()} def splitdrive(path): diff --git a/compose/volume.py b/compose/volume.py index d31417a550d..5f36e432ba9 100644 --- a/compose/volume.py +++ b/compose/volume.py @@ -1,5 +1,6 @@ import logging import re +from itertools import chain from docker.errors import NotFound from docker.utils import version_lt @@ -15,7 +16,7 @@ log = logging.getLogger(__name__) -class Volume(object): +class Volume: def __init__(self, client, project, name, driver=None, driver_opts=None, external=False, labels=None, custom_name=False): self.client = client @@ -57,13 +58,13 @@ def exists(self): def full_name(self): if self.custom_name: return self.name - return '{0}_{1}'.format(self.project.lstrip('-_'), self.name) + return '{}_{}'.format(self.project.lstrip('-_'), self.name) @property def legacy_full_name(self): if self.custom_name: return self.name - return '{0}_{1}'.format( + return '{}_{}'.format( re.sub(r'[_-]', '', self.project), self.name ) @@ -96,7 +97,7 @@ def _set_legacy_flag(self): self.legacy = False -class ProjectVolumes(object): +class ProjectVolumes: def __init__(self, volumes): self.volumes = volumes @@ -132,7 +133,7 @@ def initialize(self): volume_exists = volume.exists() if volume.external: log.debug( - 'Volume {0} declared as external. No new ' + 'Volume {} declared as external. No new ' 'volume will be created.'.format(volume.name) ) if not volume_exists: @@ -148,7 +149,7 @@ def initialize(self): if not volume_exists: log.info( - 'Creating volume "{0}" with {1} driver'.format( + 'Creating volume "{}" with {} driver'.format( volume.full_name, volume.driver or 'default' ) ) @@ -157,7 +158,7 @@ def initialize(self): check_remote_volume_config(volume.inspect(legacy=volume.legacy), volume) except NotFound: raise ConfigurationError( - 'Volume %s specifies nonexistent driver %s' % (volume.name, volume.driver) + 'Volume {} specifies nonexistent driver {}'.format(volume.name, volume.driver) ) def namespace_spec(self, volume_spec): @@ -174,7 +175,7 @@ def namespace_spec(self, volume_spec): class VolumeConfigChangedError(ConfigurationError): def __init__(self, local, property_name, local_value, remote_value): - super(VolumeConfigChangedError, self).__init__( + super().__init__( 'Configuration for volume {vol_name} specifies {property_name} ' '{local_value}, but a volume with the same name uses a different ' '{property_name} ({remote_value}). If you wish to use the new ' @@ -192,7 +193,7 @@ def check_remote_volume_config(remote, local): raise VolumeConfigChangedError(local, 'driver', local.driver, remote.get('Driver')) local_opts = local.driver_opts or {} remote_opts = remote.get('Options') or {} - for k in set.union(set(remote_opts.keys()), set(local_opts.keys())): + for k in set(chain(remote_opts, local_opts)): if k.startswith('com.docker.'): # These options are set internally continue if remote_opts.get(k) != local_opts.get(k): @@ -202,7 +203,7 @@ def check_remote_volume_config(remote, local): local_labels = local.labels or {} remote_labels = remote.get('Labels') or {} - for k in set.union(set(remote_labels.keys()), set(local_labels.keys())): + for k in set(chain(remote_labels, local_labels)): if k.startswith('com.docker.'): # We are only interested in user-specified labels continue if remote_labels.get(k) != local_labels.get(k): diff --git a/contrib/migration/migrate-compose-file-v1-to-v2.py b/contrib/migration/migrate-compose-file-v1-to-v2.py index e217b7072c3..26511206c5f 100755 --- a/contrib/migration/migrate-compose-file-v1-to-v2.py +++ b/contrib/migration/migrate-compose-file-v1-to-v2.py @@ -156,7 +156,7 @@ def main(args): opts = parse_opts(args) - with open(opts.filename, 'r') as fh: + with open(opts.filename) as fh: new_format = migrate(fh.read()) if opts.in_place: diff --git a/docker-compose_darwin.spec b/docker-compose_darwin.spec index df7fcdd6f66..62d84a9f5f6 100644 --- a/docker-compose_darwin.spec +++ b/docker-compose_darwin.spec @@ -1,4 +1,4 @@ -# -*- mode: python ; coding: utf-8 -*- +# -*- mode: python -*- block_cipher = None diff --git a/requirements.txt b/requirements.txt index 82eb84d4987..5657faa4350 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,8 +8,6 @@ docker==4.2.1 docker-pycreds==0.4.0 dockerpty==0.4.1 docopt==0.6.2 -enum34==1.1.6; python_version < '3.4' -functools32==3.2.3.post2; python_version < '3.2' idna==2.8 ipaddress==1.0.23 jsonschema==3.2.0 diff --git a/script/release/utils.py b/script/release/utils.py index 25b39ca7497..5ed53ec85b3 100644 --- a/script/release/utils.py +++ b/script/release/utils.py @@ -6,7 +6,7 @@ def update_init_py_version(version): path = os.path.join(REPO_ROOT, 'compose', '__init__.py') - with open(path, 'r') as f: + with open(path) as f: contents = f.read() contents = re.sub(r"__version__ = '[0-9a-z.-]+'", "__version__ = '{}'".format(version), contents) with open(path, 'w') as f: @@ -15,7 +15,7 @@ def update_init_py_version(version): def update_run_sh_version(version): path = os.path.join(REPO_ROOT, 'script', 'run', 'run.sh') - with open(path, 'r') as f: + with open(path) as f: contents = f.read() contents = re.sub(r'VERSION="[0-9a-z.-]+"', 'VERSION="{}"'.format(version), contents) with open(path, 'w') as f: diff --git a/setup.py b/setup.py index 79809b52432..6d8d70dd699 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import codecs import os import re @@ -50,7 +49,6 @@ def find_version(*file_paths): tests_require.append('mock >= 1.0.1, < 4') extras_require = { - ':python_version < "3.2"': ['subprocess32 >= 3.5.4, < 4'], ':python_version < "3.4"': ['enum34 >= 1.0.4, < 2'], ':python_version < "3.5"': ['backports.ssl_match_hostname >= 3.5, < 4'], ':python_version < "3.3"': ['backports.shutil_get_terminal_size == 1.0.0', @@ -94,7 +92,7 @@ def find_version(*file_paths): install_requires=install_requires, extras_require=extras_require, tests_require=tests_require, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.0, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', entry_points={ 'console_scripts': ['docker-compose=compose.cli.main:main'], }, diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index c84d3f8cb99..3aa4e1130e0 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import datetime import json import os.path @@ -101,7 +100,7 @@ def kill_service(service): container.kill() -class ContainerCountCondition(object): +class ContainerCountCondition: def __init__(self, project, expected): self.project = project @@ -114,7 +113,7 @@ def __str__(self): return "waiting for counter count == %s" % self.expected -class ContainerStateCondition(object): +class ContainerStateCondition: def __init__(self, client, name, status): self.client = client @@ -142,7 +141,7 @@ def __str__(self): class CLITestCase(DockerClientTestCase): def setUp(self): - super(CLITestCase, self).setUp() + super().setUp() self.base_dir = 'tests/fixtures/simple-composefile' self.override_dir = None @@ -164,7 +163,7 @@ def tearDown(self): if hasattr(self, '_project'): del self._project - super(CLITestCase, self).tearDown() + super().tearDown() @property def project(self): @@ -208,14 +207,14 @@ def test_help_nonexistent(self): def test_shorthand_host_opt(self): self.dispatch( - ['-H={0}'.format(os.environ.get('DOCKER_HOST', 'unix://')), + ['-H={}'.format(os.environ.get('DOCKER_HOST', 'unix://')), 'up', '-d'], returncode=0 ) def test_shorthand_host_opt_interactive(self): self.dispatch( - ['-H={0}'.format(os.environ.get('DOCKER_HOST', 'unix://')), + ['-H={}'.format(os.environ.get('DOCKER_HOST', 'unix://')), 'run', 'another', 'ls'], returncode=0 ) @@ -1454,7 +1453,7 @@ def test_up_with_volume_labels(self): if v['Name'].split('/')[-1].startswith('{}_'.format(self.project.name)) ] - assert set([v['Name'].split('/')[-1] for v in volumes]) == {volume_with_label} + assert {v['Name'].split('/')[-1] for v in volumes} == {volume_with_label} assert 'label_key' in volumes[0]['Labels'] assert volumes[0]['Labels']['label_key'] == 'label_val' @@ -1841,12 +1840,12 @@ def test_run_without_command(self): self.dispatch(['run', 'implicit']) service = self.project.get_service('implicit') containers = service.containers(stopped=True, one_off=OneOffFilter.only) - assert [c.human_readable_command for c in containers] == [u'/bin/sh -c echo "success"'] + assert [c.human_readable_command for c in containers] == ['/bin/sh -c echo "success"'] self.dispatch(['run', 'explicit']) service = self.project.get_service('explicit') containers = service.containers(stopped=True, one_off=OneOffFilter.only) - assert [c.human_readable_command for c in containers] == [u'/bin/true'] + assert [c.human_readable_command for c in containers] == ['/bin/true'] @pytest.mark.skipif(SWARM_SKIP_RM_VOLUMES, reason='Swarm DELETE /containers/ bug') def test_run_rm(self): @@ -2685,7 +2684,7 @@ def has_timestamp(string): str_iso_date, str_iso_time, container_info = string.split(' ', 2) try: return isinstance(datetime.datetime.strptime( - '%s %s' % (str_iso_date, str_iso_time), + '{} {}'.format(str_iso_date, str_iso_time), '%Y-%m-%d %H:%M:%S.%f'), datetime.datetime) except ValueError: @@ -2774,7 +2773,7 @@ def test_up_with_extends(self): self.base_dir = 'tests/fixtures/extends' self.dispatch(['up', '-d'], None) - assert set([s.name for s in self.project.services]) == {'mydb', 'myweb'} + assert {s.name for s in self.project.services} == {'mydb', 'myweb'} # Sort by name so we get [db, web] containers = sorted( diff --git a/tests/acceptance/context_test.py b/tests/acceptance/context_test.py index e17e7171785..a5d0c14730f 100644 --- a/tests/acceptance/context_test.py +++ b/tests/acceptance/context_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import shutil import unittest diff --git a/tests/helpers.py b/tests/helpers.py index d178684853b..3642e6ebc5f 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -49,7 +49,7 @@ def create_custom_host_file(client, filename, content): def create_host_file(client, filename): - with open(filename, 'r') as fh: + with open(filename) as fh: content = fh.read() return create_custom_host_file(client, filename, content) diff --git a/tests/integration/environment_test.py b/tests/integration/environment_test.py index 43df2c52b03..12a969c9428 100644 --- a/tests/integration/environment_test.py +++ b/tests/integration/environment_test.py @@ -15,7 +15,7 @@ class EnvironmentTest(DockerClientTestCase): @classmethod def setUpClass(cls): - super(EnvironmentTest, cls).setUpClass() + super().setUpClass() cls.compose_file = tempfile.NamedTemporaryFile(mode='w+b') cls.compose_file.write(bytes("""version: '3.2' services: @@ -27,7 +27,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - super(EnvironmentTest, cls).tearDownClass() + super().tearDownClass() cls.compose_file.close() @data('events', diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 19d27185c9b..062a46db74c 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -303,19 +303,19 @@ def test_start_pause_unpause_stop_kill_remove(self): db_container = db.create_container() project.start(service_names=['web']) - assert set(c.name for c in project.containers() if c.is_running) == { + assert {c.name for c in project.containers() if c.is_running} == { web_container_1.name, web_container_2.name} project.start() - assert set(c.name for c in project.containers() if c.is_running) == { + assert {c.name for c in project.containers() if c.is_running} == { web_container_1.name, web_container_2.name, db_container.name} project.pause(service_names=['web']) - assert set([c.name for c in project.containers() if c.is_paused]) == { + assert {c.name for c in project.containers() if c.is_paused} == { web_container_1.name, web_container_2.name} project.pause() - assert set([c.name for c in project.containers() if c.is_paused]) == { + assert {c.name for c in project.containers() if c.is_paused} == { web_container_1.name, web_container_2.name, db_container.name} project.unpause(service_names=['db']) @@ -325,7 +325,7 @@ def test_start_pause_unpause_stop_kill_remove(self): assert len([c.name for c in project.containers() if c.is_paused]) == 0 project.stop(service_names=['web'], timeout=1) - assert set(c.name for c in project.containers() if c.is_running) == {db_container.name} + assert {c.name for c in project.containers() if c.is_running} == {db_container.name} project.kill(service_names=['db']) assert len([c for c in project.containers() if c.is_running]) == 0 @@ -1225,8 +1225,8 @@ def test_project_up_with_network_label(self): @v2_only() def test_project_up_volumes(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) config_data = build_config( version=V2_0, services=[{ @@ -1283,9 +1283,9 @@ def test_project_up_with_volume_labels(self): if v['Name'].split('/')[-1].startswith('composetest_') ] - assert set([v['Name'].split('/')[-1] for v in volumes]) == set( - ['composetest_{}'.format(volume_name)] - ) + assert {v['Name'].split('/')[-1] for v in volumes} == { + 'composetest_{}'.format(volume_name) + } assert 'label_key' in volumes[0]['Labels'] assert volumes[0]['Labels']['label_key'] == 'label_val' @@ -1408,8 +1408,8 @@ def test_project_up_config_scale(self): @v2_only() def test_initialize_volumes(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) config_data = build_config( version=V2_0, services=[{ @@ -1432,8 +1432,8 @@ def test_initialize_volumes(self): @v2_only() def test_project_up_implicit_volume_driver(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) config_data = build_config( version=V2_0, services=[{ @@ -1547,7 +1547,7 @@ def test_project_up_with_added_secrets(self): @v2_only() def test_initialize_volumes_invalid_volume_driver(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) + vol_name = '{:x}'.format(random.getrandbits(32)) config_data = build_config( version=V2_0, @@ -1569,8 +1569,8 @@ def test_initialize_volumes_invalid_volume_driver(self): @v2_only() @no_cluster('inspect volume by name defect on Swarm Classic') def test_initialize_volumes_updated_driver(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) config_data = build_config( version=V2_0, @@ -1601,15 +1601,15 @@ def test_initialize_volumes_updated_driver(self): ) with pytest.raises(config.ConfigurationError) as e: project.volumes.initialize() - assert 'Configuration for volume {0} specifies driver smb'.format( + assert 'Configuration for volume {} specifies driver smb'.format( vol_name ) in str(e.value) @v2_only() @no_cluster('inspect volume by name defect on Swarm Classic') def test_initialize_volumes_updated_driver_opts(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) tmpdir = tempfile.mkdtemp(prefix='compose_test_') self.addCleanup(shutil.rmtree, tmpdir) driver_opts = {'o': 'bind', 'device': tmpdir, 'type': 'none'} @@ -1647,14 +1647,14 @@ def test_initialize_volumes_updated_driver_opts(self): ) with pytest.raises(config.ConfigurationError) as e: project.volumes.initialize() - assert 'Configuration for volume {0} specifies "device" driver_opt {1}'.format( + assert 'Configuration for volume {} specifies "device" driver_opt {}'.format( vol_name, driver_opts['device'] ) in str(e.value) @v2_only() def test_initialize_volumes_updated_blank_driver(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) config_data = build_config( version=V2_0, @@ -1692,8 +1692,8 @@ def test_initialize_volumes_updated_blank_driver(self): @no_cluster('inspect volume by name defect on Swarm Classic') def test_initialize_volumes_external_volumes(self): # Use composetest_ prefix so it gets garbage-collected in tearDown() - vol_name = 'composetest_{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = 'composetest_{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) self.client.create_volume(vol_name) config_data = build_config( version=V2_0, @@ -1717,7 +1717,7 @@ def test_initialize_volumes_external_volumes(self): @v2_only() def test_initialize_volumes_inexistent_external_volume(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) + vol_name = '{:x}'.format(random.getrandbits(32)) config_data = build_config( version=V2_0, @@ -1736,14 +1736,14 @@ def test_initialize_volumes_inexistent_external_volume(self): ) with pytest.raises(config.ConfigurationError) as e: project.volumes.initialize() - assert 'Volume {0} declared as external'.format( + assert 'Volume {} declared as external'.format( vol_name ) in str(e.value) @v2_only() def test_project_up_named_volumes_in_binds(self): - vol_name = '{0:x}'.format(random.getrandbits(32)) - full_vol_name = 'composetest_{0}'.format(vol_name) + vol_name = '{:x}'.format(random.getrandbits(32)) + full_vol_name = 'composetest_{}'.format(vol_name) base_file = config.ConfigFile( 'base.yml', @@ -1753,7 +1753,7 @@ def test_project_up_named_volumes_in_binds(self): 'simple': { 'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top', - 'volumes': ['{0}:/data'.format(vol_name)] + 'volumes': ['{}:/data'.format(vol_name)] }, }, 'volumes': { diff --git a/tests/integration/resilience_test.py b/tests/integration/resilience_test.py index 81cb2382fe2..2fbaafb2894 100644 --- a/tests/integration/resilience_test.py +++ b/tests/integration/resilience_test.py @@ -22,7 +22,7 @@ def setUp(self): def tearDown(self): del self.project del self.db - super(ResilienceTest, self).tearDown() + super().tearDown() def test_successful_recreate(self): self.project.up(strategy=ConvergenceStrategy.always) diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 9830accb85c..146eca2b534 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -255,7 +255,7 @@ def test_create_container_with_security_opt(self): service = self.create_service('db', security_opt=security_opt) container = service.create_container() service.start_container(container) - assert set(container.get('HostConfig.SecurityOpt')) == set([o.repr() for o in security_opt]) + assert set(container.get('HostConfig.SecurityOpt')) == {o.repr() for o in security_opt} @pytest.mark.xfail(True, reason='Not supported on most drivers') def test_create_container_with_storage_opt(self): @@ -297,7 +297,7 @@ def test_create_container_with_specified_volume(self): actual_host_path = container.get_mount(container_path)['Source'] assert path.basename(actual_host_path) == path.basename(host_path), ( - "Last component differs: %s, %s" % (actual_host_path, host_path) + "Last component differs: {}, {}".format(actual_host_path, host_path) ) @v2_3_only() @@ -858,11 +858,11 @@ def test_start_container_creates_links(self): db2 = create_and_start_container(db) create_and_start_container(web) - assert set(get_links(web.containers()[0])) == set([ + assert set(get_links(web.containers()[0])) == { db1.name, db1.name_without_project, db2.name, db2.name_without_project, 'db' - ]) + } @no_cluster('No legacy links support in Swarm') def test_start_container_creates_links_with_names(self): @@ -873,11 +873,11 @@ def test_start_container_creates_links_with_names(self): db2 = create_and_start_container(db) create_and_start_container(web) - assert set(get_links(web.containers()[0])) == set([ + assert set(get_links(web.containers()[0])) == { db1.name, db1.name_without_project, db2.name, db2.name_without_project, 'custom_link_name' - ]) + } @no_cluster('No legacy links support in Swarm') def test_start_container_with_external_links(self): @@ -893,11 +893,11 @@ def test_start_container_with_external_links(self): create_and_start_container(web) - assert set(get_links(web.containers()[0])) == set([ + assert set(get_links(web.containers()[0])) == { db_ctnrs[0].name, db_ctnrs[1].name, 'db_3' - ]) + } @no_cluster('No legacy links support in Swarm') def test_start_normal_container_does_not_create_links_to_its_own_service(self): @@ -907,7 +907,7 @@ def test_start_normal_container_does_not_create_links_to_its_own_service(self): create_and_start_container(db) c = create_and_start_container(db) - assert set(get_links(c)) == set([]) + assert set(get_links(c)) == set() @no_cluster('No legacy links support in Swarm') def test_start_one_off_container_creates_links_to_its_own_service(self): @@ -918,11 +918,11 @@ def test_start_one_off_container_creates_links_to_its_own_service(self): c = create_and_start_container(db, one_off=OneOffFilter.only) - assert set(get_links(c)) == set([ + assert set(get_links(c)) == { db1.name, db1.name_without_project, db2.name, db2.name_without_project, 'db' - ]) + } def test_start_container_builds_images(self): service = Service( @@ -1729,14 +1729,14 @@ def test_duplicate_containers(self): options = service._get_container_create_options({}, service._next_container_number()) original = Container.create(service.client, **options) - assert set(service.containers(stopped=True)) == set([original]) + assert set(service.containers(stopped=True)) == {original} assert set(service.duplicate_containers()) == set() options['name'] = 'temporary_container_name' duplicate = Container.create(service.client, **options) - assert set(service.containers(stopped=True)) == set([original, duplicate]) - assert set(service.duplicate_containers()) == set([duplicate]) + assert set(service.containers(stopped=True)) == {original, duplicate} + assert set(service.duplicate_containers()) == {duplicate} def converge(service, strategy=ConvergenceStrategy.changed): diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py index 611d0cc9408..5258e310cfa 100644 --- a/tests/integration/state_test.py +++ b/tests/integration/state_test.py @@ -39,7 +39,7 @@ def make_project(self, cfg): class BasicProjectTest(ProjectTestCase): def setUp(self): - super(BasicProjectTest, self).setUp() + super().setUp() self.cfg = { 'db': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'}, @@ -95,7 +95,7 @@ def test_all_change(self): class ProjectWithDependenciesTest(ProjectTestCase): def setUp(self): - super(ProjectWithDependenciesTest, self).setUp() + super().setUp() self.cfg = { 'db': { @@ -116,7 +116,7 @@ def setUp(self): def test_up(self): containers = self.run_up(self.cfg) - assert set(c.service for c in containers) == set(['db', 'web', 'nginx']) + assert {c.service for c in containers} == {'db', 'web', 'nginx'} def test_change_leaf(self): old_containers = self.run_up(self.cfg) @@ -124,7 +124,7 @@ def test_change_leaf(self): self.cfg['nginx']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(self.cfg) - assert set(c.service for c in new_containers - old_containers) == set(['nginx']) + assert {c.service for c in new_containers - old_containers} == {'nginx'} def test_change_middle(self): old_containers = self.run_up(self.cfg) @@ -132,7 +132,7 @@ def test_change_middle(self): self.cfg['web']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(self.cfg) - assert set(c.service for c in new_containers - old_containers) == set(['web']) + assert {c.service for c in new_containers - old_containers} == {'web'} def test_change_middle_always_recreate_deps(self): old_containers = self.run_up(self.cfg, always_recreate_deps=True) @@ -140,7 +140,7 @@ def test_change_middle_always_recreate_deps(self): self.cfg['web']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(self.cfg, always_recreate_deps=True) - assert set(c.service for c in new_containers - old_containers) == {'web', 'nginx'} + assert {c.service for c in new_containers - old_containers} == {'web', 'nginx'} def test_change_root(self): old_containers = self.run_up(self.cfg) @@ -148,7 +148,7 @@ def test_change_root(self): self.cfg['db']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(self.cfg) - assert set(c.service for c in new_containers - old_containers) == set(['db']) + assert {c.service for c in new_containers - old_containers} == {'db'} def test_change_root_always_recreate_deps(self): old_containers = self.run_up(self.cfg, always_recreate_deps=True) @@ -156,7 +156,7 @@ def test_change_root_always_recreate_deps(self): self.cfg['db']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(self.cfg, always_recreate_deps=True) - assert set(c.service for c in new_containers - old_containers) == { + assert {c.service for c in new_containers - old_containers} == { 'db', 'web', 'nginx' } @@ -213,7 +213,7 @@ def test_service_recreated_when_dependency_created(self): class ProjectWithDependsOnDependenciesTest(ProjectTestCase): def setUp(self): - super(ProjectWithDependsOnDependenciesTest, self).setUp() + super().setUp() self.cfg = { 'version': '2', @@ -238,7 +238,7 @@ def setUp(self): def test_up(self): local_cfg = copy.deepcopy(self.cfg) containers = self.run_up(local_cfg) - assert set(c.service for c in containers) == set(['db', 'web', 'nginx']) + assert {c.service for c in containers} == {'db', 'web', 'nginx'} def test_change_leaf(self): local_cfg = copy.deepcopy(self.cfg) @@ -247,7 +247,7 @@ def test_change_leaf(self): local_cfg['services']['nginx']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(local_cfg) - assert set(c.service for c in new_containers - old_containers) == set(['nginx']) + assert {c.service for c in new_containers - old_containers} == {'nginx'} def test_change_middle(self): local_cfg = copy.deepcopy(self.cfg) @@ -256,7 +256,7 @@ def test_change_middle(self): local_cfg['services']['web']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(local_cfg) - assert set(c.service for c in new_containers - old_containers) == set(['web']) + assert {c.service for c in new_containers - old_containers} == {'web'} def test_change_middle_always_recreate_deps(self): local_cfg = copy.deepcopy(self.cfg) @@ -265,7 +265,7 @@ def test_change_middle_always_recreate_deps(self): local_cfg['services']['web']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(local_cfg, always_recreate_deps=True) - assert set(c.service for c in new_containers - old_containers) == set(['web', 'nginx']) + assert {c.service for c in new_containers - old_containers} == {'web', 'nginx'} def test_change_root(self): local_cfg = copy.deepcopy(self.cfg) @@ -274,7 +274,7 @@ def test_change_root(self): local_cfg['services']['db']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(local_cfg) - assert set(c.service for c in new_containers - old_containers) == set(['db']) + assert {c.service for c in new_containers - old_containers} == {'db'} def test_change_root_always_recreate_deps(self): local_cfg = copy.deepcopy(self.cfg) @@ -283,7 +283,7 @@ def test_change_root_always_recreate_deps(self): local_cfg['services']['db']['environment'] = {'NEW_VAR': '1'} new_containers = self.run_up(local_cfg, always_recreate_deps=True) - assert set(c.service for c in new_containers - old_containers) == set(['db', 'web', 'nginx']) + assert {c.service for c in new_containers - old_containers} == {'db', 'web', 'nginx'} def test_change_root_no_recreate(self): local_cfg = copy.deepcopy(self.cfg) @@ -303,24 +303,24 @@ def test_service_removed_while_down(self): del next_cfg['services']['web']['depends_on'] containers = self.run_up(local_cfg) - assert set(c.service for c in containers) == set(['db', 'web', 'nginx']) + assert {c.service for c in containers} == {'db', 'web', 'nginx'} project = self.make_project(local_cfg) project.stop(timeout=1) next_containers = self.run_up(next_cfg) - assert set(c.service for c in next_containers) == set(['web', 'nginx']) + assert {c.service for c in next_containers} == {'web', 'nginx'} def test_service_removed_while_up(self): local_cfg = copy.deepcopy(self.cfg) containers = self.run_up(local_cfg) - assert set(c.service for c in containers) == set(['db', 'web', 'nginx']) + assert {c.service for c in containers} == {'db', 'web', 'nginx'} del local_cfg['services']['db'] del local_cfg['services']['web']['depends_on'] containers = self.run_up(local_cfg) - assert set(c.service for c in containers) == set(['web', 'nginx']) + assert {c.service for c in containers} == {'web', 'nginx'} def test_dependency_removed(self): local_cfg = copy.deepcopy(self.cfg) @@ -328,24 +328,24 @@ def test_dependency_removed(self): del next_cfg['services']['nginx']['depends_on'] containers = self.run_up(local_cfg, service_names=['nginx']) - assert set(c.service for c in containers) == set(['db', 'web', 'nginx']) + assert {c.service for c in containers} == {'db', 'web', 'nginx'} project = self.make_project(local_cfg) project.stop(timeout=1) next_containers = self.run_up(next_cfg, service_names=['nginx']) - assert set(c.service for c in next_containers if c.is_running) == set(['nginx']) + assert {c.service for c in next_containers if c.is_running} == {'nginx'} def test_dependency_added(self): local_cfg = copy.deepcopy(self.cfg) del local_cfg['services']['nginx']['depends_on'] containers = self.run_up(local_cfg, service_names=['nginx']) - assert set(c.service for c in containers) == set(['nginx']) + assert {c.service for c in containers} == {'nginx'} local_cfg['services']['nginx']['depends_on'] = ['db'] containers = self.run_up(local_cfg, service_names=['nginx']) - assert set(c.service for c in containers) == set(['nginx', 'db']) + assert {c.service for c in containers} == {'nginx', 'db'} class ServiceStateTest(DockerClientTestCase): diff --git a/tests/integration/volume_test.py b/tests/integration/volume_test.py index 2ede7bf28d2..0e7c78bc25a 100644 --- a/tests/integration/volume_test.py +++ b/tests/integration/volume_test.py @@ -18,7 +18,7 @@ def tearDown(self): except DockerException: pass del self.tmp_volumes - super(VolumeTest, self).tearDown() + super().tearDown() def create_volume(self, name, driver=None, opts=None, external=None, custom_name=False): if external: diff --git a/tests/unit/cli/command_test.py b/tests/unit/cli/command_test.py index 20702d97580..9d4db5b59a8 100644 --- a/tests/unit/cli/command_test.py +++ b/tests/unit/cli/command_test.py @@ -1,4 +1,3 @@ -# ~*~ encoding: utf-8 ~*~ import os import pytest @@ -9,7 +8,7 @@ from tests import mock -class TestGetConfigPathFromOptions(object): +class TestGetConfigPathFromOptions: def test_path_from_options(self): paths = ['one.yml', 'two.yml'] diff --git a/tests/unit/cli/docker_client_test.py b/tests/unit/cli/docker_client_test.py index 873c1ff3d82..941aed4f4b3 100644 --- a/tests/unit/cli/docker_client_test.py +++ b/tests/unit/cli/docker_client_test.py @@ -55,7 +55,7 @@ def test_custom_timeout_error(self): def test_user_agent(self): client = docker_client(os.environ) - expected = "docker-compose/{0} docker-py/{1} {2}/{3}".format( + expected = "docker-compose/{} docker-py/{} {}/{}".format( compose.__version__, docker.__version__, platform.system(), @@ -151,9 +151,9 @@ def test_assert_hostname_explicit_skip(self): def test_tls_client_and_ca_quoted_paths(self): options = { - '--tlscacert': '"{0}"'.format(self.ca_cert), - '--tlscert': '"{0}"'.format(self.client_cert), - '--tlskey': '"{0}"'.format(self.key), + '--tlscacert': '"{}"'.format(self.ca_cert), + '--tlscert': '"{}"'.format(self.client_cert), + '--tlskey': '"{}"'.format(self.key), '--tlsverify': True } result = tls_config_from_options(options) @@ -185,9 +185,9 @@ def test_tls_flags_override_environment(self): 'DOCKER_TLS_VERIFY': 'false' }) options = { - '--tlscacert': '"{0}"'.format(self.ca_cert), - '--tlscert': '"{0}"'.format(self.client_cert), - '--tlskey': '"{0}"'.format(self.key), + '--tlscacert': '"{}"'.format(self.ca_cert), + '--tlscert': '"{}"'.format(self.client_cert), + '--tlskey': '"{}"'.format(self.key), '--tlsverify': True } @@ -230,7 +230,7 @@ def test_tls_verify_default_cert_path(self): assert result.cert == (self.client_cert, self.key) -class TestGetTlsVersion(object): +class TestGetTlsVersion: def test_get_tls_version_default(self): environment = {} assert get_tls_version(environment) is None diff --git a/tests/unit/cli/errors_test.py b/tests/unit/cli/errors_test.py index cb5f59df19b..dda253a5846 100644 --- a/tests/unit/cli/errors_test.py +++ b/tests/unit/cli/errors_test.py @@ -21,7 +21,7 @@ def patch_find_executable(side_effect): side_effect=side_effect) -class TestHandleConnectionErrors(object): +class TestHandleConnectionErrors: def test_generic_connection_error(self, mock_logging): with pytest.raises(errors.ConnectionError): @@ -43,7 +43,7 @@ def test_api_error_version_mismatch(self, mock_logging): def test_api_error_version_mismatch_unicode_explanation(self, mock_logging): with pytest.raises(errors.ConnectionError): with handle_connection_errors(mock.Mock(api_version='1.22')): - raise APIError(None, None, u"client is newer than server") + raise APIError(None, None, "client is newer than server") _, args, _ = mock_logging.error.mock_calls[0] assert "Docker Engine of version 1.10.0 or greater" in args[0] @@ -57,7 +57,7 @@ def test_api_error_version_other(self, mock_logging): mock_logging.error.assert_called_once_with(msg.decode('utf-8')) def test_api_error_version_other_unicode_explanation(self, mock_logging): - msg = u"Something broke!" + msg = "Something broke!" with pytest.raises(errors.ConnectionError): with handle_connection_errors(mock.Mock(api_version='1.22')): raise APIError(None, None, msg) diff --git a/tests/unit/cli/formatter_test.py b/tests/unit/cli/formatter_test.py index 07f5a8f50c5..08752a6226b 100644 --- a/tests/unit/cli/formatter_test.py +++ b/tests/unit/cli/formatter_test.py @@ -40,10 +40,10 @@ def test_format_unicode_warn(self): message = b'\xec\xa0\x95\xec\x88\x98\xec\xa0\x95' output = self.formatter.format(make_log_record(logging.WARN, message)) expected = colors.yellow('WARNING') + ': ' - assert output == '{0}{1}'.format(expected, message.decode('utf-8')) + assert output == '{}{}'.format(expected, message.decode('utf-8')) def test_format_unicode_error(self): message = b'\xec\xa0\x95\xec\x88\x98\xec\xa0\x95' output = self.formatter.format(make_log_record(logging.ERROR, message)) expected = colors.red('ERROR') + ': ' - assert output == '{0}{1}'.format(expected, message.decode('utf-8')) + assert output == '{}{}'.format(expected, message.decode('utf-8')) diff --git a/tests/unit/cli/log_printer_test.py b/tests/unit/cli/log_printer_test.py index 38dd56c715a..aeeed31f32f 100644 --- a/tests/unit/cli/log_printer_test.py +++ b/tests/unit/cli/log_printer_test.py @@ -29,7 +29,7 @@ def mock_container(): return mock.Mock(spec=Container, name_without_project='web_1') -class TestLogPresenter(object): +class TestLogPresenter: def test_monochrome(self, mock_container): presenters = build_log_presenters(['foo', 'bar'], True) @@ -83,7 +83,7 @@ def test_build_no_log_generator(mock_container): assert "exited with code" not in output -class TestBuildLogGenerator(object): +class TestBuildLogGenerator: def test_no_log_stream(self, mock_container): mock_container.log_stream = None @@ -108,7 +108,7 @@ def test_with_log_stream(self, mock_container): assert next(generator) == "world" def test_unicode(self, output_stream): - glyph = u'\u2022\n' + glyph = '\u2022\n' mock_container.log_stream = iter([glyph.encode('utf-8')]) generator = build_log_generator(mock_container, {}) @@ -125,7 +125,7 @@ def mock_presenters(): return itertools.cycle([mock.Mock()]) -class TestWatchEvents(object): +class TestWatchEvents: def test_stop_event(self, thread_map, mock_presenters): event_stream = [{'action': 'stop', 'id': 'cid'}] @@ -167,7 +167,7 @@ def test_other_event(self, thread_map, mock_presenters): assert container_id not in thread_map -class TestConsumeQueue(object): +class TestConsumeQueue: def test_item_is_an_exception(self): diff --git a/tests/unit/cli/main_test.py b/tests/unit/cli/main_test.py index ac3df2920a7..d75b6bd4c12 100644 --- a/tests/unit/cli/main_test.py +++ b/tests/unit/cli/main_test.py @@ -22,7 +22,7 @@ def mock_container(service, number): container.Container, service=service, number=number, - name_without_project='{0}_{1}'.format(service, number)) + name_without_project='{}_{}'.format(service, number)) @pytest.fixture @@ -32,7 +32,7 @@ def logging_handler(): return logging.StreamHandler(stream=stream) -class TestCLIMainTestCase(object): +class TestCLIMainTestCase: def test_filter_attached_containers(self): containers = [ @@ -135,7 +135,7 @@ def test_get_docker_start_call(self): assert expected_docker_start_call == docker_start_call -class TestSetupConsoleHandlerTestCase(object): +class TestSetupConsoleHandlerTestCase: def test_with_tty_verbose(self, logging_handler): setup_console_handler(logging_handler, True) @@ -155,7 +155,7 @@ def test_with_not_a_tty(self, logging_handler): assert type(logging_handler.formatter) == logging.Formatter -class TestConvergeStrategyFromOptsTestCase(object): +class TestConvergeStrategyFromOptsTestCase: def test_invalid_opts(self): options = {'--force-recreate': True, '--no-recreate': True} @@ -189,7 +189,7 @@ def mock_find_executable(exe): @mock.patch('compose.cli.main.find_executable', mock_find_executable) -class TestCallDocker(object): +class TestCallDocker: def test_simple_no_options(self): with mock.patch('subprocess.call') as fake_call: call_docker(['ps'], {}, {}) diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index c6891bc3412..4e5b74843f5 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -1,4 +1,3 @@ -# encoding: utf-8 import os import shutil import tempfile diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 1f9a168c0a9..85f22187e2d 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -1,4 +1,3 @@ -# encoding: utf-8 import codecs import os import shutil @@ -3176,12 +3175,12 @@ def test_config_duplicate_mount_points(self): with self.assertRaises(ConfigurationError) as e: config.load(config1) - self.assertEquals(str(e.exception), 'Duplicate mount points: [%s]' % ( + self.assertEqual(str(e.exception), 'Duplicate mount points: [%s]' % ( ', '.join(['/tmp/foo:/tmp/foo:rw']*2))) with self.assertRaises(ConfigurationError) as e: config.load(config2) - self.assertEquals(str(e.exception), 'Duplicate mount points: [%s]' % ( + self.assertEqual(str(e.exception), 'Duplicate mount points: [%s]' % ( ', '.join(['/x:/y:rw', '/z:/y:rw']))) @@ -3827,12 +3826,12 @@ def test_home_directory_with_driver_does_not_expand(self): assert d['volumes'] == ['~:/data'] def test_volume_path_with_non_ascii_directory(self): - volume = u'/Füü/data:/data' + volume = '/Füü/data:/data' container_path = config.resolve_volume_path(".", volume) assert container_path == volume -class MergePathMappingTest(object): +class MergePathMappingTest: config_name = "" def test_empty(self): @@ -3905,7 +3904,7 @@ def test_merge_build_or_image_override_with_other(self): assert config.merge_service_dicts({'image': 'redis'}, {'build': '.'}, V1) == {'build': '.'} -class MergeListsTest(object): +class MergeListsTest: config_name = "" base_config = [] override_config = [] @@ -4338,7 +4337,7 @@ def test_resolve_environment_from_env_file_with_empty_values(self): {'env_file': ['tests/fixtures/env/resolve.env']}, Environment.from_env_file(None) ) == { - 'FILE_DEF': u'bär', + 'FILE_DEF': 'bär', 'FILE_DEF_EMPTY': '', 'ENV_DEF': 'E3', 'NO_DEF': None @@ -4964,14 +4963,14 @@ def test_split_path_mapping_with_windows_path_in_container(self): container_path = 'c:\\scarletdevil\\data' expected_mapping = (container_path, (host_path, None)) - mapping = config.split_path_mapping('{0}:{1}'.format(host_path, container_path)) + mapping = config.split_path_mapping('{}:{}'.format(host_path, container_path)) assert mapping == expected_mapping def test_split_path_mapping_with_root_mount(self): host_path = '/' container_path = '/var/hostroot' expected_mapping = (container_path, (host_path, None)) - mapping = config.split_path_mapping('{0}:{1}'.format(host_path, container_path)) + mapping = config.split_path_mapping('{}:{}'.format(host_path, container_path)) assert mapping == expected_mapping diff --git a/tests/unit/config/environment_test.py b/tests/unit/config/environment_test.py index 50738546899..6a80ff12254 100644 --- a/tests/unit/config/environment_test.py +++ b/tests/unit/config/environment_test.py @@ -1,4 +1,3 @@ -# encoding: utf-8 import codecs import os import shutil diff --git a/tests/unit/config/interpolation_test.py b/tests/unit/config/interpolation_test.py index 4efcf865a60..586e7ca5124 100644 --- a/tests/unit/config/interpolation_test.py +++ b/tests/unit/config/interpolation_test.py @@ -1,4 +1,3 @@ -# encoding: utf-8 import pytest from compose.config.environment import Environment @@ -441,7 +440,7 @@ def test_unbraced_separators(defaults_interpolator): def test_interpolate_unicode_values(): variable_mapping = { - 'FOO': '十六夜 咲夜'.encode('utf-8'), + 'FOO': '十六夜 咲夜'.encode(), 'BAR': '十六夜 咲夜' } interpol = Interpolator(TemplateWithDefaults, variable_mapping).interpolate diff --git a/tests/unit/config/sort_services_test.py b/tests/unit/config/sort_services_test.py index 430fed6a620..508c4bba190 100644 --- a/tests/unit/config/sort_services_test.py +++ b/tests/unit/config/sort_services_test.py @@ -5,7 +5,7 @@ from compose.config.types import VolumeFromSpec -class TestSortService(object): +class TestSortService: def test_sort_service_dicts_1(self): services = [ { diff --git a/tests/unit/config/types_test.py b/tests/unit/config/types_test.py index c0991b9dc14..acb8122e6d9 100644 --- a/tests/unit/config/types_test.py +++ b/tests/unit/config/types_test.py @@ -39,7 +39,7 @@ def test_parse_extra_hosts_dict(): } -class TestServicePort(object): +class TestServicePort: def test_parse_dict(self): data = { 'target': 8000, @@ -129,7 +129,7 @@ def test_parse_invalid_publish_range(self): ServicePort.parse(port_def) -class TestVolumeSpec(object): +class TestVolumeSpec: def test_parse_volume_spec_only_one_path(self): spec = VolumeSpec.parse('/the/volume') @@ -216,7 +216,7 @@ def test_parse_volume_windows_mixed_notations_native(self): ) -class TestVolumesFromSpec(object): +class TestVolumesFromSpec: services = ['servicea', 'serviceb'] diff --git a/tests/unit/progress_stream_test.py b/tests/unit/progress_stream_test.py index b885b239b92..288c9b6e448 100644 --- a/tests/unit/progress_stream_test.py +++ b/tests/unit/progress_stream_test.py @@ -1,5 +1,3 @@ -# ~*~ encoding: utf-8 ~*~ -import io import os import random import shutil @@ -75,7 +73,7 @@ def test_mismatched_encoding_stream_write(self): def mktempfile(encoding): fname = os.path.join(tmpdir, hex(random.getrandbits(128))[2:-1]) - return io.open(fname, mode='w+', encoding=encoding) + return open(fname, mode='w+', encoding=encoding) text = '就吃饭' with mktempfile(encoding='utf-8') as tf: diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 1ad49c1aae5..53063d147cf 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -1,4 +1,3 @@ -# encoding: utf-8 import datetime import os import tempfile @@ -740,7 +739,7 @@ def test_no_warning_with_no_swarm_info(self): assert fake_log.warn.call_count == 0 def test_no_such_service_unicode(self): - assert NoSuchService('十六夜 咲夜'.encode('utf-8')).msg == 'No such service: 十六夜 咲夜' + assert NoSuchService('十六夜 咲夜'.encode()).msg == 'No such service: 十六夜 咲夜' assert NoSuchService('十六夜 咲夜').msg == 'No such service: 十六夜 咲夜' def test_project_platform_value(self): diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index d4e7f3c5d94..72cb1d7f9ed 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -63,9 +63,9 @@ def test_containers_with_containers(self): assert [c.id for c in service.containers()] == list(range(3)) expected_labels = [ - '{0}=myproject'.format(LABEL_PROJECT), - '{0}=db'.format(LABEL_SERVICE), - '{0}=False'.format(LABEL_ONE_OFF), + '{}=myproject'.format(LABEL_PROJECT), + '{}=db'.format(LABEL_SERVICE), + '{}=False'.format(LABEL_ONE_OFF), ] self.mock_client.containers.assert_called_once_with( diff --git a/tests/unit/split_buffer_test.py b/tests/unit/split_buffer_test.py index f1974c83144..d6b5b884c87 100644 --- a/tests/unit/split_buffer_test.py +++ b/tests/unit/split_buffer_test.py @@ -36,7 +36,7 @@ def reader(): self.assert_produces(reader, ['abc\n', 'd']) def test_preserves_unicode_sequences_within_lines(self): - string = u"a\u2022c\n" + string = "a\u2022c\n" def reader(): yield string.encode('utf-8') diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index f1febc13e69..3052e4d8644 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -1,8 +1,7 @@ -# encoding: utf-8 from compose import utils -class TestJsonSplitter(object): +class TestJsonSplitter: def test_json_splitter_no_object(self): data = '{"foo": "bar' @@ -17,7 +16,7 @@ def test_json_splitter_leading_whitespace(self): assert utils.json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}') -class TestStreamAsText(object): +class TestStreamAsText: def test_stream_with_non_utf_unicode_character(self): stream = [b'\xed\xf3\xf3'] @@ -25,12 +24,12 @@ def test_stream_with_non_utf_unicode_character(self): assert output == '���' def test_stream_with_utf_character(self): - stream = ['ěĝ'.encode('utf-8')] + stream = ['ěĝ'.encode()] output, = utils.stream_as_text(stream) assert output == 'ěĝ' -class TestJsonStream(object): +class TestJsonStream: def test_with_falsy_entries(self): stream = [ @@ -59,7 +58,7 @@ def test_with_leading_whitespace(self): ] -class TestParseBytes(object): +class TestParseBytes: def test_parse_bytes(self): assert utils.parse_bytes('123kb') == 123 * 1024 assert utils.parse_bytes(123) == 123 @@ -67,7 +66,7 @@ def test_parse_bytes(self): assert utils.parse_bytes('123') == 123 -class TestMoreItertools(object): +class TestMoreItertools: def test_unique_everseen(self): unique = utils.unique_everseen assert list(unique([2, 1, 2, 1])) == [2, 1] diff --git a/tests/unit/volume_test.py b/tests/unit/volume_test.py index 8b2f6cfeef8..0dfbfcd40b7 100644 --- a/tests/unit/volume_test.py +++ b/tests/unit/volume_test.py @@ -10,7 +10,7 @@ def mock_client(): return mock.create_autospec(docker.APIClient) -class TestVolume(object): +class TestVolume: def test_remove_local_volume(self, mock_client): vol = volume.Volume(mock_client, 'foo', 'project')